GUI now supports displaying lists of books from both the library and the device

This commit is contained in:
Kovid Goyal 2006-11-24 05:17:38 +00:00
parent 1ec8632924
commit c8d21c5930
10 changed files with 2065 additions and 89 deletions

View File

@ -45,12 +45,12 @@ Contains the logic for communication with the device (a SONY PRS-500).
The public interface of class L{PRS500Device} defines the methods for performing various tasks. The public interface of class L{PRS500Device} defines the methods for performing various tasks.
""" """
import usb, sys, os, time import usb, sys, os, time
from base64 import b64decode as decode
from tempfile import TemporaryFile
from array import array from array import array
from xml.sax.handler import ContentHandler from xml.sax.handler import ContentHandler
from xml.sax import make_parser from xml.sax import make_parser
from xml.sax.handler import feature_namespaces from xml.sax.handler import feature_namespaces
from base64 import b64decode as decode
from tempfile import TemporaryFile
from prstypes import * from prstypes import *
from errors import * from errors import *

91
libprs500/gui/database.py Normal file
View File

@ -0,0 +1,91 @@
## 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 sqlite3 as sqlite
import os, os.path, zlib
from stat import ST_SIZE
from libprs500.lrf.meta import LRFMetaFile
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 );
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 zlib.decompress(str(raw)) if raw else None
def get_extensions(self, id):
exts = []
cur = self.con.execute("select extension from books_data where id=?", (id,))
for row in cur:
exts.append(row["extension"])
return exts
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 = 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))
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_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_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 search(self, query): pass
if __name__ == "__main__":
lbm = LibraryDatabase("/home/kovid/library.sqlite")
# 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")
# lbm.add_book("/home/kovid/documents/ebooks/hobblive01.lrf")
# 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"])

