Command line interface to 505 for linux implemented.

This commit is contained in:
Kovid Goyal 2007-10-18 17:13:59 +00:00
parent bb2f9c36c1
commit 1353492be4
6 changed files with 271 additions and 23 deletions

View File

@ -12,6 +12,7 @@
## You should have received a copy of the GNU General Public License along ## 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., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 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. 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 return rowwidths
output = StringIO.StringIO() output = StringIO.StringIO()
if path.endswith("/"): path = path[:-1] if path.endswith("/") and len(path) > 1: path = path[:-1]
dirs = dev.list(path, recurse) dirs = dev.list(path, recurse)
for dir in dirs: for dir in dirs:
if recurse: print >>output, dir[0] + ":" if recurse: print >>output, dir[0] + ":"
@ -197,8 +198,6 @@ def main():
parser.add_option("--log-packets", help="print out packet stream to stdout. "+\ 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.", "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) 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.remove_option("-h")
parser.disable_interspersed_args() # Allow unrecognized options parser.disable_interspersed_args() # Allow unrecognized options
options, args = parser.parse_args() options, args = parser.parse_args()
@ -209,8 +208,12 @@ def main():
command = args[0] command = args[0]
args = args[1:] 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: try:
dev.open()
if command == "df": if command == "df":
total = dev.total_space(end_session=False) total = dev.total_space(end_session=False)
free = dev.free_space() free = dev.free_space()
@ -226,13 +229,13 @@ def main():
print "\nBooks on storage card:" print "\nBooks on storage card:"
for book in dev.books(oncard=True): print book for book in dev.books(oncard=True): print book
elif command == "mkdir": 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: if len(args) != 1:
parser.print_help() parser.print_help()
sys.exit(1) sys.exit(1)
dev.mkdir(args[0]) dev.mkdir(args[0])
elif command == "ls": 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("--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("-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) 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"+\ 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"+\ "One of source or destination must be a path on the device. \n\nDevice paths have the form\n"+\
"prs500:mountpoint/my/path\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"+\ "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" "destination must point to a file or directory for which you have write permissions"
parser = OptionParser(usage=usage) parser = OptionParser(usage=usage)
@ -305,7 +308,7 @@ def main():
dev.get_file(path, outfile) dev.get_file(path, outfile)
elif command == "rm": 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 "+\ 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") "rm will DELETE the file. Be very CAREFUL")
options, args = parser.parse_args(args) options, args = parser.parse_args(args)
if len(args) != 1: if len(args) != 1:

View File

