/*
File: Twitter.cs
About: Custom Twitter Web Control.
Written by: Aidan Doolan (www.aidandoolan.com).
Comments: Needs to be tested fully. Use at your own risk.
Updated: 28/05/2008, added a request timeout parameter.
*/
using System;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Diagnostics;
using System.Web.Caching;
using System.Xml.XPath;
using System.Text;
using System.Collections.Generic;
using System.Net;
using System.Xml;
using BlogEngine.Core;
namespace Controls
{ public enum TwitterUpdateType
{ User,
Friends,
Public,
}
public class Twitter : Control
{ public const string TwitterCacheKey = "Controls.Twitter.Updates";
private XmlDocument _twitterXmlDoc;
private string _username;
private string _password;
private bool _cacheFlag;
private int _cacheLifeTimeInSeconds;
private bool _storeFlag;
private string _storeFilename;
private int _numberOfUpdates;
private int _timeOutInSeconds;
private TwitterUpdateType _updateType;
public string Username { get { return _username; } set { _username = value; } } public string Password { get { return _password; } set { _password = value; } } public bool CacheFlag { get { return _cacheFlag; } set { _cacheFlag = value; } } public int CacheLifeTimeInSeconds { get { return _cacheLifeTimeInSeconds; } set { _cacheLifeTimeInSeconds = value; } } public bool StoreFlag { get { return _storeFlag; } set { _storeFlag = value; } } public string StoreFilename { get { return _storeFilename; } set { _storeFilename = value; } } public int NumberOfUpdates { get { return _numberOfUpdates; } set { _numberOfUpdates = value; } } public TwitterUpdateType UpdateType { get { return _updateType; } set { _updateType = value; } } public int TimeOutInSeconds { get { return _timeOutInSeconds; } set { _timeOutInSeconds = value; } }
private string RequestUrl
{ get
{ return string.Format(@"http://twitter.com/statuses/{0}_timeline.xml?count={1}", Enum.GetName(typeof(TwitterUpdateType), _updateType).ToLower(), _numberOfUpdates);
}
}
public Twitter()
{ Username = String.Empty;
Password = String.Empty;
CacheFlag = true;
CacheLifeTimeInSeconds = 3600;
StoreFlag = true;
StoreFilename = "twitter.xml";
NumberOfUpdates = 5;
TimeOutInSeconds = 4;
UpdateType = TwitterUpdateType.User;
_twitterXmlDoc = new XmlDocument();
}
public override void RenderControl(HtmlTextWriter writer)
{ try
{ Validate(_username != String.Empty, "Username must be set.");
Validate(_password != String.Empty, "Password must be set.");
Validate(_cacheFlag ? _cacheLifeTimeInSeconds > 0 : true, "Cache life Time must be greater than 0.");
Validate(_storeFlag ? _storeFilename != String.Empty : true, "Store File name must be set.");
Validate(_numberOfUpdates > 0 && _numberOfUpdates <= 20,
"Number of updates must be greater than 0 and less than or equal to 20.");
Validate(_timeOutInSeconds > 0, "Twitter request time out must be greater than 0.");
GetTwitterUpdates();
writer.Write(RenderMarkup());
}
catch (Exception ex)
{ HttpContext.Current.Trace.Write("Twitter Control", ex.Message); writer.Write("Service unavailable. Please try again later."); }
}
protected static void Validate(bool assertion, string errorMessage)
{ if (!assertion)
{ throw new ApplicationException(errorMessage);
}
}
public string RenderMarkup()
{ StringBuilder sb = new StringBuilder();
sb.Append("<ul>");
XmlNodeList xmlStatusList = _twitterXmlDoc.SelectNodes("statuses/status"); foreach (XmlNode xmlStatus in xmlStatusList)
{ sb.Append(String.Format( "<li><span>{0}</span> <a style=\"font-size:85%\"" + "href=\"http://twitter.com/{1}/statuses/{2}\">{3}</a></li>", xmlStatus["text"].InnerText, _username, xmlStatus["id"].InnerText,
RelativeTime(xmlStatus["created_at"].InnerText)));
}
sb.Append("</ul>"); return sb.ToString();
}
private void GetTwitterUpdates()
{ if (_cacheFlag)
{ if (HttpContext.Current.Cache[TwitterCacheKey] == null)
{ RequestTwitterUpdates();
HttpContext.Current.Cache.Insert(TwitterCacheKey, _twitterXmlDoc, null,
DateTime.UtcNow + new TimeSpan(0, 0, _cacheLifeTimeInSeconds), Cache.NoSlidingExpiration);
}
else
{ _twitterXmlDoc = HttpContext.Current.Cache[TwitterCacheKey] as XmlDocument;
}
}
else
{ RequestTwitterUpdates();
}
}
private void RequestTwitterUpdates()
{ try
{ HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(RequestUrl);
request.Headers.Add(HttpRequestHeader.Authorization,
"Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(_username + ":" + _password)));
request.Method = "GET";
request.Timeout = _timeOutInSeconds * 1000;
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
{ _twitterXmlDoc.Load(response.GetResponseStream());
}
if (_storeFlag)
{ _twitterXmlDoc.Save(GetStoreFilePath());
}
}
catch (Exception ex)
{ HttpContext.Current.Trace.Write("Twitter Control", ex.Message); if (_storeFlag)
{ _twitterXmlDoc.Load(GetStoreFilePath());
}
}
}
private string GetStoreFilePath()
{ string p = BlogSettings.Instance.StorageLocation.Replace("~/", ""); string folder = System.IO.Path.Combine(System.Web.HttpRuntime.AppDomainAppPath, p);
return folder + Path.DirectorySeparatorChar + _storeFilename;
}
protected string RelativeTime(string createdAt)
{ string[] CreatedAtValues = createdAt.Split(' '); string converted = String.Format("{0} {1} {2} {3} GMT", CreatedAtValues[1], CreatedAtValues[2], CreatedAtValues[5], CreatedAtValues[3]);
TimeSpan elapsedTime = DateTime.UtcNow - DateTime.Parse(converted).ToUniversalTime();
if (elapsedTime.TotalMinutes < 1)
{ return "less than a minute ago";
}
else if (elapsedTime.TotalMinutes < 2)
{ return "about a minute ago";
}
else if (elapsedTime.TotalHours < 1)
{ return String.Format("{0} minutes ago", Convert.ToInt32(Math.Floor(elapsedTime.TotalMinutes))); }
else if (elapsedTime.TotalHours < 2)
{ return "about an hour ago";
}
else if (elapsedTime.TotalDays < 1)
{ return String.Format("about {0} hours ago", Convert.ToInt32(Math.Floor(elapsedTime.TotalHours))); }
else if (elapsedTime.TotalDays < 2)
{ return "1 day ago";
}
return String.Format("{0} days ago", Convert.ToInt32(Math.Floor(elapsedTime.TotalDays))); }
}
}