1581
libprs500/gui/images.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -14,15 +14,24 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from libprs500.communicate import PRS500Device as device from libprs500.communicate import PRS500Device as device
from libprs500.errors import * from libprs500.errors import *
from libprs500.lrf.meta import LRFMetaFile, LRFException
from database import LibraryDatabase
import images import images
from PyQt4.QtCore import Qt, SIGNAL from PyQt4.QtCore import Qt, SIGNAL
from PyQt4.Qt import QObject, QThread, QCoreApplication, QEventLoop, QString, QStandardItem, QStandardItemModel, QStatusBar, QVariant, QAbstractTableModel, \ from PyQt4.Qt import QObject, QThread, QCoreApplication, QEventLoop, QString, QStandardItem, QStandardItemModel, QStatusBar, QVariant, QAbstractTableModel, \
QAbstractItemView, QImage, QPixmap, QIcon, QSize QAbstractItemView, QImage, QPixmap, QIcon, QSize, QMessageBox, QSettings, QFileDialog, QErrorMessage
from PyQt4 import uic from PyQt4 import uic
import sys, pkg_resources, re, string import sys, pkg_resources, re, string, time, os, os.path, traceback, textwrap, zlib
from stat import ST_SIZE
from tempfile import TemporaryFile, NamedTemporaryFile
from exceptions import Exception as Exception
import xml.dom.minidom as dom
from xml.dom.ext import PrettyPrint as PrettyPrint
from operator import itemgetter from operator import itemgetter
NONE = QVariant() NONE = QVariant()
TIME_WRITE_FMT = "%d %b %Y"
COVER_HEIGHT = 80
def human_readable(size): def human_readable(size):
""" Convert a size in bytes into a human readle form """ """ Convert a size in bytes into a human readle form """
@ -34,12 +43,122 @@ def human_readable(size):
if size.find(".") > -1: size = size[:size.find(".")+2] if size.find(".") > -1: size = size[:size.find(".")+2]
return size + " " + suffix return size + " " + suffix
class DeviceBooksModel(QAbstractTableModel):
def wrap(s, width=20):
return textwrap.fill(str(s), width)
class LibraryBooksModel(QAbstractTableModel):
FIELDS = ["id", "title", "authors", "size", "date", "publisher", "tags"]
TIME_READ_FMT = "%Y-%m-%d %H:%M:%S"
def __init__(self, parent, db_path):
QAbstractTableModel.__init__(self, parent)
self.db = LibraryDatabase(db_path)
self._data = self.db.get_table(self.FIELDS)
self._orig_data = self._data
self.image_file = None
self.sort(0, Qt.DescendingOrder)
def rowCount(self, parent): return len(self._data)
def columnCount(self, parent): return len(self.FIELDS)-2
def set_data(self, db):
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self.db = db
self._data = self.db.get_table(self.FIELDS)
self._orig_data = self._data
self.sort(0, Qt.DescendingOrder)
def headerData(self, section, orientation, role):
if role != Qt.DisplayRole:
return NONE
text = ""
if orientation == Qt.Horizontal:
if section == 0: text = "Title"
elif section == 1: text = "Author(s)"
elif section == 2: text = "Size"
elif section == 3: text = "Date"
elif section == 4: text = "Publisher"
return QVariant(self.trUtf8(text))
else: return QVariant(str(1+section))
def info(self, row):
row = self._data[row]
cover = self.db.get_cover(row["id"])
exts = ",".join(self.db.get_extensions(row["id"]))
if not cover:
cover = QPixmap(":/images/book.png")
self.image_file = None
else:
pix = QPixmap()
self.image_file = NamedTemporaryFile()
self.image_file.write(cover)
self.image_file.flush()
pix.loadFromData(cover, "", Qt.AutoColor)
cover = pix.scaledToHeight(COVER_HEIGHT, Qt.SmoothTransformation)
return row["title"], row["authors"], human_readable(int(row["size"])), exts, cover
def data(self, index, role):
if role == Qt.DisplayRole:
row, col = index.row(), index.column()
text = None
row = self._data[row]
if col == 0: text = wrap(row["title"], width=25)
elif col == 1:
au = row["authors"]
if au : text = wrap(re.sub("&", "\n", au), width=25)
elif col == 2: text = human_readable(row["size"])
elif col == 3: text = time.strftime(TIME_WRITE_FMT, time.strptime(row["date"], self.TIME_READ_FMT))
elif col == 4:
pub = row["publisher"]
if pub: text = wrap(pub, 20)
if not text: text = "Unknown"
return QVariant(text)
elif role == Qt.TextAlignmentRole and index.column() in [2,3,4]:
return QVariant(Qt.AlignRight)
return NONE
def sort(self, col, order):
descending = order != Qt.AscendingOrder
def getter(key, func): return lambda x : func(itemgetter(key)(x))
if col == 0: key, func = "title", string.lower
if col == 1: key, func = "authors", lambda x : x.split()[-1:][0].lower() if x else ""
if col == 2: key, func = "size", int
if col == 3: key, func = "date", lambda x: time.mktime(time.strptime(x, self.TIME_READ_FMT))
if col == 4: key, func = "publisher", lambda x : x.lower() if x else ""
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data.sort(key=getter(key, func))
if descending: self._data.reverse()
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("sorted()"))
def search(self, query):
def query_in(book, q):
au = book["authors"]
if not au : au = "unknown"
pub = book["publisher"]
if not pub : pub = "unknown"
return q in book["title"].lower() or q in au.lower() or q in pub.lower()
queries = unicode(query, 'utf-8').lower().split()
self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data = []
for book in self._orig_data:
match = True
for q in queries:
if query_in(book, q) : continue
else:
match = False
break
if match: self._data.append(book)
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("searched()"))
class DeviceBooksModel(QAbstractTableModel):
TIME_READ_FMT = "%a, %d %b %Y %H:%M:%S %Z"
def __init__(self, parent, data): def __init__(self, parent, data):
QAbstractTableModel.__init__(self, parent) QAbstractTableModel.__init__(self, parent)
self._data = data self._data = data
self._orig_data = data self._orig_data = data
self.image_file = None
def set_data(self, data): def set_data(self, data):
self.emit(SIGNAL("layoutAboutToBeChanged()")) self.emit(SIGNAL("layoutAboutToBeChanged()"))
@ -48,7 +167,7 @@ class DeviceBooksModel(QAbstractTableModel):
self.sort(0, Qt.DescendingOrder) self.sort(0, Qt.DescendingOrder)
def rowCount(self, parent): return len(self._data) def rowCount(self, parent): return len(self._data)
def columnCount(self, parent): return 3 def columnCount(self, parent): return 4
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if role != Qt.DisplayRole: if role != Qt.DisplayRole:
@ -58,6 +177,7 @@ class DeviceBooksModel(QAbstractTableModel):
if section == 0: text = "Title" if section == 0: text = "Title"
elif section == 1: text = "Author(s)" elif section == 1: text = "Author(s)"
elif section == 2: text = "Size" elif section == 2: text = "Size"
elif section == 3: text = "Date"
return QVariant(self.trUtf8(text)) return QVariant(self.trUtf8(text))
else: return QVariant(str(1+section)) else: return QVariant(str(1+section))
@ -68,24 +188,33 @@ class DeviceBooksModel(QAbstractTableModel):
if col == 0: text = book["title"] if col == 0: text = book["title"]
elif col == 1: text = book["author"] elif col == 1: text = book["author"]
elif col == 2: text = human_readable(int(book["size"])) elif col == 2: text = human_readable(int(book["size"]))
elif col == 3: text = time.strftime(TIME_WRITE_FMT, time.strptime(book["date"], self.TIME_READ_FMT))
return QVariant(text) return QVariant(text)
elif role == Qt.TextAlignmentRole and index.column() == 2: elif role == Qt.TextAlignmentRole and index.column() in [2,3]:
return QVariant(Qt.AlignRight) return QVariant(Qt.AlignRight)
elif role == Qt.DecorationRole and index.column() == 0:
book = self._data[index.row()]
if book.has_key("thumbnail"):
return QVariant(book["thumbnail"])
return NONE return NONE
def info(self, row): def info(self, row):
row = self._data[row] row = self._data[row]
return row["title"], row["author"], human_readable(int(row["size"])), row["mime"], row["thumbnail"].pixmap(60, 80, QIcon.Normal, QIcon.On) try:
cover = row["thumbnail"]
pix = QPixmap()
self.image_file = NamedTemporaryFile()
self.image_file.write(cover)
self.image_file.flush()
pix.loadFromData(cover, "", Qt.AutoColor)
cover = pix.scaledToHeight(COVER_HEIGHT, Qt.SmoothTransformation)
except Exception, e:
self.image_file = None
cover = QPixmap(":/images/book.png")
return row["title"], row["author"], human_readable(int(row["size"])), row["mime"], cover
def sort(self, col, order): def sort(self, col, order):
def getter(key, func): return lambda x : func(itemgetter(key)(x)) def getter(key, func): return lambda x : func(itemgetter(key)(x))
if col == 0: key, func = "title", string.lower if col == 0: key, func = "title", string.lower
if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower() if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower()
if col == 2: key, func = "size", int if col == 2: key, func = "size", int
if col == 3: key, func = "date", lambda x: time.mktime(time.strptime(x, self.TIME_READ_FMT))
descending = order != Qt.AscendingOrder descending = order != Qt.AscendingOrder
self.emit(SIGNAL("layoutAboutToBeChanged()")) self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data.sort(key=getter(key, func)) self._data.sort(key=getter(key, func))
@ -94,7 +223,7 @@ class DeviceBooksModel(QAbstractTableModel):
self.emit(SIGNAL("sorted()")) self.emit(SIGNAL("sorted()"))
def search(self, query): def search(self, query):
queries = unicode(query).lower().split() queries = unicode(query, 'utf-8').lower().split()
self.emit(SIGNAL("layoutAboutToBeChanged()")) self.emit(SIGNAL("layoutAboutToBeChanged()"))
self._data = [] self._data = []
for book in self._orig_data: for book in self._orig_data:
@ -108,64 +237,198 @@ class DeviceBooksModel(QAbstractTableModel):
self.emit(SIGNAL("layoutChanged()")) self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("searched()")) self.emit(SIGNAL("searched()"))
def delete_by_path(self, path):
self.emit(SIGNAL("layoutAboutToBeChanged()"))
index = -1
for book in self._data:
if path in book["path"]:
self._data.remove(book)
break
for book in self._orig_data:
if path in book["path"]:
self._orig_data.remove(book)
break
self.emit(SIGNAL("layoutChanged()"))
self.emit(SIGNAL("deleted()"))
def path(self, index): return self._data[index.row()]["path"]
def title(self, index): return self._data[index.row()]["title"]
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): class MainWindow(QObject, Ui_MainWindow):
def tree_clicked(self, index): def tree_clicked(self, index):
def show_device(yes):
if yes: self.device_view.show(), self.library_view.hide()
else: self.device_view.hide(), self.library_view.show()
item = self.tree.itemFromIndex(index) item = self.tree.itemFromIndex(index)
text = str(item.text()) text = str(item.text())
if text == "Library": if text == "Library":
print "Library Clicked" show_device(False)
elif text == "SONY Reader": elif text == "SONY Reader":
self.set_data(self.main_books + self.card_books) show_device(True)
self.set_device_data(self.main_books + self.card_books)
elif text == "Main Memory": elif text == "Main Memory":
self.set_data(self.main_books) show_device(True)
self.set_device_data(self.main_books)
elif text == "Storage Card": elif text == "Storage Card":
self.set_data(self.card_books) show_device(True)
self.set_device_data(self.card_books)
elif text == "Books": elif text == "Books":
text = str(item.parent().text()) text = str(item.parent().text())
if text == "Library": if text == "Library":
print "Library --> Books Clicked" show_device(False)
elif text == "Main Memory": elif text == "Main Memory":
self.set_data(self.main_books) show_device(True)
self.set_device_data(self.main_books)
elif text == "Storage Card": elif text == "Storage Card":
show_device(True)
self.set_data(self.card_books) self.set_data(self.card_books)
def set_data(self, data): def set_device_data(self, data):
self.model.set_data(data) self.device_model.set_data(data)
self.table_view.resizeColumnsToContents() self.device_view.resizeColumnsToContents()
def data_sorted(self): def model_modified(self):
self.table_view.resizeRowsToContents() self.device_view.clearSelection()
self.table_view.clearSelection() self.library_view.clearSelection()
self.book_cover.hide() self.book_cover.hide()
self.book_info.hide() self.book_info.hide()
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def show_book(self, current, previous): def show_book(self, current, previous):
title, author, size, mime, thumbnail = current.model().info(current.row()) title, author, size, mime, thumbnail = current.model().info(current.row())
self.book_info.setText(self.BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime)) self.book_info.setText(self.BOOK_TEMPLATE.arg(title).arg(size).arg(author).arg(mime))
self.book_cover.setPixmap(thumbnail) self.book_cover.setPixmap(thumbnail)
try:
name = os.path.abspath(current.model().image_file.name)
self.book_cover.setToolTip('<img src="'+name+'">')
except Exception, e: self.book_cover.setToolTip('<img src=":/images/book.png">')
self.book_cover.show() self.book_cover.show()
self.book_info.show() self.book_info.show()
def searched(self):
self.table_view.clearSelection()
self.book_cover.hide()
self.book_info.hide()
self.table_view.resizeRowsToContents()
def clear(self, checked): self.search.setText("") def clear(self, checked): self.search.setText("")
def list_context_event(self, event):
print "TODO:"
def do_delete(self, rows):
if self.device_model.__class__.__name__ == "DeviceBooksdevice_model":
paths, mc, cc = [], False, False
for book in rows:
path = book.model().path(book)
if path[0] == "/": file, prefix, mc = self.main_xml, "xs1:", True
else: file, prefix, cc = self.cache_xml, "", True
file.seek(0)
document = dom.parse(file)
books = document.getElementsByTagName(prefix + "text")
for candidate in books:
if candidate.attributes["path"].value in path:
paths.append(path)
candidate.parentNode.removeChild(candidate)
break
file.close()
file = TemporaryFile()
PrettyPrint(document, file)
if len(prefix) > 0: self.main_xml = file
else: self.cache_xml = file
for path in paths:
self.dev.del_file(path)
self.device_model.delete_by_path(path)
self.cache_xml.seek(0)
self.main_xml.seek(0)
self.status("Files deleted. Updating media list on device")
if mc:
self.dev.del_file(self.dev.MEDIA_XML)
self.dev.put_file(self.main_xml, self.dev.MEDIA_XML)
if cc:
self.dev.del_file(self.card+self.dev.CACHE_XML)
self.dev.put_file(self.cache_xml, self.card+self.dev.CACHE_XML)
def delete(self, action):
self.window.setCursor(Qt.WaitCursor)
rows = self.device_view.selectionModel().selectedRows()
items = [ row.model().title(row) + ": " + row.model().path(row)[row.model().path(row).rfind("/")+1:] for row in rows ]
ret = QMessageBox.question(self.window, self.trUtf8("SONY Reader - confirm"), self.trUtf8("Are you sure you want to delete these items from the device?\n\n") + "\n".join(items),
QMessageBox.YesToAll | QMessageBox.No, QMessageBox.YesToAll)
if ret == QMessageBox.YesToAll:
self.do_delete(rows)
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.sqlite"))).toString()
def write_settings(self):
settings = QSettings()
settings.beginGroup("MainWindow")
settings.setValue("size", QVariant(self.window.size()))
settings.endGroup()
def close_event(self, e):
self.write_settings()
e.accept()
def add(self, action):
settings = QSettings()
dir = settings.value("add books dialog dir", QVariant(os.path.expanduser("~"))).toString()
files = QFileDialog.getOpenFileNames(self.window, "Choose books to add to library", dir, "Books (*.lrf *.lrx *.rtf *.pdf *.txt);;All files (*)")
if not files.isEmpty():
x = str(files[0])
settings.setValue("add books dialog dir", QVariant(os.path.dirname(x)))
files = str(files.join("|||")).split("|||")
for file in files:
file = os.path.abspath(file)
title, author, cover, publisher = None, None, None, None
if ext == "lrf":
try:
lrf = LRFMetaFile(open(file, "r+b"))
title = lrf.title
author = lrf.author
publisher = lrf.publisher
cover = lrf.thumbnail
if "unknown" in author.lower(): author = None
except IOError, e:
self.show_error(e, "Unable to access <b>"+file+"</b>")
return
except LRFException: pass
self.library_model.add(file, title, author, publisher, cover)
def show_error(self, e, msg):
QErrorMessage(self.window).showMessage(msg+"<br><b>Error: </b>"+str(e)+"<br><br>Traceback:<br>"+traceback.format_exc(e))
def __init__(self, window): def __init__(self, window):
QObject.__init__(self) QObject.__init__(self)
Ui_MainWindow.__init__(self) Ui_MainWindow.__init__(self)
self.dev = device(report_progress=self.progress) self.dev = device(report_progress=self.progress)
self.is_connected = False self.is_connected = False
self.setupUi(window) self.setupUi(window)
self.card = None self.card = None
# Create Tree self.window = window
window.closeEvent = self.close_event
self.read_settings()
# Setup Library Book list
self.library_model = LibraryBooksModel(window, str(self.database_path))
self.library_view.setModel(self.library_model)
self.library_view.setSelectionBehavior(QAbstractItemView.SelectRows)
self.library_view.setSortingEnabled(True)
self.library_view.contextMenuEvent = self.list_context_event
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)
self.library_view.resizeColumnsToContents()
# Create Device list
self.tree = QStandardItemModel() self.tree = QStandardItemModel()
library = QStandardItem(QString("Library")) library = QStandardItem(QString("Library"))
library.setIcon(QIcon(":/images/mycomputer.png")) library.setIcon(QIcon(":/images/mycomputer.png"))
@ -187,8 +450,6 @@ class MainWindow(QObject, Ui_MainWindow):
self.reader.setIcon(QIcon(":/images/reader.png")) self.reader.setIcon(QIcon(":/images/reader.png"))
self.tree.appendRow(self.reader) self.tree.appendRow(self.reader)
self.reader.setFont(font) self.reader.setFont(font)
self.treeView.setModel(self.tree) self.treeView.setModel(self.tree)
self.treeView.header().hide() self.treeView.header().hide()
self.treeView.setExpanded(library.index(), True) self.treeView.setExpanded(library.index(), True)
@ -199,17 +460,21 @@ class MainWindow(QObject, Ui_MainWindow):
QObject.connect(self.treeView, SIGNAL("activated(QModelIndex)"), self.tree_clicked) QObject.connect(self.treeView, SIGNAL("activated(QModelIndex)"), self.tree_clicked)
QObject.connect(self.treeView, SIGNAL("clicked(QModelIndex)"), self.tree_clicked) QObject.connect(self.treeView, SIGNAL("clicked(QModelIndex)"), self.tree_clicked)
# Create Table # Create Device Book list
self.model = DeviceBooksModel(window, []) self.device_model = DeviceBooksModel(window, [])
QObject.connect(self.model, SIGNAL("sorted()"), self.data_sorted) self.device_view.setModel(self.device_model)
self.table_view.setModel(self.model) self.device_view.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.device_view.setSortingEnabled(True)
self.table_view.setSortingEnabled(True) self.device_view.contextMenuEvent = self.list_context_event
QObject.connect(self.table_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book) QObject.connect(self.device_model, SIGNAL("layoutChanged()"), self.device_view.resizeRowsToContents)
QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.model.search) QObject.connect(self.device_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
QObject.connect(self.model, SIGNAL("searched()"), self.searched) QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.device_model.search)
QObject.connect(self.device_model, SIGNAL("sorted()"), self.model_modified)
QObject.connect(self.device_model, SIGNAL("searched()"), self.model_modified)
QObject.connect(self.device_model, SIGNAL("deleted()"), self.model_modified)
self.clearButton.setIcon(QIcon(":/images/clear.png")) self.clearButton.setIcon(QIcon(":/images/clear.png"))
QObject.connect(self.clearButton, SIGNAL("clicked(bool)"), self.clear) QObject.connect(self.clearButton, SIGNAL("clicked(bool)"), self.clear)
self.device_view.hide()
# Setup book display # Setup book display
self.BOOK_TEMPLATE = self.book_info.text() self.BOOK_TEMPLATE = self.book_info.text()
@ -217,11 +482,19 @@ class MainWindow(QObject, Ui_MainWindow):
self.book_cover.hide() self.book_cover.hide()
self.book_info.hide() self.book_info.hide()
# Populate toolbar
self.add_action = self.tool_bar.addAction(QIcon(":/images/fileopen.png"), "Add files to Library")
self.add_action.setShortcut(Qt.Key_A)
QObject.connect(self.add_action, SIGNAL("triggered(bool)"), self.add)
self.del_action = self.tool_bar.addAction(QIcon(":/images/delete.png"), "Delete selected items")
self.del_action.setShortcut(Qt.Key_Delete)
QObject.connect(self.del_action, SIGNAL("triggered(bool)"), self.delete)
self.device_detector = self.startTimer(1000) self.device_detector = self.startTimer(1000)
self.splitter.setStretchFactor(0,0) self.splitter.setStretchFactor(0,0)
self.splitter.setStretchFactor(1,100) self.splitter.setStretchFactor(1,100)
self.search.setFocus(Qt.OtherFocusReason) self.search.setFocus(Qt.OtherFocusReason)
self.window = window
window.show() window.show()
def timerEvent(self, e): def timerEvent(self, e):
@ -238,9 +511,10 @@ class MainWindow(QObject, Ui_MainWindow):
self.df.setText("Main memory: <br><br>Storage card:") self.df.setText("Main memory: <br><br>Storage card:")
self.card = None self.card = None
self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True) self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True)
self.model.set_data([]) self.device_model.set_data([])
self.book_cover.hide() self.book_cover.hide()
self.book_info.hide() self.book_info.hide()
self.device_view.hide()
def timeout_error(self): def timeout_error(self):
@ -260,6 +534,7 @@ class MainWindow(QObject, Ui_MainWindow):
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
def establish_connection(self): def establish_connection(self):
self.window.setCursor(Qt.WaitCursor)
self.status("Connecting to device") self.status("Connecting to device")
try: try:
space = self.dev.available_space() space = self.dev.available_space()
@ -275,20 +550,17 @@ class MainWindow(QObject, Ui_MainWindow):
sc = space[1][1] if space[1][1] else space[2][1] sc = space[1][1] if space[1][1] else space[2][1]
self.df.setText("Main memory: " + human_readable(space[0][1]) + "<br><br>Storage card: " + human_readable(sc)) self.df.setText("Main memory: " + human_readable(space[0][1]) + "<br><br>Storage card: " + human_readable(sc))
self.is_connected = True self.is_connected = True
if space[1][2] > 0: self.card = "a:/" if space[1][2] > 0: self.card = "a:"
elif space[2][2] > 0: self.card = "b:/" elif space[2][2] > 0: self.card = "b:"
else: self.card = None else: self.card = None
if self.card: self.treeView.setRowHidden(1, self.reader.index(), False) if self.card: self.treeView.setRowHidden(1, self.reader.index(), False)
else: self.treeView.setRowHidden(1, self.reader.index(), True) else: self.treeView.setRowHidden(1, self.reader.index(), True)
self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), False) self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), False)
self.status("Loading media list") self.status("Loading media list from device")
mb, cb, mx, cx = self.dev.books() mb, cb, mx, cx = self.dev.books()
for x in (mb, cb): for x in (mb, cb):
for book in x: for book in x:
if book.has_key("thumbnail"):
book["thumbnail"] = QIcon(QPixmap.fromImage(QImage.fromData(book["thumbnail"])))
else: book["thumbnail"] = QIcon(self.BOOK_IMAGE)
if "&" in book["author"]: if "&" in book["author"]:
book["author"] = re.sub(r"&\s*", r"\n", book["author"]) book["author"] = re.sub(r"&\s*", r"\n", book["author"])
@ -296,11 +568,15 @@ class MainWindow(QObject, Ui_MainWindow):
self.card_books = cb self.card_books = cb
self.main_xml = mx self.main_xml = mx
self.cache_xml = cx self.cache_xml = cx
self.window.setCursor(Qt.ArrowCursor)
def main(): def main():
from PyQt4.Qt import QApplication, QMainWindow from PyQt4.Qt import QApplication, QMainWindow
app = QApplication(sys.argv) app = QApplication(sys.argv)
window = QMainWindow() window = QMainWindow()
gui = MainWindow(window) QCoreApplication.setOrganizationName("KovidsBrain")
return app.exec_() QCoreApplication.setApplicationName("prs500-gui")
gui = MainWindow(window)
ret = app.exec_()
return ret

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>871</width> <width>878</width>
<height>631</height> <height>759</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy" > <property name="sizePolicy" >
@ -22,14 +22,24 @@
<string>SONY Reader</string> <string>SONY Reader</string>
</property> </property>
<widget class="QWidget" name="centralwidget" > <widget class="QWidget" name="centralwidget" >
<layout class="QVBoxLayout" > <layout class="QGridLayout" >
<property name="margin" > <property name="margin" >
<number>9</number> <number>9</number>
</property> </property>
<property name="spacing" > <property name="spacing" >
<number>6</number> <number>6</number>
</property> </property>
<item> <item row="1" column="0" >
<widget class="QProgressBar" name="progress_bar" >
<property name="value" >
<number>100</number>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="0" >
<widget class="QSplitter" name="splitter" > <widget class="QSplitter" name="splitter" >
<property name="orientation" > <property name="orientation" >
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -162,7 +172,25 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QTableView" name="table_view" > <widget class="QTableView" name="device_view" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>7</hsizetype>
<vsizetype>7</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="showGrid" >
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="library_view" >
<property name="sizePolicy" > <property name="sizePolicy" >
<sizepolicy> <sizepolicy>
<hsizetype>7</hsizetype> <hsizetype>7</hsizetype>
@ -220,22 +248,21 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item>
<widget class="QProgressBar" name="progress_bar" >
<property name="value" >
<number>100</number>
</property>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QToolBar" name="toolBar" > <widget class="QToolBar" name="tool_bar" >
<property name="movable" >
<bool>false</bool>
</property>
<property name="orientation" > <property name="orientation" >
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="iconSize" >
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<attribute name="toolBarArea" > <attribute name="toolBarArea" >
<number>4</number> <number>4</number>
</attribute> </attribute>

