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