GUI skeleton

Added plumbing for PyPI
This commit is contained in:
Kovid Goyal 2006-11-18 00:32:26 +00:00
parent a44a2e578b
commit 87c82ab7d7
11 changed files with 463 additions and 40 deletions

View File

@ -1,4 +1,5 @@
include libprs500 *.py
include libprs500/gui *.py
include scripts *.py
include README
include docs/pdf/api.pdf

View File

@ -1,3 +1,17 @@
## 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.
"""
This package provides an interface to the SONY Reader PRS-500 over USB.

View File

@ -184,6 +184,7 @@ class PRS500Device(object):
def safe(func):
"""
Decorator that wraps a call to C{func} to ensure that exceptions are handled correctly.
It also calls L{open} to claim the interface and initialize the Reader if needed.
As a convenience, C{safe} automatically sends the a L{USBConnect} after calling func, unless func has
a keyword argument named C{end_session} set to C{False}.
@ -192,15 +193,20 @@ class PRS500Device(object):
An L{usb.USBError} will cause the library to release control of the USB interface via a call to L{close}.
"""
def run_session(*args, **kwargs):
dev = args[0]
dev = args[0]
res = None
try:
if not dev.handle: dev.open()
res = func(*args, **kwargs)
except ArgumentError, e:
if not kwargs.has_key("end_session") or kwargs["end_session"]:
dev._send_validated_command(USBConnect())
raise e
except usb.USBError, e:
if "No such device" in str(e):
raise DeviceError()
elif "Connection timed out" in str(e):
raise TimeoutError(func.__name__)
dev.close()
raise e
if not kwargs.has_key("end_session") or kwargs["end_session"]:
@ -214,8 +220,8 @@ class PRS500Device(object):
self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID,
PRS500Device.PRS500_PRODUCT_ID,
PRS500Device.PRS500_INTERFACE_ID)
self.device = self.device_descriptor.getDevice()
self.handle = None
self.device = self.device_descriptor.getDevice() #: The actual device (PyUSB object)
self.handle = None #: Handle that is used to communicate with device. Setup in L{open}
self._log_packets = log_packets
@classmethod
@ -235,8 +241,7 @@ class PRS500Device(object):
"""
self.device = self.device_descriptor.getDevice()
if not self.device:
print >> sys.stderr, "Unable to find Sony Reader. Is it connected?"
sys.exit(1)
raise DeviceError()
self.handle = self.device.open()
self.handle.claimInterface(self.device_descriptor.interface_id)
self.handle.reset()
@ -492,11 +497,7 @@ class PRS500Device(object):
@safe
def touch(self, path, end_session=True):
"""
Create a file at path
@todo: Update file modification time if file already exists
"""
""" Create a file at path """
if path.endswith("/") and len(path) > 1: path = path[:-1]
exists, file = self._exists(path)
if exists and file.is_dir:
@ -505,11 +506,6 @@ class PRS500Device(object):
res = self._send_validated_command(FileCreate(path))
if res.code != 0:
raise PathError("Could not create file " + path + ". Response code: " + str(hex(res.code)))
## res = self._send_validated_command(SetFileInfo(path))
## if res.code != 0:
## raise ProtocolError("Unable to touch " + path + ". Response code: " + hex(res.code))
## file.wtime = int(time.time())
## self._bulk_write(file[16:])
@safe
@ -588,38 +584,38 @@ class PRS500Device(object):
"""
Return a list of all ebooks on the device
@return: A two element tuple. The first element is the books in the main memory and the second is the books on the storage card.
Each element is a list of dictionaries. Important fields in each dictionary are "title", "author", "path" and "thumbnail".
@return: A four element tuple. The first element is the books in the main memory and the second is the books on the storage card.
The first two elements are each a list of dictionaries. If there is no card the second list is empty.
Important fields in each dictionary are "title", "author", "path" and "thumbnail".
The third and fourth elements are the temporary files that hold main.xml and cache.xml
"""
buffer = TemporaryFile()
media = self.get_file("/Data/database/cache/media.xml", buffer, end_session=False)
main_xml = TemporaryFile()
media = self.get_file("/Data/database/cache/media.xml", main_xml, end_session=False)
parser = make_parser()
parser.setFeature(feature_namespaces, 0)
finder = FindBooks()
parser.setContentHandler(finder)
buffer.seek(0)
parser.parse(buffer)
buffer.close()
main_xml.seek(0)
parser.parse(main_xml)
books = finder.books
root = "a:/"
buffer = TemporaryFile()
cache_xml = TemporaryFile()
cbooks = []
try:
self.get_file("a:/Sony Reader/database/cache.xml", buffer, end_session=False)
self.get_file("a:/Sony Reader/database/cache.xml", cache_xml, end_session=False)
except PathError:
try:
self.get_file("b:/Sony Reader/database/cache.xml", buffer, end_session=False)
self.get_file("b:/Sony Reader/database/cache.xml", cache_xml, end_session=False)
root = "b:/"
except PathError:
pass
if buffer.tell() > 0:
if cache_xml.tell() > 0:
finder = FindBooks(type="cache", root=root)
buffer.seek(0)
cache_xml.seek(0)
parser.setContentHandler(finder)
parser.parse(buffer)
buffer.close()
parser.parse(cache_xml)
cbooks = finder.books
return books, cbooks
return books, cbooks, main_xml, cache_xml

