Added OPML support to the user_profile dialog

This commit is contained in:
ingkebil 2014-04-01 23:26:35 +02:00
parent 26fd1f8803
commit 05a566c275
4 changed files with 155 additions and 5 deletions

BIN
resources/images/opml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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'),
_('<p>Could not create recipe. Error:<br>%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)

View File

@ -135,6 +135,17 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="opml_button">
<property name="text">
<string>&amp;Import from OPML files</string>
</property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/opml.png</normaloff>:/images/opml.png</iconset>
</property>
</widget>
</item>
</layout>
</widget>
</item>

87
src/calibre/utils/opml.py Normal file
View File

@ -0,0 +1,87 @@
__license__ = 'GPL 3'
__copyright__ = '2014, Kenny Billiau <kennybilliau@gmail.co'
__docformat__ = 'restructuredtext en'
import time
import xml.etree.ElementTree as ET
class OPML(object):
def __init__(self, oldest_article = 7, max_articles = 100):
self.doc = None # xml document
self.outlines = None # parsed outline objects
self.oldest_article = oldest_article
self.max_articles = max_articles
def load(self, filename):
tree = ET.parse(filename)
self.doc = tree.getroot()
def parse(self):
self.outlines = self.doc.findall(u"body/outline")
for outline in self.outlines: # check for groups
#if ('type' not in outline.attrib):
feeds = [] # title, url
for feed in outline.iter('outline'):
if 'type' in feed.attrib:
feeds.append( (feed.get('title'), feed.get('xmlUrl')) )
outline.set('xmlUrl', feeds)
return self.outlines
def import_recipes(self, outlines):
nr = 0
#recipe_model = CustomRecipeModel(RecipeModel())
for outline in outlines:
src, title = self.options_to_profile(dict(
nr=nr,
title=unicode(outline.get('title')),
feeds=outline.get('xmlUrl'),
oldest_article=self.oldest_article,
max_articles=self.max_articles,
base_class='AutomaticNewsRecipe'
))
try:
compile_recipe(src)
add_custom_recipe(title, src)
except Exception as err:
# error dialog should be placed somewhere where it can have a parent
# Left it here as this way only failing feeds will silently fail
error_dialog(None, _('Invalid input'),
_('<p>Could not create recipe. Error:<br>%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)