Index: src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastActions.cs
===================================================================
--- src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastActions.cs	(revision 4212)
+++ src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastActions.cs	(working copy)
@@ -74,6 +74,12 @@
                      Catalog.GetString ("Subscribe to Podcast"),"<control><shift>F", 
                      Catalog.GetString ("Subscribe to a new podcast"),
                      OnPodcastAdd
+                ),
+                new ActionEntry (
+                    "OpmlAddAction", Stock.Add,
+                    Catalog.GetString ("Import OPML"),"<control><shift>P", 
+                    Catalog.GetString ("Import OPML"),
+                     OnOpmlAdd
                 )         
             });
             
@@ -265,7 +271,137 @@
 				}
             }        
         }
+        // bel 20080706
+        // I'm hardcoding the download here and this should be moved to it's own task so 
+        // that the user can cancel, view progress, etc.
+        // This is mostly cut and pasted from the FeedUpdateTask
+        ///IDEA: It would be neato to set up an OPML so that all the feeds can be updated
+        //       if the OPML has been updated.  This would enable you to keep
+        //       podcast subscriptions synchronized between computers. neato!!
+        // I know, bad idea decaring these here but it works for now.
+        Migo.Net.AsyncWebClient wc = null;
+        FeedAutoDownload opmlPreference;
         