View File

@ -24,6 +24,16 @@ class ProtocolError(Exception):
def __init__(self, msg):
Exception.__init__(self, msg)
class TimeoutError(ProtocolError):
""" There was a timeout during communication """
def __init__(self, func_name):
ProtocolError.__init__(self, "There was a timeout while communicating with the device in function: "+func_name)
class DeviceError(ProtocolError):
""" Raised when device is not found """
def __init__(self):
ProtocolError.__init__(self, "Unable to find SONY Reader. Is it connected?")
class PacketError(ProtocolError):
""" Errors with creating/interpreting packets """

16
libprs500/gui/__init__.py Normal file
View File

@ -0,0 +1,16 @@
## 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.
__docformat__ = "epytext"
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"

88
libprs500/gui/main.py Normal file
View File

@ -0,0 +1,88 @@
## 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.
from main_gui import Ui_MainWindow
from libprs500.communicate import PRS500Device as device
from libprs500.errors import *
from PyQt4.Qt import QThread
class DeviceDetector(QThread):
def __init__(self, detected_slot):
QThread.__init__(self)
self.dev = None
self.detected_slot = detected_slot
self.removed = False
def run(self):
wait = 1
while None == self.msleep(wait):
wait = 1000
if self.removed or not self.dev:
self.dev = device()
self.removed = False
self.detected_slot()
class MainWindow(Ui_MainWindow):
def safe(func):
def call_func(*args, **kwargs):
window = args[0]
res = None
try:
res = func(*args, **kwargs)
except DeviceError:
window.device_removed()
except TimeoutError, e:
print e
window.timeout_error()
return res
return call_func
@apply
def dev():
def fget(self):
return self.detector.dev
return property(**locals())
def __init__(self, window, log_packets):
Ui_MainWindow.__init__(self)
self.log_packets = log_packets
self.detector = DeviceDetector(self.establish_connection)
self.detector.start()
self.setupUi(window)
window.show()
def device_removed(self, timeout=False):
""" @todo: implement this """
self.detector.removed = True
def timeout_error(self):
""" @todo: update status bar """
self.detector.sleep(10)
self.device_removed(timeout=True)
@safe
def establish_connection(self):
mb, cb, mx, cx = self.dev.books()
self.main_books = mb
self.card_books = cb
self.main_xml = mx
self.cache_xml = cx
print self.main_books + self.card_books

136
libprs500/gui/main.ui Normal file
View File

