mirror of
git://github.com/kovidgoyal/calibre.git
synced 2025-12-23 20:24:45 +01:00
405 lines
16 KiB
Python
Executable file
405 lines
16 KiB
Python
Executable file
#!/usr/bin/env python
|
|
## Copyright (C) 2006 Kovid Goyal kovid@kovidgoyal.net
|
|
## This program is free software; you can redistribute it and/or modify
|
|
## it under the terms of the GNU General Public License as published by
|
|
## the Free Software Foundation; either version 2 of the License, or
|
|
## (at your option) any later version.
|
|
##
|
|
## This program is distributed in the hope that it will be useful,
|
|
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
## GNU General Public License for more details.
|
|
##
|
|
## You should have received a copy of the GNU General Public License along
|
|
## with this program; if not, write to the Free Software Foundation, Inc.,
|
|
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
### End point description for PRS-500 procductId=667
|
|
### Endpoint Descriptor:
|
|
### bLength 7
|
|
### bDescriptorType 5
|
|
### bEndpointAddress 0x81 EP 1 IN
|
|
### bmAttributes 2
|
|
### Transfer Type Bulk
|
|
### Synch Type None
|
|
### Usage Type Data
|
|
### wMaxPacketSize 0x0040 1x 64 bytes
|
|
### bInterval 0
|
|
### Endpoint Descriptor:
|
|
### bLength 7
|
|
### bDescriptorType 5
|
|
### bEndpointAddress 0x02 EP 2 OUT
|
|
### bmAttributes 2
|
|
### Transfer Type Bulk
|
|
### Synch Type None
|
|
### Usage Type Data
|
|
### wMaxPacketSize 0x0040 1x 64 bytes
|
|
### bInterval 0
|
|
###
|
|
###
|
|
### Endpoint 0x81 is device->host and endpoint 0x02 is host->device. You can establish Stream pipes to/from these endpoints for Bulk transfers.
|
|
### Has two configurations 1 is the USB charging config 2 is the self-powered config.
|
|
### I think config management is automatic. Endpoints are the same
|
|
|
|
import sys, usb, logging, StringIO, time
|
|
from optparse import OptionParser
|
|
|
|
from prstypes import *
|
|
from errors import *
|
|
from terminfo import TerminalController
|
|
|
|
#try:
|
|
# import psyco
|
|
# psyco.full()
|
|
#except ImportError:
|
|
# print 'Psyco not installed, the program will just run slower'
|
|
_term = None
|
|
|
|
LOG_PACKETS=False # If True all packets are looged to stdout
|
|
MINIMUM_COL_WIDTH = 12
|
|
|
|
class File(object):
|
|
def __init__(self, file):
|
|
self.is_dir = file[1].is_dir
|
|
self.is_readonly = file[1].is_readonly
|
|
self.size = file[1].file_size
|
|
self.ctime = file[1].ctime
|
|
self.wtime = file[1].wtime
|
|
path = file[0]
|
|
if path.endswith("/"): path = path[:-1]
|
|
self.path = path
|
|
self.name = path[path.rfind("/")+1:].rstrip()
|
|
|
|
def __repr__(self):
|
|
return self.path
|
|
|
|
@apply
|
|
def mode_string():
|
|
doc=""" The mode string for this file. There are only two modes read-only and read-write """
|
|
def fget(self):
|
|
mode, x = "-", "-"
|
|
if self.is_dir: mode, x = "d", "x"
|
|
if self.is_readonly: mode += "r-"+x+"r-"+x+"r-"+x
|
|
else: mode += "rw"+x+"rw"+x+"rw"+x
|
|
return mode
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def name_in_color():
|
|
doc=""" The name in ANSI text. Directories are blue, ebooks are green """
|
|
def fget(self):
|
|
cname = self.name
|
|
blue, green, normal = "", "", ""
|
|
if _term: blue, green, normal = _term.BLUE, _term.GREEN, _term.NORMAL
|
|
if self.is_dir: cname = blue + self.name + normal
|
|
else:
|
|
ext = self.name[self.name.rfind("."):]
|
|
if ext in (".pdf", ".rtf", ".lrf", ".lrx", ".txt"): cname = green + self.name + normal
|
|
return cname
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def human_readable_size():
|
|
doc=""" File size in human readable form """
|
|
def fget(self):
|
|
if self.size < 1024: divisor, suffix = 1, ""
|
|
elif self.size < 1024*1024: divisor, suffix = 1024., "M"
|
|
elif self.size < 1024*1024*1024: divisor, suffix = 1024*1024, "G"
|
|
size = str(self.size/divisor)
|
|
if size.find(".") > -1: size = size[:size.find(".")+2]
|
|
return size + suffix
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def modification_time():
|
|
doc=""" Last modified time in the Linux ls -l format """
|
|
def fget(self):
|
|
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.wtime))
|
|
return property(**locals())
|
|
|
|
@apply
|
|
def creation_time():
|
|
doc=""" Last modified time in the Linux ls -l format """
|
|
def fget(self):
|
|
return time.strftime("%Y-%m-%d %H:%M", time.gmtime(self.ctime))
|
|
return property(**locals())
|
|
|
|
|
|
class DeviceDescriptor:
|
|
def __init__(self, vendor_id, product_id, interface_id) :
|
|
self.vendor_id = vendor_id
|
|
self.product_id = product_id
|
|
self.interface_id = interface_id
|
|
|
|
def getDevice(self) :
|
|
"""
|
|
Return the device corresponding to the device descriptor if it is
|
|
available on a USB bus. Otherwise, return None. Note that the
|
|
returned device has yet to be claimed or opened.
|
|
"""
|
|
buses = usb.busses()
|
|
for bus in buses :
|
|
for device in bus.devices :
|
|
if device.idVendor == self.vendor_id :
|
|
if device.idProduct == self.product_id :
|
|
return device
|
|
return None
|
|
|
|
|
|
|
|
|
|
class PRS500Device(object):
|
|
SONY_VENDOR_ID = 0x054c
|
|
PRS500_PRODUCT_ID = 0x029b
|
|
PRS500_INTERFACE_ID = 0
|
|
PRS500_BULK_IN_EP = 0x81
|
|
PRS500_BULK_OUT_EP = 0x02
|
|
|
|
def __init__(self) :
|
|
self.device_descriptor = DeviceDescriptor(PRS500Device.SONY_VENDOR_ID,
|
|
PRS500Device.PRS500_PRODUCT_ID,
|
|
PRS500Device.PRS500_INTERFACE_ID)
|
|
self.device = self.device_descriptor.getDevice()
|
|
self.handle = None
|
|
|
|
@classmethod
|
|
def _validate_response(cls, res, type=0x00, number=0x00):
|
|
if type != res.type or number != res.rnumber:
|
|
raise ProtocolError("Inavlid response.\ntype: expected="+hex(type)+" actual="+hex(res.type)+
|
|
"\nrnumber: expected="+hex(number)+" actual="+hex(res.rnumber))
|
|
|
|
def open(self) :
|
|
self.device = self.device_descriptor.getDevice()
|
|
if not self.device:
|
|
print >> sys.stderr, "Unable to find Sony Reader. Is it connected?"
|
|
sys.exit(1)
|
|
self.handle = self.device.open()
|
|
if sys.platform == 'darwin' :
|
|
# XXX : For some reason, Mac OS X doesn't set the
|
|
# configuration automatically like Linux does.
|
|
self.handle.setConfiguration(1)
|
|
self.handle.claimInterface(self.device_descriptor.interface_id)
|
|
self.handle.reset()
|
|
|
|
def close(self):
|
|
self.handle.releaseInterface()
|
|
self.handle, self.device = None, None
|
|
|
|
def _send_command(self, command, response_type=Response, timeout=100):
|
|
"""
|
|
Send command to device and return its response.
|
|
|
|
command -- an object of type Command or one of its derived classes
|
|
response_type -- an object of type 'type'. The return packet from the device is returned as an object of type response_type.
|
|
timeout -- the time to wait for a response from the device, in milliseconds
|
|
"""
|
|
if LOG_PACKETS: print "Command\n%s\n--\n"%command
|
|
bytes_sent = self.handle.controlMsg(0x40, 0x80, command)
|
|
if bytes_sent != len(command):
|
|
raise ControlError(desc="Could not send control request to device\n" + str(query.query))
|
|
response = response_type(self.handle.controlMsg(0xc0, 0x81, Response.SIZE, timeout=timeout))
|
|
if LOG_PACKETS: print "Response\n%s\n--\n"%response
|
|
return response
|
|
|
|
def _send_validated_command(self, command, cnumber=None, response_type=Response, timeout=100):
|
|
""" Wrapper around _send_command that checks if the response's rnumber == cnumber or command.number if cnumber==None """
|
|
if cnumber == None: cnumber = command.number
|
|
res = self._send_command(command, response_type=response_type, timeout=timeout)
|
|
PRS500Device._validate_response(res, type=command.type, number=cnumber)
|
|
return res
|
|
|
|
def _bulk_read(self, data_type=Answer, size=4096):
|
|
data = data_type(self.handle.bulkRead(PRS500Device.PRS500_BULK_IN_EP, size))
|
|
if LOG_PACKETS: print "Answer\n%s\n--\n"%data
|
|
return data
|
|
|
|
def _read_single_bulk_packet(self, command_number=0x00, data_type=Answer, size=4096):
|
|
data = self._bulk_read(data_type=data_type, size=size)
|
|
self._send_validated_command(AcknowledgeBulkRead(data.id), cnumber=command_number)
|
|
return data
|
|
|
|
def _test_bulk_reads(self):
|
|
self._send_validated_command( ShortCommand(number=0x00, type=0x01, command=0x00) )
|
|
self._read_single_bulk_packet(command_number=0x00, size=24)
|
|
|
|
def _start_session(self):
|
|
self.handle.reset()
|
|
self._test_bulk_reads()
|
|
self._send_validated_command( ShortCommand(number=0x0107, command=0x028000, type=0x01) ) # TODO: Figure out the meaning of this command
|
|
self._test_bulk_reads()
|
|
self._send_validated_command( ShortCommand(number=0x0106, type=0x01, command=0x312d) ) # TODO: Figure out the meaning of this command
|
|
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x01) )
|
|
|
|
def _end_session(self):
|
|
self._send_validated_command( ShortCommand(number=0x01, type=0x01, command=0x00) )
|
|
|
|
def _run_session(self, *args):
|
|
self._start_session()
|
|
res = None
|
|
try:
|
|
res = args[0](args[1:])
|
|
finally:
|
|
self._end_session()
|
|
pass
|
|
return res
|
|
|
|
def _get_path_properties(self, path):
|
|
res = self._send_validated_command(PathQuery(path), response_type=ListResponse)
|
|
data = self._read_single_bulk_packet(size=0x28, data_type=PathAnswer, command_number=PathQuery.PROPERTIES)
|
|
if res.path_not_found : raise PathError(path[:-1] + " does not exist on device")
|
|
if res.is_invalid : raise PathError(path[:-1] + " is not a valid path")
|
|
if res.is_unmounted : raise PathError(path[:-1] + " is not mounted")
|
|
return (res, data)
|
|
|
|
def _list(self, args):
|
|
path = args[0]
|
|
if not path.endswith("/"): path += "/" # Initially assume path is a directory
|
|
files = []
|
|
res, data = self._get_path_properties(path)
|
|
if res.is_file:
|
|
path = path[:-1]
|
|
res, data = self._get_path_properties(path)
|
|
files = [ (path, data) ]
|
|
else:
|
|
self._send_validated_command(PathQuery(path, number=PathQuery.ID), response_type=ListResponse)
|
|
id = self._read_single_bulk_packet(size=0x14, data_type=IdAnswer, command_number=PathQuery.ID).id
|
|
next = ShortCommand.list_command(id=id)
|
|
cnumber = next.number
|
|
items = []
|
|
while True:
|
|
res = self._send_validated_command(next, response_type=ListResponse)
|
|
size = res.data[2] + 16
|
|
data = self._read_single_bulk_packet(size=size, data_type=ListAnswer, command_number=cnumber)
|
|
# path_not_found seems to happen if the usb server doesn't have the permissions to access the directory
|
|
if res.is_eol or res.path_not_found: break
|
|
items.append(data.name)
|
|
for item in items:
|
|
ipath = path + item
|
|
res, data = self._get_path_properties(ipath)
|
|
files.append( (ipath, data) )
|
|
files.sort()
|
|
return files
|
|
|
|
def list(self, path, recurse=False):
|
|
files = self._run_session(self._list, path)
|
|
files = [ File(file) for file in files ]
|
|
dirs = [(path, files)]
|
|
for file in files:
|
|
if recurse and file.is_dir and not file.path.startswith(("/dev","/proc")):
|
|
dirs[len(dirs):] = self.list(file.path, recurse=True)
|
|
return dirs
|
|
|
|
def ls(self, path, recurse=False, color=False, human_readable_size=False, ll=False, cols=0):
|
|
def col_split(l, cols): # split list l into columns
|
|
rows = len(l) / cols
|
|
if len(l) % cols:
|
|
rows += 1
|
|
m = []
|
|
for i in range(rows):
|
|
m.append(l[i::rows])
|
|
return m
|
|
|
|
def row_widths(table): # Calculate widths for each column in the row-wise table
|
|
tcols = len(table[0])
|
|
rowwidths = [ 0 for i in range(tcols) ]
|
|
for row in table:
|
|
c = 0
|
|
for item in row:
|
|
rowwidths[c] = len(item) if len(item) > rowwidths[c] else rowwidths[c]
|
|
c += 1
|
|
return rowwidths
|
|
|
|
output = StringIO.StringIO()
|
|
if path.endswith("/"): path = path[:-1]
|
|
dirs = self.list(path, recurse)
|
|
for dir in dirs:
|
|
if recurse: print >>output, dir[0] + ":"
|
|
lsoutput, lscoloutput = [], []
|
|
files = dir[1]
|
|
maxlen = 0
|
|
if ll: # Calculate column width for size column
|
|
for file in files:
|
|
size = len(str(file.size))
|
|
if human_readable_size: size = len(file.human_readable_size)
|
|
if size > maxlen: maxlen = size
|
|
for file in files:
|
|
name = file.name
|
|
lsoutput.append(name)
|
|
if color: name = file.name_in_color
|
|
lscoloutput.append(name)
|
|
if ll:
|
|
size = str(file.size)
|
|
if human_readable_size: size = file.human_readable_size
|
|
print >>output, file.mode_string, ("%"+str(maxlen)+"s")%size, file.modification_time, name
|
|
if not ll and len(lsoutput) > 0:
|
|
trytable = []
|
|
for colwidth in range(MINIMUM_COL_WIDTH, cols):
|
|
trycols = int(cols/colwidth)
|
|
trytable = col_split(lsoutput, trycols)
|
|
works = True
|
|
for row in trytable:
|
|
row_break = False
|
|
for item in row:
|
|
if len(item) > colwidth - 1:
|
|
works, row_break = False, True
|
|
break
|
|
if row_break: break
|
|
if works: break
|
|
rowwidths = row_widths(trytable)
|
|
trytablecol = col_split(lscoloutput, len(trytable[0]))
|
|
for r in range(len(trytable)):
|
|
for c in range(len(trytable[r])):
|
|
padding = rowwidths[c] - len(trytable[r][c])
|
|
print >>output, trytablecol[r][c], "".ljust(padding),
|
|
print >>output
|
|
print >>output
|
|
listing = output.getvalue().rstrip()+ "\n"
|
|
output.close()
|
|
return listing
|
|
|
|
|
|
|
|
def main(argv):
|
|
if _term : cols = _term.COLS
|
|
else: cols = 70
|
|
|
|
parser = OptionParser(usage="usage: %prog command [options] args\n\ncommand is one of: ls, get, put or rm\n\n"+
|
|
"For help on a particular command: %prog command")
|
|
parser.add_option("--log-packets", help="print out packet stream to stdout", dest="log_packets", action="store_true", default=False)
|
|
parser.remove_option("-h")
|
|
parser.disable_interspersed_args() # Allow unrecognized options
|
|
options, args = parser.parse_args()
|
|
global LOG_PACKETS
|
|
LOG_PACKETS = options.log_packets
|
|
if len(args) < 1:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
command = args[0]
|
|
args = args[1:]
|
|
dev = PRS500Device()
|
|
if command == "ls":
|
|
parser = OptionParser(usage="usage: %prog ls [options] path\n\npath must begin with /,a:/ or b:/")
|
|
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 unless other times are selected)", 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.remove_option("-h")
|
|
parser.add_option("-h", "--human-readable", help="show sizes in human readable format", dest="hrs", action="store_true", default=False)
|
|
options, args = parser.parse_args(args)
|
|
if len(args) < 1:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
dev.open()
|
|
try:
|
|
print dev.ls(args[0], color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
|
except PathError, e:
|
|
print >> sys.stderr, e
|
|
sys.exit(1)
|
|
finally:
|
|
dev.close()
|
|
else:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
if __name__ == "__main__":
|
|
_term = TerminalController()
|
|
main(sys.argv)
|