+        private void RunOpmlDialog ()
+        {        
+            Uri feedUri = null;
+            //Default the subscribeDialog to download none
+            // by default since an import of an OPML could be huge!!
+            PodcastSubscribeDialog subscribeDialog = new PodcastSubscribeDialog (FeedAutoDownload.None);
+            subscribeDialog.Title = Catalog.GetString("Import OPML File");
+            subscribeDialog.Header = Catalog.GetString("Import OPML File");
+            subscribeDialog.Message = Catalog.GetString("Please enter the URL of the OPML file that contains the podcasts to which you would like to subscribe.");
+            subscribeDialog.SyncText = Catalog.GetString("When new episodes are available:  \n* for all imported podcasts");
+            ResponseType response = (ResponseType) subscribeDialog.Run ();
+
+            opmlPreference = subscribeDialog.SyncPreference;
+            
+            subscribeDialog.Destroy ();
+
+            if (response == ResponseType.Ok) {
+                string url = subscribeDialog.Url.Trim ().Trim ('/');
+                
+                if (String.IsNullOrEmpty (subscribeDialog.Url)) {
+                    return;
+                }
+                
+    				    if (!TryParseUrl (url, out feedUri)) {
+                    HigMessageDialog.RunHigMessageDialog (
+                        null,
+                        DialogFlags.Modal,
+                        MessageType.Warning,
+                        ButtonsType.Ok,
+                        Catalog.GetString ("Invalid URL"),
+                        Catalog.GetString ("Podcast URL is invalid.")
+                    );
+				        } else {
+                    try {                                                                       
+                        wc = new Migo.Net.AsyncWebClient ();                  
+                        wc.Timeout = (30 * 1000); // 30 Seconds  
+                        wc.DownloadStringCompleted += OnDownloadDataReceived;
+                        
+                        wc.DownloadStringAsync (feedUri);
+                    } catch (Exception e) {
+                        if (wc != null) {
+                            wc.DownloadStringCompleted -= OnDownloadDataReceived;
+                        }
+                        
+                        Hyena.Log.Exception (e);
+                    }
+              
+        				}
+            }        
+        }
+        private void OnDownloadDataReceived (object sender, Migo.Net.DownloadStringCompletedEventArgs args) 
+        {
+              Uri feedUri = null;
+              wc.DownloadStringCompleted -= OnDownloadDataReceived;
+              FeedDownloadError error = FeedDownloadError.None;
+              
+              System.Net.WebException we = args.Error as System.Net.WebException;
+              if (we == null) {
+                  try {
+                      OpmlParser parser = new OpmlParser (args.Result);
+                      if (0 == parser.Feeds.Count) 
+                      {
+                          HigMessageDialog.RunHigMessageDialog (
+                              null,
+                              DialogFlags.Modal,
+                              MessageType.Warning,
+                              ButtonsType.Ok,
+                              Catalog.GetString ("No Podcasts Found"),
+                              Catalog.GetString ("The OPML did not contain any podcasts.")
+                          );
+                      }
+
+                      foreach (string url in parser.Feeds) 
+                      {
+                          //This is some duplicate code from above and should probably be
+                          // centralized.
+                  				if (!TryParseUrl (url, out feedUri)) {
+                              HigMessageDialog.RunHigMessageDialog (
+                                  null,
+                                  DialogFlags.Modal,
+                                  MessageType.Warning,
+                                  ButtonsType.Ok,
+                                  Catalog.GetString ("Invalid URL"),
+                                  Catalog.GetString ("Podcast URL is invalid.")
+                              );
+                    				} else {
+                    				    SubscribeToPodcast (feedUri, opmlPreference); 
+                    				}
+                        
+                      }
+                  } catch (FormatException e) {
+                      Hyena.Log.Exception (e);
+                      error = FeedDownloadError.InvalidFeedFormat;
+                  }
+              } else {
+                  error = FeedDownloadError.DownloadFailed;
+                  System.Net.HttpWebResponse resp = we.Response as System.Net.HttpWebResponse;
+                  if (resp != null) {
+                      switch (resp.StatusCode) {
+                      case System.Net.HttpStatusCode.NotFound:
+                          error = FeedDownloadError.DoesNotExist;
+                          break;                                
+                      case System.Net.HttpStatusCode.Gone:
+                          error = FeedDownloadError.DoesNotExist;
+                          break;                                
+                      case System.Net.HttpStatusCode.NotModified:
+                          error = FeedDownloadError.None;
+                          break;
+                      case System.Net.HttpStatusCode.Unauthorized:
+                          error = FeedDownloadError.UnsupportedAuth;
+                          break;                                
+                      default:
+                          error = FeedDownloadError.DownloadFailed;
+                          break;
+                      }
+                  }
+              }
+              if (FeedDownloadError.None != error) Hyena.Log.Debug("An error occurred " + error.ToString());
+        }
+       
         /*private void RunConfirmDeleteDialog (bool feed, 
                                              int selCount, 
                                              out bool delete, 
@@ -371,6 +507,11 @@
             RunSubscribeDialog ();
         }
         
+        private void OnOpmlAdd (object sender, EventArgs e)
+        {
+            RunOpmlDialog ();
+        }
+        
         private void OnPodcastUpdate (object sender, EventArgs e)
         {
             foreach (Feed feed in source.FeedModel.SelectedItems) {
Index: src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs
===================================================================
--- src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs	(revision 4212)
+++ src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/PodcastManager/Dialog/PodcastSubscribeDialog.cs	(working copy)
@@ -45,9 +45,30 @@
     internal class PodcastSubscribeDialog : Dialog
     {
         private Entry url_entry;
+        private Label header;
+        private WrapLabel message;
+        private Label sync_text;
         private Gtk.AccelGroup accelGroup;
-        private SyncPreferenceComboBox syncCombo;        
+        private SyncPreferenceComboBox syncCombo;
+    
+        public string Header {
+            set {             
+                header.Markup = String.Format (
+                    "<big><b>{0}</b></big>",
+                    GLib.Markup.EscapeText (value)
+                );
+            }
+        }
 
+        public string Message {
+            get { return message.Markup; }
+            set { message.Markup = value; }
+        }
+
+        public string SyncText {
+            set {  sync_text.Markup = value; }
+        }
+
         public string Url {
             get { return url_entry.Text; }
             set { url_entry.Text = value; }
@@ -58,14 +79,20 @@
             get { return syncCombo.ActiveSyncPreference; }
         }
 
-        public PodcastSubscribeDialog () : base (Catalog.GetString("Subscribe"), null, DialogFlags.Modal | DialogFlags.NoSeparator)
+        //Added the ability to default the combobox to a specified value.
+        // Defaulted to 'One'.
+        public PodcastSubscribeDialog () : this(FeedAutoDownload.One)
         {
+        }
+    
+        public PodcastSubscribeDialog (FeedAutoDownload syncPref) : base (Catalog.GetString("Subscribe"), null, DialogFlags.Modal | DialogFlags.NoSeparator)
+        {
             accelGroup = new Gtk.AccelGroup();
             AddAccelGroup (accelGroup);
-            BuildWindow ();
+            BuildWindow (syncPref);
         }
 
-        private void BuildWindow ()
+        private void BuildWindow (FeedAutoDownload syncPref)
         {
             DefaultWidth = 475;
 
@@ -86,7 +113,7 @@
             VBox contentBox = new VBox();
             contentBox.Spacing = 12;
 
-            Label header = new Label();
+            header = new Label();
             header.Markup = String.Format (
                 "<big><b>{0}</b></big>",
                 GLib.Markup.EscapeText (Catalog.GetString ("Subscribe to New Podcast"))
@@ -95,7 +122,7 @@
             header.Justify = Justification.Left;
             header.SetAlignment (0.0f, 0.0f);
 
-            WrapLabel message = new WrapLabel ();
+            message = new WrapLabel ();
             message.Markup = Catalog.GetString (
                 "Please enter the URL of the podcast to which you would like to subscribe."
             );
@@ -108,14 +135,14 @@
             //expander_children.BorderWidth = 6;
             expander_children.Spacing = 6;
 
-            Label sync_text = new Label (
+            sync_text = new Label (
                 Catalog.GetString ("When new episodes are available:  ")
             );
             
             sync_text.SetAlignment (0.0f, 0.0f);
             sync_text.Justify = Justification.Left;
 
-            syncCombo = new SyncPreferenceComboBox ();
+            syncCombo = new SyncPreferenceComboBox (syncPref);
 
             expander_children.PackStart (sync_text);
             expander_children.PackStart (syncCombo);
Index: src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml
===================================================================
--- src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml	(revision 4212)
+++ src/Extensions/Banshee.Podcasting/Resources/GlobalUI.xml	(working copy)
@@ -3,6 +3,7 @@
       <menu name="MediaMenu" action="MediaMenuAction">
         <placeholder name="BelowOpenLocation">
           <menuitem name="PodcastAdd" action="PodcastAddAction" />
+          <menuitem name="OpmlAdd" action="OpmlAddAction" />
         </placeholder>
       </menu>
     </menubar>
Index: src/Libraries/Migo/Migo.Syndication/RssParser.cs
===================================================================
--- src/Libraries/Migo/Migo.Syndication/RssParser.cs	(revision 4212)
+++ src/Libraries/Migo/Migo.Syndication/RssParser.cs	(working copy)
@@ -68,8 +68,9 @@
                 }
 
                 if (!loaded) {
-                    Hyena.Log.Exception (e);
-                    throw new FormatException ("Invalid XML document.");                                  
+                    //bel 20080706 - I found it helpful to know which RSS feed failed.
+                    Hyena.Log.Exception ("Error parsing feed: " + url + "\n\n".PadRight(80,'-') + "\n" + xml + "\n\n".PadRight(80, '-') + "\n", e);
+                    throw new FormatException ("Invalid XML document.\n\t" + url);                                  
                 }
             }
             CheckRss ();
Index: src/Libraries/Migo/Migo.Syndication/OpmlParser.cs
===================================================================
--- src/Libraries/Migo/Migo.Syndication/OpmlParser.cs	(revision 4212)
+++ src/Libraries/Migo/Migo.Syndication/OpmlParser.cs	(working copy)
@@ -35,9 +35,10 @@
     public class OpmlParser
     {
         private XmlDocument doc;
+        private System.Collections.Generic.List<string> feeds = null;
         //private XmlNamespaceManager ns_mgr;
         
-        public OpmlParser (string url, string xml)
+        public OpmlParser (string xml)
         {
             doc = new XmlDocument ();
             try {
@@ -56,6 +57,30 @@
             VerifyOpml ();
         }
     
+        public System.Collections.Generic.List<string> Feeds 
+        {
+          get 
+          {
+              if (null == feeds) 
+              {
+                  feeds = new System.Collections.Generic.List<string>();
+                  GetFeeds(doc.SelectSingleNode ("/opml/body"));
+              }
+              return feeds;
+          }
+        }
+
+        private void GetFeeds(XmlNode baseNode) 
+        {
+          XmlNodeList nodes = baseNode.SelectNodes("./outline");
+          foreach (XmlNode node in nodes) 
+          {
+            if (null != node.Attributes["xmlUrl"]) feeds.Add(node.Attributes["xmlUrl"].Value);
+            //parse outline nodes recursively.
+            GetFeeds(node);
+          }
+        }
+
         /*public Feed CreateFeed ()
         {
             return UpdateFeed (new Feed ());
Index: src/Libraries/Migo/Migo.Syndication/FeedUpdateTask.cs
===================================================================
--- src/Libraries/Migo/Migo.Syndication/FeedUpdateTask.cs	(revision 4212)
+++ src/Libraries/Migo/Migo.Syndication/FeedUpdateTask.cs	(working copy)
@@ -158,7 +158,7 @@
                         error = FeedDownloadError.None;
                         notify_on_save = feed.LastBuildDate > last_built_at;
                     } catch (FormatException e) {
-                        Log.Exception (e);
+                        Log.Exception ("Error parsing feed: " + feed.Title + " <" + feed.Url + ">", e);
                         error = FeedDownloadError.InvalidFeedFormat;
                     }
                 } else {
