diff --git a/README b/README
index bca84415ff..b78423228f 100644
--- a/README
+++ b/README
@@ -1,19 +1,22 @@
-Library implementing a reverse engineered protocol to communicate with the Sony Reader PRS-500.
-
Requirements:
1) Python >= 2.5
2) PyUSB >= 0.3.4 (http://sourceforge.net/projects/pyusb/)
+3) For the GUI:
+ - pyxml >= 0.84
+ - PyQt4 >= 4.1
Installation:
As root
python setup.py install
-Usage:
-Add the following to /etc/udev/rules.d/90-local.rules
+On Linux, to enable access to the reader for non-root users, you need to add the following to
+/etc/udev/rules.d/90-local.rules
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
-and run udevstart to enable access to the reader for non-root users. You may have to adjust the GROUP and the location of the
-rules file to suit your distribution.
+You may have to adjust the GROUP and the location of the 90-local.rules file to suit your distribution.
-Usage information is provided when you run the script prs500.py
+Usage:
+1) A command line interface is provided via, the command prs500
+2) A GUI is provided via the command prs500-gui
+3) You can read/edit the metadata of LRF files via the command lrf-meta
diff --git a/libprs500/gui/database.py b/libprs500/gui/database.py
index e49da8ebe3..17e591f845 100644
--- a/libprs500/gui/database.py
+++ b/libprs500/gui/database.py
@@ -22,7 +22,7 @@ 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 );
+ cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP, comments TEXT );
create table if not exists books_data(id INTEGER, extension TEXT, data BLOB);
"""
@@ -53,11 +53,19 @@ class LibraryDatabase(object):
if "unknown" in author.lower(): author = None
file = zlib.compress(open(file).read())
if cover: cover = sqlite.Binary(zlib.compress(cover))
- self.con.execute("insert into books_meta (title, authors, publisher, size, tags, cover) values (?,?,?,?,?,?)", (title, author, publisher, size, None, cover))
+ self.con.execute("insert into books_meta (title, authors, publisher, size, tags, cover, comments) values (?,?,?,?,?,?)", (title, author, publisher, size, None, cover, 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()
+ 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 get_table(self, columns):
cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta")
@@ -68,6 +76,24 @@ class LibraryDatabase(object):
rows.append(r)
return rows
+ def get_format(self, id, ext):
+ ext = ext.lower()
+ cur = self.cur.execute("select data from books_data where id=? and extension=?",(id, ext))
+ try: data = cur.next()
+ except: pass
+ else: return zlib.decompress(str(data["data"]))
+
+ def add_format(self, id, ext, 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
@@ -76,11 +102,19 @@ class LibraryDatabase(object):
data[field] = row[field]
return data
+ def set_metadata(self, id, title=None, authors=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(zlib.compress(cover))
+ self.con.execute('update books_meta set title=?, authors=?, publisher=?, tags=?, cover=?, comments=? where id=?', (title, authors, publisher, tags, cover, comments, id))
+ self.con.commit()
def search(self, query): pass
-if __name__ == "__main__":
- lbm = LibraryDatabase("/home/kovid/library.sqlite")
+#if __name__ == "__main__":
+# lbm = LibraryDatabase("/home/kovid/library.db")
# lbm.add_book("/home/kovid/documents/ebooks/hobbfar01.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbfar02.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbfar03.lrf")
@@ -88,4 +122,4 @@ if __name__ == "__main__":
# lbm.add_book("/home/kovid/documents/ebooks/hobbtawny01.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbtawny02.lrf")
# lbm.add_book("/home/kovid/documents/ebooks/hobbtawny03.lrf")
- print lbm.get_table(["id","title"])
+# print lbm.get_table(["id","title"])
diff --git a/libprs500/gui/editbook.py b/libprs500/gui/editbook.py
new file mode 100644
index 0000000000..d22a53d9bd
--- /dev/null
+++ b/libprs500/gui/editbook.py
@@ -0,0 +1,90 @@
+## Copyright (C) 2006 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.
+import sys, os, pkg_resources, StringIO
+from PyQt4 import uic
+from PyQt4.QtCore import Qt, SIGNAL
+from PyQt4.Qt import QObject, QDialog, QPixmap
+from libprs500.lrf.meta import LRFMeta
+
+ui = pkg_resources.resource_stream(__name__, "editbook.ui")
+sys.path.append(os.path.dirname(ui.name))
+Ui_BookEditDialog, bclass = uic.loadUiType(pkg_resources.resource_stream(__name__, "editbook.ui"))
+
+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)
+ 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()
+ 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, tags=tags, publisher=publisher, comments=comments, cover=self.cover_data)
+ lrf = self.db.get_format(self.id, "lrf")
+ if lrf:
+ lrf = StringIO.StringIO(lrf)
+ lf = LRFMeta(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 __init__(self, dialog, id, db):
+ Ui_BookEditDialog.__init__(self)
+ self.parent = dialog
+ self.setupUi(dialog)
+ self.db = db
+ self.id = id
+ self.cover_data = None
+ QObject.connect(self.cover_button, SIGNAL("clicked(bool)"), self.select_cover)
+ QObject.connect(self.button_box, SIGNAL("accepted()"), self.write_data)
+ data = self.db.get_row_by_id(self.id, ["title","authors","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 "")
+ 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"))
diff --git a/libprs500/gui/editbook.ui b/libprs500/gui/editbook.ui
index e90c549f86..b65f4b3542 100644
--- a/libprs500/gui/editbook.ui
+++ b/libprs500/gui/editbook.ui
@@ -242,7 +242,11 @@
6
-
-
+
+
+ true
+
+
-
@@ -353,22 +357,6 @@
-
- button_box
- accepted()
- BookEditDialog
- accept()
-
-
- 248
- 254
-
-
- 157
- 274
-
-
-
button_box
rejected()
@@ -385,5 +373,21 @@
+
+ button_box
+ accepted()
+ BookEditDialog
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
diff --git a/libprs500/gui/images/fileopen.png b/libprs500/gui/images/fileopen.png
new file mode 100644
index 0000000000..503a004591
Binary files /dev/null and b/libprs500/gui/images/fileopen.png differ
diff --git a/libprs500/gui/main.py b/libprs500/gui/main.py
index ac034d6c14..67f4dfcaa3 100644
--- a/libprs500/gui/main.py
+++ b/libprs500/gui/main.py
@@ -16,9 +16,11 @@ from libprs500.communicate import PRS500Device as device
from libprs500.errors import *
from libprs500.lrf.meta import LRFMetaFile, LRFException
from database import LibraryDatabase
+from editbook import EditBookDialog
+
from PyQt4.QtCore import Qt, SIGNAL
from PyQt4.Qt import QObject, QThread, QCoreApplication, QEventLoop, QString, QStandardItem, QStandardItemModel, QStatusBar, QVariant, QAbstractTableModel, \
- QAbstractItemView, QImage, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage
+ QAbstractItemView, QImage, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage, QDialog
from PyQt4 import uic
import sys, pkg_resources, re, string, time, os, os.path, traceback, textwrap, zlib
from stat import ST_SIZE
@@ -34,7 +36,7 @@ TIME_WRITE_FMT = "%d %b %Y"
COVER_HEIGHT = 80
def human_readable(size):
- """ Convert a size in bytes into a human readle form """
+ """ Convert a size in bytes into a human readable form """
if size < 1024: divisor, suffix = 1, "B"
elif size < 1024*1024: divisor, suffix = 1024., "KB"
elif size < 1024*1024*1024: divisor, suffix = 1024*1024, "MB"
@@ -96,6 +98,12 @@ class LibraryBooksModel(QAbstractTableModel):
cover = pix.scaledToHeight(COVER_HEIGHT, Qt.SmoothTransformation)
return row["title"], row["authors"], human_readable(int(row["size"])), exts, cover
+ def id_from_index(self, index): return self._data[index.row()]["id"]
+
+ def refresh_row(self, row):
+ self._data[row] = self.db.get_row_by_id(self._data[row]["id"], self.FIELDS)
+ self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), self.index(row, 0), self.index(row, self.columnCount(0)-1))
+
def data(self, index, role):
if role == Qt.DisplayRole:
row, col = index.row(), index.column()
@@ -258,7 +266,7 @@ class DeviceBooksModel(QAbstractTableModel):
ui = pkg_resources.resource_stream(__name__, "main.ui")
sys.path.append(os.path.dirname(ui.name))
-Ui_MainWindow, bclass = uic.loadUiType(pkg_resources.resource_stream(__name__, "main.ui"))
+Ui_MainWindow, bclass = uic.loadUiType(pkg_resources.resource_stream(__name__, "main.ui"))
class MainWindow(QObject, Ui_MainWindow):
def show_device(self, yes):
@@ -373,7 +381,7 @@ class MainWindow(QObject, Ui_MainWindow):
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.sqlite"))).toString()
+ self.database_path = settings.value("database path", QVariant(os.path.expanduser("~/library.db"))).toString()
def write_settings(self):
settings = QSettings()
@@ -411,14 +419,23 @@ class MainWindow(QObject, Ui_MainWindow):
self.library_model.add(file, title, author, publisher, cover)
def edit(self, action):
- pass
+ 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)
+ ed = EditBookDialog(dialog, id, self.library_model.db)
+ if dialog.exec_() == QDialog.Accepted:
+ self.library_model.refresh_row(row.row())
+
def show_error(self, e, msg):
QErrorMessage(self.window).showMessage(msg+"
Error: "+str(e)+"
Traceback:
"+traceback.format_exc(e))
def __init__(self, window):
QObject.__init__(self)
- Ui_MainWindow.__init__(self)
+ Ui_MainWindow.__init__(self)
+
self.dev = device(report_progress=self.progress)
self.is_connected = False
self.setupUi(window)
@@ -548,7 +565,7 @@ class MainWindow(QObject, Ui_MainWindow):
self.status("Connecting to device")
try:
space = self.dev.available_space()
- except TimeoutError:
+ except ProtocolError:
c = 0
self.status("Waiting for device to initialize")
while c < 100: # Delay for 10s while device is initializing
diff --git a/prs-500.e4p b/prs-500.e4p
index ae64148905..38f2e97943 100644
--- a/prs-500.e4p
+++ b/prs-500.e4p
@@ -1,7 +1,7 @@
-
+
Python
@@ -70,6 +70,11 @@
gui
database.py
+
+ libprs500
+ gui
+ editbook.py
+