Saturday, February 09, 2008

dnrtv_2 In Part 1 of this series I went over the creation of a Windows Service and it's remote client. For this installment, I will be covering the use of "LINQ to XML" to gather the feed, and the saving and loading of the XML configuration files. I'll also show some code to download and unzip the videos using SharpZipLib from ic#code.

The first bit of code we are going to need is a class to hold our feed data. We will be using a List<dnrFeedList> to query against.  Here the code for the class. Nothing strange here, just a datetime field and four strings.

   1:    public class dnrFeedList
   2:      {
   3:          public string Title { get; set; }
   4:          public DateTime Published { get; set; }
   5:          public string Enclosure { get; set; }
   6:          public string Description { get; set; }
   7:          public string GUID { get; set; }
   8:      }

Let's make a few changes to our service. First we need to change our services' timer elapsed event. We want to download the feed list and loop through each show we have not already downloaded. After each download we want to update our XML file in case the program is shutdown. After all shows are downloaded for that day, we will update the last checked date.

   1:   void PollTimer_Elapsed(object sender, ElapsedEventArgs e)
   2:          {
   3:              if (Feed.LastCheckDate < DateTime.Now)
   4:              {
   5:                  foreach (dnrFeedList f in Feed.GetList())
   6:                  {
   7:                      Feed.DownloadFile(f);
   8:                      Feed.SaveXML();
   9:                  }
  10:                  // Add a day to our last check date. 
  11:                  Feed.LastCheckDate = Feed.LastCheckDate.AddDays(1);
  12:                  Feed.SaveXML();
  13:              }
  14:          }

Now we want to pull down the feed from DNRTV. The following LINQ to XML code has been posted online several times over on blogs such as Scott Guthrie. The only addition I made was to the where clause to filter out the videos that have already been downloaded. We will it in a generic called Episode List. This list is a List<String> that contains the shows GUID.  After each successful downloaded, we add that shows GUID to this list.

Also needed was a DateTimeZone class to parse in and correct the time zone issue ( ex. "-0500" instead of "EST" ).  I won't post that code here, but it will be included in the final download.

   1:  public List<dnrFeedList> GetList()
   2:          {
   3:   
   4:              XDocument x = XDocument.Load(URL);
   5:              var feeds = from feed in x.Descendants("item")
   6:                          orderby DateTimeZone.ParseDateTime(feed.Element("pubDate").Value.ToString()) descending
   7:                          where EpisodeList.Contains(feed.Element("guid").Value) == false
   8:                          select new dnrFeedList
   9:                          {
  10:                              Title = feed.Element("title").Value.ToString(),
  11:                              Published = DateTimeZone.ParseDateTime(feed.Element("pubDate").Value.ToString()),
  12:                              Enclosure = feed.Element("enclosure").Attribute("url").Value.ToString(),
  13:                              Description = feed.Element("description").Value.ToString(),
  14:                              GUID = feed.Element("guid").Value
  15:                          };
  16:   
  17:              return feeds.ToList();
  18:          }

Now that we have our feed list, we loop through each show and do our download using a generic TEMP.ZIP filename for each download. Afterward, we unzip the video into the video directory, add the GUID to our Episode List  and delete the TEMP.ZIP.

   1:  public void DownloadFile(dnrFeedList uri)
   2:          {
   3:              // Download ZIP file with TEMP.ZIP as the filename. 
   4:              // It will be deleted after it is unzipped. 
   5:              string filename = this.VideoDirectory + @"TEMP.ZIP";
   6:          
   7:              WebClient web = new WebClient();
   8:              web.DownloadFile(uri.Enclosure.ToString(), filename);
   9:   
  10:              const int bufferSize = 4096;
  11:              byte[] buffer = new byte[bufferSize];
  12:              int count = 0;
  13:   
  14:              // Here we are using the SharpZipLib from ic#code.
  15:              ZipInputStream s = new ZipInputStream(File.OpenRead(filename));
  16:              ZipEntry f;
  17:   
  18:              while ((f = s.GetNextEntry()) != null)
  19:              {
  20:                  string out_filename = this.VideoDirectory + string.Format("{0}", f.Name);
  21:                  if (!File.Exists(out_filename))
  22:                  {
  23:                      FileStream sw = new FileStream(out_filename, FileMode.Create, FileAccess.Write, FileShare.None);
  24:   
  25:                      while (true)
  26:                      {
  27:                          count = s.Read(buffer, 0, bufferSize);
  28:                          if (count > 0)
  29:                          {
  30:                              sw.Write(buffer, 0, bufferSize);
  31:                          }
  32:                          else break;
  33:                      }
  34:                      sw.Close();
  35:                  }
  36:              }
  37:   
  38:              // We need to the GUID for this show to our already downloaded
  39:              // episode list. Delete the Temp.zip afterward.
  40:              EpisodeList.Add(uri.GUID.ToString());
  41:              File.Delete(filename);
  42:          }

The final parts of the code I want to go over are the loading and saving of the configuration XML file. This file contains the last date checked and a list of already downloaded shows. It amazes me that this file can be created in two statements. It is longer than two lines, but it is still just two statements, a constructor and a save method. Notice the LINQ to Generic query for the show list section. Pretty cool.

   1:   public void SaveXML()
   2:          {
   3:   
   4:              XDocument doc = new XDocument(
   5:                  new XDeclaration("1.0", "utf-8", "yes"),
   6:                  new XComment("dnrTv Aggregator Configuration"),
   7:                  new XElement("Aggregator",
   8:                      new XElement("Configuration", new XElement("LastCheckDate", DateTime.Now.ToShortDateString())),
   9:                      new XElement("ShowList", from s in EpisodeList
  10:                                               select new XElement("Show", new XElement("GUID", s.ToString()))
  11:                              )));
  12:   
  13:              doc.Save(path);
  14:          }

Loading the file back in is just about as simple. In fact, there is likely a cooler way of doing this that I just have not discovered yet. Drop me a comment if you have one. Now we use LINQ to XML to pull the data and load it into our previous downloaded show list.

   1:    private void LoadXML()
   2:          {
   3:              if (File.Exists(path))
   4:              {
   5:                  XElement doc = XElement.Load(path);
   6:   
   7:                  LastCheckDate = DateTime.Parse(doc.Element("Configuration").Element("LastCheckDate").Value);
   8:                  var SavedList = from d in doc.Element("ShowList").Elements("Show")
   9:                                  select (string)d.Element("GUID");
  10:   
  11:                  foreach (string s in SavedList)
  12:                  {
  13:                      EpisodeList.Add(s);
  14:                  }
  15:   
  16:              }
  17:          }

I have this code up and running on my Homer Server. It is checking daily for new shows and then downloads them to a shared video directory. My next step will be to rewrite my client application to use WCF has it communication protocol and to fancy up it's features. At that point I will be posting the full code for download.

Code Updated 2-10-2008

kick it on DotNetKicks.com

posted on Saturday, February 09, 2008 1:27:27 PM (Eastern Standard Time, UTC-05:00)  #    Comments [2]