mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Buff up jobs management
This commit is contained in:
parent
1461146624
commit
ef0df0e877
@ -1,4 +1,4 @@
|
|||||||
UI = main_ui.py dialogs/metadata_single_ui.py dialogs/metadata_bulk_ui.py
|
UI = main_ui.py dialogs/metadata_single_ui.py dialogs/metadata_bulk_ui.py dialogs/jobs_ui.py
|
||||||
RC = images_rc.pyc
|
RC = images_rc.pyc
|
||||||
|
|
||||||
%_ui.py : %.ui
|
%_ui.py : %.ui
|
||||||
|
@ -12,9 +12,7 @@
|
|||||||
## You should have received a copy of the GNU General Public License along
|
## You should have received a copy of the GNU General Public License along
|
||||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
|
||||||
import traceback
|
from PyQt4.QtCore import QThread, SIGNAL, QObject
|
||||||
|
|
||||||
from PyQt4.QtCore import QThread, SIGNAL, QObject, Qt
|
|
||||||
|
|
||||||
from libprs500.devices.prs500.driver import PRS500
|
from libprs500.devices.prs500.driver import PRS500
|
||||||
|
|
||||||
@ -45,37 +43,6 @@ class DeviceDetector(QThread):
|
|||||||
device[1] ^= True
|
device[1] ^= True
|
||||||
self.msleep(self.sleep_time)
|
self.msleep(self.sleep_time)
|
||||||
|
|
||||||
class DeviceJob(QThread):
|
|
||||||
'''
|
|
||||||
Worker thread that communicates with device.
|
|
||||||
'''
|
|
||||||
def __init__(self, id, mutex, func, *args, **kwargs):
|
|
||||||
QThread.__init__(self)
|
|
||||||
self.id = id
|
|
||||||
self.func = func
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.mutex = mutex
|
|
||||||
self.result = None
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
if self.mutex != None:
|
|
||||||
self.mutex.lock()
|
|
||||||
last_traceback, exception = None, None
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
self.result = self.func(self.progress_update, *self.args, **self.kwargs)
|
|
||||||
except Exception, err:
|
|
||||||
exception = err
|
|
||||||
last_traceback = traceback.format_exc()
|
|
||||||
finally:
|
|
||||||
if self.mutex != None:
|
|
||||||
self.mutex.unlock()
|
|
||||||
self.emit(SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
|
||||||
self.id, self.result, exception, last_traceback)
|
|
||||||
|
|
||||||
def progress_update(self, val):
|
|
||||||
self.emit(SIGNAL('status_update(int)'), int(val))
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceManager(QObject):
|
class DeviceManager(QObject):
|
||||||
@ -87,6 +54,7 @@ class DeviceManager(QObject):
|
|||||||
def info_func(self):
|
def info_func(self):
|
||||||
''' Return callable that returns device information and free space on device'''
|
''' Return callable that returns device information and free space on device'''
|
||||||
def get_device_information(updater):
|
def get_device_information(updater):
|
||||||
|
'''Get device information'''
|
||||||
self.device.set_progress_reporter(updater)
|
self.device.set_progress_reporter(updater)
|
||||||
info = self.device.get_device_information(end_session=False)
|
info = self.device.get_device_information(end_session=False)
|
||||||
info = [i.replace('\x00', '').replace('\x01', '') for i in info]
|
info = [i.replace('\x00', '').replace('\x01', '') for i in info]
|
||||||
@ -98,6 +66,7 @@ class DeviceManager(QObject):
|
|||||||
def books_func(self):
|
def books_func(self):
|
||||||
'''Return callable that returns the list of books on device as two booklists'''
|
'''Return callable that returns the list of books on device as two booklists'''
|
||||||
def books(updater):
|
def books(updater):
|
||||||
|
'''Get metadata from device'''
|
||||||
self.device.set_progress_reporter(updater)
|
self.device.set_progress_reporter(updater)
|
||||||
mainlist = self.device.books(oncard=False, end_session=False)
|
mainlist = self.device.books(oncard=False, end_session=False)
|
||||||
cardlist = self.device.books(oncard=True)
|
cardlist = self.device.books(oncard=True)
|
||||||
@ -107,14 +76,17 @@ class DeviceManager(QObject):
|
|||||||
def sync_booklists_func(self):
|
def sync_booklists_func(self):
|
||||||
'''Upload booklists to device'''
|
'''Upload booklists to device'''
|
||||||
def sync_booklists(updater, booklists):
|
def sync_booklists(updater, booklists):
|
||||||
|
'''Sync metadata to device'''
|
||||||
self.device.set_progress_reporter(updater)
|
self.device.set_progress_reporter(updater)
|
||||||
self.device.sync_booklists(booklists)
|
self.device.sync_booklists(booklists, end_session=False)
|
||||||
|
return self.device.card_prefix(end_session=False), self.device.free_space()
|
||||||
return sync_booklists
|
return sync_booklists
|
||||||
|
|
||||||
def upload_books_func(self):
|
def upload_books_func(self):
|
||||||
'''Upload books to device'''
|
'''Upload books to device'''
|
||||||
def upload_books(updater, files, names, on_card=False):
|
def upload_books(updater, files, names, on_card=False):
|
||||||
return self.device.upload_books(files, names, on_card, end_session=True)
|
'''Upload books to device: '''
|
||||||
|
return self.device.upload_books(files, names, on_card, end_session=False)
|
||||||
return upload_books
|
return upload_books
|
||||||
|
|
||||||
def add_books_to_metadata(self, locations, metadata, booklists):
|
def add_books_to_metadata(self, locations, metadata, booklists):
|
||||||
@ -123,6 +95,7 @@ class DeviceManager(QObject):
|
|||||||
def delete_books_func(self):
|
def delete_books_func(self):
|
||||||
'''Remove books from device'''
|
'''Remove books from device'''
|
||||||
def delete_books(updater, paths):
|
def delete_books(updater, paths):
|
||||||
|
'''Delete books from device'''
|
||||||
self.device.delete_books(paths, end_session=True)
|
self.device.delete_books(paths, end_session=True)
|
||||||
return delete_books
|
return delete_books
|
||||||
|
|
||||||
|
@ -17,10 +17,11 @@
|
|||||||
from PyQt4.QtCore import QObject
|
from PyQt4.QtCore import QObject
|
||||||
from PyQt4.QtGui import QDialog
|
from PyQt4.QtGui import QDialog
|
||||||
|
|
||||||
class ModalDialog(QObject):
|
class Dialog(QObject):
|
||||||
def __init__(self, window):
|
def __init__(self, window):
|
||||||
QObject.__init__(self, window)
|
QObject.__init__(self, window)
|
||||||
self.dialog = QDialog(window)
|
self.dialog = QDialog(window)
|
||||||
self.accept = self.dialog.accept
|
self.accept = self.dialog.accept
|
||||||
self.reject = self.dialog.reject
|
self.reject = self.dialog.reject
|
||||||
self.window = window
|
self.window = window
|
||||||
|
self.isVisible = self.dialog.isVisible
|
||||||
|
40
src/libprs500/gui2/dialogs/jobs.py
Normal file
40
src/libprs500/gui2/dialogs/jobs.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
## Copyright (C) 2007 Kovid Goyal kovid@kovidgoyal.net
|
||||||
|
## This program is free software; you can redistribute it and/or modify
|
||||||
|
## it under the terms of the GNU General Public License as published by
|
||||||
|
## the Free Software Foundation; either version 2 of the License, or
|
||||||
|
## (at your option) any later version.
|
||||||
|
##
|
||||||
|
## This program is distributed in the hope that it will be useful,
|
||||||
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
## GNU General Public License for more details.
|
||||||
|
##
|
||||||
|
## You should have received a copy of the GNU General Public License along
|
||||||
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
'''Display active jobs'''
|
||||||
|
|
||||||
|
from PyQt4.QtCore import Qt, QObject, SIGNAL
|
||||||
|
|
||||||
|
from libprs500.gui2.dialogs import Dialog
|
||||||
|
from libprs500.gui2.dialogs.jobs_ui import Ui_JobsDialog
|
||||||
|
from libprs500 import __appname__
|
||||||
|
|
||||||
|
class JobsDialog(Ui_JobsDialog, Dialog):
|
||||||
|
def __init__(self, window, model):
|
||||||
|
Ui_JobsDialog.__init__(self)
|
||||||
|
Dialog.__init__(self, window)
|
||||||
|
self.setupUi(self.dialog)
|
||||||
|
self.jobs_view.setModel(model)
|
||||||
|
self.model = model
|
||||||
|
self.dialog.setWindowModality(Qt.NonModal)
|
||||||
|
self.dialog.setWindowTitle(__appname__ + ' - Active Jobs')
|
||||||
|
QObject.connect(self.jobs_view.model(), SIGNAL('modelReset()'),
|
||||||
|
self.jobs_view.resizeColumnsToContents)
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
self.dialog.show()
|
||||||
|
self.jobs_view.resizeColumnsToContents()
|
||||||
|
|
||||||
|
def hide(self):
|
||||||
|
self.dialog.hide()
|
50
src/libprs500/gui2/dialogs/jobs.ui
Normal file
50
src/libprs500/gui2/dialogs/jobs.ui
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<ui version="4.0" >
|
||||||
|
<class>JobsDialog</class>
|
||||||
|
<widget class="QDialog" name="JobsDialog" >
|
||||||
|
<property name="geometry" >
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>633</width>
|
||||||
|
<height>542</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle" >
|
||||||
|
<string>Active Jobs</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon" >
|
||||||
|
<iconset resource="../images.qrc" >:/images/jobs.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" >
|
||||||
|
<item>
|
||||||
|
<widget class="QTableView" name="jobs_view" >
|
||||||
|
<property name="contextMenuPolicy" >
|
||||||
|
<enum>Qt::NoContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="editTriggers" >
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="alternatingRowColors" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode" >
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior" >
|
||||||
|
<enum>QAbstractItemView::SelectRows</enum>
|
||||||
|
</property>
|
||||||
|
<property name="iconSize" >
|
||||||
|
<size>
|
||||||
|
<width>32</width>
|
||||||
|
<height>32</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../images.qrc" />
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -18,13 +18,13 @@
|
|||||||
from PyQt4.QtCore import SIGNAL, QObject
|
from PyQt4.QtCore import SIGNAL, QObject
|
||||||
|
|
||||||
from libprs500.gui2 import qstring_to_unicode
|
from libprs500.gui2 import qstring_to_unicode
|
||||||
from libprs500.gui2.dialogs import ModalDialog
|
from libprs500.gui2.dialogs import Dialog
|
||||||
from libprs500.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
from libprs500.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
||||||
|
|
||||||
class MetadataBulkDialog(Ui_MetadataBulkDialog, ModalDialog):
|
class MetadataBulkDialog(Ui_MetadataBulkDialog, Dialog):
|
||||||
def __init__(self, window, rows, db):
|
def __init__(self, window, rows, db):
|
||||||
Ui_MetadataBulkDialog.__init__(self)
|
Ui_MetadataBulkDialog.__init__(self)
|
||||||
ModalDialog.__init__(self, window)
|
Dialog.__init__(self, window)
|
||||||
self.setupUi(self.dialog)
|
self.setupUi(self.dialog)
|
||||||
self.db = db
|
self.db = db
|
||||||
self.ids = [ db.id(r) for r in rows]
|
self.ids = [ db.id(r) for r in rows]
|
||||||
@ -42,7 +42,7 @@ class MetadataBulkDialog(Ui_MetadataBulkDialog, ModalDialog):
|
|||||||
id, name = i
|
id, name = i
|
||||||
self.series.addItem(name)
|
self.series.addItem(name)
|
||||||
|
|
||||||
|
self.series.lineEdit().setText('')
|
||||||
|
|
||||||
self.dialog.exec_()
|
self.dialog.exec_()
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ from PyQt4.Qt import QObject, QPixmap, QListWidgetItem, QErrorMessage
|
|||||||
|
|
||||||
from libprs500.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
|
from libprs500.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
|
||||||
choose_files, pixmap_to_data, BOOK_EXTENSIONS, choose_images
|
choose_files, pixmap_to_data, BOOK_EXTENSIONS, choose_images
|
||||||
from libprs500.gui2.dialogs import ModalDialog
|
from libprs500.gui2.dialogs import Dialog
|
||||||
from libprs500.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog
|
from libprs500.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog
|
||||||
|
|
||||||
class Format(QListWidgetItem):
|
class Format(QListWidgetItem):
|
||||||
@ -34,7 +34,7 @@ class Format(QListWidgetItem):
|
|||||||
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
|
QListWidgetItem.__init__(self, file_icon_provider().icon_from_ext(ext),
|
||||||
ext.upper(), parent, QListWidgetItem.UserType)
|
ext.upper(), parent, QListWidgetItem.UserType)
|
||||||
|
|
||||||
class MetadataSingleDialog(Ui_MetadataSingleDialog, ModalDialog):
|
class MetadataSingleDialog(Ui_MetadataSingleDialog, Dialog):
|
||||||
|
|
||||||
def select_cover(self, checked):
|
def select_cover(self, checked):
|
||||||
files = choose_images(self.window, 'change cover dialog',
|
files = choose_images(self.window, 'change cover dialog',
|
||||||
@ -121,7 +121,7 @@ class MetadataSingleDialog(Ui_MetadataSingleDialog, ModalDialog):
|
|||||||
|
|
||||||
def __init__(self, window, row, db):
|
def __init__(self, window, row, db):
|
||||||
Ui_MetadataSingleDialog.__init__(self)
|
Ui_MetadataSingleDialog.__init__(self)
|
||||||
ModalDialog.__init__(self, window)
|
Dialog.__init__(self, window)
|
||||||
self.setupUi(self.dialog)
|
self.setupUi(self.dialog)
|
||||||
self.splitter.setStretchFactor(100, 1)
|
self.splitter.setStretchFactor(100, 1)
|
||||||
self.db = db
|
self.db = db
|
||||||
|
@ -1380,7 +1380,6 @@
|
|||||||
rx="162.459"
|
rx="162.459"
|
||||||
cy="68"
|
cy="68"
|
||||||
cx="181.01601"
|
cx="181.01601"
|
||||||
style="fill:url(#linearGradient11897)"
|
|
||||||
sodipodi:cx="181.01601"
|
sodipodi:cx="181.01601"
|
||||||
sodipodi:cy="68"
|
sodipodi:cy="68"
|
||||||
sodipodi:rx="162.459"
|
sodipodi:rx="162.459"
|
||||||
@ -2190,7 +2189,7 @@
|
|||||||
<path
|
<path
|
||||||
d="M 188.42089,-54.769775 C 170.74798,-54.769775 156.4209,-40.44269 156.4209,-22.76977 C 156.4209,-5.09686 170.74798,9.230225 188.42089,9.230225 C 206.09381,9.230225 220.4209,-5.09686 220.4209,-22.76977 C 220.4209,-40.44269 206.09381,-54.769775 188.42089,-54.769775 z M 188.42089,-11.22879 C 182.04713,-11.22879 176.87991,-16.396005 176.87991,-22.76977 C 176.87991,-29.14407 182.04713,-34.31076 188.42089,-34.31076 C 194.79414,-34.31076 199.96188,-29.14407 199.96188,-22.76977 C 199.96188,-16.396005 194.79414,-11.22879 188.42089,-11.22879 z"
|
d="M 188.42089,-54.769775 C 170.74798,-54.769775 156.4209,-40.44269 156.4209,-22.76977 C 156.4209,-5.09686 170.74798,9.230225 188.42089,9.230225 C 206.09381,9.230225 220.4209,-5.09686 220.4209,-22.76977 C 220.4209,-40.44269 206.09381,-54.769775 188.42089,-54.769775 z M 188.42089,-11.22879 C 182.04713,-11.22879 176.87991,-16.396005 176.87991,-22.76977 C 176.87991,-29.14407 182.04713,-34.31076 188.42089,-34.31076 C 194.79414,-34.31076 199.96188,-29.14407 199.96188,-22.76977 C 199.96188,-16.396005 194.79414,-11.22879 188.42089,-11.22879 z"
|
||||||
id="path27365"
|
id="path27365"
|
||||||
style="fill:url(#linearGradient27367);fill-opacity:1" />
|
style="fill-opacity:1" />
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<linearGradient
|
<linearGradient
|
||||||
inkscape:collect="always"
|
inkscape:collect="always"
|
||||||
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
@ -12,14 +12,66 @@
|
|||||||
## You should have received a copy of the GNU General Public License along
|
## You should have received a copy of the GNU General Public License along
|
||||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
import traceback, textwrap
|
||||||
|
|
||||||
from PyQt4.QtCore import QAbstractTableModel, QMutex, QObject, SIGNAL
|
from PyQt4.QtCore import QAbstractTableModel, QMutex, QObject, SIGNAL, Qt, \
|
||||||
|
QVariant, QThread
|
||||||
|
from PyQt4.QtGui import QIcon
|
||||||
|
|
||||||
from libprs500.gui2.device import DeviceJob
|
from libprs500.gui2 import NONE
|
||||||
|
|
||||||
class JobException(Exception):
|
class JobException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class Job(QThread):
|
||||||
|
''' Class to run a function in a separate thread with optional mutex based locking.'''
|
||||||
|
def __init__(self, id, description, mutex, func, *args, **kwargs):
|
||||||
|
'''
|
||||||
|
@param id: Number. Id of this thread.
|
||||||
|
@param description: String. Description of this job.
|
||||||
|
@param mutex: A QMutex or None. Is locked before function is run.
|
||||||
|
@param func: A callable that should be executed in this thread.
|
||||||
|
'''
|
||||||
|
QThread.__init__(self)
|
||||||
|
self.id = id
|
||||||
|
self.func = func
|
||||||
|
self.description = description if description else 'Device Job #' + str(self.id)
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.mutex = mutex
|
||||||
|
self.result = None
|
||||||
|
self.percent_done = 0
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
if self.mutex != None:
|
||||||
|
self.mutex.lock()
|
||||||
|
last_traceback, exception = None, None
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
self.result = self.func(self.progress_update, *self.args, **self.kwargs)
|
||||||
|
except Exception, err:
|
||||||
|
exception = err
|
||||||
|
last_traceback = traceback.format_exc()
|
||||||
|
finally:
|
||||||
|
if self.mutex != None:
|
||||||
|
self.mutex.unlock()
|
||||||
|
self.emit(SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||||
|
self.id, self.result, exception, last_traceback)
|
||||||
|
|
||||||
|
def progress_update(self, val):
|
||||||
|
self.percent_done = val
|
||||||
|
self.emit(SIGNAL('status_update(int, int)'), self.id, int(val))
|
||||||
|
|
||||||
|
class DeviceJob(Job):
|
||||||
|
'''
|
||||||
|
Jobs that involve communication with the device.
|
||||||
|
'''
|
||||||
|
def __init__(self, id, description, mutex, func, *args, **kwargs):
|
||||||
|
Job.__init__(self, id, description, mutex, func, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class JobManager(QAbstractTableModel):
|
class JobManager(QAbstractTableModel):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -31,19 +83,33 @@ class JobManager(QAbstractTableModel):
|
|||||||
self.device_lock = QMutex()
|
self.device_lock = QMutex()
|
||||||
self.cleanup_lock = QMutex()
|
self.cleanup_lock = QMutex()
|
||||||
self.cleanup = {}
|
self.cleanup = {}
|
||||||
|
self.device_job_icon = QVariant(QIcon(':/images/reader.svg'))
|
||||||
|
self.job_icon = QVariant(QIcon(':/images/jobs.svg'))
|
||||||
|
self.wrapper = textwrap.TextWrapper(width=40)
|
||||||
|
|
||||||
def create_job(self, job_class, lock, *args, **kwargs):
|
def create_job(self, job_class, description, lock, *args, **kwargs):
|
||||||
self.job_create_lock.lock()
|
self.job_create_lock.lock()
|
||||||
try:
|
try:
|
||||||
self.next_id += 1
|
self.next_id += 1
|
||||||
job = job_class(self.next_id, lock, *args, **kwargs)
|
job = job_class(self.next_id, description, lock, *args, **kwargs)
|
||||||
QObject.connect(job, SIGNAL('finished()'), self.cleanup_jobs)
|
QObject.connect(job, SIGNAL('finished()'), self.cleanup_jobs)
|
||||||
|
QObject.connect(job, SIGNAL('status_update(int, int)'), self.status_update)
|
||||||
self.jobs[self.next_id] = job
|
self.jobs[self.next_id] = job
|
||||||
self.emit(SIGNAL('job_added(int)'), self.next_id)
|
self.emit(SIGNAL('job_added(int)'), self.next_id)
|
||||||
|
self.reset()
|
||||||
return job
|
return job
|
||||||
finally:
|
finally:
|
||||||
self.job_create_lock.unlock()
|
self.job_create_lock.unlock()
|
||||||
|
|
||||||
|
def has_device_jobs(self):
|
||||||
|
for job in self.jobs.values():
|
||||||
|
if isinstance(job, DeviceJob):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has_jobs(self):
|
||||||
|
return len(self.jobs.values()) > 0
|
||||||
|
|
||||||
def run_device_job(self, slot, callable, *args, **kwargs):
|
def run_device_job(self, slot, callable, *args, **kwargs):
|
||||||
'''
|
'''
|
||||||
Run a job to communicate with the device.
|
Run a job to communicate with the device.
|
||||||
@ -53,7 +119,9 @@ class JobManager(QAbstractTableModel):
|
|||||||
@param args: The arguments to pass to callable
|
@param args: The arguments to pass to callable
|
||||||
@param kwargs: The keyword arguments to pass to callable
|
@param kwargs: The keyword arguments to pass to callable
|
||||||
'''
|
'''
|
||||||
job = self.create_job(DeviceJob, self.device_lock, callable, *args, **kwargs)
|
desc = callable.__doc__ if callable.__doc__ else ''
|
||||||
|
desc += kwargs.pop('job_extra_description', '')
|
||||||
|
job = self.create_job(DeviceJob, desc, self.device_lock, callable, *args, **kwargs)
|
||||||
QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
QObject.connect(job, SIGNAL('jobdone(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||||
self.job_done)
|
self.job_done)
|
||||||
if slot:
|
if slot:
|
||||||
@ -68,15 +136,18 @@ class JobManager(QAbstractTableModel):
|
|||||||
'''
|
'''
|
||||||
self.job_remove_lock.lock()
|
self.job_remove_lock.lock()
|
||||||
try:
|
try:
|
||||||
job = self.jobs.pop(id)
|
job = self.jobs.pop(id)
|
||||||
|
self.reset()
|
||||||
self.cleanup_lock.lock()
|
self.cleanup_lock.lock()
|
||||||
self.cleanup[id] = job
|
self.cleanup[id] = job
|
||||||
self.cleanup_lock.unlock()
|
self.cleanup_lock.unlock()
|
||||||
|
self.emit(SIGNAL('job_done(int)'), id)
|
||||||
if len(self.jobs.keys()) == 0:
|
if len(self.jobs.keys()) == 0:
|
||||||
self.emit(SIGNAL('no_more_jobs()'))
|
self.emit(SIGNAL('no_more_jobs()'))
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.job_remove_lock.unlock()
|
self.job_remove_lock.unlock()
|
||||||
|
|
||||||
def cleanup_jobs(self):
|
def cleanup_jobs(self):
|
||||||
self.cleanup_lock.lock()
|
self.cleanup_lock.lock()
|
||||||
toast = []
|
toast = []
|
||||||
@ -86,4 +157,53 @@ class JobManager(QAbstractTableModel):
|
|||||||
for id in toast:
|
for id in toast:
|
||||||
self.cleanup.pop(id)
|
self.cleanup.pop(id)
|
||||||
self.cleanup_lock.unlock()
|
self.cleanup_lock.unlock()
|
||||||
|
|
||||||
|
|
||||||
|
def rowCount(self, parent):
|
||||||
|
return len(self.jobs)
|
||||||
|
|
||||||
|
def columnCount(self, parent):
|
||||||
|
return 3
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role):
|
||||||
|
if role != Qt.DisplayRole:
|
||||||
|
return NONE
|
||||||
|
if orientation == Qt.Horizontal:
|
||||||
|
if section == 0: text = "Job"
|
||||||
|
elif section == 1: text = "Status"
|
||||||
|
elif section == 2: text = "Progress"
|
||||||
|
return QVariant(self.trUtf8(text))
|
||||||
|
else:
|
||||||
|
return QVariant(section+1)
|
||||||
|
|
||||||
|
def data(self, index, role):
|
||||||
|
if role not in (Qt.DisplayRole, Qt.DecorationRole):
|
||||||
|
return NONE
|
||||||
|
row, col = index.row(), index.column()
|
||||||
|
keys = self.jobs.keys()
|
||||||
|
keys.sort()
|
||||||
|
job = self.jobs[keys[row]]
|
||||||
|
if role == Qt.DisplayRole:
|
||||||
|
if col == 0:
|
||||||
|
return QVariant('\n'.join(self.wrapper.wrap(job.description)))
|
||||||
|
if col == 1:
|
||||||
|
status = 'Waiting'
|
||||||
|
if job.isRunning():
|
||||||
|
status = 'Working'
|
||||||
|
if job.isFinished():
|
||||||
|
status = 'Done'
|
||||||
|
return QVariant(status)
|
||||||
|
if col == 2:
|
||||||
|
p = str(job.percent_done) + r'%'
|
||||||
|
return QVariant(p)
|
||||||
|
if role == Qt.DecorationRole and col == 0:
|
||||||
|
return self.device_job_icon if isinstance(job, DeviceJob) else self.job_icon
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
def status_update(self, id, progress):
|
||||||
|
keys = self.jobs.keys()
|
||||||
|
keys.sort()
|
||||||
|
row = keys.index(id)
|
||||||
|
index = self.index(row, 2)
|
||||||
|
self.emit(SIGNAL('dataChanged(QModelIndex, QModelIndex)'), index, index)
|
||||||
|
|
@ -16,7 +16,7 @@ import os, tempfile, sys
|
|||||||
|
|
||||||
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
|
||||||
QSettings, QVariant, QSize, QThread
|
QSettings, QVariant, QSize, QThread
|
||||||
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon
|
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox
|
||||||
from PyQt4.QtSvg import QSvgRenderer
|
from PyQt4.QtSvg import QSvgRenderer
|
||||||
|
|
||||||
from libprs500 import __version__, __appname__
|
from libprs500 import __version__, __appname__
|
||||||
@ -32,6 +32,8 @@ from libprs500.gui2.status import StatusBar
|
|||||||
from libprs500.gui2.jobs import JobManager, JobException
|
from libprs500.gui2.jobs import JobManager, JobException
|
||||||
from libprs500.gui2.dialogs.metadata_single import MetadataSingleDialog
|
from libprs500.gui2.dialogs.metadata_single import MetadataSingleDialog
|
||||||
from libprs500.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
from libprs500.gui2.dialogs.metadata_bulk import MetadataBulkDialog
|
||||||
|
from libprs500.gui2.dialogs.jobs import JobsDialog
|
||||||
|
|
||||||
|
|
||||||
class Main(QObject, Ui_MainWindow):
|
class Main(QObject, Ui_MainWindow):
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ class Main(QObject, Ui_MainWindow):
|
|||||||
self.setupUi(window)
|
self.setupUi(window)
|
||||||
self.read_settings()
|
self.read_settings()
|
||||||
self.job_manager = JobManager()
|
self.job_manager = JobManager()
|
||||||
|
self.jobs_dialog = JobsDialog(self.window, self.job_manager)
|
||||||
self.device_manager = None
|
self.device_manager = None
|
||||||
self.upload_memory = {}
|
self.upload_memory = {}
|
||||||
self.delete_memory = {}
|
self.delete_memory = {}
|
||||||
@ -64,10 +67,10 @@ class Main(QObject, Ui_MainWindow):
|
|||||||
self.vanity.setText(self.vanity_template.arg(' '))
|
self.vanity.setText(self.vanity_template.arg(' '))
|
||||||
|
|
||||||
####################### Status Bar #####################
|
####################### Status Bar #####################
|
||||||
self.status_bar = StatusBar()
|
self.status_bar = StatusBar(self.jobs_dialog)
|
||||||
self.window.setStatusBar(self.status_bar)
|
self.window.setStatusBar(self.status_bar)
|
||||||
QObject.connect(self.job_manager, SIGNAL('job_added(int)'), self.status_bar.job_added)
|
QObject.connect(self.job_manager, SIGNAL('job_added(int)'), self.status_bar.job_added)
|
||||||
QObject.connect(self.job_manager, SIGNAL('no_more_jobs()'), self.status_bar.no_more_jobs)
|
QObject.connect(self.job_manager, SIGNAL('job_done(int)'), self.status_bar.job_done)
|
||||||
|
|
||||||
####################### Setup Toolbar #####################
|
####################### Setup Toolbar #####################
|
||||||
sm = QMenu()
|
sm = QMenu()
|
||||||
@ -195,6 +198,8 @@ class Main(QObject, Ui_MainWindow):
|
|||||||
if exception:
|
if exception:
|
||||||
self.job_exception(id, exception, formatted_traceback)
|
self.job_exception(id, exception, formatted_traceback)
|
||||||
return
|
return
|
||||||
|
cp, fs = result
|
||||||
|
self.location_view.model().update_devices(cp, fs)
|
||||||
############################################################################
|
############################################################################
|
||||||
|
|
||||||
|
|
||||||
@ -236,9 +241,11 @@ class Main(QObject, Ui_MainWindow):
|
|||||||
Upload books to device.
|
Upload books to device.
|
||||||
@param files: List of either paths to files or file like objects
|
@param files: List of either paths to files or file like objects
|
||||||
'''
|
'''
|
||||||
|
titles = ', '.join([i['title'] for i in metadata])
|
||||||
id = self.job_manager.run_device_job(self.books_uploaded,
|
id = self.job_manager.run_device_job(self.books_uploaded,
|
||||||
self.device_manager.upload_books_func(),
|
self.device_manager.upload_books_func(),
|
||||||
files, names, on_card=on_card
|
files, names, on_card=on_card,
|
||||||
|
job_extra_description=titles
|
||||||
)
|
)
|
||||||
self.upload_memory[id] = metadata
|
self.upload_memory[id] = metadata
|
||||||
|
|
||||||
@ -434,8 +441,21 @@ class Main(QObject, Ui_MainWindow):
|
|||||||
settings.endGroup()
|
settings.endGroup()
|
||||||
|
|
||||||
def close_event(self, e):
|
def close_event(self, e):
|
||||||
self.write_settings()
|
msg = 'There are active jobs. Are you sure you want to quit?'
|
||||||
e.accept()
|
if self.job_manager.has_device_jobs():
|
||||||
|
msg = '<p>'+__appname__ + ' is communicating with the device!<br>'+\
|
||||||
|
'Quitting may cause corruption on the device.<br>'+\
|
||||||
|
'Are you sure you want to quit?'
|
||||||
|
if self.job_manager.has_jobs():
|
||||||
|
d = QMessageBox(QMessageBox.Warning, 'WARNING: Active jobs', msg,
|
||||||
|
QMessageBox.Yes|QMessageBox.No, self.window)
|
||||||
|
d.setIconPixmap(QPixmap(':/images/dialog_warning.svg'))
|
||||||
|
d.setDefaultButton(QMessageBox.No)
|
||||||
|
if d.exec_() == QMessageBox.Yes:
|
||||||
|
self.write_settings()
|
||||||
|
e.accept()
|
||||||
|
else:
|
||||||
|
e.ignore()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
|
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
|
||||||
|
@ -12,10 +12,14 @@
|
|||||||
## You should have received a copy of the GNU General Public License along
|
## You should have received a copy of the GNU General Public License along
|
||||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
from libprs500.gui2.dialogs.jobs import JobsDialog
|
||||||
|
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap
|
from PyQt4.QtGui import QStatusBar, QMovie, QLabel, QFrame, QHBoxLayout, QPixmap, \
|
||||||
|
QVBoxLayout, QSizePolicy
|
||||||
from PyQt4.QtCore import Qt, QSize
|
from PyQt4.QtCore import Qt, QSize
|
||||||
|
from libprs500.gui2 import qstring_to_unicode
|
||||||
|
|
||||||
class BookInfoDisplay(QFrame):
|
class BookInfoDisplay(QFrame):
|
||||||
class BookCoverDisplay(QLabel):
|
class BookCoverDisplay(QLabel):
|
||||||
@ -39,7 +43,7 @@ class BookInfoDisplay(QFrame):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
QLabel.__init__(self)
|
QLabel.__init__(self)
|
||||||
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||||||
self.setText('')#<table><tr><td>row 1</td><td>row 2</td></tr><tr><td>fsfdsfsfsfsfsfsdfsffsfsd</td></tr></table>')
|
self.setText('')
|
||||||
|
|
||||||
def __init__(self, clear_message):
|
def __init__(self, clear_message):
|
||||||
QFrame.__init__(self)
|
QFrame.__init__(self)
|
||||||
@ -74,35 +78,83 @@ class BookInfoDisplay(QFrame):
|
|||||||
self.clear_message()
|
self.clear_message()
|
||||||
self.setVisible(True)
|
self.setVisible(True)
|
||||||
|
|
||||||
class MovieButton(QLabel):
|
class BusyIndicator(QLabel):
|
||||||
def __init__(self, movie):
|
def __init__(self, movie, jobs_dialog):
|
||||||
QLabel.__init__(self)
|
QLabel.__init__(self)
|
||||||
self.movie = movie
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
|
self.setToolTip('Click to see list of active jobs.')
|
||||||
self.setMovie(movie)
|
self.setMovie(movie)
|
||||||
self.movie.start()
|
movie.start()
|
||||||
self.movie.setPaused(True)
|
movie.setPaused(True)
|
||||||
|
self.jobs_dialog = jobs_dialog
|
||||||
|
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event):
|
||||||
|
if self.jobs_dialog.isVisible():
|
||||||
|
self.jobs_dialog.hide()
|
||||||
|
else:
|
||||||
|
self.jobs_dialog.show()
|
||||||
|
|
||||||
|
|
||||||
|
class MovieButton(QFrame):
|
||||||
|
def __init__(self, movie, jobs_dialog):
|
||||||
|
QFrame.__init__(self)
|
||||||
|
self.setLayout(QVBoxLayout())
|
||||||
|
self.movie_widget = BusyIndicator(movie, jobs_dialog)
|
||||||
|
self.movie = movie
|
||||||
|
self.layout().addWidget(self.movie_widget)
|
||||||
|
self.jobs = QLabel('<b>Jobs: 0')
|
||||||
|
self.jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)
|
||||||
|
self.layout().addWidget(self.jobs)
|
||||||
|
self.layout().setAlignment(self.jobs, Qt.AlignHCenter)
|
||||||
|
self.jobs.setMargin(0)
|
||||||
|
self.layout().setMargin(0)
|
||||||
|
self.jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
|
||||||
|
|
||||||
|
|
||||||
class StatusBar(QStatusBar):
|
class StatusBar(QStatusBar):
|
||||||
def __init__(self):
|
def __init__(self, jobs_dialog):
|
||||||
QStatusBar.__init__(self)
|
QStatusBar.__init__(self)
|
||||||
self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'))
|
self.movie_button = MovieButton(QMovie(':/images/jobs-animated.mng'), jobs_dialog)
|
||||||
self.addPermanentWidget(self.movie_button)
|
self.addPermanentWidget(self.movie_button)
|
||||||
self.book_info = BookInfoDisplay(self.clearMessage)
|
self.book_info = BookInfoDisplay(self.clearMessage)
|
||||||
self.addWidget(self.book_info)
|
self.addWidget(self.book_info)
|
||||||
|
|
||||||
|
def jobs(self):
|
||||||
|
src = qstring_to_unicode(self.movie_button.jobs.text())
|
||||||
|
return int(src.rpartition(':')[2].lstrip())
|
||||||
|
|
||||||
|
|
||||||
def job_added(self, id):
|
def job_added(self, id):
|
||||||
|
jobs = self.movie_button.jobs
|
||||||
|
src = qstring_to_unicode(jobs.text())
|
||||||
|
num = self.jobs()
|
||||||
|
nnum = num+1
|
||||||
|
text = src.replace(str(num), str(nnum))
|
||||||
|
jobs.setText(text)
|
||||||
if self.movie_button.movie.state() == QMovie.Paused:
|
if self.movie_button.movie.state() == QMovie.Paused:
|
||||||
self.movie_button.movie.setPaused(False)
|
self.movie_button.movie.setPaused(False)
|
||||||
|
|
||||||
|
def job_done(self, id):
|
||||||
|
jobs = self.movie_button.jobs
|
||||||
|
src = qstring_to_unicode(jobs.text())
|
||||||
|
num = self.jobs()
|
||||||
|
nnum = num-1
|
||||||
|
text = src.replace(str(num), str(nnum))
|
||||||
|
jobs.setText(text)
|
||||||
|
if nnum == 0:
|
||||||
|
self.no_more_jobs()
|
||||||
|
|
||||||
def no_more_jobs(self):
|
def no_more_jobs(self):
|
||||||
if self.movie_button.movie.state() == QMovie.Running:
|
if self.movie_button.movie.state() == QMovie.Running:
|
||||||
self.movie_button.movie.setPaused(True)
|
self.movie_button.movie.setPaused(True)
|
||||||
self.movie_button.movie.jumpToFrame(0) # This causes MNG error 11, but seems to work regardless
|
# This causes MNG error 11
|
||||||
|
#self.movie_button.movie.jumpToFrame(0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Used to create the animated status icon
|
# Used to create the animated status icon
|
||||||
from PyQt4.Qt import QApplication, QPainter, QSvgRenderer, QPixmap, QColor
|
from PyQt4.Qt import QApplication, QPainter, QSvgRenderer, QColor
|
||||||
from subprocess import check_call
|
from subprocess import check_call
|
||||||
import os
|
import os
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user