View File

@ -4,5 +4,7 @@
<file>images/mycomputer.png</file> <file>images/mycomputer.png</file>
<file>images/reader.png</file> <file>images/reader.png</file>
<file>images/clear.png</file> <file>images/clear.png</file>
<file>images/delete.png</file>
<file>images/fileopen.png</file>
</qresource> </qresource>
</RCC> </RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -305,12 +305,13 @@ def main():
import sys, os.path import sys, os.path
from optparse import OptionParser from optparse import OptionParser
from libprs500 import __version__ as VERSION from libprs500 import __version__ as VERSION
parser = OptionParser(usage="usage: %prog [options] mybook.lrf", version=VERSION) parser = OptionParser(usage="usage: %prog [options] mybook.lrf\n\nWARNING: Based on reverse engineering the LRF format. Making changes may render your LRF file unreadable. ", version=VERSION)
parser.add_option("-t", "--title", action="store", type="string", dest="title", help="Set the book title") parser.add_option("-t", "--title", action="store", type="string", dest="title", help="Set the book title")
parser.add_option("-a", "--author", action="store", type="string", dest="author", help="Set the author") parser.add_option("-a", "--author", action="store", type="string", dest="author", help="Set the author")
parser.add_option("-c", "--category", action="store", type="string", dest="category", help="The category this book belongs to. E.g.: History") parser.add_option("-c", "--category", action="store", type="string", dest="category", help="The category this book belongs to. E.g.: History")
parser.add_option("--thumbnail", action="store", type="string", dest="thumbnail", help="Path to a graphic that will be set as this files' thumbnail") parser.add_option("--thumbnail", action="store", type="string", dest="thumbnail", help="Path to a graphic that will be set as this files' thumbnail")
parser.add_option("-p", "--page", action="store", type="string", dest="page", help="Set the current page number (I think)") parser.add_option("--get-thumbnail", action="store_true", dest="get_thumbnail", default=False, help="Extract thumbnail from LRF file")
parser.add_option("-p", "--page", action="store", type="string", dest="page", help="Don't know what this is for")
options, args = parser.parse_args() options, args = parser.parse_args()
if len(args) != 1: if len(args) != 1:
parser.print_help() parser.print_help()
@ -325,20 +326,17 @@ def main():
lrf.thumbnail = f.read() lrf.thumbnail = f.read()
f.close() f.close()
t = lrf.thumbnail if options.get_thumbnail:
td = "None" t = lrf.thumbnail
if t and len(t) > 0: td = "None"
td = os.path.basename(args[0])+"_thumbnail_."+lrf.thumbail_extension() if t and len(t) > 0:
f = open(td, "w") td = os.path.basename(args[0])+"_thumbnail_."+lrf.thumbail_extension()
f.write(t) f = open(td, "w")
f.close() f.write(t)
f.close()
fields = LRFMetaFile.__dict__.items() fields = LRFMetaFile.__dict__.items()
for f in fields: for f in fields:
if "XML" in str(f): if "XML" in str(f):
print str(f[1]) + ":", lrf.__getattribute__(f[0]) print str(f[1]) + ":", lrf.__getattribute__(f[0])
print "Thumbnail:", td if options.get_thumbnail: print "Thumbnail:", td
print "object index offset:", hex(lrf.object_index_offset)
print "toc object offset", hex(lrf.toc_object_offset)

View File

@ -167,6 +167,7 @@ class field(object):
obj.pack(val, start=self._start, fmt=self._fmt) obj.pack(val, start=self._start, fmt=self._fmt)
def __repr__(self): def __repr__(self):
typ = ""
if self._fmt == DWORD: typ = "unsigned int" if self._fmt == DWORD: typ = "unsigned int"
if self._fmt == DDWORD: typ = "unsigned long long" if self._fmt == DDWORD: typ = "unsigned long long"
return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start) return "An " + typ + " stored in " + str(struct.calcsize(self._fmt)) + " bytes starting at byte " + str(self._start)