diff --git a/resources/images/opml.png b/resources/images/opml.png new file mode 100644 index 0000000000..f4457d0b51 Binary files /dev/null and b/resources/images/opml.png differ diff --git a/src/calibre/gui2/dialogs/user_profiles.py b/src/calibre/gui2/dialogs/user_profiles.py index 48404f5c03..d06443e3d6 100644 --- a/src/calibre/gui2/dialogs/user_profiles.py +++ b/src/calibre/gui2/dialogs/user_profiles.py @@ -8,11 +8,13 @@ from PyQt4.Qt import (QUrl, QAbstractListModel, Qt, QVariant, QFont) from calibre.web.feeds.recipes import compile_recipe, custom_recipes from calibre.web.feeds.news import AutomaticNewsRecipe from calibre.gui2.dialogs.user_profiles_ui import Ui_Dialog +from calibre.gui2.dialogs.message_box import MessageBox from calibre.gui2 import error_dialog, question_dialog, open_url, \ choose_files, ResizableDialog, NONE, open_local_file from calibre.gui2.widgets import PythonHighlighter from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.icu import sort_key +from calibre.utils.opml import OPML class CustomRecipeModel(QAbstractListModel): @@ -90,6 +92,7 @@ class UserProfiles(ResizableDialog, Ui_Dialog): self.remove_profile_button.clicked[(bool)].connect(self.remove_selected_items) self.add_feed_button.clicked[(bool)].connect(self.add_feed) self.load_button.clicked[()].connect(self.load) + self.opml_button.clicked[()].connect(self.opml_import) self.builtin_recipe_button.clicked[()].connect(self.add_builtin_recipe) self.share_button.clicked[()].connect(self.share) self.show_recipe_files_button.clicked.connect(self.show_recipe_files) @@ -201,15 +204,20 @@ class UserProfiles(ResizableDialog, Ui_Dialog): self.feed_title.setText('') self.feed_url.setText('') - def options_to_profile(self): + def options_to_profile(self, **kw): classname = 'BasicUserRecipe'+str(int(time.time())) - title = unicode(self.profile_title.text()).strip() + if 'nr' in kw: + classname = classname + str(kw['nr']) + title = kw['title'] if 'title' in kw else self.profile_title.text() + title = unicode(title).strip() if not title: title = classname self.profile_title.setText(title) - oldest_article = self.oldest_article.value() - max_articles = self.max_articles.value() - feeds = [i.user_data for i in self.added_feeds.items()] + oldest_article = kw['oldest_article'] if 'oldest_article' in kw else self.oldest_article.value() + max_articles = kw['max_articles'] if 'max_articles' in kw else self.max_articles.value() + feeds = kw['feeds'] \ + if 'feeds' in kw \ + else [i.user_data for i in self.added_feeds.items()] src = '''\ class %(classname)s(%(base_class)s): @@ -346,6 +354,50 @@ class %(classname)s(%(base_class)s): self.model.add(title, profile) self.clear() + def opml_import(self): + opml_files = choose_files(self, 'OPML chooser dialog', + _('Select OPML file'), filters=[(_('OPML'), ['opml'])] ) + + if not opml_files: + return + + opml = OPML(self.oldest_article.value(), self.max_articles.value()); + for opml_file in opml_files: + opml.load(opml_file) + outlines = opml.parse() + nr = 0 + for outline in outlines: + src, title = self.options_to_profile(**{ + 'nr':nr, + 'title':unicode(outline.get('title')), + 'feeds':outline.get('xmlUrl'), + 'oldest_article':self.oldest_article.value(), + 'max_articles':self.max_articles.value(), + }) + + try: + compile_recipe(src) + except Exception as err: + error_dialog(self, _('Invalid input'), + _('
Could not create recipe. Error:
%s')%str(err)).exec_()
+ return
+ profile = src
+ if self._model.has_title(title):
+ if question_dialog(self, _('Replace recipe?'),
+ _('A custom recipe named %s already exists. Do you want to '
+ 'replace it?')%title):
+ self._model.replace_by_title(title, profile)
+ else:
+ return
+ else:
+ self.model.add(title, profile)
+ nr+=1
+ self.clear()
+
+ msg_box = MessageBox(MessageBox.INFO, "Finished", "OPML to Recipe conversion complete", parent=self,
+ show_copy_button=False)
+ msg_box.exec_()
+
def populate_options(self, profile):
self.oldest_article.setValue(profile.oldest_article)
self.max_articles.setValue(profile.max_articles_per_feed)
diff --git a/src/calibre/gui2/dialogs/user_profiles.ui b/src/calibre/gui2/dialogs/user_profiles.ui
index 1e29477e6c..bde6f7e187 100644
--- a/src/calibre/gui2/dialogs/user_profiles.ui
+++ b/src/calibre/gui2/dialogs/user_profiles.ui
@@ -135,6 +135,17 @@
+
%s')%str(err)).exec_()
+ nr+=1
+
+ #recipe_model.add(title, src)
+
+
+ def options_to_profile(self, recipe):
+ classname = 'BasicUserRecipe'+str(recipe.get('nr'))+str(int(time.time()))
+ title = recipe.get('title').strip()
+ if not title:
+ title = classname
+ oldest_article = self.oldest_article
+ max_articles = self.max_articles
+ feeds = recipe.get('feeds')
+
+ src = '''\
+class %(classname)s(%(base_class)s):
+ title = %(title)s
+ oldest_article = %(oldest_article)d
+ max_articles_per_feed = %(max_articles)d
+ auto_cleanup = True
+
+ feeds = %(feeds)s
+'''%dict(classname=classname, title=repr(title),
+ feeds=repr(feeds), oldest_article=oldest_article,
+ max_articles=max_articles,
+ base_class='AutomaticNewsRecipe')
+ return src, title
+
+if __name__ == '__main__':
+ opml = OPML();
+ opml.load('/media/sf_Kenny/Downloads/feedly.opml')
+ outlines = opml.parse()
+ print(len(opml.outlines))
+ opml.import_recipes(outlines)
+