diff --git a/src/libprs500/devices/interface.py b/src/libprs500/devices/interface.py index ad9dcc3afa..923e864e47 100644 --- a/src/libprs500/devices/interface.py +++ b/src/libprs500/devices/interface.py @@ -75,9 +75,9 @@ class Device(object): def total_space(self, end_session=True): """ Get total space available on the mountpoints: - 1. Main memory - 2. Memory Stick - 3. SD Card + 1. Main memory + 2. Memory Stick + 3. SD Card @return: A 3 element list with total space in bytes of (1, 2, 3). If a particular device doesn't have any of these locations it should return 0. @@ -99,9 +99,9 @@ class Device(object): def books(self, oncard=False, end_session=True): """ Return a list of ebooks on the device. - @param oncard: If True return a list of ebooks on the storage card, - otherwise return list of ebooks in main memory of device. - If True and no books on card return empty list. + @param oncard: If True return a list of ebooks on the storage card, + otherwise return list of ebooks in main memory of device. + If True and no books on card return empty list. @return: A BookList. """ raise NotImplementedError() diff --git a/src/libprs500/devices/prs500/cli/main.py b/src/libprs500/devices/prs500/cli/main.py index 84a6477214..28b9743c6f 100755 --- a/src/libprs500/devices/prs500/cli/main.py +++ b/src/libprs500/devices/prs500/cli/main.py @@ -12,6 +12,7 @@ ## 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 libprs500.devices.prs505.driver import PRS505 """ Provides a command-line and optional graphical interface to the SONY Reader PRS-500. @@ -136,7 +137,7 @@ def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, l return rowwidths output = StringIO.StringIO() - if path.endswith("/"): path = path[:-1] + if path.endswith("/") and len(path) > 1: path = path[:-1] dirs = dev.list(path, recurse) for dir in dirs: if recurse: print >>output, dir[0] + ":" @@ -197,8 +198,6 @@ 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("--unlock", help="Unlock device with KEY. For e.g. --unlock=1234", \ - dest='key', default='-1') parser.remove_option("-h") parser.disable_interspersed_args() # Allow unrecognized options options, args = parser.parse_args() @@ -209,8 +208,12 @@ def main(): command = args[0] args = args[1:] - dev = PRS500(key=options.key, log_packets=options.log_packets) + dev = PRS500(log_packets=options.log_packets) + if not dev.is_connected(): + dev = PRS505() + try: + dev.open() if command == "df": total = dev.total_space(end_session=False) free = dev.free_space() @@ -226,13 +229,13 @@ def main(): print "\nBooks on storage card:" for book in dev.books(oncard=True): print book elif command == "mkdir": - parser = OptionParser(usage="usage: %prog mkdir [options] path\nCreate a directory on the device\n\npath must begin with /,a:/ or b:/") + parser = OptionParser(usage="usage: %prog mkdir [options] path\nCreate a directory on the device\n\npath must begin with / or card:/") if len(args) != 1: parser.print_help() sys.exit(1) dev.mkdir(args[0]) elif command == "ls": - parser = OptionParser(usage="usage: %prog ls [options] path\nList files on the device\n\npath must begin with /,a:/ or b:/") + parser = OptionParser(usage="usage: %prog ls [options] path\nList files on the device\n\npath must begin with / or card:/") parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False) parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time, in the local timezone). Times are local.", dest="ll", action="store_true", default=False) parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False) @@ -249,7 +252,7 @@ def main(): usage="usage: %prog cp [options] source destination\nCopy files to/from the device\n\n"+\ "One of source or destination must be a path on the device. \n\nDevice paths have the form\n"+\ "prs500:mountpoint/my/path\n"+\ - "where mountpoint is one of /, a: or b:\n\n"+\ + "where mountpoint is one of / or card:/\n\n"+\ "source must point to a file for which you have read permissions\n"+\ "destination must point to a file or directory for which you have write permissions" parser = OptionParser(usage=usage) @@ -305,7 +308,7 @@ def main(): dev.get_file(path, outfile) elif command == "rm": parser = OptionParser(usage="usage: %prog rm path\nDelete files from the device\n\npath should point to a file or empty directory on the device "+\ - "and must begin with /,a:/ or b:/\n\n"+\ + "and must begin with / or card:/\n\n"+\ "rm will DELETE the file. Be very CAREFUL") options, args = parser.parse_args(args) if len(args) != 1: diff --git a/src/libprs500/devices/prs500/driver.py b/src/libprs500/devices/prs500/driver.py index 4cf74da0c1..14e45dc9d7 100755 --- a/src/libprs500/devices/prs500/driver.py +++ b/src/libprs500/devices/prs500/driver.py @@ -12,7 +12,6 @@ ## 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 StringIO import StringIO ### End point description for PRS-500 procductId=667 ### Endpoint Descriptor: @@ -51,6 +50,7 @@ import sys, os from tempfile import TemporaryFile from array import array from functools import wraps +from StringIO import StringIO from libprs500.devices.interface import Device from libprs500.devices.libusb import Error as USBError @@ -58,7 +58,7 @@ from libprs500.devices.libusb import get_device_by_id from libprs500.devices.prs500.prstypes import * from libprs500.devices.errors import * from libprs500.devices.prs500.books import BookList, fix_ids -from libprs500 import __author__, __appname__ +from libprs500 import __author__ # Protocol versions libprs500 has been tested with KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L] @@ -459,6 +459,7 @@ class PRS500(Device): """ if path.endswith("/"): path = path[:-1] # We only copy files + path = path.replace('card:/', self.card_prefix(False)) _file = self.path_properties(path, end_session=False) if _file.is_dir: raise PathError("Cannot read as " + path + " is a directory") @@ -520,6 +521,7 @@ class PRS500(Device): """ Do a non recursive listsing of path """ if not path.endswith("/"): path += "/" # Initially assume path is a directory + path = path.replace('card:/', self.card_prefix(False)) files = [] candidate = self.path_properties(path, end_session=False) if not candidate.is_dir: @@ -643,6 +645,7 @@ class PRS500(Device): @todo: Update file modification time if it exists. Opening the file in write mode and then closing it doesn't work. """ + path = path.replace('card:/', self.card_prefix(False)) if path.endswith("/") and len(path) > 1: path = path[:-1] exists, _file = self._exists(path) @@ -669,6 +672,7 @@ class PRS500(Device): bytes = infile.tell() - pos start_pos = pos infile.seek(pos) + path = path.replace('card:/', self.card_prefix(False)) exists, dest = self._exists(path) if exists: if dest.is_dir: @@ -737,6 +741,8 @@ class PRS500(Device): @safe def mkdir(self, path, end_session=True): """ Make directory """ + if path.startswith('card:/'): + path = path.replace('card:/', self.card_prefix(False)) if not path.endswith("/"): path += "/" error_prefix = "Cannot create directory " + path @@ -754,6 +760,7 @@ class PRS500(Device): @safe def rm(self, path, end_session=True): """ Delete path from device if it is a file or an empty directory """ + path = path.replace('card:/', self.card_prefix(False)) dir = self.path_properties(path, end_session=False) if not dir.is_dir: self.del_file(path, end_session=False) @@ -945,4 +952,3 @@ class PRS500(Device): f.seek(0) self.put_file(f, path, replace_file=True, end_session=False) f.close() - diff --git a/src/libprs500/devices/prs505/__init__.py b/src/libprs500/devices/prs505/__init__.py new file mode 100644 index 0000000000..aaf49de99e --- /dev/null +++ b/src/libprs500/devices/prs505/__init__.py @@ -0,0 +1,14 @@ +## Copyright (C) 2007 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. diff --git a/src/libprs500/devices/prs505/driver.py b/src/libprs500/devices/prs505/driver.py new file mode 100644 index 0000000000..7826642ff7 --- /dev/null +++ b/src/libprs500/devices/prs505/driver.py @@ -0,0 +1,195 @@ +## Copyright (C) 2007 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 libprs500.devices.prs500.books import BookList +import shutil +'''''' + +from libprs500.devices.interface import Device +from libprs500.devices.errors import DeviceError + +from libprs500 import islinux, iswindows + +import sys, os + +class File(object): + def __init__(self, path): + stats = os.stat(path) + self.is_dir = os.path.isdir(path) + self.is_readonly = not os.access(path, os.W_OK) + self.ctime = stats.st_ctime + self.wtime = stats.st_mtime + self.size = stats.st_size + if path.endswith(os.sep): + path = path[:-1] + self.path = path + self.name = os.path.basename(path) + + +class PRS505(Device): + VENDOR_ID = 0x054c #: SONY Vendor Id + PRODUCT_ID = 0x031e #: Product Id for the PRS-505 + PRODUCT_NAME = 'Sony Portable Reader System' + + MEDIA_XML = 'database/cache/media.xml' + CACHE_XML = 'Sony Reader/database/cache.xml' + + LINUX_DEVICE_NODE = 'sony_prs_505' + LINUX_DEVICE_PATH = os.path.join('/dev', LINUX_DEVICE_NODE) + + def __init__(self): + self._main_prefix = self._card_prefix = None + self.hm = None + if islinux: + import dbus + self.bus = dbus.SystemBus() + self.hm = dbus.Interface(self.bus.get_object("org.freedesktop.Hal", "/org/freedesktop/Hal/Manager"), "org.freedesktop.Hal.Manager") + + def is_connected(self): + if self.hm is not None: # linux + devs = self.hm.FindDeviceStringMatch('info.product', 'Sony Portable Reader System') + for dev in devs: + obj = self.bus.get_object("org.freedesktop.Hal", dev) + if obj.GetPropertyInteger('usb_device.product_id', dbus_interface='org.freedesktop.Hal.Device') == self.__class__.PRODUCT_ID: + return True + return False + + def open_linux(self): + try: + mm = self.hm.FindDeviceStringMatch('volume.label', 'Sony Reader Main Memory')[0] + except: + raise DeviceError('Unable to find %s. Is it connected?'%(self.__class__.__name__,)) + try: + sc = self.hm.FindDeviceStringMatch('volume.label', 'Sony Reader Storage Card')[0] + except: + sc = None + + def conditional_mount(dev): + mmo = self.bus.get_object("org.freedesktop.Hal", dev) + label = mmo.GetPropertyString('volume.label', dbus_interface='org.freedesktop.Hal.Device') + is_mounted = mmo.GetPropertyString('volume.is_mounted', dbus_interface='org.freedesktop.Hal.Device') + mount_point = mmo.GetPropertyString('volume.mount_point', dbus_interface='org.freedesktop.Hal.Device') + fstype = mmo.GetPropertyString('volume.fstype', dbus_interface='org.freedesktop.Hal.Device') + if is_mounted: + return str(mount_point) + mmo.Mount(label, fstype, ['umask=077', 'uid='+str(os.getuid())],# 'gid='+str(os.getgid())], + dbus_interface='org.freedesktop.Hal.Device.Volume') + return label+os.sep + + self._main_prefix = conditional_mount(mm)+os.sep + self._card_prefix = None + if sc is not None: + self._card_prefix = conditional_mount(sc)+os.sep + + def open(self): + if self.hm is not None: # linux + self.open_linux() + + def set_progress_reporter(self, pr): + self.report_progress = pr + + def get_device_information(self, end_session=True): + return ('PRS-505', '', '', '') + + def card_prefix(self, end_session=True): + return self._card_prefix + + def total_space(self, end_session=True): + if not iswindows: + msz = 0 + if self._main_prefix is not None: + stats = os.statvfs(self._main_prefix) + msz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) + csz = 0 + if self._card_prefix is not None: + stats = os.statvfs(self._card_prefix) + csz = stats.f_frsize * (stats.f_blocks + stats.f_bavail - stats.f_bfree) + + return (msz, 0, csz) + + def free_space(self, end_session=True): + if not iswindows: + msz = 0 + if self._main_prefix is not None: + stats = os.statvfs(self._main_prefix) + msz = stats.f_bsize * stats.f_bavail + csz = 0 + if self._card_prefix is not None: + stats = os.statvfs(self._card_prefix) + csz = stats.f_bsize * stats.f_bavail + + return (msz, 0, csz) + + def books(self, oncard=False, end_session=True): + db = self.__class__.CACHE_XML if oncard else self.__class__.MEDIA_XML + prefix = self._card_prefix if oncard else self._main_prefix + f = open(prefix + db, 'rb') + bl = BookList(root='', sfile=f) + paths = bl.purge_corrupted_files() + for path in paths: + if os.path.exists(path): + os.unlink(path) + return bl + + def munge_path(self, path): + if path.startswith('/') and not path.startswith(self._main_prefix): + path = self._main_prefix + path[1:] + elif path.startswith('card:/'): + path = path.replace('card:/', self._card_prefix) + return path + + def mkdir(self, path, end_session=True): + """ Make directory """ + path = self.munge_path(path) + os.mkdir(path) + + def list(self, path, recurse=False, end_session=True): + path = self.munge_path(path) + if os.path.isfile(path): + return [(os.path.dirname(path), [File(path)])] + entries = [File(os.path.join(path, f)) for f in os.listdir(path)] + dirs = [(path, entries)] + for _file in entries: + if recurse and _file.is_dir: + dirs[len(dirs):] = self.list(_file.path, recurse=True, end_session=False) + return dirs + + def get_file(self, path, outfile, end_session=True): + path = self.munge_path(path) + src = open(path, 'rb') + shutil.copyfileobj(src, outfile, 10*1024*1024) + + def put_file(self, infile, path, end_session=True): + path = self.munge_path(path) + if os.path.isdir(path): + path = os.path.join(path, infile.name) + dest = open(path, 'wb') + shutil.copyfileobj(infile, dest, 10*1024*1024) + + def rm(self, path, end_session=True): + path = self.munge_path(path) + os.unlink(path) + + def touch(self, path, end_session=True): + path = self.munge_path(path) + if not os.path.exists(path): + open(path, 'w').close() + if not os.path.isdir(path): + os.utime(path, None) + +def main(args=sys.argv): + return 0 + +if __name__ == '__main__': + sys.exit(main()) \ No newline at end of file diff --git a/src/libprs500/linux.py b/src/libprs500/linux.py index 39a4acf58d..697eaac585 100644 --- a/src/libprs500/linux.py +++ b/src/libprs500/linux.py @@ -14,7 +14,7 @@ ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning import shutil ''' Post installation script for linux ''' -import sys, os +import sys, os, stat from subprocess import check_call from libprs500 import __version__ @@ -178,14 +178,44 @@ def setup_udev_rules(): groups = open('/etc/group', 'rb').read() group = 'plugdev' if 'plugdev' in groups else 'usb' udev = open('/etc/udev/rules.d/95-libprs500.rules', 'w') - udev.write(('''# Sony Reader PRS-500\n''' - '''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="%(group)s"\n''' - '''# Sony Reader PRS-505\n''' - '''BUS=="usb", SYSFS{idProduct}=="031e", SYSFS{idVendor}=="054c", MODE="660", GROUP="%(group)s"\n''')%dict(group=group,) + udev.write('''# Sony Reader PRS-500\n''' + '''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="%s"\n'''%(group,) ) udev.close() + open('/usr/share/hal/fdi/policy/20thirdparty/10-libprs500.fdi', 'w').write( +'''\ + + + + + + + + + Sony Reader Main Memory + + + + + + + + + + + Sony Reader Storage Card + + + + + + + +''') + check_call('/etc/init.d/hald restart', shell=True) + try: - check_call('udevstart', shell=True) + check_call('udevcontrol reload_rules', shell=True) except: try: check_call('/etc/init.d/udev reload', shell=True)