Thursday, February 02, 2006

PwopWatcher

Note: I did some blog refactoring, and extracted this information into it's own post. It originally was here.

My little program is a console app that I scheduled to run every 3 hours using the Windows Task Scheduler. It looks to the config file for a list of feeds, and then fetches the RSS from those feeds, and searches for //item/enclosure[@url] elements in order to get the URL for each .torrent file that Pwop publishes. The .torrent file is then downloaded and saved to the drop directory that I configured µTorrent to watch. I mean, this is what everyone's looking for when they say that they want RSS capabilities in their BitTorrent client, right?

Oh, and I kind of take advantage of a little error-handling feature of µTorrent: if it already loaded a particular torrent, it will simply log a warning if you try to load that torrent again. This is useful because my console app blindly downloads torrents each time that it runs, but µTorrent just ignores the duplicates (only new torrents placed in the drop directory will be processed).

I call the program PwopWatcher, but only because I'm not too imaginative in naming projects, and chose this name because... well, because I use it to watch the Pwop RSS feeds (in other words, I don't mean to imply that it was produced by Pwop Productions).

It is a .NET 2.0 console application, and here's the source code (for Program.cs, the main class file that VS2005 creates for Console Application projects). I'm listing it here because I don't think this is rocket science, and it will hopefully benefit people looking for examples of processing RSS feeds and downloading files from the Internet using the .NET 2.0 Framework.


class Program
{
  static string dropDir = @"c:\pwop\drop\";

  static void Main(string[] args)
  {
    try
    {

      dropDir = System.Configuration.ConfigurationManager.AppSettings["dropDir"];

      if (!dropDir.EndsWith(@"\"))
      {
        dropDir += @"\";
      }

      foreach (string feed in System.Configuration.ConfigurationManager.AppSettings["feeds"].Split(";".ToCharArray()))
      {
        ParseTorrentsFromFeed(feed);
      }
    }
    catch (Exception ex)
    {
      Console.Beep();
      Console.WriteLine(ex.ToString());
    }

    System.Threading.Thread.Sleep(30000);
  }

  static void ParseTorrentsFromFeed(string feed)
  {
    if (feed == string.Empty || feed == null)
      return;

    System.Xml.XmlDocument xDoc = new System.Xml.XmlDocument();

    try
    {
      xDoc.Load(feed);
    }
    catch (System.Xml.XmlException ex)
    {
      Console.Beep();
      Console.WriteLine("XmlException encountered while loading feed:\n\n" + ex.ToString());
      return;
    }

    System.Xml.XmlNodeList nl = xDoc.SelectNodes("//item/enclosure[@url]");

    foreach (System.Xml.XmlNode n in nl)
    {
      DownloadTorrent(n.Attributes["url"].Value);
    }

  }

  static void DownloadTorrent(string url)
  {
    try
    {
      string filename = url.Substring(url.LastIndexOf("/") + 1);

      using (System.Net.WebClient wc = new System.Net.WebClient())
      {
        wc.DownloadFile(url, dropDir + filename);
        Console.WriteLine("Fetched " + filename);
      }
    }
    catch (Exception ex)
    {
      Console.Beep();
      Console.WriteLine("Exception encountered while downloading torrent:\n\n" + ex.ToString());
      return;
    }
  }
}


The config file is expected to have two appSettings:

"feeds" is a semicolon-delimited list of RSS feeds
"dropDir" is the directory where the .torrent files will be saved to