Raising the pylint score of libprs500/gui/*.py

This commit is contained in:
Kovid Goyal 2006-12-20 02:08:57 +00:00
parent 97fe142281
commit 18111d2d50
6 changed files with 1576 additions and 1360 deletions

View File

@ -12,6 +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. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
""" The GUI to libprs500. Also has ebook library management features. """
__docformat__ = "epytext" __docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
APP_TITLE = "libprs500" APP_TITLE = "libprs500"
@ -23,34 +24,36 @@ from PyQt4 import QtCore, QtGui # Needed in globals() for import_ui
error_dialog = None error_dialog = None
def extension(path): def extension(path):
return os.path.splitext(path)[1][1:].lower() return os.path.splitext(path)[1][1:].lower()
def installErrorHandler(dialog): def installErrorHandler(dialog):
global error_dialog global error_dialog
error_dialog = dialog error_dialog = dialog
error_dialog.resize(600, 400) error_dialog.resize(600, 400)
error_dialog.setWindowTitle(APP_TITLE + " - Error") error_dialog.setWindowTitle(APP_TITLE + " - Error")
error_dialog.setModal(True) error_dialog.setModal(True)
def Warning(msg, e):
print >> sys.stderr, msg def _Warning(msg, e):
if e: traceback.print_exc(e) print >> sys.stderr, msg
if e: traceback.print_exc(e)
def Error(msg, e): def Error(msg, e):
if error_dialog: if error_dialog:
if e: msg += "<br>" + traceback.format_exc(e) if e: msg += "<br>" + traceback.format_exc(e)
msg = re.sub("Traceback", "<b>Traceback</b>", msg) msg = re.sub("Traceback", "<b>Traceback</b>", msg)
msg = re.sub(r"\n", "<br>", msg) msg = re.sub(r"\n", "<br>", msg)
error_dialog.showMessage(msg) error_dialog.showMessage(msg)
error_dialog.show() error_dialog.show()
def import_ui(name): def import_ui(name):
uifile = pkg_resources.resource_stream(__name__, name) uifile = pkg_resources.resource_stream(__name__, name)
code_string = StringIO.StringIO() code_string = StringIO.StringIO()
winfo = compiler.UICompiler().compileUi(uifile, code_string) winfo = compiler.UICompiler().compileUi(uifile, code_string)
ui = pkg_resources.resource_filename(__name__, name) ui = pkg_resources.resource_filename(__name__, name)
exec code_string.getvalue() exec code_string.getvalue()
return locals()[winfo["uiclass"]] return locals()[winfo["uiclass"]]
from libprs500.gui.widgets import LibraryBooksView, DeviceBooksView, CoverDisplay, DeviceView # Needed in globals() for import_ui # Needed in globals() for import_ui
from libprs500.gui.widgets import LibraryBooksView, \
DeviceBooksView, CoverDisplay, DeviceView

View File

@ -20,146 +20,189 @@ from libprs500.lrf.meta import LRFMetaFile
from cStringIO import StringIO as cStringIO from cStringIO import StringIO as cStringIO
class LibraryDatabase(object): class LibraryDatabase(object):
BOOKS_SQL = """
create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT, authors TEXT, publisher TEXT, size INTEGER, tags TEXT,
cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP, comments TEXT, rating INTEGER);
create table if not exists books_data(id INTEGER, extension TEXT, data BLOB);
"""
def __init__(self, dbpath):
self.con = sqlite.connect(dbpath)
self.con.row_factory = sqlite.Row # Allow case insensitive field access by name
self.con.executescript(LibraryDatabase.BOOKS_SQL)
def get_cover(self, id):
raw = self.con.execute("select cover from books_meta where id=?", (id,)).next()["cover"]
return decompress(str(raw)) if raw else None
def get_extensions(self, id): BOOKS_SQL = """
exts = [] create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT,
cur = self.con.execute("select extension from books_data where id=?", (id,)) authors TEXT, publisher TEXT, size INTEGER, tags TEXT,
for row in cur: cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP,
exts.append(row["extension"]) comments TEXT, rating INTEGER);
return exts create table if not exists books_data(id INTEGER, extension TEXT, data BLOB);
def add_book(self, path):
file = os.path.abspath(path)
title, author, publisher, size, cover = os.path.basename(file), None, None, os.stat(file)[ST_SIZE], None
ext = title[title.rfind(".")+1:].lower() if title.find(".") > -1 else None
if ext == "lrf":
lrf = LRFMetaFile(open(file, "r+b"))
title, author, cover, publisher = lrf.title, lrf.author.strip(), lrf.thumbnail, lrf.publisher.strip()
if "unknown" in publisher.lower(): publisher = None
if "unknown" in author.lower(): author = None
file = compress(open(file).read())
if cover: cover = sqlite.Binary(compress(cover))
self.con.execute("insert into books_meta (title, authors, publisher, size, tags, cover, comments, rating) values (?,?,?,?,?,?,?,?)", (title, author, publisher, size, None, cover, None, None))
id = self.con.execute("select max(id) from books_meta").next()[0]
self.con.execute("insert into books_data values (?,?,?)", (id, ext, sqlite.Binary(file)))
self.con.commit()
return id
def get_row_by_id(self, id, columns):
""" @param columns: list of column names """
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta where id=?", (id,))
row, r = cur.next(), {}
for c in columns: r[c] = row[c]
return r
def commit(self): self.con.commit()
def delete_by_id(self, id):
self.con.execute("delete from books_meta where id=?", (id,))
self.con.execute("delete from books_data where id=?", (id,))
def get_table(self, columns):
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta")
rows = []
for row in cur:
r = {}
for c in columns: r[c] = row[c]
rows.append(r)
return rows
def get_format(self, id, ext):
"""
Return format C{ext} corresponding to the logical book C{id} or None if the format is unavailable.
Format is returned as a string of binary data suitable for C{ file.write} operations.
"""
ext = ext.lower()
cur = self.con.execute("select data from books_data where id=? and extension=?",(id, ext))
try: data = cur.next()
except: pass
else: return decompress(str(data["data"]))
def add_format(self, id, ext, data):
""" """
If data for format ext already exists, it is replaced
@type ext: string or None
@type data: string
"""
try:
data.seek(0)
data = data.read()
except AttributeError: pass
if ext: ext = ext.strip().lower()
data = sqlite.Binary(compress(data))
cur = self.con.execute("select extension from books_data where id=? and extension=?", (id, ext))
present = True
try: cur.next()
except: present = False
if present:
self.con.execute("update books_data set data=? where id=? and extension=?", (data, id, ext))
else:
self.con.execute("insert into books_data (id, extension, data) values (?, ?, ?)", (id, ext, data))
self.con.commit()
def get_meta_data(self, id):
try: row = self.con.execute("select * from books_meta where id=?", (id,)).next()
except StopIteration: return None
data = {}
for field in ("id", "title", "authors", "publisher", "size", "tags", "cover", "date"):
data[field] = row[field]
return data
def set_metadata(self, id, title=None, authors=None, rating=None, publisher=None, tags=None, cover=None, comments=None): def __init__(self, dbpath):
if authors and not len(authors): authors = None self.con = sqlite.connect(dbpath)
if publisher and not len(publisher): publisher = None # Allow case insensitive field access by name
if tags and not len(tags): tags = None self.con.row_factory = sqlite.Row
if comments and not len(comments): comments = None self.con.executescript(LibraryDatabase.BOOKS_SQL)
if cover: cover = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set title=?, authors=?, publisher=?, tags=?, cover=?, comments=?, rating=? where id=?', (title, authors, publisher, tags, cover, comments, rating, id))
self.con.commit()
def set_metadata_item(self, id, col, val):
self.con.execute('update books_meta set '+col+'=? where id=?',(val, id))
if col in ["authors", "title"]:
lrf = self.get_format(id, "lrf")
if lrf:
c = cStringIO()
c.write(lrf)
lrf = LRFMetaFile(c)
if col == "authors": lrf.authors = val
else: lrf.title = val
self.add_format(id, "lrf", c.getvalue())
self.con.commit()
def update_cover(self, id, cover): def get_cover(self, _id):
data = None raw = self.con.execute("select cover from books_meta where id=?", (_id,))\
if cover: data = sqlite.Binary(compress(cover)) .next()["cover"]
self.con.execute('update books_meta set cover=? where id=?', (data, id)) return decompress(str(raw)) if raw else None
lrf = self.get_format(id, "lrf")
if lrf: def get_extensions(self, _id):
c = cStringIO() exts = []
c.write(lrf) cur = self.con.execute("select extension from books_data where id=?", (_id,))
lrf = LRFMetaFile(c) for row in cur:
lrf.thumbnail = cover exts.append(row["extension"])
self.add_format(id, "lrf", c.getvalue()) return exts
self.commit()
def add_book(self, path):
_file = os.path.abspath(path)
title, author, publisher, size, cover = os.path.basename(_file), \
None, None, os.stat(_file)[ST_SIZE], None
ext = title[title.rfind(".")+1:].lower() if title.find(".") > -1 else None
if ext == "lrf":
lrf = LRFMetaFile(open(_file, "r+b"))
title, author, cover, publisher = lrf.title, lrf.author.strip(), \
lrf.thumbnail, lrf.publisher.strip()
if "unknown" in publisher.lower():
publisher = None
if "unknown" in author.lower():
author = None
_file = compress(open(_file).read())
if cover:
cover = sqlite.Binary(compress(cover))
self.con.execute("insert into books_meta (title, authors, publisher, "+\
"size, tags, cover, comments, rating) values "+\
"(?,?,?,?,?,?,?,?)", \
(title, author, publisher, size, None, cover, None, None))
_id = self.con.execute("select max(id) from books_meta").next()[0]
self.con.execute("insert into books_data values (?,?,?)", \
(_id, ext, sqlite.Binary(_file)))
self.con.commit()
return _id
def get_row_by_id(self, _id, columns):
""" @param columns: list of column names """
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta where id=?"\
, (_id,))
row, r = cur.next(), {}
for c in columns:
r[c] = row[c]
return r
def commit(self): self.con.commit()
def delete_by_id(self, _id):
self.con.execute("delete from books_meta where id=?", (_id,))
self.con.execute("delete from books_data where id=?", (_id,))
def get_table(self, columns):
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta")
rows = []
for row in cur:
r = {}
for c in columns:
r[c] = row[c]
rows.append(r)
return rows
def get_format(self, _id, ext):
"""
Return format C{ext} corresponding to the logical book C{id} or
None if the format is unavailable.
Format is returned as a string of binary data suitable for
C{ file.write} operations.
"""
ext = ext.lower()
cur = self.con.execute("select data from books_data where id=? and "+\
"extension=?",(_id, ext))
try:
data = cur.next()
except:
pass
else:
return decompress(str(data["data"]))
def add_format(self, _id, ext, data):
"""
If data for format ext already exists, it is replaced
@type ext: string or None
@type data: string
"""
try:
data.seek(0)
data = data.read()
except AttributeError: pass
if ext: ext = ext.strip().lower()
data = sqlite.Binary(compress(data))
cur = self.con.execute("select extension from books_data where id=? "+\
"and extension=?", (_id, ext))
present = True
try: cur.next()
except: present = False
if present:
self.con.execute("update books_data set data=? where id=? "+\
"and extension=?", (data, _id, ext))
else:
self.con.execute("insert into books_data (id, extension, data) "+\
"values (?, ?, ?)", (_id, ext, data))
self.con.commit()
def get_meta_data(self, _id):
try:
row = self.con.execute("select * from books_meta where id=?", \
(_id,)).next()
except StopIteration:
return None
data = {}
for field in ("id", "title", "authors", "publisher", "size", "tags",
"cover", "date"):
data[field] = row[field]
return data
def set_metadata(self, _id, title=None, authors=None, rating=None, \
publisher=None, tags=None, cover=None, \
comments=None):
if authors and not len(authors):
authors = None
if publisher and not len(publisher):
publisher = None
if tags and not len(tags):
tags = None
if comments and not len(comments):
comments = None
if cover:
cover = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set title=?, authors=?, '+\
'publisher=?, tags=?, cover=?, comments=?, rating=? '+\
'where id=?', \
(title, authors, publisher, tags, cover, comments, \
rating, _id))
self.con.commit()
def set_metadata_item(self, _id, col, val):
self.con.execute('update books_meta set '+col+'=? where id=?', \
(val, _id))
if col in ["authors", "title"]:
lrf = self.get_format(_id, "lrf")
if lrf:
c = cStringIO()
c.write(lrf)
lrf = LRFMetaFile(c)
if col == "authors":
lrf.authors = val
else: lrf.title = val
self.add_format(_id, "lrf", c.getvalue())
self.con.commit()
def update_cover(self, _id, cover):
data = None
if cover:
data = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set cover=? where id=?', (data, _id))
lrf = self.get_format(_id, "lrf")
if lrf:
c = cStringIO()
c.write(lrf)
lrf = LRFMetaFile(c)
lrf.thumbnail = cover
self.add_format(_id, "lrf", c.getvalue())
self.commit()
#if __name__ == "__main__": #if __name__ == "__main__":

View File

@ -20,114 +20,114 @@ from libprs500.lrf.meta import LRFMetaFile
from libprs500.gui import import_ui from libprs500.gui import import_ui
class Format(QListWidgetItem): class Format(QListWidgetItem):
def __init__(self, parent, ext, data): def __init__(self, parent, ext, data):
self.data = data self.data = data
self.ext = ext self.ext = ext
QListWidgetItem.__init__(self, ext.upper(), parent, QListWidgetItem.UserType) QListWidgetItem.__init__(self, ext.upper(), parent, QListWidgetItem.UserType)
Ui_BookEditDialog = import_ui("editbook.ui") Ui_BookEditDialog = import_ui("editbook.ui")
class EditBookDialog(Ui_BookEditDialog): class EditBookDialog(Ui_BookEditDialog):
def select_cover(self, checked):
settings = QSettings()
dir = settings.value("change cover dir", QVariant(os.path.expanduser("~"))).toString()
file = QFileDialog.getOpenFileName(self.window, "Choose cover for " + str(self.title.text()), dir, "Images (*.png *.gif *.jpeg *.jpg);;All files (*)")
if len(str(file)):
file = os.path.abspath(file)
settings.setValue("change cover dir", QVariant(os.path.dirname(file)))
if not os.access(file, os.R_OK):
QErrorMessage(self.parent).showMessage("You do not have permission to read the file: " + file)
return
cf, cover = None, None
try:
cf = open(file, "rb")
cover = cf.read()
except IOError, e: QErrorMessage(self.parent).showMessage("There was an error reading from file: " + file + "\n"+str(e))
if cover:
pix = QPixmap()
pix.loadFromData(cover, "", Qt.AutoColor)
if pix.isNull(): QErrorMessage(self.parent).showMessage(file + " is not a valid picture")
else:
self.cover_path.setText(file)
self.cover.setPixmap(pix)
self.cover_data = cover
def write_data(self):
title = str(self.title.text()).strip()
authors = str(self.authors.text()).strip()
rating = self.rating.value()
tags = str(self.tags.text()).strip()
publisher = str(self.publisher.text()).strip()
comments = str(self.comments.toPlainText()).strip()
self.db.set_metadata(self.id, title=title, authors=authors, rating=rating, tags=tags, publisher=publisher, comments=comments, cover=self.cover_data)
if self.formats_changed:
for r in range(self.formats.count()):
format = self.formats.item(r)
self.db.add_format(self.id, format.ext, format.data)
lrf = self.db.get_format(self.id, "lrf")
if lrf:
lrf = StringIO.StringIO(lrf)
lf = LRFMetaFile(lrf)
if title: lf.title = title
if authors: lf.title = authors
if publisher: lf.publisher = publisher
if self.cover_data: lf.thumbnail = self.cover_data
self.db.add_format(self.id, "lrf", lrf.getvalue())
def select_cover(self, checked):
def add_format(self, x): settings = QSettings()
dir = settings.value("add formats dialog dir", QVariant(os.path.expanduser("~"))).toString() dir = settings.value("change cover dir", QVariant(os.path.expanduser("~"))).toString()
files = QFileDialog.getOpenFileNames(self.window, "Choose formats for " + str(self.title.text()), dir, "Books (*.lrf *.lrx *.rtf *.txt *.html *.xhtml *.htm *.rar);;All files (*)") file = QFileDialog.getOpenFileName(self.window, "Choose cover for " + str(self.title.text()), dir, "Images (*.png *.gif *.jpeg *.jpg);;All files (*)")
if not files.isEmpty(): if len(str(file)):
x = str(files[0]) file = os.path.abspath(file)
settings.setValue("add formats dialog dir", QVariant(os.path.dirname(x))) settings.setValue("change cover dir", QVariant(os.path.dirname(file)))
files = str(files.join("|||")).split("|||") if not os.access(file, os.R_OK):
for file in files: QErrorMessage(self.parent).showMessage("You do not have permission to read the file: " + file)
file = os.path.abspath(file) return
if not os.access(file, os.R_OK): cf, cover = None, None
QErrorMessage(self.parent).showMessage("You do not have permission to read the file: " + file) try:
continue cf = open(file, "rb")
f, data = None, None cover = cf.read()
try: except IOError, e: QErrorMessage(self.parent).showMessage("There was an error reading from file: " + file + "\n"+str(e))
f = open(file, "rb") if cover:
data = f.read() pix = QPixmap()
except IOError, e: QErrorMessage(self.parent).showMessage("There was an error reading from file: " + file + "\n"+str(e)) pix.loadFromData(cover, "", Qt.AutoColor)
if data: if pix.isNull(): QErrorMessage(self.parent).showMessage(file + " is not a valid picture")
ext = file[file.rfind(".")+1:].lower() if file.find(".") > -1 else None else:
Format(self.formats, ext, data) self.cover_path.setText(file)
self.formats_changed = True self.cover.setPixmap(pix)
self.cover_data = cover
def remove_format(self, x):
rows = self.formats.selectionModel().selectedRows(0) def write_data(self):
for row in rows: title = str(self.title.text()).strip()
item = self.formats.takeItem(row.row()) authors = str(self.authors.text()).strip()
self.formats_changed = True rating = self.rating.value()
tags = str(self.tags.text()).strip()
def __init__(self, dialog, id, db): publisher = str(self.publisher.text()).strip()
Ui_BookEditDialog.__init__(self) comments = str(self.comments.toPlainText()).strip()
self.parent = dialog self.db.set_metadata(self.id, title=title, authors=authors, rating=rating, tags=tags, publisher=publisher, comments=comments, cover=self.cover_data)
self.setupUi(dialog) if self.formats_changed:
self.splitter.setStretchFactor(100,1) for r in range(self.formats.count()):
self.db = db format = self.formats.item(r)
self.id = id self.db.add_format(self.id, format.ext, format.data)
self.cover_data = None lrf = self.db.get_format(self.id, "lrf")
self.formats_changed = False if lrf:
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), self.select_cover) lrf = StringIO.StringIO(lrf)
QObject.connect(self.button_box, SIGNAL("accepted()"), self.write_data) lf = LRFMetaFile(lrf)
QObject.connect(self.add_format_button, SIGNAL("clicked(bool)"), self.add_format) if title: lf.title = title
QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), self.remove_format) if authors: lf.title = authors
data = self.db.get_row_by_id(self.id, ["title","authors","rating","publisher","tags","comments"]) if publisher: lf.publisher = publisher
self.title.setText(data["title"]) if self.cover_data: lf.thumbnail = self.cover_data
self.authors.setText(data["authors"] if data["authors"] else "") self.db.add_format(self.id, "lrf", lrf.getvalue())
self.publisher.setText(data["publisher"] if data["publisher"] else "")
self.tags.setText(data["tags"] if data["tags"] else "")
if data["rating"] > 0: self.rating.setValue(data["rating"]) def add_format(self, x):
self.comments.setPlainText(data["comments"] if data["comments"] else "") dir = settings.value("add formats dialog dir", QVariant(os.path.expanduser("~"))).toString()
cover = self.db.get_cover(self.id) files = QFileDialog.getOpenFileNames(self.window, "Choose formats for " + str(self.title.text()), dir, "Books (*.lrf *.lrx *.rtf *.txt *.html *.xhtml *.htm *.rar);;All files (*)")
if cover: if not files.isEmpty():
pm = QPixmap() x = str(files[0])
pm.loadFromData(cover, "", Qt.AutoColor) settings.setValue("add formats dialog dir", QVariant(os.path.dirname(x)))
self.cover.setPixmap(pm) files = str(files.join("|||")).split("|||")
else: for file in files:
self.cover.setPixmap(QPixmap(":/default_cover")) file = os.path.abspath(file)
if not os.access(file, os.R_OK):
QErrorMessage(self.parent).showMessage("You do not have permission to read the file: " + file)
continue
f, data = None, None
try:
f = open(file, "rb")
data = f.read()
except IOError, e: QErrorMessage(self.parent).showMessage("There was an error reading from file: " + file + "\n"+str(e))
if data:
ext = file[file.rfind(".")+1:].lower() if file.find(".") > -1 else None
Format(self.formats, ext, data)
self.formats_changed = True
def remove_format(self, x):
rows = self.formats.selectionModel().selectedRows(0)
for row in rows:
item = self.formats.takeItem(row.row())
self.formats_changed = True
def __init__(self, dialog, id, db):
Ui_BookEditDialog.__init__(self)
self.parent = dialog
self.setupUi(dialog)
self.splitter.setStretchFactor(100,1)
self.db = db
self.id = id
self.cover_data = None
self.formats_changed = False
QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), self.select_cover)
QObject.connect(self.button_box, SIGNAL("accepted()"), self.write_data)
QObject.connect(self.add_format_button, SIGNAL("clicked(bool)"), self.add_format)
QObject.connect(self.remove_format_button, SIGNAL("clicked(bool)"), self.remove_format)
data = self.db.get_row_by_id(self.id, ["title","authors","rating","publisher","tags","comments"])
self.title.setText(data["title"])
self.authors.setText(data["authors"] if data["authors"] else "")
self.publisher.setText(data["publisher"] if data["publisher"] else "")
self.tags.setText(data["tags"] if data["tags"] else "")
if data["rating"] > 0: self.rating.setValue(data["rating"])
self.comments.setPlainText(data["comments"] if data["comments"] else "")
cover = self.db.get_cover(self.id)
if cover:
pm = QPixmap()
pm.loadFromData(cover, "", Qt.AutoColor)
self.cover.setPixmap(pm)
else:
self.cover.setPixmap(QPixmap(":/default_cover"))

View File

@ -11,466 +11,541 @@
## ##
## 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.Warning
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QSettings, QVariant, QSize, QEventLoop, QString, QBuffer, QIODevice, QModelIndex """ Create and launch the GUI """
from PyQt4.QtGui import QPixmap, QAbstractItemView, QErrorMessage, QMessageBox, QFileDialog, QIcon import sys
from PyQt4.Qt import qInstallMsgHandler, qDebug, qFatal, qWarning, qCritical import re
from PyQt4 import uic import os
import traceback
import tempfile
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, \
QSettings, QVariant, QSize, QEventLoop, QString, \
QBuffer, QIODevice, QModelIndex
from PyQt4.QtGui import QPixmap, QErrorMessage, \
QMessageBox, QFileDialog, QIcon, QDialog
from PyQt4.Qt import qDebug, qFatal, qWarning, qCritical
from libprs500.communicate import PRS500Device as device from libprs500.communicate import PRS500Device as device
from libprs500.books import fix_ids from libprs500.books import fix_ids
from libprs500.errors import * from libprs500.errors import *
from libprs500.lrf.meta import LRFMetaFile, LRFException from libprs500.gui import import_ui, installErrorHandler, Error, _Warning, \
from libprs500.gui import import_ui, installErrorHandler, Error, Warning, extension, APP_TITLE extension, APP_TITLE
from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, DeviceModel, TableView from libprs500.gui.widgets import LibraryBooksModel, DeviceBooksModel, \
DeviceModel
from database import LibraryDatabase from database import LibraryDatabase
from editbook import EditBookDialog from editbook import EditBookDialog
import sys, re, os, traceback, tempfile
DEFAULT_BOOK_COVER = None DEFAULT_BOOK_COVER = None
LIBRARY_BOOK_TEMPLATE = QString("<table><tr><td><b>Formats:</b> %1 </td><td><b>Tags:</b> %2</td></tr><tr><td><b>Comments:</b>%3</td></tr></table>") LIBRARY_BOOK_TEMPLATE = QString("<table><tr><td><b>Formats:</b> %1 \
DEVICE_BOOK_TEMPLATE = QString("<table><tr><td><b>Title: </b>%1</td><td><b>&nbsp;Size:</b> %2</td></tr><tr><td><b>Author: </b>%3</td><td><b>&nbsp;Type: </b>%4</td></tr></table>") </td><td><b>Tags:</b> %2</td></tr> \
<tr><td><b>Comments:</b>%3</td></tr></table>")
DEVICE_BOOK_TEMPLATE = QString("<table><tr><td><b>Title: </b>%1</td><td> \
<b>&nbsp;Size:</b> %2</td></tr>\
<tr><td><b>Author: </b>%3</td>\
<td><b>&nbsp;Type: </b>%4</td></tr></table>")
Ui_MainWindow = import_ui("main.ui") Ui_MainWindow = import_ui("main.ui")
class MainWindow(QObject, Ui_MainWindow): class Main(QObject, Ui_MainWindow):
""" Create GUI """
def show_device(self, yes): def show_device(self, yes):
""" If C{yes} show the items on the device otherwise show the items in the library """ """
self.device_view.selectionModel().reset(), self.library_view.selectionModel().reset() If C{yes} show the items on the device otherwise show the items
self.book_cover.hide(), self.book_info.hide() in the library
if yes: """
self.device_view.show(), self.library_view.hide() self.device_view.selectionModel().reset()
self.book_cover.setAcceptDrops(False) self.library_view.selectionModel().reset()
self.current_view = self.device_view self.book_cover.hide()
else: self.book_info.hide()
self.device_view.hide(), self.library_view.show() if yes:
self.book_cover.setAcceptDrops(True) self.device_view.show()
self.current_view = self.library_view self.library_view.hide()
self.current_view.sortByColumn(3, Qt.DescendingOrder) self.book_cover.setAcceptDrops(False)
self.current_view = self.device_view
else:
def tree_clicked(self, index): self.device_view.hide()
if index.isValid(): self.library_view.show()
self.search.clear() self.book_cover.setAcceptDrops(True)
show_dev = True self.current_view = self.library_view
model = self.device_tree.model() self.current_view.sortByColumn(3, Qt.DescendingOrder)
if model.is_library(index):
show_dev = False
elif model.is_reader(index):
self.device_view.setModel(self.reader_model)
QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
elif model.is_card(index):
self.device_view.setModel(self.card_model)
QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
self.show_device(show_dev)
def model_modified(self):
if self.library_view.isVisible(): view = self.library_view
else: view = self.device_view
view.clearSelection()
view.resizeColumnsToContents()
self.book_cover.hide()
self.book_info.hide()
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def resize_columns(self, topleft, bottomright):
if self.library_view.isVisible(): view = self.library_view
else: view = self.device_view
for c in range(topleft.column(), bottomright.column()+1):
view.resizeColumnToContents(c)
def show_book(self, current, previous):
if self.library_view.isVisible():
formats, tags, comments, cover = current.model().info(current.row())
data = LIBRARY_BOOK_TEMPLATE.arg(formats).arg(tags).arg(comments)
tooltip = "To save the cover, drag it to the desktop.<br>To change the cover drag the new cover onto this picture"
else:
title, author, size, mime, cover = current.model().info(current.row())
data = DEVICE_BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)
tooltip = "To save the cover, drag it to the desktop."
self.book_info.setText(data)
self.book_cover.setToolTip(tooltip)
if not cover: cover = DEFAULT_BOOK_COVER
self.book_cover.setPixmap(cover)
self.book_cover.show()
self.book_info.show()
def formats_added(self, index):
if index == self.library_view.currentIndex():
self.show_book(index, index)
def delete(self, action):
rows = self.current_view.selectionModel().selectedRows()
if not len(rows): return
count = str(len(rows))
ret = QMessageBox.question(self.window, self.trUtf8(APP_TITLE + " - confirm"), self.trUtf8("Are you sure you want to <b>permanently delete</b> these ") +count+self.trUtf8(" item(s)?"), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if ret != QMessageBox.Yes: return
self.window.setCursor(Qt.WaitCursor)
if self.library_view.isVisible():
self.library_model.delete(self.library_view.selectionModel().selectedRows())
else:
self.status("Deleting files from device")
paths = self.device_view.model().delete(rows)
for path in paths:
self.status("Deleting "+path[path.rfind("/")+1:])
self.dev.del_file(path, end_session=False)
fix_ids(self.reader_model.booklist, self.card_model.booklist)
self.status("Syncing media list to reader")
self.dev.upload_book_list(self.reader_model.booklist)
if len(self.card_model.booklist):
self.status("Syncing media list to card")
self.dev.upload_book_list(self.card_model.booklist)
self.update_availabe_space()
self.show_book(self.current_view.currentIndex(), QModelIndex())
self.window.setCursor(Qt.ArrowCursor)
def read_settings(self):
settings = QSettings()
settings.beginGroup("MainWindow")
self.window.resize(settings.value("size", QVariant(QSize(1000, 700))).toSize())
settings.endGroup()
self.database_path = settings.value("database path", QVariant(os.path.expanduser("~/library.db"))).toString()
def write_settings(self): def tree_clicked(self, index):
settings = QSettings() if index.isValid():
settings.beginGroup("MainWindow") self.search.clear()
settings.setValue("size", QVariant(self.window.size())) show_dev = True
settings.endGroup() model = self.device_tree.model()
if model.is_library(index):
def close_event(self, e): show_dev = False
self.write_settings() elif model.is_reader(index):
e.accept() self.device_view.setModel(self.reader_model)
QObject.connect(self.device_view.selectionModel(), \
def add(self, action): SIGNAL("currentChanged(QModelIndex, QModelIndex)"), \
settings = QSettings() self.show_book)
dir = settings.value("add books dialog dir", QVariant(os.path.expanduser("~"))).toString() elif model.is_card(index):
files = QFileDialog.getOpenFileNames(self.window, "Choose books to add to library", dir, "Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)") self.device_view.setModel(self.card_model)
if not files.isEmpty(): QObject.connect(self.device_view.selectionModel(), \
x = str(files[0]) SIGNAL("currentChanged(QModelIndex, QModelIndex)"), \
settings.setValue("add books dialog dir", QVariant(os.path.dirname(x))) self.show_book)
files = str(files.join("|||")).split("|||") self.show_device(show_dev)
self.add_books(files)
def add_books(self, files):
self.window.setCursor(Qt.WaitCursor)
for file in files:
file = os.path.abspath(file)
self.library_view.model().add_book(file)
if self.library_view.isVisible(): self.search.clear()
else: self.library_model.search("")
hv = self.library_view.horizontalHeader()
col = hv.sortIndicatorSection()
order = hv.sortIndicatorOrder()
self.library_view.model().sort(col, order)
self.window.setCursor(Qt.ArrowCursor)
def model_modified(self):
def edit(self, action): if self.library_view.isVisible(): view = self.library_view
if self.library_view.isVisible(): else: view = self.device_view
rows = self.library_view.selectionModel().selectedRows() view.clearSelection()
for row in rows: view.resizeColumnsToContents()
id = self.library_model.id_from_index(row) self.book_cover.hide()
dialog = QDialog(self.window) self.book_info.hide()
ed = EditBookDialog(dialog, id, self.library_model.db) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
if dialog.exec_() == QDialog.Accepted:
self.library_model.refresh_row(row.row())
def update_cover(self, pix):
if not pix.isNull():
try:
self.library_view.model().update_cover(self.library_view.currentIndex(), pix)
self.book_cover.setPixmap(pix)
except Exception, e: Error("Unable to change cover", e)
def upload_books(self, to, files, ids):
oncard = False if to == "reader" else True
booklists = (self.reader_model.booklist, self.card_model.booklist)
def update_models():
hv = self.device_view.horizontalHeader()
col = hv.sortIndicatorSection()
order = hv.sortIndicatorOrder()
model = self.card_model if oncard else self.reader_model
model.sort(col, order)
if self.device_view.isVisible() and self.device_view.model() == model: self.search.clear()
else: model.search("")
def sync_lists():
self.status("Syncing media list to device main memory")
self.dev.upload_book_list(booklists[0])
if len(booklists[1]):
self.status("Syncing media list to storage card")
self.dev.upload_book_list(booklists[1])
self.window.setCursor(Qt.WaitCursor)
ename = "file"
try:
if ids:
for id in ids:
formats = []
info = self.library_view.model().book_info(id)
if info["cover"]:
pix = QPixmap()
pix.loadFromData(str(info["cover"]))
if pix.isNull(): pix = DEFAULT_BOOK_COVER
pix = pix.scaledToHeight(self.dev.THUMBNAIL_HEIGHT, Qt.SmoothTransformation)
buffer = QBuffer()
buffer.open(QIODevice.WriteOnly)
pix.save(buffer, "JPEG")
info["cover"] = (pix.width(), pix.height(), str(buffer.buffer()))
ename = info["title"]
for f in files:
if re.match("......_"+str(id)+"_", os.path.basename(f)):
formats.append(f)
file = None
try:
for format in self.dev.FORMATS:
for f in formats:
if extension(f) == format:
file = f
raise StopIteration()
except StopIteration: pass
if not file:
Error("The library does not have any formats that can be viewed on the device for " + ename, None)
continue
f = open(file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, "libprs500_"+str(id)+"."+extension(file), info, booklists, oncard=oncard, end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + " already exists on the device", None)
self.progress(100)
continue
else: raise
finally: f.close()
sync_lists()
else:
for file in files:
ename = file
if extension(file) not in self.dev.FORMATS:
Error(ename + " is not in a supported format")
continue
info = { "title":os.path.basename(file), "authors":"Unknown", "cover":(None, None, None) }
f = open(file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, os.path.basename(file), info, booklists, oncard=oncard, end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + " already exists on the device", None)
self.progress(100)
continue
else: raise
finally: f.close()
sync_lists()
except Exception, e:
Error("Unable to send "+ename+" to device", e)
finally:
self.window.setCursor(Qt.ArrowCursor)
self.update_availabe_space()
def __init__(self, window, log_packets):
QObject.__init__(self)
Ui_MainWindow.__init__(self)
self.dev = device(report_progress=self.progress, log_packets=log_packets)
self.setupUi(window)
self.card = None
self.window = window
window.closeEvent = self.close_event
self.read_settings()
# Setup Library Book list def resize_columns(self, topleft, bottomright):
self.library_model = LibraryBooksModel(window) if self.library_view.isVisible():
self.library_model.set_data(LibraryDatabase(str(self.database_path))) view = self.library_view
self.library_view.setModel(self.library_model) else: view = self.device_view
self.current_view = self.library_view for c in range(topleft.column(), bottomright.column()+1):
QObject.connect(self.library_model, SIGNAL("layoutChanged()"), self.library_view.resizeRowsToContents) view.resizeColumnToContents(c)
QObject.connect(self.library_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.library_model.search)
QObject.connect(self.library_model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(self.library_model, SIGNAL("searched()"), self.model_modified)
QObject.connect(self.library_model, SIGNAL("deleted()"), self.model_modified)
QObject.connect(self.library_model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
QObject.connect(self.library_view, SIGNAL('books_dropped'), self.add_books)
QObject.connect(self.library_model, SIGNAL('formats_added'), self.formats_added)
self.library_view.resizeColumnsToContents()
# Create Device tree def show_book(self, current, previous):
model = DeviceModel(self.device_tree) if self.library_view.isVisible():
QObject.connect(self.device_tree, SIGNAL("activated(QModelIndex)"), self.tree_clicked) formats, tags, comments, cover = current.model().info(current.row())
QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), self.tree_clicked) data = LIBRARY_BOOK_TEMPLATE.arg(formats).arg(tags).arg(comments)
QObject.connect(model, SIGNAL('books_dropped'), self.add_books) tooltip = "To save the cover, drag it to the desktop.<br>To \
QObject.connect(model, SIGNAL('upload_books'), self.upload_books) change the cover drag the new cover onto this picture"
self.device_tree.setModel(model) else:
title, author, size, mime, cover = current.model().info(current.row())
data = DEVICE_BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)
tooltip = "To save the cover, drag it to the desktop."
self.book_info.setText(data)
self.book_cover.setToolTip(tooltip)
if not cover: cover = DEFAULT_BOOK_COVER
self.book_cover.setPixmap(cover)
self.book_cover.show()
self.book_info.show()
# Create Device Book list def formats_added(self, index):
self.reader_model = DeviceBooksModel(window) if index == self.library_view.currentIndex():
self.card_model = DeviceBooksModel(window) self.show_book(index, index)
self.device_view.setModel(self.reader_model)
QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
for model in (self.reader_model, self. card_model):
QObject.connect(model, SIGNAL("layoutChanged()"), self.device_view.resizeRowsToContents)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), model.search)
QObject.connect(model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(model, SIGNAL("searched()"), self.model_modified)
QObject.connect(model, SIGNAL("deleted()"), self.model_modified)
QObject.connect(model, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
# Setup book display def delete(self, action):
self.book_cover.hide() rows = self.current_view.selectionModel().selectedRows()
self.book_info.hide() if not len(rows):
return
count = str(len(rows))
ret = QMessageBox.question(self.window, self.trUtf8(APP_TITLE + \
" - confirm"), self.trUtf8("Are you sure you want to \
<b>permanently delete</b> these ") +count+self.trUtf8(" item(s)?"), \
QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
if ret != QMessageBox.Yes:
return
self.window.setCursor(Qt.WaitCursor)
if self.library_view.isVisible():
self.library_model.delete(self.library_view.selectionModel()\
.selectedRows())
else:
self.status("Deleting files from device")
paths = self.device_view.model().delete(rows)
for path in paths:
self.status("Deleting "+path[path.rfind("/")+1:])
self.dev.del_file(path, end_session=False)
fix_ids(self.reader_model.booklist, self.card_model.booklist)
self.status("Syncing media list to reader")
self.dev.upload_book_list(self.reader_model.booklist)
if len(self.card_model.booklist):
self.status("Syncing media list to card")
self.dev.upload_book_list(self.card_model.booklist)
self.update_availabe_space()
self.show_book(self.current_view.currentIndex(), QModelIndex())
self.window.setCursor(Qt.ArrowCursor)
# Connect actions def read_settings(self):
QObject.connect(self.action_add, SIGNAL("triggered(bool)"), self.add) settings = QSettings()
QObject.connect(self.action_del, SIGNAL("triggered(bool)"), self.delete) settings.beginGroup("MainWindow")
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"), self.edit) self.window.resize(settings.value("size", QVariant(QSize(1000, 700))).\
toSize())
settings.endGroup()
self.database_path = settings.value("database path", QVariant(os.path\
.expanduser("~/library.db"))).toString()
# DnD setup def write_settings(self):
QObject.connect(self.book_cover, SIGNAL("cover_received(QPixmap)"), self.update_cover) settings = QSettings()
settings.beginGroup("MainWindow")
settings.setValue("size", QVariant(self.window.size()))
settings.endGroup()
self.detector = DeviceConnectDetector(self.dev) def close_event(self, e):
self.connect(self.detector, SIGNAL("device_connected()"), self.establish_connection) self.write_settings()
self.connect(self.detector, SIGNAL("device_removed()"), self.device_removed) e.accept()
self.search.setFocus(Qt.OtherFocusReason)
self.show_device(False)
self.df_template = self.df.text()
self.df.setText(self.df_template.arg("").arg("").arg(""))
window.show()
def device_removed(self): def add(self, action):
""" @todo: only reset stuff if library is not shown """ settings = QSettings()
self.df.setText(self.df_template.arg("").arg("").arg("")) _dir = settings.value("add books dialog dir", \
self.device_tree.hide_reader(True) QVariant(os.path.expanduser("~"))).toString()
self.device_tree.hide_card(True) files = QFileDialog.getOpenFileNames(self.window, \
self.book_cover.hide() "Choose books to add to library", _dir, \
self.book_info.hide() "Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)")
self.device_view.hide() if not files.isEmpty():
self.library_view.show() x = str(files[0])
settings.setValue("add books dialog dir", QVariant(os.path.dirname(x)))
def progress(self, val): files = str(files.join("|||")).split("|||")
if val < 0: self.add_books(files)
self.progress_bar.setMaximum(0)
else: self.progress_bar.setValue(val)
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def status(self, msg):
self.progress_bar.setMaximum(100)
self.progress_bar.reset()
self.progress_bar.setFormat(msg + ": %p%")
self.progress(0)
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def establish_connection(self):
self.window.setCursor(Qt.WaitCursor)
self.status("Connecting to device")
try:
info = self.dev.get_device_information(end_session=False)
except DeviceBusy, e:
qFatal(str(e))
except DeviceError:
self.dev.reconnect()
self.detector.connection_failed()
return
except ProtocolError, e:
traceback.print_exc(e)
qFatal("Unable to connect to device. Please try unplugging and reconnecting it")
self.df.setText(self.df_template.arg("Connected: "+info[0]).arg(info[1]).arg(info[2]))
self.update_availabe_space(end_session=False)
self.card = self.dev.card()
self.is_connected = True
if self.card: self.device_tree.hide_card(False)
else: self.device_tree.hide_card(True)
self.device_tree.hide_reader(False)
self.status("Loading media list from SONY Reader")
self.reader_model.set_data(self.dev.books(end_session=False))
if self.card: self.status("Loading media list from Storage Card")
self.card_model.set_data(self.dev.books(oncard=True))
self.progress(100)
self.window.setCursor(Qt.ArrowCursor)
def update_availabe_space(self, end_session=True): def add_books(self, files):
space = self.dev.free_space(end_session=end_session) self.window.setCursor(Qt.WaitCursor)
sc = space[1] if int(space[1])>0 else space[2] for _file in files:
self.device_tree.model().update_free_space(space[0], sc) _file = os.path.abspath(_file)
self.library_view.model().add_book(_file)
if self.library_view.isVisible(): self.search.clear()
else: self.library_model.search("")
hv = self.library_view.horizontalHeader()
col = hv.sortIndicatorSection()
order = hv.sortIndicatorOrder()
self.library_view.model().sort(col, order)
self.window.setCursor(Qt.ArrowCursor)
class LockFile(object):
def __init__(self, path):
self.path = path
f =open(path, "w")
f.close()
def __del__(self): def edit(self, action):
if os.access(self.path, os.F_OK): os.remove(self.path) if self.library_view.isVisible():
rows = self.library_view.selectionModel().selectedRows()
for row in rows:
_id = self.library_model.id_from_index(row)
dialog = QDialog(self.window)
EditBookDialog(dialog, _id, self.library_model.db)
if dialog.exec_() == QDialog.Accepted:
self.library_model.refresh_row(row.row())
class DeviceConnectDetector(QObject):
def update_cover(self, pix):
def timerEvent(self, e): if not pix.isNull():
if e.timerId() == self.device_detector: try:
is_connected = self.dev.is_connected() self.library_view.model().update_cover(self.library_view\
if is_connected and not self.is_connected: .currentIndex(), pix)
self.emit(SIGNAL("device_connected()")) self.book_cover.setPixmap(pix)
self.is_connected = True except Exception, e: Error("Unable to change cover", e)
elif not is_connected and self.is_connected:
self.emit(SIGNAL("device_removed()")) def upload_books(self, to, files, ids):
self.is_connected = False oncard = False if to == "reader" else True
booklists = (self.reader_model.booklist, self.card_model.booklist)
def update_models():
hv = self.device_view.horizontalHeader()
col = hv.sortIndicatorSection()
order = hv.sortIndicatorOrder()
model = self.card_model if oncard else self.reader_model
model.sort(col, order)
if self.device_view.isVisible() and self.device_view.model()\
== model: self.search.clear()
else: model.search("")
def connection_failed(self): def sync_lists():
# TODO: Do something intelligent if we're using HAL self.status("Syncing media list to device main memory")
self.is_connected = False self.dev.upload_book_list(booklists[0])
if len(booklists[1]):
def udi_is_device(self, udi): self.status("Syncing media list to storage card")
ans = False self.dev.upload_book_list(booklists[1])
try:
devobj = bus.get_object('org.freedesktop.Hal', udi) self.window.setCursor(Qt.WaitCursor)
dev = dbus.Interface(devobj, "org.freedesktop.Hal.Device") ename = "file"
properties = dev.GetAllProperties() try:
vendor_id, product_id = int(properties["usb_device.vendor_id"]), int(properties["usb_device.product_id"]) if ids:
if self.dev.signature() == (vendor_id, product_id): ans = True for _id in ids:
except: formats = []
self.device_detector = self.startTimer(1000) info = self.library_view.model().book_info(_id)
return ans if info["cover"]:
pix = QPixmap()
def device_added_callback(self, udi): pix.loadFromData(str(info["cover"]))
if self.udi_is_device(udi): if pix.isNull():
self.emit(SIGNAL("device_connected()")) pix = DEFAULT_BOOK_COVER
pix = pix.scaledToHeight(self.dev.THUMBNAIL_HEIGHT, \
def device_removed_callback(self, udi): Qt.SmoothTransformation)
if self.udi_is_device(udi): _buffer = QBuffer()
self.emit(SIGNAL("device_removed()")) _buffer.open(QIODevice.WriteOnly)
pix.save(_buffer, "JPEG")
def __init__(self, dev): info["cover"] = (pix.width(), pix.height(), \
QObject.__init__(self) str(_buffer.buffer()))
self.dev = dev ename = info["title"]
try: for f in files:
raise Exception("DBUS doesn't support the Qt mainloop") if re.match("......_"+str(_id)+"_", os.path.basename(f)):
import dbus formats.append(f)
bus = dbus.SystemBus() _file = None
hal_manager_obj = bus.get_object('org.freedesktop.Hal', '/org/freedesktop/Hal/Manager') try:
hal_manager = dbus.Interface(hal_manager_obj, 'org.freedesktop.Hal.Manager') for format in self.dev.FORMATS:
hal_manager.connect_to_signal('DeviceAdded', self.device_added_callback) for f in formats:
hal_manager.connect_to_signal('DeviceRemoved', self.device_removed_callback) if extension(f) == format:
except Exception, e: _file = f
#Warning("Could not connect to HAL", e) raise StopIteration()
self.is_connected = False except StopIteration: pass
self.device_detector = self.startTimer(1000) if not _file:
Error("The library does not have any formats that "+\
"can be viewed on the device for " + ename, None)
continue
f = open(_file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, "libprs500_"+str(_id)+"."+\
extension(_file), info, booklists, oncard=oncard, \
end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + \
" already exists on the device", None)
self.progress(100)
continue
else: raise
finally: f.close()
sync_lists()
else:
for _file in files:
ename = _file
if extension(_file) not in self.dev.FORMATS:
Error(ename + " is not in a supported format")
continue
info = { "title":os.path.basename(_file), \
"authors":"Unknown", "cover":(None, None, None) }
f = open(_file, "rb")
self.status("Sending "+info["title"]+" to device")
try:
self.dev.add_book(f, os.path.basename(_file), info, \
booklists, oncard=oncard, end_session=False)
update_models()
except PathError, e:
if "already exists" in str(e):
Error(info["title"] + \
" already exists on the device", None)
self.progress(100)
continue
else: raise
finally: f.close()
sync_lists()
except Exception, e:
Error("Unable to send "+ename+" to device", e)
finally:
self.window.setCursor(Qt.ArrowCursor)
self.update_availabe_space()
def __init__(self, window, log_packets):
QObject.__init__(self)
Ui_MainWindow.__init__(self)
self.dev = device(report_progress=self.progress, log_packets=log_packets)
self.setupUi(window)
self.card = None
self.window = window
window.closeEvent = self.close_event
self.read_settings()
# Setup Library Book list
self.library_model = LibraryBooksModel(window)
self.library_model.set_data(LibraryDatabase(str(self.database_path)))
self.library_view.setModel(self.library_model)
self.current_view = self.library_view
QObject.connect(self.library_model, SIGNAL("layoutChanged()"), \
self.library_view.resizeRowsToContents)
QObject.connect(self.library_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), \
self.library_model.search)
QObject.connect(self.library_model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(self.library_model, SIGNAL("searched()"), \
self.model_modified)
QObject.connect(self.library_model, SIGNAL("deleted()"), \
self.model_modified)
QObject.connect(self.library_model, \
SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.resize_columns)
QObject.connect(self.library_view, \
SIGNAL('books_dropped'), self.add_books)
QObject.connect(self.library_model, \
SIGNAL('formats_added'), self.formats_added)
self.library_view.resizeColumnsToContents()
# Create Device tree
model = DeviceModel(self.device_tree)
QObject.connect(self.device_tree, SIGNAL("activated(QModelIndex)"), \
self.tree_clicked)
QObject.connect(self.device_tree, SIGNAL("clicked(QModelIndex)"), \
self.tree_clicked)
QObject.connect(model, SIGNAL('books_dropped'), self.add_books)
QObject.connect(model, SIGNAL('upload_books'), self.upload_books)
self.device_tree.setModel(model)
# Create Device Book list
self.reader_model = DeviceBooksModel(window)
self.card_model = DeviceBooksModel(window)
self.device_view.setModel(self.reader_model)
QObject.connect(self.device_view.selectionModel(), \
SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
for model in (self.reader_model, self. card_model):
QObject.connect(model, SIGNAL("layoutChanged()"), \
self.device_view.resizeRowsToContents)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), \
model.search)
QObject.connect(model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(model, SIGNAL("searched()"), self.model_modified)
QObject.connect(model, SIGNAL("deleted()"), self.model_modified)
QObject.connect(model, SIGNAL("dataChanged(QModelIndex, QModelIndex)")\
, self.resize_columns)
# Setup book display
self.book_cover.hide()
self.book_info.hide()
# Connect actions
QObject.connect(self.action_add, SIGNAL("triggered(bool)"), self.add)
QObject.connect(self.action_del, SIGNAL("triggered(bool)"), self.delete)
QObject.connect(self.action_edit, SIGNAL("triggered(bool)"), self.edit)
# DnD setup
QObject.connect(self.book_cover, SIGNAL("cover_received(QPixmap)"), \
self.update_cover)
self.detector = DeviceConnectDetector(self.dev)
self.connect(self.detector, SIGNAL("device_connected()"), \
self.establish_connection)
self.connect(self.detector, SIGNAL("device_removed()"), self.device_removed)
self.search.setFocus(Qt.OtherFocusReason)
self.show_device(False)
self.df_template = self.df.text()
self.df.setText(self.df_template.arg("").arg("").arg(""))
window.show()
def device_removed(self):
""" @todo: only reset stuff if library is not shown """
self.df.setText(self.df_template.arg("").arg("").arg(""))
self.device_tree.hide_reader(True)
self.device_tree.hide_card(True)
self.book_cover.hide()
self.book_info.hide()
self.device_view.hide()
self.library_view.show()
def progress(self, val):
if val < 0:
self.progress_bar.setMaximum(0)
else: self.progress_bar.setValue(val)
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def status(self, msg):
self.progress_bar.setMaximum(100)
self.progress_bar.reset()
self.progress_bar.setFormat(msg + ": %p%")
self.progress(0)
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def establish_connection(self):
self.window.setCursor(Qt.WaitCursor)
self.status("Connecting to device")
try:
info = self.dev.get_device_information(end_session=False)
except DeviceBusy, e:
qFatal(str(e))
except DeviceError:
self.dev.reconnect()
self.detector.connection_failed()
return
except ProtocolError, e:
traceback.print_exc(e)
qFatal("Unable to connect to device. Please try unplugging and"+\
" reconnecting it")
self.df.setText(self.df_template.arg("Connected: "+info[0])\
.arg(info[1]).arg(info[2]))
self.update_availabe_space(end_session=False)
self.card = self.dev.card()
self.is_connected = True
if self.card: self.device_tree.hide_card(False)
else: self.device_tree.hide_card(True)
self.device_tree.hide_reader(False)
self.status("Loading media list from SONY Reader")
self.reader_model.set_data(self.dev.books(end_session=False))
if self.card: self.status("Loading media list from Storage Card")
self.card_model.set_data(self.dev.books(oncard=True))
self.progress(100)
self.window.setCursor(Qt.ArrowCursor)
def update_availabe_space(self, end_session=True):
space = self.dev.free_space(end_session=end_session)
sc = space[1] if int(space[1])>0 else space[2]
self.device_tree.model().update_free_space(space[0], sc)
class LockFile(object):
def __init__(self, path):
self.path = path
f = open(path, "w")
f.close()
def __del__(self):
if os.access(self.path, os.F_OK): os.remove(self.path)
class DeviceConnectDetector(QObject):
def timerEvent(self, e):
if e.timerId() == self.device_detector:
is_connected = self.dev.is_connected()
if is_connected and not self.is_connected:
self.emit(SIGNAL("device_connected()"))
self.is_connected = True
elif not is_connected and self.is_connected:
self.emit(SIGNAL("device_removed()"))
self.is_connected = False
def connection_failed(self):
# TODO: Do something intelligent if we're using HAL
self.is_connected = False
def udi_is_device(self, udi):
ans = False
try:
devobj = bus.get_object('org.freedesktop.Hal', udi)
dev = dbus.Interface(devobj, "org.freedesktop.Hal.Device")
properties = dev.GetAllProperties()
vendor_id = int(properties["usb_device.vendor_id"]),
product_id = int(properties["usb_device.product_id"])
if self.dev.signature() == (vendor_id, product_id): ans = True
except:
self.device_detector = self.startTimer(1000)
return ans
def device_added_callback(self, udi):
if self.udi_is_device(udi):
self.emit(SIGNAL("device_connected()"))
def device_removed_callback(self, udi):
if self.udi_is_device(udi):
self.emit(SIGNAL("device_removed()"))
def __init__(self, dev):
QObject.__init__(self)
self.dev = dev
try:
raise Exception("DBUS doesn't support the Qt mainloop")
import dbus
bus = dbus.SystemBus()
hal_manager_obj = bus.get_object('org.freedesktop.Hal',\
'/org/freedesktop/Hal/Manager')
hal_manager = dbus.Interface(hal_manager_obj,\
'org.freedesktop.Hal.Manager')
hal_manager.connect_to_signal('DeviceAdded', \
self.device_added_callback)
hal_manager.connect_to_signal('DeviceRemoved', \
self.device_removed_callback)
except Exception, e:
#_Warning("Could not connect to HAL", e)
self.is_connected = False
self.device_detector = self.startTimer(1000)
def main(): def main():
from optparse import OptionParser from optparse import OptionParser
from libprs500 import __version__ as VERSION from libprs500 import __version__ as VERSION
lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock") lock = os.path.join(tempfile.gettempdir(),"libprs500_gui_lock")
if os.access(lock, os.F_OK): if os.access(lock, os.F_OK):
print >>sys.stderr, "Another instance of", APP_TITLE, "is running" print >>sys.stderr, "Another instance of", APP_TITLE, "is running"
print >>sys.stderr, "If you are sure this is not the case then manually delete the file", lock print >>sys.stderr, "If you are sure this is not the case then "+\
sys.exit(1) "manually delete the file", lock
sys.exit(1)
parser = OptionParser(usage="usage: %prog [options]", version=VERSION) parser = OptionParser(usage="usage: %prog [options]", version=VERSION)
parser.add_option("--log-packets", help="print out packet stream to stdout. "+\ parser.add_option("--log-packets", help="print out packet stream to stdout. "+\
"The numbers in the left column are byte offsets that allow the packet size to be read off easily.", \ "The numbers in the left column are byte offsets that allow"+\
" the packet size to be read off easily.", \
dest="log_packets", action="store_true", default=False) dest="log_packets", action="store_true", default=False)
options, args = parser.parse_args() options, args = parser.parse_args()
from PyQt4.Qt import QApplication, QMainWindow from PyQt4.Qt import QApplication, QMainWindow
@ -483,9 +558,9 @@ def main():
installErrorHandler(QErrorMessage(window)) installErrorHandler(QErrorMessage(window))
QCoreApplication.setOrganizationName("KovidsBrain") QCoreApplication.setOrganizationName("KovidsBrain")
QCoreApplication.setApplicationName(APP_TITLE) QCoreApplication.setApplicationName(APP_TITLE)
gui = MainWindow(window, options.log_packets) Main(window, options.log_packets)
lock = LockFile(lock) lock = LockFile(lock)
return app.exec_() return app.exec_()
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

File diff suppressed because it is too large Load Diff

View File

@ -89,5 +89,6 @@ try:
except ImportError: except ImportError:
print "You do not have PyQt4 installed. The GUI will not work. You can obtain PyQt4 from http://www.riverbankcomputing.co.uk/pyqt/download.php" print "You do not have PyQt4 installed. The GUI will not work. You can obtain PyQt4 from http://www.riverbankcomputing.co.uk/pyqt/download.php"
else: else:
import PyQt4.Qt
if PyQt4.Qt.PYQT_VERSION < 0x40101: if PyQt4.Qt.PYQT_VERSION < 0x40101:
print "WARNING: The GUI needs PyQt >= 4.1.1" print "WARNING: The GUI needs PyQt >= 4.1.1"