mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Added support for progress reporting in libprs500. Only implemented in get_file
Created a rudimentary GUI that parses the media list on the device abd displays the books Switched to setuptools
This commit is contained in:
parent
87c82ab7d7
commit
edbdd7e496
@ -1,6 +0,0 @@
|
||||
include libprs500 *.py
|
||||
include libprs500/gui *.py
|
||||
include scripts *.py
|
||||
include README
|
||||
include docs/pdf/api.pdf
|
||||
recursive-include docs/html *
|
222
ez_setup.py
Normal file
222
ez_setup.py
Normal file
@ -0,0 +1,222 @@
|
||||
#!python
|
||||
"""Bootstrap setuptools installation
|
||||
|
||||
If you want to use setuptools in your package's setup.py, just include this
|
||||
file in the same directory with it, and add this to the top of your setup.py::
|
||||
|
||||
from ez_setup import use_setuptools
|
||||
use_setuptools()
|
||||
|
||||
If you want to require a specific version of setuptools, set a download
|
||||
mirror, or use an alternate download directory, you can do so by supplying
|
||||
the appropriate options to ``use_setuptools()``.
|
||||
|
||||
This file can also be run as a script to install or upgrade setuptools.
|
||||
"""
|
||||
import sys
|
||||
DEFAULT_VERSION = "0.6c3"
|
||||
DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3]
|
||||
|
||||
md5_data = {
|
||||
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
|
||||
'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
|
||||
'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
|
||||
'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
|
||||
'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
|
||||
'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
|
||||
'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
|
||||
'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
|
||||
'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
|
||||
'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
|
||||
'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
|
||||
'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
|
||||
'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
|
||||
'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
|
||||
'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
|
||||
}
|
||||
|
||||
import sys, os
|
||||
|
||||
def _validate_md5(egg_name, data):
|
||||
if egg_name in md5_data:
|
||||
from md5 import md5
|
||||
digest = md5(data).hexdigest()
|
||||
if digest != md5_data[egg_name]:
|
||||
print >>sys.stderr, (
|
||||
"md5 validation of %s failed! (Possible download problem?)"
|
||||
% egg_name
|
||||
)
|
||||
sys.exit(2)
|
||||
return data
|
||||
|
||||
|
||||
def use_setuptools(
|
||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
||||
download_delay=15
|
||||
):
|
||||
"""Automatically find/download setuptools and make it available on sys.path
|
||||
|
||||
`version` should be a valid setuptools version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end with
|
||||
a '/'). `to_dir` is the directory where setuptools will be downloaded, if
|
||||
it is not already available. If `download_delay` is specified, it should
|
||||
be the number of seconds that will be paused before initiating a download,
|
||||
should one be required. If an older version of setuptools is installed,
|
||||
this routine will print a message to ``sys.stderr`` and raise SystemExit in
|
||||
an attempt to abort the calling script.
|
||||
"""
|
||||
try:
|
||||
import setuptools
|
||||
if setuptools.__version__ == '0.0.1':
|
||||
print >>sys.stderr, (
|
||||
"You have an obsolete version of setuptools installed. Please\n"
|
||||
"remove it from your system entirely before rerunning this script."
|
||||
)
|
||||
sys.exit(2)
|
||||
except ImportError:
|
||||
egg = download_setuptools(version, download_base, to_dir, download_delay)
|
||||
sys.path.insert(0, egg)
|
||||
import setuptools; setuptools.bootstrap_install_from = egg
|
||||
|
||||
import pkg_resources
|
||||
try:
|
||||
pkg_resources.require("setuptools>="+version)
|
||||
|
||||
except pkg_resources.VersionConflict, e:
|
||||
# XXX could we install in a subprocess here?
|
||||
print >>sys.stderr, (
|
||||
"The required version of setuptools (>=%s) is not available, and\n"
|
||||
"can't be installed while this script is running. Please install\n"
|
||||
" a more recent version first.\n\n(Currently using %r)"
|
||||
) % (version, e.args[0])
|
||||
sys.exit(2)
|
||||
|
||||
def download_setuptools(
|
||||
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
|
||||
delay = 15
|
||||
):
|
||||
"""Download setuptools from a specified location and return its filename
|
||||
|
||||
`version` should be a valid setuptools version number that is available
|
||||
as an egg for download under the `download_base` URL (which should end
|
||||
with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
||||
`delay` is the number of seconds to pause before an actual download attempt.
|
||||
"""
|
||||
import urllib2, shutil
|
||||
egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
|
||||
url = download_base + egg_name
|
||||
saveto = os.path.join(to_dir, egg_name)
|
||||
src = dst = None
|
||||
if not os.path.exists(saveto): # Avoid repeated downloads
|
||||
try:
|
||||
from distutils import log
|
||||
if delay:
|
||||
log.warn("""
|
||||
---------------------------------------------------------------------------
|
||||
This script requires setuptools version %s to run (even to display
|
||||
help). I will attempt to download it for you (from
|
||||
%s), but
|
||||
you may need to enable firewall access for this script first.
|
||||
I will start the download in %d seconds.
|
||||
|
||||
(Note: if this machine does not have network access, please obtain the file
|
||||
|
||||
%s
|
||||
|
||||
and place it in this directory before rerunning this script.)
|
||||
---------------------------------------------------------------------------""",
|
||||
version, download_base, delay, url
|
||||
); from time import sleep; sleep(delay)
|
||||
log.warn("Downloading %s", url)
|
||||
src = urllib2.urlopen(url)
|
||||
# Read/write all in one block, so we don't create a corrupt file
|
||||
# if the download is interrupted.
|
||||
data = _validate_md5(egg_name, src.read())
|
||||
dst = open(saveto,"wb"); dst.write(data)
|
||||
finally:
|
||||
if src: src.close()
|
||||
if dst: dst.close()
|
||||
return os.path.realpath(saveto)
|
||||
|
||||
def main(argv, version=DEFAULT_VERSION):
|
||||
"""Install or upgrade setuptools and EasyInstall"""
|
||||
|
||||
try:
|
||||
import setuptools
|
||||
except ImportError:
|
||||
egg = None
|
||||
try:
|
||||
egg = download_setuptools(version, delay=0)
|
||||
sys.path.insert(0,egg)
|
||||
from setuptools.command.easy_install import main
|
||||
return main(list(argv)+[egg]) # we're done here
|
||||
finally:
|
||||
if egg and os.path.exists(egg):
|
||||
os.unlink(egg)
|
||||
else:
|
||||
if setuptools.__version__ == '0.0.1':
|
||||
# tell the user to uninstall obsolete version
|
||||
use_setuptools(version)
|
||||
|
||||
req = "setuptools>="+version
|
||||
import pkg_resources
|
||||
try:
|
||||
pkg_resources.require(req)
|
||||
except pkg_resources.VersionConflict:
|
||||
try:
|
||||
from setuptools.command.easy_install import main
|
||||
except ImportError:
|
||||
from easy_install import main
|
||||
main(list(argv)+[download_setuptools(delay=0)])
|
||||
sys.exit(0) # try to force an exit
|
||||
else:
|
||||
if argv:
|
||||
from setuptools.command.easy_install import main
|
||||
main(argv)
|
||||
else:
|
||||
print "Setuptools version",version,"or greater has been installed."
|
||||
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
|
||||
|
||||
|
||||
|
||||
def update_md5(filenames):
|
||||
"""Update our built-in md5 registry"""
|
||||
|
||||
import re
|
||||
from md5 import md5
|
||||
|
||||
for name in filenames:
|
||||
base = os.path.basename(name)
|
||||
f = open(name,'rb')
|
||||
md5_data[base] = md5(f.read()).hexdigest()
|
||||
f.close()
|
||||
|
||||
data = [" %r: %r,\n" % it for it in md5_data.items()]
|
||||
data.sort()
|
||||
repl = "".join(data)
|
||||
|
||||
import inspect
|
||||
srcfile = inspect.getsourcefile(sys.modules[__name__])
|
||||
f = open(srcfile, 'rb'); src = f.read(); f.close()
|
||||
|
||||
match = re.search("\nmd5_data = {\n([^}]+)}", src)
|
||||
if not match:
|
||||
print >>sys.stderr, "Internal error!"
|
||||
sys.exit(2)
|
||||
|
||||
src = src[:match.start(1)] + repl + src[match.end(1):]
|
||||
f = open(srcfile,'w')
|
||||
f.write(src)
|
||||
f.close()
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
if len(sys.argv)>2 and sys.argv[1]=='--md5update':
|
||||
update_md5(sys.argv[2:])
|
||||
else:
|
||||
main(sys.argv[1:])
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -24,7 +24,7 @@ The public interface of libprs500 is in L{libprs500.communicate}. To use it
|
||||
>>> dev.close()
|
||||
|
||||
There is also a script L{prs500} that provides a command-line interface to libprs500. See the script
|
||||
for more usage examples.
|
||||
for more usage examples. A GUI is available via the command prs500-gui.
|
||||
|
||||
The packet structure used by the SONY Reader USB protocol is defined in the module L{prstypes}. The communication logic
|
||||
is defined in the module L{communicate}.
|
||||
@ -34,6 +34,6 @@ the following rule in C{/etc/udev/rules.d/90-local.rules} ::
|
||||
BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="plugdev"
|
||||
You may have to adjust the GROUP and the location of the rules file to suit your distribution.
|
||||
"""
|
||||
VERSION = "0.2.1"
|
||||
__version__ = "0.2.1"
|
||||
__docformat__ = "epytext"
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
21
libprs500/cli/__init__.py
Normal file
21
libprs500/cli/__init__.py
Normal file
@ -0,0 +1,21 @@
|
||||
## 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.
|
||||
|
||||
For usage information run the script.
|
||||
"""
|
||||
__docformat__ = "epytext"
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
@ -1,4 +1,3 @@
|
||||
#!/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
|
||||
@ -14,7 +13,7 @@
|
||||
## 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.
|
||||
Provides a command-line and optional graphical interface to the SONY Reader PRS-500.
|
||||
|
||||
For usage information run the script.
|
||||
"""
|
||||
@ -22,9 +21,9 @@ For usage information run the script.
|
||||
import StringIO, sys, time, os
|
||||
from optparse import OptionParser
|
||||
|
||||
from libprs500 import VERSION
|
||||
from libprs500 import __version__ as VERSION
|
||||
from libprs500.communicate import PRS500Device
|
||||
from libprs500.terminfo import TerminalController
|
||||
from terminfo import TerminalController
|
||||
from libprs500.errors import ArgumentError, DeviceError
|
||||
|
||||
|
||||
@ -184,25 +183,13 @@ 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)
|
||||
return 1
|
||||
|
||||
command = args[0]
|
||||
args = args[1:]
|
||||
@ -242,7 +229,7 @@ def main():
|
||||
options, args = parser.parse_args(args)
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
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)
|
||||
@ -257,7 +244,7 @@ def main():
|
||||
options, args = parser.parse_args(args)
|
||||
if len(args) != 2:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
if args[0].startswith("prs500:"):
|
||||
outfile = args[1]
|
||||
path = args[0][7:]
|
||||
@ -269,7 +256,7 @@ def main():
|
||||
except IOError, e:
|
||||
print >> sys.stderr, e
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
dev.get_file(path, outfile)
|
||||
outfile.close()
|
||||
elif args[1].startswith("prs500:"):
|
||||
@ -278,19 +265,19 @@ def main():
|
||||
except IOError, e:
|
||||
print >> sys.stderr, e
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
dev.put_file(infile, args[1][7:])
|
||||
infile.close()
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
elif command == "cat":
|
||||
outfile = sys.stdout
|
||||
parser = OptionParser(usage="usage: %prog cat path\n\npath should point to a file on the device and must begin with /,a:/ or b:/")
|
||||
options, args = parser.parse_args(args)
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
if args[0].endswith("/"): path = args[0][:-1]
|
||||
else: path = args[0]
|
||||
outfile = sys.stdout
|
||||
@ -302,7 +289,7 @@ def main():
|
||||
options, args = parser.parse_args(args)
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
dev.rm(args[0])
|
||||
elif command == "touch":
|
||||
parser = OptionParser(usage="usage: %prog touch path\n\npath should point to a file on the device and must begin with /,a:/ or b:/\n\n"+
|
||||
@ -310,17 +297,13 @@ def main():
|
||||
options, args = parser.parse_args(args)
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
dev.touch(args[0])
|
||||
else:
|
||||
parser.print_help()
|
||||
if dev.handle: dev.close()
|
||||
sys.exit(1)
|
||||
return 1
|
||||
except (ArgumentError, DeviceError), e:
|
||||
print >>sys.stderr, e
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if dev.handle: dev.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
return 1
|
||||
return 0
|
@ -14,6 +14,8 @@
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
import sys, re
|
||||
|
||||
""" Get information about the terminal we are running in """
|
||||
|
||||
class TerminalController:
|
||||
"""
|
||||
A class that can be used to portably generate formatted output to
|
@ -73,8 +73,9 @@ class FindBooks(ContentHandler):
|
||||
def __init__(self, src):
|
||||
dict.__init__(self, src)
|
||||
if not self.has_key("author") or len(self["author"].rstrip()) == 0:
|
||||
self["author"] = "Unknown"
|
||||
self["title"] = self["title"].strip()
|
||||
self["author"] = u"Unknown"
|
||||
self["title"] = unicode(self["title"].strip())
|
||||
self["author"] = unicode(self["author"])
|
||||
|
||||
def __repr__(self):
|
||||
return self["title"] + " by " + self["author"] + " at " + self["path"]
|
||||
@ -98,7 +99,7 @@ class FindBooks(ContentHandler):
|
||||
def startElement(self, name, attrs):
|
||||
if name == self.book_name:
|
||||
data = FindBooks.Book(attrs)
|
||||
data["path"] = self.root + data["path"]
|
||||
data["path"] = unicode(self.root + data["path"])
|
||||
self.books.append(data)
|
||||
self.in_book = True
|
||||
elif self.in_book and name == self.thumbnail_name:
|
||||
@ -215,15 +216,23 @@ class PRS500Device(object):
|
||||
|
||||
return run_session
|
||||
|
||||
def __init__(self, log_packets=False) :
|
||||
""" @param log_packets: If true the packet stream to/from the device is logged """
|
||||
def __init__(self, log_packets=False, report_progress=None) :
|
||||
"""
|
||||
@param log_packets: If true the packet stream to/from the device is logged
|
||||
@param: report_progress: Function that is called with a % progress (number between 0 and 100) for various tasks
|
||||
If it is called with -1 that means that the task does not have any progress information
|
||||
"""
|
||||
self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID,
|
||||
PRS500Device.PRS500_PRODUCT_ID,
|
||||
PRS500Device.PRS500_INTERFACE_ID)
|
||||
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
|
||||
self.report_progress = report_progress
|
||||
|
||||
def is_connected(self):
|
||||
return self.device_descriptor.getDevice() != None
|
||||
|
||||
@classmethod
|
||||
def _validate_response(cls, res, type=0x00, number=0x00):
|
||||
""" Raise a ProtocolError if the type and number of C{res} is not the same as C{type} and C{number}. """
|
||||
@ -388,8 +397,8 @@ class PRS500Device(object):
|
||||
if res.code != 0:
|
||||
raise PathError("Unable to open " + path + " for reading. Response code: " + hex(res.code))
|
||||
id = self._bulk_read(20, data_type=IdAnswer, command_number=FileOpen.NUMBER)[0].id
|
||||
bytes_left, chunk_size, pos = bytes, 0x8000, 0
|
||||
while bytes_left > 0:
|
||||
bytes_left, chunk_size, pos = bytes, 0x8000, 0
|
||||
while bytes_left > 0:
|
||||
if chunk_size > bytes_left: chunk_size = bytes_left
|
||||
res = self._send_validated_command(FileIO(id, pos, chunk_size))
|
||||
if res.code != 0:
|
||||
@ -405,6 +414,7 @@ class PRS500Device(object):
|
||||
raise ArgumentError("File get operation failed. Could not write to local location: " + str(e))
|
||||
bytes_left -= chunk_size
|
||||
pos += chunk_size
|
||||
if self.report_progress: self.report_progress(int(100*((1.*pos)/bytes)))
|
||||
self._send_validated_command(FileClose(id))
|
||||
# Not going to check response code to see if close was successful as there's not much we can do if it wasnt
|
||||
|
||||
@ -478,11 +488,12 @@ class PRS500Device(object):
|
||||
@return: A list of tuples. Each tuple has form ("location", free space, total space)
|
||||
"""
|
||||
data = []
|
||||
if self.report_progress: self.report_progress(-1)
|
||||
for path in ("/Data/", "a:/", "b:/"):
|
||||
res = self._send_validated_command(FreeSpaceQuery(path),timeout=5000) # Timeout needs to be increased as it takes time to read card
|
||||
buffer_size = 16 + res.data[2]
|
||||
pkt = self._bulk_read(buffer_size, data_type=FreeSpaceAnswer, command_number=FreeSpaceQuery.NUMBER)[0]
|
||||
data.append( (path, pkt.free_space, pkt.total) )
|
||||
data.append( (path, pkt.free_space, pkt.total) )
|
||||
return data
|
||||
|
||||
def _exists(self, path):
|
||||
|
@ -12,77 +12,295 @@
|
||||
## 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
|
||||
import images
|
||||
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
|
||||
from PyQt4 import uic
|
||||
import sys, pkg_resources, re, string
|
||||
from operator import itemgetter
|
||||
|
||||
NONE = QVariant()
|
||||
|
||||
class DeviceDetector(QThread):
|
||||
def __init__(self, detected_slot):
|
||||
QThread.__init__(self)
|
||||
self.dev = None
|
||||
self.detected_slot = detected_slot
|
||||
self.removed = False
|
||||
def human_readable(size):
|
||||
""" Convert a size in bytes into a human readle 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"
|
||||
elif size < 1024*1024*1024*1024: divisor, suffix = 1024*1024, "GB"
|
||||
size = str(size/divisor)
|
||||
if size.find(".") > -1: size = size[:size.find(".")+2]
|
||||
return size + " " + suffix
|
||||
|
||||
class DeviceBooksModel(QAbstractTableModel):
|
||||
|
||||
def __init__(self, parent, data):
|
||||
QAbstractTableModel.__init__(self, parent)
|
||||
self._data = data
|
||||
self._orig_data = data
|
||||
|
||||
def set_data(self, data):
|
||||
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
||||
self._data = data
|
||||
self._orig_data = data
|
||||
self.sort(0, Qt.DescendingOrder)
|
||||
|
||||
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()
|
||||
|
||||
def rowCount(self, parent): return len(self._data)
|
||||
def columnCount(self, parent): return 3
|
||||
|
||||
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"
|
||||
return QVariant(self.trUtf8(text))
|
||||
else: return QVariant(str(1+section))
|
||||
|
||||
def data(self, index, role):
|
||||
if role == Qt.DisplayRole:
|
||||
row, col = index.row(), index.column()
|
||||
book = self._data[row]
|
||||
if col == 0: text = book["title"]
|
||||
elif col == 1: text = book["author"]
|
||||
elif col == 2: text = human_readable(int(book["size"]))
|
||||
return QVariant(text)
|
||||
elif role == Qt.TextAlignmentRole and index.column() == 2:
|
||||
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
|
||||
|
||||
def info(self, 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)
|
||||
|
||||
def sort(self, col, order):
|
||||
def getter(key, func): return lambda x : func(itemgetter(key)(x))
|
||||
if col == 0: key, func = "title", string.lower
|
||||
if col == 1: key, func = "author", lambda x : x.split()[-1:][0].lower()
|
||||
if col == 2: key, func = "size", int
|
||||
descending = order != Qt.AscendingOrder
|
||||
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):
|
||||
queries = unicode(query).lower().split()
|
||||
self.emit(SIGNAL("layoutAboutToBeChanged()"))
|
||||
self._data = []
|
||||
for book in self._orig_data:
|
||||
match = True
|
||||
for q in queries:
|
||||
if q in book["title"].lower() or q in book["author"].lower(): continue
|
||||
else:
|
||||
match = False
|
||||
break
|
||||
if match: self._data.append(book)
|
||||
self.emit(SIGNAL("layoutChanged()"))
|
||||
self.emit(SIGNAL("searched()"))
|
||||
|
||||
|
||||
class MainWindow(Ui_MainWindow):
|
||||
Ui_MainWindow, bclass = uic.loadUiType(pkg_resources.resource_stream(__name__, "main.ui"))
|
||||
class MainWindow(QObject, 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
|
||||
def tree_clicked(self, index):
|
||||
item = self.tree.itemFromIndex(index)
|
||||
text = str(item.text())
|
||||
if text == "Library":
|
||||
print "Library Clicked"
|
||||
elif text == "SONY Reader":
|
||||
self.set_data(self.main_books + self.card_books)
|
||||
elif text == "Main Memory":
|
||||
self.set_data(self.main_books)
|
||||
elif text == "Storage Card":
|
||||
self.set_data(self.card_books)
|
||||
elif text == "Books":
|
||||
text = str(item.parent().text())
|
||||
if text == "Library":
|
||||
print "Library --> Books Clicked"
|
||||
elif text == "Main Memory":
|
||||
self.set_data(self.main_books)
|
||||
elif text == "Storage Card":
|
||||
self.set_data(self.card_books)
|
||||
|
||||
@apply
|
||||
def dev():
|
||||
def fget(self):
|
||||
return self.detector.dev
|
||||
return property(**locals())
|
||||
def set_data(self, data):
|
||||
self.model.set_data(data)
|
||||
self.table_view.resizeColumnsToContents()
|
||||
|
||||
|
||||
def __init__(self, window, log_packets):
|
||||
Ui_MainWindow.__init__(self)
|
||||
self.log_packets = log_packets
|
||||
self.detector = DeviceDetector(self.establish_connection)
|
||||
self.detector.start()
|
||||
def data_sorted(self):
|
||||
self.table_view.resizeRowsToContents()
|
||||
self.table_view.clearSelection()
|
||||
self.book_cover.hide()
|
||||
self.book_info.hide()
|
||||
|
||||
def show_book(self, current, previous):
|
||||
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_cover.setPixmap(thumbnail)
|
||||
self.book_cover.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 __init__(self, window):
|
||||
QObject.__init__(self)
|
||||
Ui_MainWindow.__init__(self)
|
||||
self.dev = device(report_progress=self.progress)
|
||||
self.is_connected = False
|
||||
self.setupUi(window)
|
||||
self.card = None
|
||||
# Create Tree
|
||||
self.tree = QStandardItemModel()
|
||||
library = QStandardItem(QString("Library"))
|
||||
library.setIcon(QIcon(":/images/mycomputer.png"))
|
||||
font = library.font()
|
||||
font.setBold(True)
|
||||
self.tree.appendRow(library)
|
||||
library.setFont(font)
|
||||
library.appendRow(QStandardItem(QString("Books")))
|
||||
blank = QStandardItem(" ")
|
||||
blank.setEnabled(False)
|
||||
self.tree.appendRow(blank)
|
||||
self.reader = QStandardItem(QString("SONY Reader"))
|
||||
mm = QStandardItem(QString("Main Memory"))
|
||||
mm.appendRow(QStandardItem(QString("Books")))
|
||||
self.reader.appendRow(mm)
|
||||
mc = QStandardItem(QString("Storage Card"))
|
||||
mc.appendRow(QStandardItem(QString("Books")))
|
||||
self.reader.appendRow(mc)
|
||||
self.reader.setIcon(QIcon(":/images/reader.png"))
|
||||
self.tree.appendRow(self.reader)
|
||||
self.reader.setFont(font)
|
||||
|
||||
|
||||
self.treeView.setModel(self.tree)
|
||||
self.treeView.header().hide()
|
||||
self.treeView.setExpanded(library.index(), True)
|
||||
self.treeView.setExpanded(self.reader.index(), True)
|
||||
self.treeView.setExpanded(mm.index(), True)
|
||||
self.treeView.setExpanded(mc.index(), True)
|
||||
self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True)
|
||||
QObject.connect(self.treeView, SIGNAL("activated(QModelIndex)"), self.tree_clicked)
|
||||
QObject.connect(self.treeView, SIGNAL("clicked(QModelIndex)"), self.tree_clicked)
|
||||
|
||||
# Create Table
|
||||
self.model = DeviceBooksModel(window, [])
|
||||
QObject.connect(self.model, SIGNAL("sorted()"), self.data_sorted)
|
||||
self.table_view.setModel(self.model)
|
||||
self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.table_view.setSortingEnabled(True)
|
||||
QObject.connect(self.table_view.selectionModel(), SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self.show_book)
|
||||
QObject.connect(self.search, SIGNAL("textChanged(QString)"), self.model.search)
|
||||
QObject.connect(self.model, SIGNAL("searched()"), self.searched)
|
||||
self.clearButton.setIcon(QIcon(":/images/clear.png"))
|
||||
QObject.connect(self.clearButton, SIGNAL("clicked(bool)"), self.clear)
|
||||
|
||||
# Setup book display
|
||||
self.BOOK_TEMPLATE = self.book_info.text()
|
||||
self.BOOK_IMAGE = QPixmap(":/images/book.png")
|
||||
self.book_cover.hide()
|
||||
self.book_info.hide()
|
||||
|
||||
self.device_detector = self.startTimer(1000)
|
||||
self.splitter.setStretchFactor(0,0)
|
||||
self.splitter.setStretchFactor(1,100)
|
||||
self.search.setFocus(Qt.OtherFocusReason)
|
||||
self.window = window
|
||||
window.show()
|
||||
|
||||
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.establish_connection()
|
||||
elif not is_connected and self.is_connected:
|
||||
self.device_removed()
|
||||
|
||||
def device_removed(self, timeout=False):
|
||||
""" @todo: implement this """
|
||||
self.detector.removed = True
|
||||
""" @todo: only reset stuff if library is not shown """
|
||||
self.is_connected = False
|
||||
self.df.setText("Main memory: <br><br>Storage card:")
|
||||
self.card = None
|
||||
self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), True)
|
||||
self.model.set_data([])
|
||||
self.book_cover.hide()
|
||||
self.book_info.hide()
|
||||
|
||||
|
||||
def timeout_error(self):
|
||||
""" @todo: update status bar """
|
||||
self.detector.sleep(10)
|
||||
self.device_removed(timeout=True)
|
||||
""" @todo: display error dialog """
|
||||
pass
|
||||
|
||||
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%")
|
||||
QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)
|
||||
|
||||
@safe
|
||||
def establish_connection(self):
|
||||
mb, cb, mx, cx = self.dev.books()
|
||||
self.status("Connecting to device")
|
||||
try:
|
||||
space = self.dev.available_space()
|
||||
except TimeoutError:
|
||||
c = 0
|
||||
self.status("Waiting for device to initialize")
|
||||
while c < 100: # Delay for 10s while device is initializing
|
||||
if c % 10 == c/10:
|
||||
self.progress(c)
|
||||
QThread.currentThread().msleep(100)
|
||||
c += 1
|
||||
space = self.dev.available_space()
|
||||
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.is_connected = True
|
||||
if space[1][2] > 0: self.card = "a:/"
|
||||
elif space[2][2] > 0: self.card = "b:/"
|
||||
else: self.card = None
|
||||
if self.card: self.treeView.setRowHidden(1, self.reader.index(), False)
|
||||
else: self.treeView.setRowHidden(1, self.reader.index(), True)
|
||||
self.treeView.setRowHidden(2, self.tree.invisibleRootItem().index(), False)
|
||||
self.status("Loading media list")
|
||||
mb, cb, mx, cx = self.dev.books()
|
||||
|
||||
for x in (mb, cb):
|
||||
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"]:
|
||||
book["author"] = re.sub(r"&\s*", r"\n", book["author"])
|
||||
|
||||
self.main_books = mb
|
||||
self.card_books = cb
|
||||
self.main_xml = mx
|
||||
self.cache_xml = cx
|
||||
print self.main_books + self.card_books
|
||||
|
||||
|
||||
def main():
|
||||
from PyQt4.Qt import QApplication, QMainWindow
|
||||
app = QApplication(sys.argv)
|
||||
window = QMainWindow()
|
||||
gui = MainWindow(window)
|
||||
return app.exec_()
|
||||
|
||||
|
@ -1,49 +1,41 @@
|
||||
## 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" >
|
||||
<author>Kovid Goyal</author>
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow" >
|
||||
<property name="geometry" >
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>885</width>
|
||||
<width>871</width>
|
||||
<height>631</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy>
|
||||
<hsizetype>5</hsizetype>
|
||||
<vsizetype>5</vsizetype>
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle" >
|
||||
<string>SONY Reader</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget" >
|
||||
<layout class="QGridLayout" >
|
||||
<layout class="QVBoxLayout" >
|
||||
<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>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<widget class="QWidget" name="" >
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
@ -51,77 +43,195 @@
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clear" >
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
<widget class="QTreeView" name="treeView" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy>
|
||||
<hsizetype>1</hsizetype>
|
||||
<vsizetype>7</vsizetype>
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search" >
|
||||
<property name="acceptDrops" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="frameShadow" >
|
||||
<enum>QFrame::Plain</enum>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy" >
|
||||
<enum>Qt::ScrollBarAsNeeded</enum>
|
||||
</property>
|
||||
<property name="autoScroll" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="autoFillBackground" >
|
||||
<bool>false</bool>
|
||||
<property name="alternatingRowColors" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Search title and author</string>
|
||||
<property name="indentation" >
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="frame" >
|
||||
<property name="uniformRowHeights" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="df" >
|
||||
<property name="autoFillBackground" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>Main Memory:<br><br>Storage card:</string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</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>
|
||||
</widget>
|
||||
<widget class="QWidget" name="" >
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="frameShadow" >
|
||||
<enum>QFrame::Plain</enum>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="autoScroll" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<item>
|
||||
<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="QLabel" name="label" >
|
||||
<property name="text" >
|
||||
<string>&Search:</string>
|
||||
</property>
|
||||
<property name="buddy" >
|
||||
<cstring>search</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="search" >
|
||||
<property name="enabled" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="acceptDrops" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip" >
|
||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="whatsThis" >
|
||||
<string>Search the list of books by title or author<br><br>Words separated by spaces are ANDed</string>
|
||||
</property>
|
||||
<property name="autoFillBackground" >
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string/>
|
||||
</property>
|
||||
<property name="frame" >
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clearButton" >
|
||||
<property name="toolTip" >
|
||||
<string>Reset Quick Search</string>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="table_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>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="book_cover" >
|
||||
<property name="text" >
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="book_info" >
|
||||
<property name="sizePolicy" >
|
||||
<sizepolicy>
|
||||
<hsizetype>5</hsizetype>
|
||||
<vsizetype>5</vsizetype>
|
||||
<horstretch>10</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text" >
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" >
|
||||
<layout class="QVBoxLayout" >
|
||||
<property name="margin" >
|
||||
<number>0</number>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progress_bar" >
|
||||
<property name="value" >
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="spacing" >
|
||||
<number>6</number>
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</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>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar" />
|
||||
<widget class="QToolBar" name="toolBar" >
|
||||
<property name="orientation" >
|
||||
<enum>Qt::Horizontal</enum>
|
||||
@ -131,6 +241,7 @@
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
<includes/>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -1,94 +0,0 @@
|
||||
# -*- 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))
|
||||
|
8
libprs500/gui/resources/images.qrc
Normal file
8
libprs500/gui/resources/images.qrc
Normal file
@ -0,0 +1,8 @@
|
||||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>images/book.png</file>
|
||||
<file>images/mycomputer.png</file>
|
||||
<file>images/reader.png</file>
|
||||
<file>images/clear.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
BIN
libprs500/gui/resources/images/book.png
Normal file
BIN
libprs500/gui/resources/images/book.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
libprs500/gui/resources/images/clear.png
Normal file
BIN
libprs500/gui/resources/images/clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 929 B |
BIN
libprs500/gui/resources/images/mycomputer.png
Normal file
BIN
libprs500/gui/resources/images/mycomputer.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
libprs500/gui/resources/images/reader.png
Normal file
BIN
libprs500/gui/resources/images/reader.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
126
prs-500.e3p
126
prs-500.e3p
@ -1,126 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Project SYSTEM "Project-3.9.dtd">
|
||||
<!-- Project file for project prs-500 -->
|
||||
<!-- Saved: 2006-11-07, 00:09:05 -->
|
||||
<!-- Copyright (C) 2006 Kovid Goyal, kovid@kovidgoyal.net -->
|
||||
<Project version="3.9">
|
||||
<ProgLanguage mixed="0">Python</ProgLanguage>
|
||||
<UIType>Console</UIType>
|
||||
<Description>Library to communicate with the Sony Reader PRS-500 via USB</Description>
|
||||
<Version></Version>
|
||||
<Author>Kovid Goyal</Author>
|
||||
<Email>kovid@kovidgoyal.net</Email>
|
||||
<Sources>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>communicate.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>terminfo.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>prstypes.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>errors.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Name>setup.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>scripts</Dir>
|
||||
<Name>prs500.py</Name>
|
||||
</Source>
|
||||
</Sources>
|
||||
<Forms>
|
||||
</Forms>
|
||||
<Translations>
|
||||
</Translations>
|
||||
<Interfaces>
|
||||
</Interfaces>
|
||||
<Others>
|
||||
<Other>
|
||||
<Name>epydoc.conf</Name>
|
||||
</Other>
|
||||
<Other>
|
||||
<Name>epydoc-pdf.conf</Name>
|
||||
</Other>
|
||||
</Others>
|
||||
<MainScript>
|
||||
<Dir>scripts</Dir>
|
||||
<Name>prs500.py</Name>
|
||||
</MainScript>
|
||||
<Vcs>
|
||||
<VcsType>Subversion</VcsType>
|
||||
<VcsOptions>(dp0
|
||||
S'status'
|
||||
p1
|
||||
(lp2
|
||||
S''
|
||||
p3
|
||||
asS'log'
|
||||
p4
|
||||
(lp5
|
||||
g3
|
||||
asS'global'
|
||||
p6
|
||||
(lp7
|
||||
g3
|
||||
asS'update'
|
||||
p8
|
||||
(lp9
|
||||
g3
|
||||
asS'remove'
|
||||
p10
|
||||
(lp11
|
||||
g3
|
||||
asS'add'
|
||||
p12
|
||||
(lp13
|
||||
g3
|
||||
asS'tag'
|
||||
p14
|
||||
(lp15
|
||||
g3
|
||||
asS'export'
|
||||
p16
|
||||
(lp17
|
||||
g3
|
||||
asS'commit'
|
||||
p18
|
||||
(lp19
|
||||
g3
|
||||
asS'diff'
|
||||
p20
|
||||
(lp21
|
||||
g3
|
||||
asS'checkout'
|
||||
p22
|
||||
(lp23
|
||||
g3
|
||||
asS'history'
|
||||
p24
|
||||
(lp25
|
||||
g3
|
||||
as.</VcsOptions>
|
||||
<VcsOtherData>(dp0
|
||||
S'standardLayout'
|
||||
p1
|
||||
I01
|
||||
s.</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" />
|
||||
</FiletypeAssociations>
|
||||
</Project>
|
211
prs-500.e4p
Normal file
211
prs-500.e4p
Normal file
@ -0,0 +1,211 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE Project SYSTEM "Project-4.0.dtd">
|
||||
<!-- eric4 project file for project prs-500 -->
|
||||
<!-- Saved: 2006-11-20, 20:22:57 -->
|
||||
<!-- Copyright (C) 2006 Kovid Goyal, kovid@kovidgoyal.net -->
|
||||
<Project version="4.0">
|
||||
<ProgLanguage mixed="0">Python</ProgLanguage>
|
||||
<UIType>Qt4</UIType>
|
||||
<Description>Library to communicate with the Sony Reader PRS-500 via USB</Description>
|
||||
<Version></Version>
|
||||
<Author>Kovid Goyal</Author>
|
||||
<Email>kovid@kovidgoyal.net</Email>
|
||||
<Sources>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>communicate.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>prstypes.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>errors.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Name>setup.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>cli</Dir>
|
||||
<Name>terminfo.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>cli</Dir>
|
||||
<Name>main.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>cli</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>main.py</Name>
|
||||
</Source>
|
||||
<Source>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>__init__.py</Name>
|
||||
</Source>
|
||||
</Sources>
|
||||
<Forms>
|
||||
<Form>
|
||||
<Dir>libprs500</Dir>
|
||||
<Dir>gui</Dir>
|
||||
<Name>main.ui</Name>
|
||||
</Form>
|
||||
</Forms>
|
||||
<Translations>
|
||||
</Translations>
|
||||
<Resources>
|
||||
</Resources>
|
||||
<Interfaces>
|
||||
</Interfaces>
|
||||
<Others>
|
||||
<Other>
|
||||
<Name>epydoc.conf</Name>
|
||||
</Other>
|
||||
<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>
|
||||
<Dir>gui</Dir>
|
||||
<Name>main.py</Name>
|
||||
</MainScript>
|
||||
<Vcs>
|
||||
<VcsType>Subversion</VcsType>
|
||||
<VcsOptions>
|
||||
<dict>
|
||||
<key>
|
||||
<string>status</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>log</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>global</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>update</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>remove</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>add</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>tag</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>export</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>commit</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>diff</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>checkout</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
<key>
|
||||
<string>history</string>
|
||||
</key>
|
||||
<value>
|
||||
<list>
|
||||
<string></string>
|
||||
</list>
|
||||
</value>
|
||||
</dict>
|
||||
</VcsOptions>
|
||||
<VcsOtherData>
|
||||
<dict>
|
||||
<key>
|
||||
<string>standardLayout</string>
|
||||
</key>
|
||||
<value>
|
||||
<bool>True</bool>
|
||||
</value>
|
||||
</dict>
|
||||
</VcsOtherData>
|
||||
</Vcs>
|
||||
<FiletypeAssociations>
|
||||
<FiletypeAssociation pattern="*.ui.h" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.ui" type="FORMS" />
|
||||
<FiletypeAssociation pattern="*.idl" type="INTERFACES" />
|
||||
<FiletypeAssociation pattern="*.py" type="SOURCES" />
|
||||
<FiletypeAssociation pattern="*.ptl" type="SOURCES" />
|
||||
</FiletypeAssociations>
|
||||
</Project>
|
38
setup.py
38
setup.py
@ -13,26 +13,42 @@
|
||||
## 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
|
||||
import ez_setup
|
||||
ez_setup.use_setuptools()
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
from libprs500 import __version__ as VERSION
|
||||
|
||||
# TODO: Dependencies
|
||||
|
||||
setup(name='libprs500',
|
||||
entry_points = {
|
||||
'console_scripts': [ 'prs500 = libprs500.cli.main:main' ],
|
||||
'gui_scripts' : [ 'prs500-gui = libprs500.gui.main:main']
|
||||
},
|
||||
include_package_data = True,
|
||||
version=VERSION,
|
||||
description='Library to interface with the Sony Portable Reader 500 over USB.',
|
||||
long_description =
|
||||
"""
|
||||
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 and a graphical user interface via the script prs500.py.
|
||||
""",
|
||||
libprs500 is library to interface with the `SONY Portable 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 and a graphical user interface via the script prs500.py.
|
||||
|
||||
For SVN access: svn co https://kovidgoyal.net/svn/code/prs-500
|
||||
|
||||
.. _SONY Portable Reader: http://Sony.com/reader
|
||||
.. _USB: http://www.usb.org
|
||||
""",
|
||||
author='Kovid Goyal',
|
||||
author_email='kovid@kovidgoyal.net',
|
||||
provides=['libprs500'],
|
||||
requires=['pyusb'],
|
||||
packages = ['libprs500', 'libprs500.gui'],
|
||||
scripts = ['scripts/prs500.py'],
|
||||
provides=['libprs500'],
|
||||
packages = find_packages(),
|
||||
license = 'GPL',
|
||||
url = 'http://www.python.org/pypi/libprs500/',
|
||||
classifiers = [
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Development Status :: 2 - Pre-Alpha',
|
||||
'Environment :: Console',
|
||||
'Environment :: X11 Applications :: Qt',
|
||||
'Intended Audience :: Developers',
|
||||
|
Loading…
x
Reference in New Issue
Block a user