PageRenderTime 127ms CodeModel.GetById 18ms RepoModel.GetById 2ms app.codeStats 0ms

/BlogEngine/BlogEngine.NET/widgets/Twitter/widget.ascx.cs

#
C# | 555 lines | 317 code | 84 blank | 154 comment | 43 complexity | c8c1b1e10b13d785a2edb8e353250ee2 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <summary>
  3. // The widget.
  4. // </summary>
  5. // --------------------------------------------------------------------------------------------------------------------
  6. namespace Widgets.Twitter
  7. {
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Net;
  13. using System.Text.RegularExpressions;
  14. using System.Web;
  15. using System.Web.Hosting;
  16. using System.Web.UI.WebControls;
  17. using System.Xml;
  18. using App_Code.Controls;
  19. using BlogEngine.Core;
  20. /// <summary>
  21. /// The widget.
  22. /// </summary>
  23. public partial class Widget : WidgetBase
  24. {
  25. #region Constants and Fields
  26. /// <summary>
  27. /// The link format.
  28. /// </summary>
  29. private const string LinkFormat = "<a href=\"{0}{1}\" rel=\"nofollow\">{1}</a>";
  30. /// <summary>
  31. /// The twitter feeds cache key.
  32. /// </summary>
  33. private const string TwitterFeedsCacheKey = "twits";
  34. /// <summary>
  35. /// The twitter settings cache key.
  36. /// </summary>
  37. private const string TwitterSettingsCacheKey = "twitter-settings"; // same key used in edit.ascx.cs.
  38. /// <summary>
  39. /// The link regex.
  40. /// </summary>
  41. private static readonly Regex LinkRegex =
  42. new Regex(
  43. "((http://|https://|www\\.)([A-Z0-9.\\-]{1,})\\.[0-9A-Z?;~&\\(\\)#,=\\-_\\./\\+]{2,})",
  44. RegexOptions.Compiled | RegexOptions.IgnoreCase);
  45. /// <summary>
  46. /// The last feed data file name.
  47. /// </summary>
  48. private static Dictionary<Guid, string> lastFeedDataFileName = new Dictionary<Guid, string>();
  49. #endregion
  50. #region Properties
  51. /// <summary>
  52. /// Gets a value indicating whether or not the widget can be edited.
  53. /// <remarks>
  54. /// The only way a widget can be editable is by adding a edit.ascx file to the widget folder.
  55. /// </remarks>
  56. /// </summary>
  57. /// <value></value>
  58. public override bool IsEditable
  59. {
  60. get
  61. {
  62. return true;
  63. }
  64. }
  65. /// <summary>
  66. /// Gets the name. It must be exactly the same as the folder that contains the widget.
  67. /// </summary>
  68. /// <value></value>
  69. public override string Name
  70. {
  71. get
  72. {
  73. return "Twitter";
  74. }
  75. }
  76. #endregion
  77. #region Public Methods
  78. /// <summary>
  79. /// Evaluates the replacement for each link match.
  80. /// </summary>
  81. /// <param name="match">
  82. /// The match.
  83. /// </param>
  84. /// <returns>
  85. /// The evaluator.
  86. /// </returns>
  87. public string Evaluator(Match match)
  88. {
  89. var info = CultureInfo.InvariantCulture;
  90. return string.Format(info, LinkFormat, !match.Value.Contains("://") ? "http://" : string.Empty, match.Value);
  91. }
  92. /// <summary>
  93. /// This method works as a substitute for Page_Load. You should use this method for
  94. /// data binding etc. instead of Page_Load.
  95. /// </summary>
  96. public override void LoadWidget()
  97. {
  98. var settings = this.GetTwitterSettings();
  99. if (settings.AccountUrl != null && !string.IsNullOrEmpty(settings.FollowMeText))
  100. {
  101. this.hlTwitterAccount.NavigateUrl = settings.AccountUrl.ToString();
  102. this.hlTwitterAccount.Text = settings.FollowMeText;
  103. }
  104. if (settings.FeedUrl == null)
  105. {
  106. return;
  107. }
  108. if (Blog.CurrentInstance.Cache[TwitterFeedsCacheKey] == null)
  109. {
  110. var doc = GetLastFeed();
  111. if (doc != null)
  112. {
  113. Blog.CurrentInstance.Cache[TwitterFeedsCacheKey] = doc.OuterXml;
  114. }
  115. }
  116. if (Blog.CurrentInstance.Cache[TwitterFeedsCacheKey] != null)
  117. {
  118. var xml = (string)Blog.CurrentInstance.Cache[TwitterFeedsCacheKey];
  119. var doc = new XmlDocument();
  120. doc.LoadXml(xml);
  121. this.BindFeed(doc, settings.MaxItems);
  122. }
  123. if (DateTime.Now <= settings.LastModified.AddMinutes(settings.PollingInterval))
  124. {
  125. return;
  126. }
  127. settings.LastModified = DateTime.Now;
  128. BeginGetFeed(settings.FeedUrl);
  129. }
  130. #endregion
  131. #region Methods
  132. /// <summary>
  133. /// Handles the ItemDataBound event of the repItems control.
  134. /// </summary>
  135. /// <param name="sender">
  136. /// The source of the event.
  137. /// </param>
  138. /// <param name="e">
  139. /// The <see cref="System.Web.UI.WebControls.RepeaterItemEventArgs"/> instance containing the event data.
  140. /// </param>
  141. protected void RepItemsItemDataBound(object sender, RepeaterItemEventArgs e)
  142. {
  143. var text = (Label)e.Item.FindControl("lblItem");
  144. var date = (Label)e.Item.FindControl("lblDate");
  145. var twit = (Twit)e.Item.DataItem;
  146. text.Text = twit.Title;
  147. date.Text = twit.PubDate.ToString("MMMM d. HH:mm");
  148. }
  149. /// <summary>
  150. /// Gets the last name of the feed data file.
  151. /// </summary>
  152. /// <returns>
  153. /// The get last feed data file name.
  154. /// </returns>
  155. private static string GetLastFeedDataFileName()
  156. {
  157. if (!lastFeedDataFileName.ContainsKey(Blog.CurrentInstance.Id))
  158. {
  159. lastFeedDataFileName[Blog.CurrentInstance.Id] = HostingEnvironment.MapPath(Path.Combine(Blog.CurrentInstance.StorageLocation, "twitter_feeds.xml"));
  160. }
  161. return lastFeedDataFileName[Blog.CurrentInstance.Id];
  162. }
  163. /// <summary>
  164. /// Saves the last feed.
  165. /// </summary>
  166. /// <param name="doc">
  167. /// The xml doc.
  168. /// </param>
  169. private static void SaveLastFeed(XmlDocument doc)
  170. {
  171. try
  172. {
  173. var file = GetLastFeedDataFileName();
  174. doc.Save(file);
  175. }
  176. catch (Exception ex)
  177. {
  178. Utils.Log(string.Format("Error saving last twitter feed load to data store. Error: {0}", ex.Message));
  179. }
  180. }
  181. /// <summary>
  182. /// Begins the get feed.
  183. /// </summary>
  184. /// <param name="url">The URL to get.</param>
  185. private static void BeginGetFeed(Uri url)
  186. {
  187. try
  188. {
  189. var request = (HttpWebRequest)WebRequest.Create(url);
  190. request.Credentials = CredentialCache.DefaultNetworkCredentials;
  191. GetRequestData data = new GetRequestData()
  192. {
  193. BlogInstanceId = Blog.CurrentInstance.Id,
  194. HttpWebRequest = request
  195. };
  196. request.BeginGetResponse(EndGetResponse, data);
  197. }
  198. catch (Exception ex)
  199. {
  200. var msg = "Error requesting Twitter feed.";
  201. msg += string.Format(" {0}", ex.Message);
  202. Utils.Log(msg);
  203. }
  204. }
  205. /// <summary>
  206. /// Binds the feed.
  207. /// </summary>
  208. /// <param name="doc">
  209. /// The xml doc.
  210. /// </param>
  211. /// <param name="maxItems">
  212. /// The max items.
  213. /// </param>
  214. private void BindFeed(XmlDocument doc, int maxItems)
  215. {
  216. var items = doc.SelectNodes("//channel/item");
  217. var twits = new List<Twit>();
  218. var count = 0;
  219. if (items != null)
  220. {
  221. for (var i = 0; i < items.Count; i++)
  222. {
  223. if (count == maxItems)
  224. {
  225. break;
  226. }
  227. var node = items[i];
  228. var twit = new Twit();
  229. var descriptionNode = node.SelectSingleNode("description");
  230. if (descriptionNode != null)
  231. {
  232. var title = descriptionNode.InnerText;
  233. if (title.Contains("@"))
  234. {
  235. continue;
  236. }
  237. if (title.Contains(":"))
  238. {
  239. var start = title.IndexOf(":") + 1;
  240. title = title.Substring(start);
  241. }
  242. twit.Title = this.ResolveLinks(title);
  243. }
  244. var pubdateNode = node.SelectSingleNode("pubDate");
  245. if (pubdateNode != null)
  246. {
  247. twit.PubDate = DateTime.Parse(pubdateNode.InnerText, CultureInfo.InvariantCulture);
  248. }
  249. var linkNode = node.SelectSingleNode("link");
  250. if (linkNode != null)
  251. {
  252. twit.Url = new Uri(linkNode.InnerText, UriKind.Absolute);
  253. }
  254. twits.Add(twit);
  255. count++;
  256. }
  257. }
  258. twits.Sort();
  259. this.repItems.DataSource = twits;
  260. this.repItems.DataBind();
  261. }
  262. /// <summary>
  263. /// Ends the get response.
  264. /// </summary>
  265. /// <param name="result">
  266. /// The result.
  267. /// </param>
  268. private static void EndGetResponse(IAsyncResult result)
  269. {
  270. try
  271. {
  272. GetRequestData data = (GetRequestData)result.AsyncState;
  273. Blog.InstanceIdOverride = data.BlogInstanceId;
  274. using (var response = (HttpWebResponse)data.HttpWebRequest.GetResponse())
  275. {
  276. var doc = new XmlDocument();
  277. var responseStream = response.GetResponseStream();
  278. if (responseStream != null)
  279. {
  280. doc.Load(responseStream);
  281. }
  282. Blog.CurrentInstance.Cache[TwitterFeedsCacheKey] = doc.OuterXml;
  283. SaveLastFeed(doc);
  284. }
  285. }
  286. catch (Exception ex)
  287. {
  288. var msg = "Error retrieving Twitter feed.";
  289. msg += string.Format(" {0}", ex.Message);
  290. Utils.Log(msg);
  291. }
  292. }
  293. /// <summary>
  294. /// Gets the last feed.
  295. /// </summary>
  296. /// <returns>
  297. /// The xml document.
  298. /// </returns>
  299. private static XmlDocument GetLastFeed()
  300. {
  301. var file = GetLastFeedDataFileName();
  302. XmlDocument doc = null;
  303. try
  304. {
  305. if (File.Exists(file))
  306. {
  307. doc = new XmlDocument();
  308. doc.Load(file);
  309. }
  310. }
  311. catch (Exception ex)
  312. {
  313. Utils.Log("Error retrieving last twitter feed load from data store. Error: " + ex.Message);
  314. }
  315. return doc;
  316. }
  317. /// <summary>
  318. /// Gets the twitter settings.
  319. /// </summary>
  320. /// <returns>
  321. /// The twitter settings.
  322. /// </returns>
  323. private TwitterSettings GetTwitterSettings()
  324. {
  325. var twitterSettings = Blog.CurrentInstance.Cache[TwitterSettingsCacheKey] as TwitterSettings;
  326. if (twitterSettings != null)
  327. {
  328. return twitterSettings;
  329. }
  330. twitterSettings = new TwitterSettings();
  331. // Defaults
  332. var maxItems = 3;
  333. var pollingInterval = 15;
  334. const string FollowMeText = "Follow me";
  335. var settings = this.GetSettings();
  336. if (settings.ContainsKey("accounturl") && !string.IsNullOrEmpty(settings["accounturl"]))
  337. {
  338. Uri accountUrl;
  339. Uri.TryCreate(settings["accounturl"], UriKind.Absolute, out accountUrl);
  340. twitterSettings.AccountUrl = accountUrl;
  341. }
  342. if (settings.ContainsKey("feedurl") && !string.IsNullOrEmpty(settings["feedurl"]))
  343. {
  344. Uri feedUrl;
  345. Uri.TryCreate(settings["feedurl"], UriKind.Absolute, out feedUrl);
  346. twitterSettings.FeedUrl = feedUrl;
  347. }
  348. if (settings.ContainsKey("pollinginterval") && !string.IsNullOrEmpty(settings["pollinginterval"]))
  349. {
  350. int tempPollingInterval;
  351. if (int.TryParse(settings["pollinginterval"], out tempPollingInterval))
  352. {
  353. if (tempPollingInterval > 0)
  354. {
  355. pollingInterval = tempPollingInterval;
  356. }
  357. }
  358. }
  359. twitterSettings.PollingInterval = pollingInterval;
  360. if (settings.ContainsKey("maxitems") && !string.IsNullOrEmpty(settings["maxitems"]))
  361. {
  362. int tempMaxItems;
  363. if (int.TryParse(settings["maxitems"], out tempMaxItems))
  364. {
  365. if (tempMaxItems > 0)
  366. {
  367. maxItems = tempMaxItems;
  368. }
  369. }
  370. }
  371. twitterSettings.MaxItems = maxItems;
  372. twitterSettings.FollowMeText = settings.ContainsKey("followmetext") &&
  373. !string.IsNullOrEmpty(settings["followmetext"])
  374. ? settings["followmetext"]
  375. : FollowMeText;
  376. Blog.CurrentInstance.Cache[TwitterSettingsCacheKey] = twitterSettings;
  377. return twitterSettings;
  378. }
  379. /// <summary>
  380. /// The event handler that is triggered every time a comment is served to a client.
  381. /// </summary>
  382. /// <param name="body">
  383. /// The body string.
  384. /// </param>
  385. /// <returns>
  386. /// The resolve links.
  387. /// </returns>
  388. private string ResolveLinks(string body)
  389. {
  390. return LinkRegex.Replace(body, new MatchEvaluator(this.Evaluator));
  391. }
  392. #endregion
  393. /// <summary>
  394. /// Data used during the async HTTP request for tweets.
  395. /// </summary>
  396. private class GetRequestData
  397. {
  398. public Guid BlogInstanceId { get; set; }
  399. public HttpWebRequest HttpWebRequest { get; set; }
  400. }
  401. /// <summary>
  402. /// The tweet.
  403. /// </summary>
  404. private struct Twit : IComparable<Twit>
  405. {
  406. #region Constants and Fields
  407. /// <summary>
  408. /// The pub date.
  409. /// </summary>
  410. public DateTime PubDate;
  411. /// <summary>
  412. /// The title.
  413. /// </summary>
  414. public string Title;
  415. /// <summary>
  416. /// The url.
  417. /// </summary>
  418. public Uri Url;
  419. #endregion
  420. #region Implemented Interfaces
  421. #region IComparable<Twit>
  422. /// <summary>
  423. /// The compare to.
  424. /// </summary>
  425. /// <param name="other">
  426. /// The other.
  427. /// </param>
  428. /// <returns>
  429. /// The compare to.
  430. /// </returns>
  431. public int CompareTo(Twit other)
  432. {
  433. return other.PubDate.CompareTo(this.PubDate);
  434. }
  435. #endregion
  436. #endregion
  437. }
  438. /// <summary>
  439. /// The twitter settings.
  440. /// </summary>
  441. internal class TwitterSettings
  442. {
  443. #region Constants and Fields
  444. /// <summary>
  445. /// The account url.
  446. /// </summary>
  447. public Uri AccountUrl;
  448. /// <summary>
  449. /// The feed url.
  450. /// </summary>
  451. public Uri FeedUrl;
  452. /// <summary>
  453. /// The follow me text.
  454. /// </summary>
  455. public string FollowMeText;
  456. /// <summary>
  457. /// The last modified.
  458. /// </summary>
  459. public DateTime LastModified;
  460. /// <summary>
  461. /// The max items.
  462. /// </summary>
  463. public int MaxItems;
  464. /// <summary>
  465. /// The polling interval.
  466. /// </summary>
  467. public int PollingInterval;
  468. #endregion
  469. }
  470. }
  471. }