Implement ability to specify a time of day for scheduled news downloads. Fixes #1397 (Inability to Specify exactly download schedule for news)

This commit is contained in:
Kovid Goyal 2009-01-26 16:18:54 -08:00
parent fbaca75458
commit 0bb41a00a5
4 changed files with 253 additions and 98 deletions

View File

@ -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)

View File

@ -6,7 +6,7 @@
<x>0</x>
<y>0</y>
<width>726</width>
<height>551</height>
<height>575</height>
</rect>
</property>
<property name="windowTitle" >
@ -42,25 +42,12 @@
</item>
<item row="0" column="1" >
<layout class="QVBoxLayout" name="verticalLayout_3" >
<item>
<spacer name="horizontalSpacer_3" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="detail_box" >
<property name="title" >
<string>Schedule for download</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" >
<layout class="QVBoxLayout" name="verticalLayout_4" >
<item>
<widget class="QLabel" name="title" >
<property name="font" >
@ -110,70 +97,97 @@
<item>
<widget class="QCheckBox" name="schedule" >
<property name="text" >
<string>&amp;Schedule for download every:</string>
<string>&amp;Schedule for download:</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" >
<item>
<spacer name="horizontalSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDoubleSpinBox" name="interval" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour.</string>
</property>
<property name="suffix" >
<string> days</string>
</property>
<property name="decimals" >
<number>1</number>
</property>
<property name="minimum" >
<double>0.000000000000000</double>
</property>
<property name="maximum" >
<double>365.100000000000023</double>
</property>
<property name="singleStep" >
<double>1.000000000000000</double>
</property>
<property name="value" >
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
<widget class="QWidget" native="1" name="widget" >
<property name="enabled" >
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" >
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QRadioButton" name="daily_button" >
<property name="text" >
<string>Every </string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="day" />
</item>
<item>
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>at</string>
</property>
</widget>
</item>
<item>
<widget class="QTimeEdit" name="time" />
</item>
<item>
<spacer name="horizontalSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" >
<item>
<widget class="QRadioButton" name="interval_button" >
<property name="text" >
<string>Every </string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="interval" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Expanding" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip" >
<string>Interval at which to download this recipe. A value of zero means that the recipe will be downloaded every hour.</string>
</property>
<property name="suffix" >
<string> days</string>
</property>
<property name="decimals" >
<number>1</number>
</property>
<property name="minimum" >
<double>0.000000000000000</double>
</property>
<property name="maximum" >
<double>365.100000000000023</double>
</property>
<property name="singleStep" >
<double>1.000000000000000</double>
</property>
<property name="value" >
<double>1.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="last_downloaded" >
@ -315,8 +329,8 @@
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<x>248</x>
<y>254</y>
<x>613</x>
<y>824</y>
</hint>
<hint type="destinationlabel" >
<x>157</x>
@ -331,8 +345,8 @@
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<x>316</x>
<y>260</y>
<x>681</x>
<y>824</y>
</hint>
<hint type="destinationlabel" >
<x>286</x>
@ -340,5 +354,85 @@
</hint>
</hints>
</connection>
<connection>
<sender>schedule</sender>
<signal>toggled(bool)</signal>
<receiver>widget</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>454</x>
<y>147</y>
</hint>
<hint type="destinationlabel" >
<x>461</x>
<y>168</y>
</hint>
</hints>
</connection>
<connection>
<sender>schedule</sender>
<signal>toggled(bool)</signal>
<receiver>widget</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>458</x>
<y>137</y>
</hint>
<hint type="destinationlabel" >
<x>461</x>
<y>169</y>
</hint>
</hints>
</connection>
<connection>
<sender>daily_button</sender>
<signal>toggled(bool)</signal>
<receiver>day</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>421</x>
<y>186</y>
</hint>
<hint type="destinationlabel" >
<x>500</x>
<y>184</y>
</hint>
</hints>
</connection>
<connection>
<sender>daily_button</sender>
<signal>toggled(bool)</signal>
<receiver>time</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>442</x>
<y>193</y>
</hint>
<hint type="destinationlabel" >
<x>603</x>
<y>183</y>
</hint>
</hints>
</connection>
<connection>
<sender>interval_button</sender>
<signal>toggled(bool)</signal>
<receiver>interval</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<x>428</x>
<y>213</y>
</hint>
<hint type="destinationlabel" >
<x>495</x>
<y>218</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -5,8 +5,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>719</width>
<height>612</height>
<width>738</width>
<height>640</height>
</rect>
</property>
<property name="windowTitle" >
@ -33,8 +33,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>711</width>
<height>572</height>
<width>730</width>
<height>600</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3" >

View File

@ -46,7 +46,7 @@ What formats does |app| support conversion to/from?
| | | | | |
| | PDF | ✔ | ✔ | ✔ |
| | | | | |
| | LRS | | ✔ | |
| | LRS | | ✔ | |
+-------------------+--------+------------------+-----------------------+-----------------------+