%PDF- %PDF-
| Direktori : /usr/lib/calibre/calibre/devices/ |
| Current File : //usr/lib/calibre/calibre/devices/cli.py |
#! /usr/bin/python3.10
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
"""
Provides a command-line interface to ebook devices.
For usage information run the script.
"""
import sys, time, os
from optparse import OptionParser
from calibre import __version__, __appname__, human_readable, fsync, prints
from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked
from calibre.customize.ui import device_plugins
from calibre.devices.scanner import DeviceScanner
from calibre.utils.config import device_prefs
from polyglot.io import PolyglotStringIO
MINIMUM_COL_WIDTH = 12 # : Minimum width of columns in ls output
class FileFormatter:
def __init__(self, file):
self.is_dir = file.is_dir
self.is_readonly = file.is_readonly
self.size = file.size
self.ctime = file.ctime
self.wtime = file.wtime
self.name = file.name
self.path = file.path
@property
def mode_string(self):
""" The mode string for this file. There are only two modes read-only and read-write """
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
@property
def isdir_name(self):
'''Return self.name + '/' if self is a directory'''
name = self.name
if self.is_dir:
name += '/'
return name
@property
def name_in_color(self):
""" The name in ANSI text. Directories are blue, ebooks are green """
cname = self.name
blue, green, normal = "", "", ""
if self.term:
blue, green, normal = self.term.BLUE, self.term.GREEN, self.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
@property
def human_readable_size(self):
""" File size in human readable form """
return human_readable(self.size)
@property
def modification_time(self):
""" Last modified time in the Linux ls -l format """
return time.strftime("%Y-%m-%d %H:%M", time.localtime(self.wtime))
@property
def creation_time(self):
""" Last modified time in the Linux ls -l format """
return time.strftime("%Y-%m-%d %H:%M", time.localtime(self.ctime))
def info(dev):
info = dev.get_device_information()
print("Device name: ", info[0])
print("Device version: ", info[1])
print("Software version:", info[2])
print("Mime type: ", info[3])
def ls(dev, path, recurse=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 = PolyglotStringIO()
if path.endswith("/") and len(path) > 1:
path = path[:-1]
dirs = dev.list(path, recurse)
for dir in dirs:
if recurse:
prints(dir[0] + ":", file=output)
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:
file = FileFormatter(file)
size = len(file.human_readable_size)
if size > maxlen:
maxlen = size
for file in files:
file = FileFormatter(file)
name = file.name if ll else file.isdir_name
lsoutput.append(name)
lscoloutput.append(name)
if ll:
size = str(file.size)
if human_readable_size:
size = file.human_readable_size
prints(file.mode_string, ("%"+str(maxlen)+"s")%size, file.modification_time, name, file=output)
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])
prints(trytablecol[r][c], "".ljust(padding), end=' ', file=output)
prints(file=output)
prints(file=output)
listing = output.getvalue().rstrip() + "\n"
output.close()
return listing
def shutdown_plugins():
for d in device_plugins():
try:
d.shutdown()
except:
pass
def main():
from calibre.utils.terminal import geometry
cols = geometry()[0]
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand "+
"is one of: info, books, df, ls, cp, mkdir, touch, cat, rm, eject, test_file\n\n"+
"For help on a particular command: %prog command", version=__appname__+" version: " + __version__)
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.remove_option("-h")
parser.disable_interspersed_args() # Allow unrecognized options
options, args = parser.parse_args()
if len(args) < 1:
parser.print_help()
return 1
command = args[0]
args = args[1:]
dev = None
scanner = DeviceScanner()
scanner.scan()
connected_devices = []
for d in device_plugins():
try:
d.startup()
except:
print('Startup failed for device plugin: %s'%d)
if d.MANAGES_DEVICE_PRESENCE:
cd = d.detect_managed_devices(scanner.devices)
if cd is not None:
connected_devices.append((cd, d))
dev = d
break
continue
ok, det = scanner.is_device_connected(d)
if ok:
dev = d
dev.reset(log_packets=options.log_packets, detected_device=det)
connected_devices.append((det, dev))
if dev is None:
print('Unable to find a connected ebook reader.', file=sys.stderr)
shutdown_plugins()
return 1
for det, d in connected_devices:
try:
d.open(det, None)
except:
continue
else:
dev = d
d.specialize_global_preferences(device_prefs)
break
try:
if command == "df":
total = dev.total_space(end_session=False)
free = dev.free_space()
where = ("Memory", "Card A", "Card B")
print("Filesystem\tSize \tUsed \tAvail \tUse%")
for i in range(3):
print("%-10s\t%s\t%s\t%s\t%s"%(where[i], human_readable(total[i]), human_readable(total[i]-free[i]), human_readable(free[i]),
str(0 if total[i]==0 else int(100*(total[i]-free[i])/(total[i]*1.)))+"%"))
elif command == 'eject':
dev.eject()
elif command == "books":
print("Books in main memory:")
for book in dev.books():
print(book)
print("\nBooks on storage carda:")
for book in dev.books(oncard='carda'):
print(book)
print("\nBooks on storage cardb:")
for book in dev.books(oncard='cardb'):
print(book)
elif command == "mkdir":
parser = OptionParser(usage="usage: %prog mkdir [options] path\nCreate a folder 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 / or card:/")
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.", # noqa
dest="ll", action="store_true", default=False)
parser.add_option("-R", help="Recursively list subfolders 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()
return 1
print(ls(dev, args[0], recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols), end=' ')
elif command == "info":
info(dev)
elif command == "cp":
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"+\
"dev:mountpoint/my/path\n"+\
"where mountpoint is one of / or carda: or cardb:/\n\n"+\
"source must point to a file for which you have read permissions\n"+\
"destination must point to a file or folder for which you have write permissions"
parser = OptionParser(usage=usage)
parser.add_option('-f', '--force', dest='force', action='store_true', default=False,
help='Overwrite the destination file if it exists already.')
options, args = parser.parse_args(args)
if len(args) != 2:
parser.print_help()
return 1
if args[0].startswith("dev:"):
outfile = args[1]
path = args[0][4:]
if path.endswith("/"):
path = path[:-1]
if os.path.isdir(outfile):
outfile = os.path.join(outfile, path[path.rfind("/")+1:])
try:
outfile = lopen(outfile, "wb")
except OSError as e:
print(e, file=sys.stderr)
parser.print_help()
return 1
dev.get_file(path, outfile)
fsync(outfile)
outfile.close()
elif args[1].startswith("dev:"):
try:
infile = lopen(args[0], "rb")
except OSError as e:
print(e, file=sys.stderr)
parser.print_help()
return 1
dev.put_file(infile, args[1][4:], replace_file=options.force)
infile.close()
else:
parser.print_help()
return 1
elif command == "cat":
outfile = sys.stdout
parser = OptionParser(
usage="usage: %prog cat path\nShow file on the device\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()
return 1
if args[0].endswith("/"):
path = args[0][:-1]
else:
path = args[0]
outfile = sys.stdout
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 folder on the device "+
"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:
parser.print_help()
return 1
dev.rm(args[0])
elif command == "touch":
parser = OptionParser(usage="usage: %prog touch path\nCreate an empty file on the device\n\npath should point to a file on the device and must begin with /,a:/ or b:/\n\n"+ # noqa
"Unfortunately, I can't figure out how to update file times on the device, so if path already exists, touch does nothing")
options, args = parser.parse_args(args)
if len(args) != 1:
parser.print_help()
return 1
dev.touch(args[0])
elif command == 'test_file':
parser = OptionParser(usage=("usage: %prog test_file path\n"
'Open device, copy file specified by path to device and '
'then eject device.'))
options, args = parser.parse_args(args)
if len(args) != 1:
parser.print_help()
return 1
path = args[0]
from calibre.ebooks.metadata.meta import get_metadata
mi = get_metadata(lopen(path, 'rb'), path.rpartition('.')[-1].lower())
print(dev.upload_books([args[0]], [os.path.basename(args[0])],
end_session=False, metadata=[mi]))
dev.eject()
else:
parser.print_help()
if getattr(dev, 'handle', False):
dev.close()
return 1
except DeviceLocked:
print("The device is locked. Use the --unlock option", file=sys.stderr)
except (ArgumentError, DeviceError) as e:
print(e, file=sys.stderr)
return 1
finally:
shutdown_plugins()
return 0
if __name__ == '__main__':
main()