diff --git a/Changelog.yaml b/Changelog.yaml index dfc9b9efe6..b297823841 100644 --- a/Changelog.yaml +++ b/Changelog.yaml @@ -19,6 +19,10 @@ # new recipes: # - title: +# - title: "Launch of a new website that catalogues DRM free books. http://drmfree.calibre-ebook.com" +# description: "A growing catalogue of DRM free books. Books that you actually own after buying instead of renting." +# type: major + - version: 0.7.47 date: 2011-02-25 @@ -88,8 +92,8 @@ - title: "Various Romanian news sources" author: Silviu Coatara - - title: "Osnews.pl and SwiatKindle" - author: Mori + - title: "Osnews.pl and SwiatCzytnikow" + author: Tomasz Dlugosz - title: "Roger Ebert Journal" author: Shane Erstad diff --git a/resources/recipes/buffalo_news.recipe b/resources/recipes/buffalo_news.recipe new file mode 100644 index 0000000000..92c96757ae --- /dev/null +++ b/resources/recipes/buffalo_news.recipe @@ -0,0 +1,56 @@ +__license__ = 'GPL v3' +__author__ = 'Todd Chapman' +__copyright__ = 'Todd Chapman' +__version__ = 'v0.1' +__date__ = '26 February 2011' + +''' +http://www.buffalonews.com/RSS/ +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1298680852(BasicNewsRecipe): + title = u'Buffalo News' + __author__ = 'ChappyOnIce' + language = 'en' + oldest_article = 2 + max_articles_per_feed = 20 + encoding = 'utf-8' + remove_javascript = True + keep_only_tags = [ + dict(name='div', attrs={'class':['main-content-left']}) + ] + + remove_tags = [ + dict(name='div', attrs={'id':['commentCount']}), + dict(name='div', attrs={'class':['story-list-links']}) + ] + + remove_tags_after = dict(name='div', attrs={'class':['body storyContent']}) + conversion_options = { + 'base_font_size' : 14, + } + feeds = [(u'City of Buffalo', u'http://www.buffalonews.com/city/communities/buffalo/?widget=rssfeed&view=feed&contentId=77944'), + (u'Southern Erie County', u'http://www.buffalonews.com/city/communities/southern-erie/?widget=rssfeed&view=feed&contentId=77944'), + (u'Eastern Erie County', u'http://www.buffalonews.com/city/communities/eastern-erie/?widget=rssfeed&view=feed&contentId=77944'), + (u'Southern Tier', u'http://www.buffalonews.com/city/communities/southern-tier/?widget=rssfeed&view=feed&contentId=77944'), + (u'Niagara County', u'http://www.buffalonews.com/city/communities/niagara-county/?widget=rssfeed&view=feed&contentId=77944'), + (u'Business', u'http://www.buffalonews.com/business/?widget=rssfeed&view=feed&contentId=77944'), + (u'MoneySmart', u'http://www.buffalonews.com/business/moneysmart/?widget=rssfeed&view=feed&contentId=77944'), + (u'Bills & NFL', u'http://www.buffalonews.com/sports/bills-nfl/?widget=rssfeed&view=feed&contentId=77944'), + (u'Sabres & NHL', u'http://www.buffalonews.com/sports/sabres-nhl/?widget=rssfeed&view=feed&contentId=77944'), + (u'Bob DiCesare', u'http://www.buffalonews.com/sports/columns/bob-dicesare/?widget=rssfeed&view=feed&contentId=77944'), + (u'Bucky Gleason', u'http://www.buffalonews.com/sports/columns/bucky-gleason/?widget=rssfeed&view=feed&contentId=77944'), + (u'Mark Gaughan', u'http://www.buffalonews.com/sports/bills-nfl/inside-the-nfl/?widget=rssfeed&view=feed&contentId=77944'), + (u'Mike Harrington', u'http://www.buffalonews.com/sports/columns/mike-harrington/?widget=rssfeed&view=feed&contentId=77944'), + (u'Jerry Sullivan', u'http://www.buffalonews.com/sports/columns/jerry-sullivan/?widget=rssfeed&view=feed&contentId=77944'), + (u'Other Sports Columns', u'http://www.buffalonews.com/sports/columns/other-sports-columns/?widget=rssfeed&view=feed&contentId=77944'), + (u'Life', u'http://www.buffalonews.com/life/?widget=rssfeed&view=feed&contentId=77944'), + (u'Bruce Andriatch', u'http://www.buffalonews.com/city/columns/bruce-andriatch/?widget=rssfeed&view=feed&contentId=77944'), + (u'Donn Esmonde', u'http://www.buffalonews.com/city/columns/donn-esmonde/?widget=rssfeed&view=feed&contentId=77944'), + (u'Rod Watson', u'http://www.buffalonews.com/city/columns/rod-watson/?widget=rssfeed&view=feed&contentId=77944'), + (u'Entertainment', u'http://www.buffalonews.com/entertainment/?widget=rssfeed&view=feed&contentId=77944'), + (u'Off Main Street', u'http://www.buffalonews.com/city/columns/off-main-street/?widget=rssfeed&view=feed&contentId=77944'), + (u'Editorials', u'http://www.buffalonews.com/editorial-page/buffalo-news-editorials/?widget=rssfeed&view=feed&contentId=77944') + ] diff --git a/resources/recipes/dotpod.recipe b/resources/recipes/dotpod.recipe new file mode 100644 index 0000000000..b04945e6d4 --- /dev/null +++ b/resources/recipes/dotpod.recipe @@ -0,0 +1,27 @@ +__license__ = 'GPL v3' +__copyright__ = '2011-2011, Federico Escalada ' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Dotpod(BasicNewsRecipe): + __author__ = 'Federico Escalada' + description = 'Tecnologia y Comunicacion Audiovisual' + encoding = 'utf-8' + language = 'es' + max_articles_per_feed = 100 + no_stylesheets = True + oldest_article = 7 + publication_type = 'blog' + title = 'Dotpod' + authors = 'Federico Picone' + + conversion_options = { + 'authors' : authors + ,'comments' : description + ,'language' : language + } + + feeds = [('Dotpod', 'http://www.dotpod.com.ar/feed/')] + + remove_tags = [dict(name='div', attrs={'class':'feedflare'})] + diff --git a/src/calibre/gui2/actions/fetch_news.py b/src/calibre/gui2/actions/fetch_news.py index 5c2a5e9663..fe51012e31 100644 --- a/src/calibre/gui2/actions/fetch_news.py +++ b/src/calibre/gui2/actions/fetch_news.py @@ -58,6 +58,20 @@ class FetchNewsAction(InterfaceAction): self.scheduler.recipe_download_failed(arg) return self.gui.job_exception(job) id = self.gui.library_view.model().add_news(pt.name, arg) + + # Arg may contain a "keep_issues" variable. If it is non-zero, + # delete all but newest x issues. + try: + keep_issues = int(arg['keep_issues']) + except: + keep_issues = 0 + if keep_issues > 0: + ids_with_tag = list(sorted(self.gui.library_view.model(). + db.tags_older_than(arg['title'], None), reverse=True)) + ids_to_delete = ids_with_tag[keep_issues:] + if ids_to_delete: + self.gui.library_view.model().delete_books_by_id(ids_to_delete) + self.gui.library_view.model().reset() sync = self.gui.news_to_be_synced sync.add(id) diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index b6a3bed3eb..48d0d67255 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -10,11 +10,9 @@ Scheduler for automated recipe downloads from datetime import timedelta from PyQt4.Qt import QDialog, SIGNAL, Qt, QTime, QObject, QMenu, \ - QAction, QIcon, QMutex, QTimer, pyqtSignal, QWidget, QHBoxLayout, \ - QLabel + QAction, QIcon, QMutex, QTimer, pyqtSignal from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog -from calibre.gui2.search_box import SearchBox2 from calibre.gui2 import config as gconf, error_dialog from calibre.web.feeds.recipes.model import RecipeModel from calibre.ptempfile import PersistentTemporaryFile @@ -28,18 +26,12 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.setupUi(self) self.recipe_model = recipe_model self.recipe_model.do_refresh() + self.count_label.setText( + _('%s news sources') % + self.recipe_model.showing_count) - self._cont = QWidget(self) - self._cont.l = QHBoxLayout() - self._cont.setLayout(self._cont.l) - self._cont.la = QLabel(_('&Search:')) - self._cont.l.addWidget(self._cont.la, 1) - self.search = SearchBox2(self) - self._cont.l.addWidget(self.search, 100) - self._cont.la.setBuddy(self.search) - self.search.setMinimumContentsLength(25) self.search.initialize('scheduler_search_history') - self.recipe_box.layout().insertWidget(0, self._cont) + self.search.setMinimumContentsLength(15) self.search.search.connect(self.recipe_model.search) self.recipe_model.searched.connect(self.search.search_done, type=Qt.QueuedConnection) @@ -153,9 +145,12 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.recipe_model.un_schedule_recipe(urn) add_title_tag = self.add_title_tag.isChecked() + keep_issues = u'0' + if self.keep_issues.isEnabled(): + keep_issues = unicode(self.keep_issues.value()) custom_tags = unicode(self.custom_tags.text()).strip() custom_tags = [x.strip() for x in custom_tags.split(',')] - self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags) + self.recipe_model.customize_recipe(urn, add_title_tag, custom_tags, keep_issues) return True def initialize_detail_box(self, urn): @@ -215,9 +210,16 @@ class SchedulerDialog(QDialog, Ui_Dialog): if d < timedelta(days=366): self.last_downloaded.setText(_('Last downloaded')+': '+tm) - add_title_tag, custom_tags = customize_info + add_title_tag, custom_tags, keep_issues = customize_info self.add_title_tag.setChecked(add_title_tag) self.custom_tags.setText(u', '.join(custom_tags)) + try: + keep_issues = int(keep_issues) + except: + keep_issues = 0 + self.keep_issues.setValue(keep_issues) + self.keep_issues.setEnabled(self.add_title_tag.isChecked()) + class Scheduler(QObject): @@ -299,7 +301,7 @@ class Scheduler(QObject): un = pw = None if account_info is not None: un, pw = account_info - add_title_tag, custom_tags = customize_info + add_title_tag, custom_tags, keep_issues = customize_info script = self.recipe_model.get_recipe(urn) pt = PersistentTemporaryFile('_builtin.recipe') pt.write(script) @@ -312,6 +314,7 @@ class Scheduler(QObject): 'recipe':pt.name, 'title':recipe.get('title',''), 'urn':urn, + 'keep_issues':keep_issues } self.download_queue.add(urn) self.start_recipe_fetch.emit(arg) diff --git a/src/calibre/gui2/dialogs/scheduler.ui b/src/calibre/gui2/dialogs/scheduler.ui index 8e6ab37162..26953bbe16 100644 --- a/src/calibre/gui2/dialogs/scheduler.ui +++ b/src/calibre/gui2/dialogs/scheduler.ui @@ -14,358 +14,403 @@ Schedule news download - + :/images/scheduler.png:/images/scheduler.png - - - - - Recipes + + + + + &Search: + + + search - - - - - false - - - - 16 - 16 - - - - true - - - true - - - - - - - Download all scheduled recipes at once - - - Download &all scheduled - - - - - - - - - - - - - - - - QFrame::NoFrame + + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 469 + 504 + + + + + 0 - - true - - - - - 0 - 0 - 375 - 502 - - - - + + + 0 - - - - - 0 - 100 - - - - 0 - - - - &Schedule - - + + + &Schedule + + + + + + blurb + + + true + + + true + + + + + + + &Schedule for download: + + + + + - + - blurb - - - Qt::RichText - - - true - - - true + Every - + + + + day + + + + + Monday + + + + + Tuesday + + + + + Wednesday + + + + + Thursday + + + + + Friday + + + + + Saturday + + + + + Sunday + + + + + + - &Schedule for download: + at - - - - - Every - - - - - - - - day - - - - - Monday - - - - - Tuesday - - - - - Wednesday - - - - - Thursday - - - - - Friday - - - - - Saturday - - - - - Sunday - - - - - - - - at - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + - - - - - Every - - - - - - - - 0 - 0 - - - - Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour. - - - days - - - 1 - - - 0.000000000000000 - - - 365.100000000000023 - - - 1.000000000000000 - - - 1.000000000000000 - - - - - - - - - - - - - - - - &Account - - - - - - - - - &Username: - - - username - - - - - - - &Password: - - - password - - - - - - - QLineEdit::Password - - - - - - - &Show password - - - - - - - - - - For the scheduling to work, you must leave calibre running. - - - true - - - - - - - - &Advanced - - - - - - Add &title as tag - - - - - - - &Extra tags: - - - custom_tags - - - - - - - - + - Qt::Vertical + Qt::Horizontal - 20 - 40 + 40 + 20 - - - - - - - &Download now - - - - - + + + + + + + Every + + + + + + + + 0 + 0 + + + + Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour. + + + days + + + 1 + + + 0.000000000000000 + + + 365.100000000000023 + + + 1.000000000000000 + + + 1.000000000000000 + + + + + + + + + + + + true + + + + + + + &Account + + + + + + + + + &Username: + + + username + + + + + + + &Password: + + + password + + + + + + + QLineEdit::Password + + + + + + + &Show password + + + + + + + + + + For the scheduling to work, you must leave calibre running. + + + true + + + + + + + + &Advanced + + + + + + Add &title as tag + + + + + + + &Extra tags: + + + custom_tags + + + + + + + Maximum number of copies (issues) of this recipe to keep. Set to 0 to keep all (disable). + + + &Keep at most: + + + keep_issues + + + + + + + <p>When set, this option will cause calibre to keep, at most, the specified number of issues of this periodical. Every time a new issue is downloaded, the oldest one is deleted, if the total is larger than this number. +<p>Note that this feature only works if you have the option to add the title as tag checked, above. +<p>Also, the setting for deleting periodicals older than a number of days, below, takes priority over this setting. + + + all issues + + + issues + + + 100000 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + &Download now + + + + + + + + + + + + 0 + 0 + + + + false + + + + 16 + 16 + + + + true + + + true + + + + + + + + + &Delete downloaded news older than: + + + old_news + + + + + + + <p>Delete downloaded news older than the specified number of days. Set to zero to disable. +<p>You can also control the maximum number of issues of a specific periodical that are kept by clicking the Advanced tab for that periodical above. + + + never delete + + + days + + + 1000 + - + Qt::Horizontal @@ -375,24 +420,35 @@ - - + + - Delete downloaded news older than the specified number of days. Set to zero to disable. + Download all scheduled news sources at once - - days + + Download &all scheduled - - Delete downloaded news older than + + + + + + - - 1000 + + Qt::AlignCenter + + + SearchBox2 + QComboBox +
calibre/gui2/search_box.h
+
+
@@ -436,12 +492,12 @@ setEnabled(bool) - 456 - 173 + 458 + 155 - 537 - 176 + 573 + 158 @@ -452,12 +508,12 @@ setEnabled(bool) - 456 - 173 + 458 + 155 - 647 - 176 + 684 + 157 @@ -468,12 +524,28 @@ setEnabled(bool) - 456 - 239 + 458 + 212 - 495 - 218 + 752 + 215 + + + + + add_title_tag + toggled(bool) + keep_issues + setEnabled(bool) + + + 508 + 42 + + + 577 + 108 diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 5986717753..8abe2d433d 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -376,7 +376,7 @@ class TagsView(QTreeView): # {{{ action='delete_user_category', key=key)) self.context_menu.addSeparator() # Hide/Show/Restore categories -# if not key.startswith('@') or key.find('.') < 0: + #if not key.startswith('@') or key.find('.') < 0: self.context_menu.addAction(_('Hide category %s') % category, partial(self.context_menu_handler, action='hide', category=key)) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 4be2ba4340..bf3e9c8a14 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1501,13 +1501,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): ############# End get_categories def tags_older_than(self, tag, delta): + ''' + Return the ids of all books having the tag ``tag`` that are older than + than the specified time. tag comparison is case insensitive. + + :param delta: A timedelta object or None. If None, then all ids with + the tag are returned. + ''' tag = tag.lower().strip() now = nowf() tindex = self.FIELD_MAP['timestamp'] gindex = self.FIELD_MAP['tags'] for r in self.data._data: if r is not None: - if (now - r[tindex]) > delta: + if delta is None or (now - r[tindex]) > delta: tags = r[gindex] if tags and tag in [x.strip() for x in tags.lower().split(',')]: diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index cb7f4d62ff..8a78815751 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -81,7 +81,7 @@ Device Integration What devices does |app| support? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -At the moment |app| has full support for the SONY PRS line, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle line, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. +At the moment |app| has full support for the SONY PRS line, Barnes & Noble Nook line, Cybook Gen 3/Opus, Amazon Kindle line, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook line, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3 and clones, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Connect to folder` function you can use it with any ebook reader that exports itself as a USB disk. How can I help get my device supported in |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/calibre/web/feeds/recipes/collection.py b/src/calibre/web/feeds/recipes/collection.py index 5dd360213b..cd5a220dc3 100644 --- a/src/calibre/web/feeds/recipes/collection.py +++ b/src/calibre/web/feeds/recipes/collection.py @@ -201,12 +201,14 @@ class SchedulerConfig(object): self.root.append(sr) self.write_scheduler_file() - def customize_recipe(self, urn, add_title_tag, custom_tags): + # 'keep_issues' argument for recipe-specific number of copies to keep + def customize_recipe(self, urn, add_title_tag, custom_tags, keep_issues): with self.lock: for x in list(self.iter_customization()): if x.get('id') == urn: self.root.remove(x) cs = E.recipe_customization({ + 'keep_issues' : keep_issues, 'id' : urn, 'add_title_tag' : 'yes' if add_title_tag else 'no', 'custom_tags' : ','.join(custom_tags), @@ -317,16 +319,18 @@ class SchedulerConfig(object): return x.get('username', ''), x.get('password', '') def get_customize_info(self, urn): + keep_issues = 0 add_title_tag = True custom_tags = [] with self.lock: for x in self.iter_customization(): if x.get('id', False) == urn: + keep_issues = x.get('keep_issues', '0') add_title_tag = x.get('add_title_tag', 'yes') == 'yes' custom_tags = [i.strip() for i in x.get('custom_tags', '').split(',')] break - return add_title_tag, custom_tags + return add_title_tag, custom_tags, keep_issues def get_schedule_info(self, urn): with self.lock: diff --git a/src/calibre/web/feeds/recipes/model.py b/src/calibre/web/feeds/recipes/model.py index 559a5c08dd..553fdcc3c3 100644 --- a/src/calibre/web/feeds/recipes/model.py +++ b/src/calibre/web/feeds/recipes/model.py @@ -196,6 +196,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): lang_map = {} self.all_urns = set([]) self.showing_count = 0 + self.builtin_count = 0 for x in self.custom_recipe_collection: urn = x.get('id') self.all_urns.add(urn) @@ -211,6 +212,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): lang_map[lang] = factory(NewsCategory, new_root, lang) factory(NewsItem, lang_map[lang], urn, x.get('title')) self.showing_count += 1 + self.builtin_count += 1 for x in self.scheduler_config.iter_recipes(): urn = x.get('id') if urn not in self.all_urns: @@ -354,9 +356,9 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): self.scheduler_config.schedule_recipe(self.recipe_from_urn(urn), sched_type, schedule) - def customize_recipe(self, urn, add_title_tag, custom_tags): + def customize_recipe(self, urn, add_title_tag, custom_tags, keep_issues): self.scheduler_config.customize_recipe(urn, add_title_tag, - custom_tags) + custom_tags, keep_issues) def get_to_be_downloaded_recipes(self): ans = self.scheduler_config.get_to_be_downloaded_recipes()