@ -12,7 +12,6 @@
## You should have received a copy of the GNU General Public License along ## 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., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
from StringIO import StringIO
### End point description for PRS-500 procductId=667 ### End point description for PRS-500 procductId=667
### Endpoint Descriptor: ### Endpoint Descriptor:
@ -51,6 +50,7 @@ import sys, os
from tempfile import TemporaryFile from tempfile import TemporaryFile
from array import array from array import array
from functools import wraps from functools import wraps
from StringIO import StringIO
from libprs500.devices.interface import Device from libprs500.devices.interface import Device
from libprs500.devices.libusb import Error as USBError 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.prs500.prstypes import *
from libprs500.devices.errors import * from libprs500.devices.errors import *
from libprs500.devices.prs500.books import BookList, fix_ids 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 # Protocol versions libprs500 has been tested with
KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L] KNOWN_USB_PROTOCOL_VERSIONS = [0x3030303030303130L]
@ -459,6 +459,7 @@ class PRS500(Device):
""" """
if path.endswith("/"): if path.endswith("/"):
path = path[:-1] # We only copy files path = path[:-1] # We only copy files
path = path.replace('card:/', self.card_prefix(False))
_file = self.path_properties(path, end_session=False) _file = self.path_properties(path, end_session=False)
if _file.is_dir: if _file.is_dir:
raise PathError("Cannot read as " + path + " is a directory") raise PathError("Cannot read as " + path + " is a directory")
@ -520,6 +521,7 @@ class PRS500(Device):
""" Do a non recursive listsing of path """ """ Do a non recursive listsing of path """
if not path.endswith("/"): if not path.endswith("/"):
path += "/" # Initially assume path is a directory path += "/" # Initially assume path is a directory
path = path.replace('card:/', self.card_prefix(False))
files = [] files = []
candidate = self.path_properties(path, end_session=False) candidate = self.path_properties(path, end_session=False)
if not candidate.is_dir: if not candidate.is_dir:
@ -643,6 +645,7 @@ class PRS500(Device):
@todo: Update file modification time if it exists. @todo: Update file modification time if it exists.
Opening the file in write mode and then closing it doesn't work. 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: if path.endswith("/") and len(path) > 1:
path = path[:-1] path = path[:-1]
exists, _file = self._exists(path) exists, _file = self._exists(path)
@ -669,6 +672,7 @@ class PRS500(Device):
bytes = infile.tell() - pos bytes = infile.tell() - pos
start_pos = pos start_pos = pos
infile.seek(pos) infile.seek(pos)
path = path.replace('card:/', self.card_prefix(False))
exists, dest = self._exists(path) exists, dest = self._exists(path)
if exists: if exists:
if dest.is_dir: if dest.is_dir:
@ -737,6 +741,8 @@ class PRS500(Device):
@safe @safe
def mkdir(self, path, end_session=True): def mkdir(self, path, end_session=True):
""" Make directory """ """ Make directory """
if path.startswith('card:/'):
path = path.replace('card:/', self.card_prefix(False))
if not path.endswith("/"): if not path.endswith("/"):
path += "/" path += "/"
error_prefix = "Cannot create directory " + path error_prefix = "Cannot create directory " + path
@ -754,6 +760,7 @@ class PRS500(Device):
@safe @safe
def rm(self, path, end_session=True): def rm(self, path, end_session=True):
""" Delete path from device if it is a file or an empty directory """ """ 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) dir = self.path_properties(path, end_session=False)
if not dir.is_dir: if not dir.is_dir:
self.del_file(path, end_session=False) self.del_file(path, end_session=False)
@ -945,4 +952,3 @@ class PRS500(Device):
f.seek(0) f.seek(0)
self.put_file(f, path, replace_file=True, end_session=False) self.put_file(f, path, replace_file=True, end_session=False)
f.close() f.close()

View File

@ -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.

View File

@ -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())

View File

@ -14,7 +14,7 @@
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.Warning
import shutil import shutil
''' Post installation script for linux ''' ''' Post installation script for linux '''
import sys, os import sys, os, stat
from subprocess import check_call from subprocess import check_call
from libprs500 import __version__ from libprs500 import __version__
@ -178,14 +178,44 @@ def setup_udev_rules():
groups = open('/etc/group', 'rb').read() groups = open('/etc/group', 'rb').read()
group = 'plugdev' if 'plugdev' in groups else 'usb' group = 'plugdev' if 'plugdev' in groups else 'usb'
udev = open('/etc/udev/rules.d/95-libprs500.rules', 'w') udev = open('/etc/udev/rules.d/95-libprs500.rules', 'w')
udev.write(('''# Sony Reader PRS-500\n''' udev.write('''# Sony Reader PRS-500\n'''
'''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="%(group)s"\n''' '''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="%s"\n'''%(group,)
'''# Sony Reader PRS-505\n'''
'''BUS=="usb", SYSFS{idProduct}=="031e", SYSFS{idVendor}=="054c", MODE="660", GROUP="%(group)s"\n''')%dict(group=group,)
) )
udev.close() udev.close()
open('/usr/share/hal/fdi/policy/20thirdparty/10-libprs500.fdi', 'w').write(
'''\
<?xml version="1.0" encoding="UTF-8"?>
<deviceinfo version="0.2">
<device>
<match key="info.category" string="volume">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="0x054c">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="0x031e">
<match key="volume.is_partition" bool="false">
<merge key="volume.label" type="string">Sony Reader Main Memory</merge>
</match>
</match>
</match>
</match>
</device>
<device>
<match key="info.category" string="volume">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.vendor_id" int="0x054c">
<match key="@info.parent:@info.parent:@info.parent:@info.parent:usb.product_id" int="0x031e">
<match key="volume.is_partition" bool="true">
<merge key="volume.label" type="string">Sony Reader Storage Card</merge>
</match>
</match>
</match>
</match>
</device>
</deviceinfo>
''')
check_call('/etc/init.d/hald restart', shell=True)
try: try:
check_call('udevstart', shell=True) check_call('udevcontrol reload_rules', shell=True)
except: except:
try: try:
check_call('/etc/init.d/udev reload', shell=True) check_call('/etc/init.d/udev reload', shell=True)