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

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

View File

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

View File

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

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
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(
'''\
<?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:
check_call('udevstart', shell=True)
check_call('udevcontrol reload_rules', shell=True)
except:
try:
check_call('/etc/init.d/udev reload', shell=True)