@ -0,0 +1,136 @@
## 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.
<ui version="4.0" >
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>885</width>
<height>631</height>
</rect>
</property>
<property name="windowTitle" >
<string>SONY Reader</string>
</property>
<widget class="QWidget" name="centralwidget" >
<layout class="QGridLayout" >
<property name="margin" >
<number>9</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item row="0" column="1" >
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item>
<layout class="QHBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item>
<widget class="QToolButton" name="clear" >
<property name="text" >
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="search" >
<property name="acceptDrops" >
<bool>false</bool>
</property>
<property name="autoFillBackground" >
<bool>false</bool>
</property>
<property name="text" >
<string>Search title and author</string>
</property>
<property name="frame" >
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListView" name="listView" >
<property name="sizePolicy" >
<sizepolicy>
<hsizetype>5</hsizetype>
<vsizetype>7</vsizetype>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShadow" >
<enum>QFrame::Plain</enum>
</property>
<property name="autoScroll" >
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0" >
<layout class="QVBoxLayout" >
<property name="margin" >
<number>0</number>
</property>
<property name="spacing" >
<number>6</number>
</property>
<item>
<widget class="QTreeView" name="treeView" >
<property name="frameShadow" >
<enum>QFrame::Plain</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="df" >
<property name="text" >
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar" />
<widget class="QToolBar" name="toolBar" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<attribute name="toolBarArea" >
<number>4</number>
</attribute>
</widget>
</widget>
<resources/>
<connections/>
</ui>

