diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index a4bfe9f665..86b3190c04 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -7,11 +7,11 @@ __docformat__ = 'restructuredtext en' Scheduler for automated recipe downloads ''' -import sys, copy +import sys, copy, time from datetime import datetime, timedelta from PyQt4.Qt import QDialog, QApplication, QLineEdit, QPalette, SIGNAL, QBrush, \ QColor, QAbstractListModel, Qt, QVariant, QFont, QIcon, \ - QFile, QObject, QTimer, QMutex, QMenu, QAction + QFile, QObject, QTimer, QMutex, QMenu, QAction, QTime from calibre import english_sort from calibre.gui2.dialogs.scheduler_ui import Ui_Dialog @@ -66,7 +66,10 @@ class Recipe(object): return self.id == getattr(other, 'id', None) def __repr__(self): - return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), self.schedule) + schedule = self.schedule + if schedule and schedule > 1e5: + schedule = decode_schedule(schedule) + return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), schedule) builtin_recipes = [Recipe(m, r, True) for r, m in zip(recipes, recipe_modules)] @@ -169,6 +172,11 @@ class RecipeModel(QAbstractListModel, SearchQueryParser): return QVariant(icon) return NONE + + def update_recipe_schedule(self, recipe): + for srecipe in self.recipes: + if srecipe == recipe: + srecipe.schedule = recipe.schedule class Search(QLineEdit): @@ -210,7 +218,17 @@ class Search(QLineEdit): text = unicode(self.text()) self.emit(SIGNAL('search(PyQt_PyObject)'), text) - +def encode_schedule(day, hour, minute): + day = 1e7 * (day+1) + hour = 1e4 * (hour+1) + return day + hour + minute + 1 + +def decode_schedule(num): + raw = '%d'%int(num) + day = int(raw[0]) + hour = int(raw[2:4]) + minute = int(raw[-2:]) + return day-1, hour-1, minute-1 class SchedulerDialog(QDialog, Ui_Dialog): @@ -228,17 +246,22 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.connect(self.username, SIGNAL('textEdited(QString)'), self.set_account_info) self.connect(self.password, SIGNAL('textEdited(QString)'), self.set_account_info) self.connect(self.schedule, SIGNAL('stateChanged(int)'), self.do_schedule) - self.connect(self.schedule, SIGNAL('stateChanged(int)'), - lambda state: self.interval.setEnabled(state == Qt.Checked)) self.connect(self.show_password, SIGNAL('stateChanged(int)'), lambda state: self.password.setEchoMode(self.password.Normal if state == Qt.Checked else self.password.Password)) self.connect(self.interval, SIGNAL('valueChanged(double)'), self.do_schedule) + self.connect(self.day, SIGNAL('currentIndexChanged(int)'), self.do_schedule) + self.connect(self.time, SIGNAL('timeChanged(QTime)'), self.do_schedule) + for button in (self.daily_button, self.interval_button): + self.connect(button, SIGNAL('toggled(bool)'), self.do_schedule) self.connect(self.search, SIGNAL('search(PyQt_PyObject)'), self._model.search) self.connect(self._model, SIGNAL('modelReset()'), lambda : self.detail_box.setVisible(False)) self.connect(self.download, SIGNAL('clicked()'), self.download_now) self.search.setFocus(Qt.OtherFocusReason) self.old_news.setValue(gconf['oldest_news']) self.rnumber.setText(_('%d recipes')%self._model.rowCount(None)) + for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'), + _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')): + self.day.addItem(day) def download_now(self): recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) @@ -252,6 +275,8 @@ class SchedulerDialog(QDialog, Ui_Dialog): config[key] = (username, password) if username and password else None def do_schedule(self, *args): + if not getattr(self, 'allow_scheduling', False): + return recipe = self.recipes.currentIndex() if not recipe.isValid(): return @@ -263,17 +288,26 @@ class SchedulerDialog(QDialog, Ui_Dialog): else: recipe.last_downloaded = datetime.fromordinal(1) recipes.append(recipe) - recipe.schedule = self.interval.value() - if recipe.schedule < 0.1: - recipe.schedule = 1/24. if recipe.needs_subscription and not config['recipe_account_info_%s'%recipe.id]: error_dialog(self, _('Must set account information'), _('This recipe requires a username and password')).exec_() self.schedule.setCheckState(Qt.Unchecked) return + if self.interval_button.isChecked(): + recipe.schedule = self.interval.value() + if recipe.schedule < 0.1: + recipe.schedule = 1/24. + else: + day_of_week = self.day.currentIndex() - 1 + if day_of_week < 0: + day_of_week = 7 + t = self.time.time() + hour, minute = t.hour(), t.minute() + recipe.schedule = encode_schedule(day_of_week, hour, minute) else: if recipe in recipes: recipes.remove(recipe) save_recipes(recipes) + self._model.update_recipe_schedule(recipe) self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes) def show_recipe(self, index): @@ -282,8 +316,26 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.title.setText(recipe.title) self.author.setText(_('Created by: ') + recipe.author) self.description.setText(recipe.description if recipe.description else '') + self.allow_scheduling = False + schedule = -1 if recipe.schedule is None else recipe.schedule + if schedule < 1e5 and schedule >= 0: + self.interval.setValue(schedule) + self.interval_button.setChecked(True) + self.day.setEnabled(False), self.time.setEnabled(False) + else: + if schedule > 0: + day, hour, minute = decode_schedule(schedule) + else: + day, hour, minute = 7, 12, 0 + if day == 7: + day = -1 + self.day.setCurrentIndex(day+1) + self.time.setTime(QTime(hour, minute)) + self.daily_button.setChecked(True) + self.interval_button.setChecked(False) + self.interval.setEnabled(False) self.schedule.setChecked(recipe.schedule is not None) - self.interval.setValue(recipe.schedule if recipe.schedule is not None else 1) + self.allow_scheduling = True self.detail_box.setVisible(True) self.account.setVisible(recipe.needs_subscription) self.interval.setEnabled(self.schedule.checkState() == Qt.Checked) @@ -365,13 +417,22 @@ class Scheduler(QObject): self.dirtied = False needs_downloading = set([]) self.debug('Checking...') - now = datetime.utcnow() + nowt = datetime.utcnow() for recipe in self.recipes: if recipe.schedule is None: continue - delta = now - recipe.last_downloaded - if delta > timedelta(days=recipe.schedule): - needs_downloading.add(recipe) + delta = nowt - recipe.last_downloaded + if recipe.schedule < 1e5: + if delta > timedelta(days=recipe.schedule): + needs_downloading.add(recipe) + else: + day, hour, minute = decode_schedule(recipe.schedule) + now = time.localtime() + day_matches = day > 6 or day == now.tm_wday + tnow = now.tm_hour*60 + now.tm_min + matches = day_matches and (hour*60+minute) < tnow + if matches and delta >= timedelta(days=1): + needs_downloading.add(recipe) self.debug('Needs downloading:', needs_downloading) diff --git a/src/calibre/gui2/dialogs/scheduler.ui b/src/calibre/gui2/dialogs/scheduler.ui index 818282da89..2cf22f7191 100644 --- a/src/calibre/gui2/dialogs/scheduler.ui +++ b/src/calibre/gui2/dialogs/scheduler.ui @@ -6,7 +6,7 @@ 0 0 726 - 551 + 575 @@ -42,25 +42,12 @@ - - - - Qt::Horizontal - - - - 40 - 0 - - - - Schedule for download - + @@ -110,70 +97,97 @@ - &Schedule for download every: + &Schedule for download: - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 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 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + false + + + + + + + + Every + + + + + + + + + + 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 + + + + + + + @@ -315,8 +329,8 @@ accept() - 248 - 254 + 613 + 824 157 @@ -331,8 +345,8 @@ reject() - 316 - 260 + 681 + 824 286 @@ -340,5 +354,85 @@ + + schedule + toggled(bool) + widget + setDisabled(bool) + + + 454 + 147 + + + 461 + 168 + + + + + schedule + toggled(bool) + widget + setEnabled(bool) + + + 458 + 137 + + + 461 + 169 + + + + + daily_button + toggled(bool) + day + setEnabled(bool) + + + 421 + 186 + + + 500 + 184 + + + + + daily_button + toggled(bool) + time + setEnabled(bool) + + + 442 + 193 + + + 603 + 183 + + + + + interval_button + toggled(bool) + interval + setEnabled(bool) + + + 428 + 213 + + + 495 + 218 + + + diff --git a/src/calibre/gui2/dialogs/user_profiles.ui b/src/calibre/gui2/dialogs/user_profiles.ui index 8177e6f3dd..10e7b9d1df 100644 --- a/src/calibre/gui2/dialogs/user_profiles.ui +++ b/src/calibre/gui2/dialogs/user_profiles.ui @@ -5,8 +5,8 @@ 0 0 - 719 - 612 + 738 + 640 @@ -33,8 +33,8 @@ 0 0 - 711 - 572 + 730 + 600 diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 6142852920..f587a0122a 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -46,7 +46,7 @@ What formats does |app| support conversion to/from? | | | | | | | | PDF | ✔ | ✔ | ✔ | | | | | | | -| | LRS | | ✔ | ✔ | +| | LRS | | ✔ | | +-------------------+--------+------------------+-----------------------+-----------------------+