mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
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:
parent
fbaca75458
commit
0bb41a00a5
@ -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)
|
||||
|
||||
|
@ -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>&Schedule for download every:</string>
|
||||
<string>&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>
|
||||
|
@ -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" >
|
||||
|
@ -46,7 +46,7 @@ What formats does |app| support conversion to/from?
|
||||
| | | | | |
|
||||
| | PDF | ✔ | ✔ | ✔ |
|
||||
| | | | | |
|
||||
| | LRS | | ✔ | ✔ |
|
||||
| | LRS | | ✔ | |
|
||||
+-------------------+--------+------------------+-----------------------+-----------------------+
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user