94
libprs500/gui/main_gui.py Normal file
View File

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'main.ui'
#
# Created: Thu Nov 16 20:48:21 2006
# by: PyQt4 UI code generator 4-snapshot-20061112
#
# WARNING! All changes made in this file will be lost!
import sys
from PyQt4 import QtCore, QtGui
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(QtCore.QSize(QtCore.QRect(0,0,885,631).size()).expandedTo(MainWindow.minimumSizeHint()))
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridlayout = QtGui.QGridLayout(self.centralwidget)
self.gridlayout.setMargin(9)
self.gridlayout.setSpacing(6)
self.gridlayout.setObjectName("gridlayout")
self.vboxlayout = QtGui.QVBoxLayout()
self.vboxlayout.setMargin(0)
self.vboxlayout.setSpacing(6)
self.vboxlayout.setObjectName("vboxlayout")
self.hboxlayout = QtGui.QHBoxLayout()
self.hboxlayout.setMargin(0)
self.hboxlayout.setSpacing(6)
self.hboxlayout.setObjectName("hboxlayout")
self.clear = QtGui.QToolButton(self.centralwidget)
self.clear.setObjectName("clear")
self.hboxlayout.addWidget(self.clear)
self.search = QtGui.QLineEdit(self.centralwidget)
self.search.setAcceptDrops(False)
self.search.setAutoFillBackground(False)
self.search.setFrame(True)
self.search.setObjectName("search")
self.hboxlayout.addWidget(self.search)
self.vboxlayout.addLayout(self.hboxlayout)
self.listView = QtGui.QListView(self.centralwidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Policy(5),QtGui.QSizePolicy.Policy(7))
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.listView.sizePolicy().hasHeightForWidth())
self.listView.setSizePolicy(sizePolicy)
self.listView.setFrameShadow(QtGui.QFrame.Plain)
self.listView.setAutoScroll(False)
self.listView.setObjectName("listView")
self.vboxlayout.addWidget(self.listView)
self.gridlayout.addLayout(self.vboxlayout,0,1,1,1)
self.vboxlayout1 = QtGui.QVBoxLayout()
self.vboxlayout1.setMargin(0)
self.vboxlayout1.setSpacing(6)
self.vboxlayout1.setObjectName("vboxlayout1")
self.treeView = QtGui.QTreeView(self.centralwidget)
self.treeView.setFrameShadow(QtGui.QFrame.Plain)
self.treeView.setObjectName("treeView")
self.vboxlayout1.addWidget(self.treeView)
self.df = QtGui.QLabel(self.centralwidget)
self.df.setObjectName("df")
self.vboxlayout1.addWidget(self.df)
self.gridlayout.addLayout(self.vboxlayout1,0,0,1,1)
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.toolBar = QtGui.QToolBar(MainWindow)
self.toolBar.setOrientation(QtCore.Qt.Horizontal)
self.toolBar.setObjectName("toolBar")
MainWindow.addToolBar(self.toolBar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.clear.setText(QtGui.QApplication.translate("MainWindow", "...", None, QtGui.QApplication.UnicodeUTF8))
self.search.setText(QtGui.QApplication.translate("MainWindow", "Search title and author", None, QtGui.QApplication.UnicodeUTF8))
self.df.setText(QtGui.QApplication.translate("MainWindow", "TextLabel", None, QtGui.QApplication.UnicodeUTF8))

View File

@ -1,3 +1,17 @@
## 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, re
class TerminalController:

View File

@ -1,4 +1,18 @@
#!/usr/bin/env python
## 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.
"""
Provides a command-line interface to the SONY Reader PRS-500.
@ -11,7 +25,7 @@ from optparse import OptionParser
from libprs500 import VERSION
from libprs500.communicate import PRS500Device
from libprs500.terminfo import TerminalController
from libprs500.errors import ArgumentError
from libprs500.errors import ArgumentError, DeviceError
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
@ -170,17 +184,29 @@ def main():
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.",
dest="log_packets", action="store_true", default=False)
parser.add_option("--gui", help="Run a Graphical User Interface", dest="gui", action="store_true", default=False)
parser.remove_option("-h")
parser.disable_interspersed_args() # Allow unrecognized options
options, args = parser.parse_args()
if options.gui:
try:
from PyQt4.Qt import QApplication, QMainWindow
from libprs500.gui.main import MainWindow
app = QApplication(args)
window = QMainWindow()
mw = MainWindow(window, options.log_packets)
sys.exit(app.exec_())
except ImportError, e:
print >>sys.stderr, "You dont have PyQt4 installed:", e
sys.exit(1)
if len(args) < 1:
parser.print_help()
sys.exit(1)
sys.exit(1)
command = args[0]
args = args[1:]
dev = PRS500Device(log_packets=options.log_packets)
dev.open()
try:
if command == "df":
data = dev.available_space()
@ -190,7 +216,9 @@ def main():
str(0 if datum[2]==0 else int(100*(datum[2]-datum[1])/(datum[2]*1.)))+"%"
print "%-10s\t%s\t%s\t%s\t%s"%(datum[0], total, used, free, percent)
elif command == "books":
main, card = dev.books()
main, card, d1, d2 = dev.books()
d1.close()
d2.close()
print "Books in main memory:"
for book in main:
print book
@ -215,7 +243,6 @@ def main():
if len(args) != 1:
parser.print_help()
sys.exit(1)
dev.open()
print ls(dev, args[0], term, color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
elif command == "info":
info(dev)
@ -289,7 +316,7 @@ def main():
parser.print_help()
if dev.handle: dev.close()
sys.exit(1)
except ArgumentError, e:
except (ArgumentError, DeviceError), e:
print >>sys.stderr, e
sys.exit(1)
finally:

View File

@ -1,3 +1,17 @@
## 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.
#!/usr/bin/env python
from distutils.core import setup
from libprs500 import VERSION
@ -7,14 +21,27 @@ setup(name='libprs500',
description='Library to interface with the Sony Portable Reader 500 over USB.',
long_description =
"""
libprs500 is library to interface with the Sony Portable Reader 500 over USB.
libprs500 is library to interface with the _Sony Portable Reader:http://Sony.com/reader over USB.
It provides methods to list the contents of the file system on the device as well as
copy files from and to the device. It also provides a command line interface via the script prs500.py.
copy files from and to the device. It also provides a command line and a graphical user interface via the script prs500.py.
""",
author='Kovid Goyal',
author_email='kovid@kovidgoyal.net',
provides=['libprs500'],
requires=['pyusb'],
packages = ['libprs500'],
scripts = ['scripts/prs500.py']
packages = ['libprs500', 'libprs500.gui'],
scripts = ['scripts/prs500.py'],
classifiers = [
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Environment :: X11 Applications :: Qt',
'Intended Audience :: Developers',
'Intended Audience :: End Users/Desktop',
'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: English',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Hardware :: Hardware Drivers'
]
)