mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Possible fix for available space bug in the GUI
This commit is contained in:
parent
00ae3e2193
commit
38273e5b10
17
README
17
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
|
||||
|
@ -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"])
|
||||
|
90
libprs500/gui/editbook.py
Normal file
90
libprs500/gui/editbook.py
Normal file
@ -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"))
|
@ -242,7 +242,11 @@
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="cover_path" />
|
||||
<widget class="QLineEdit" name="cover_path" >
|
||||
<property name="readOnly" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="cover_button" >
|
||||
@ -353,22 +357,6 @@
|
||||
<include location="images.qrc" />
|
||||
</resources>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>BookEditDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>rejected()</signal>
|
||||
@ -385,5 +373,21 @@
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>button_box</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>BookEditDialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel" >
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel" >
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
|
BIN
libprs500/gui/images/fileopen.png
Normal file
BIN
libprs500/gui/images/fileopen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
@ -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+"<br><b>Error: </b>"+str(e)+"<br><br>Traceback:<br>"+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
|
||||
|
21
prs-500.e4p
21
prs-500.e4p
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Project SYSTEM "Project-4.0.dtd">
|
||||
<!-- eric4 project file for project prs-500 -->
|
||||
<!-- Saved: 2006-11-24, 20:24:29 -->
|
||||
<!-- Saved: 2006-11-29, 11:51:16 -->
|
||||
<!-- Copyright (C) 2006 Kovid Goyal, kovid@kovidgoyal.net -->
|
||||
<Project version="4.0">
|
||||
<ProgLanguage mixed="0">Python</ProgLanguage>
|
||||
@ -70,6 +70,11 @@
|
||||
<Dir>gui</Dir>
|
||||
<Name>database.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>editbook.py</Name>
|
||||
</Source>
|
||||
</Sources>
|
||||
<Forms>
|
||||
<Form>
|
||||
@ -96,12 +101,6 @@
|
||||
<Other>
|
||||
<Name>epydoc-pdf.conf</Name>
|
||||
</Other>
|
||||
<Other>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Dir>resources</Dir>
|
||||
<Name>images.qrc</Name>
|
||||
</Other>
|
||||
</Others>
|
||||
<MainScript>
|
||||
<Dir>libprs500</Dir>
|
||||
@ -222,10 +221,10 @@
|
||||
</VcsOtherData>
|
||||
</Vcs>
|
||||
<FiletypeAssociations>
|
||||
<FiletypeAssociation pattern="*.ui.h" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.ui" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES" />
|
||||
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
|
||||
<FiletypeAssociation pattern="*.py" type="SOURCES" />
|
||||
<FiletypeAssociation pattern="*.ui.h" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES" />
|
||||
<FiletypeAssociation pattern="*.ui" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
|
||||
</FiletypeAssociations>
|
||||
</Project>
|
||||
|
Loading…
x
Reference in New Issue
Block a user