2
# -*- coding: utf-8 -*-
4
# (c) Copyright 2003-2006 Hewlett-Packard Development Company, L.P.
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
__title__ = 'Photo Card Access Utility'
25
__doc__ = "Access inserted photo cards on supported HPLIP printers. This provides an alternative for older devices that do not support USB mass storage or for access to photo cards over a network. (Shell version)"
40
from base import device, service, utils #, exif
42
from pcard import photocard
44
USAGE = [(__doc__, "", "name", True),
45
("Usage: hp-photo [PRINTER|DEVICE-URI] [OPTIONS]", "", "summary", True),
51
utils.USAGE_BUS1, utils.USAGE_BUS2,
52
utils.USAGE_LOGGING1, utils.USAGE_LOGGING2, utils.USAGE_LOGGING3,
57
utils.USAGE_STD_NOTES1, utils.USAGE_STD_NOTES2,
58
("3. Use 'help' command at the pcard:> prompt for command help.", "", "note", False),
60
("hp-unload", "", "seealso", False),
63
def usage(typ='text'):
65
utils.log_title(__title__, __version__)
67
utils.format_text(USAGE, typ, __title__, 'hp-photo', __version__)
72
## Console class (from ASPN Python Cookbook)
73
## Author: James Thiele
74
## Date: 27 April 2004
76
## Location: http://www.eskimo.com/~jet/python/examples/cmd/
77
## Copyright (c) 2004, James Thiele
79
class Console(cmd.Cmd):
81
def __init__(self, pc):
82
cmd.Cmd.__init__(self)
83
self.intro = "Type 'help' for a list of commands. Type 'exit' to quit."
85
disk_info = self.pc.info()
86
pc.write_protect = disk_info[8]
88
log.warning("Photo card is write protected.")
89
self.prompt = utils.bold("pcard: %s > " % self.pc.pwd())
91
## Command definitions ##
92
def do_hist(self, args):
93
"""Print a list of commands that have been entered"""
96
def do_exit(self, args):
97
"""Exits from the console"""
100
def do_quit(self, args):
101
"""Exits from the console"""
104
## Command definitions to support Cmd object functionality ##
105
def do_EOF(self, args):
106
"""Exit on system end of file character"""
107
return self.do_exit(args)
109
def do_help(self, args):
110
"""Get help on commands
111
'help' or '?' with no arguments prints a list of commands for which help is available
112
'help <command>' or '? <command>' gives help on <command>
114
## The only reason to define this method is for the help text in the doc string
115
cmd.Cmd.do_help(self, args)
117
## Override methods in Cmd object ##
119
"""Initialization before prompting user for commands.
120
Despite the claims in the Cmd documentaion, Cmd.preloop() is not a stub.
122
cmd.Cmd.preloop(self) ## sets up command completion
123
self._hist = [] ## No history yet
124
self._locals = {} ## Initialize execution namespace for user
128
"""Take care of any unfinished business.
129
Despite the claims in the Cmd documentaion, Cmd.postloop() is not a stub.
131
cmd.Cmd.postloop(self) ## Clean up command completion
134
def precmd(self, line):
135
""" This method is called after the line has been input but before
136
it has been interpreted. If you want to modifdy the input line
137
before execution (for example, variable substitution) do it here.
139
self._hist += [line.strip()]
142
def postcmd(self, stop, line):
143
"""If you want to stop the console, return something that evaluates to true.
144
If you want to do some post command processing, do it here.
149
"""Do nothing on empty input line"""
152
def default(self, line):
153
print utils.bold("ERROR: Unrecognized command. Use 'help' to list commands.")
155
def do_ldir(self, args):
156
""" List local directory contents."""
159
def do_lls(self, args):
160
""" List local directory contents."""
163
def do_dir(self, args):
164
"""Synonym for the ls command."""
165
return self.do_ls(args)
167
def do_ls(self, args):
168
"""List photo card directory contents."""
169
args = args.strip().lower()
170
files = self.pc.ls(True, args)
173
formatter = utils.TextFormatter(
175
{'width': 14, 'margin' : 2},
176
{'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT},
177
{'width': 30, 'margin' : 2},
182
print utils.bold(formatter.compose(("Name", "Size", "Type")))
185
for d in self.pc.current_directories():
186
if d[0] in ('.', '..'):
187
print formatter.compose((d[0], "", "directory"))
189
print formatter.compose((d[0] + "/", "", "directory"))
191
for f in self.pc.current_files():
192
print formatter.compose((f[0], utils.format_bytes(f[2]), self.pc.classify_file(f[0])))
196
print utils.bold("% d files, %s" % (num_files, utils.format_bytes(total_size, True)))
199
def do_df(self, args):
200
"""Display free space on photo card.
202
-h\tDisplay in human readable format
204
freespace = self.pc.df()
206
if args.strip().lower() == '-h':
207
fs = utils.format_bytes(freespace)
209
fs = utils.commafy(freespace)
211
print "Freespace = %s Bytes" % fs
214
def do_cp(self, args, remove_after_copy=False):
215
"""Copy files from photo card to current local directory.
217
\tcp FILENAME(S)|GLOB PATTERN(S)
219
\tCopy all JPEG and GIF files and a file named thumbs.db from photo card to local directory:
220
\tcp *.jpg *.gif thumbs.db
222
args = args.strip().lower()
224
matched_files = self.pc.match_files(args)
226
if len(matched_files) == 0:
227
print "ERROR: File(s) not found."
229
total, delta = self.pc.cp_multiple(matched_files, remove_after_copy, self.cp_status_callback, self.rm_status_callback)
231
print utils.bold("\n%s transfered in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/(delta)))
233
def do_unload(self, args):
234
"""Unload all image files from photocard to current local directory.
236
\tSubdirectories on photo card are not preserved
238
-x\tDon't remove files after copy
239
-p\tPrint unload list but do not copy or remove files"""
240
args = args.lower().strip().split()
243
if self.pc.write_protect:
244
log.error("Photo card is write protected. -x not allowed.")
250
unload_list = self.pc.get_unload_list()
253
if len(unload_list) > 0:
257
for u in unload_list:
258
max_len = max(max_len, len(u[0]))
260
formatter = utils.TextFormatter(
262
{'width': max_len+2, 'margin' : 2},
263
{'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT},
264
{'width': 12, 'margin' : 2},
269
print utils.bold(formatter.compose(("Name", "Size", "Type")))
272
for u in unload_list:
273
print formatter.compose(('%s' % u[0], utils.format_bytes(u[1]), '%s/%s' % (u[2], u[3])))
277
print utils.bold("Found %d files to unload, %s" % (len(unload_list), utils.format_bytes(total, True)))
279
print utils.bold("Unloading %d files..." % len(unload_list))
280
total, delta, was_cancelled = self.pc.unload(unload_list, self.cp_status_callback, self.rm_status_callback, dont_remove)
281
print utils.bold("\n%s unloaded in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/delta))
284
print "No image, audio, or video files found."
287
def cp_status_callback(self, src, trg, size):
288
print "Copying %s to %s (%s)..." % (src, trg, utils.format_bytes(size))
290
def rm_status_callback(self, src):
291
print "Removing %s..." % src
295
def do_rm(self, args):
296
"""Remove files from photo card."""
297
if self.pc.write_protect:
298
log.error("Photo card is write protected. rm not allowed.")
301
args = args.strip().lower()
303
matched_files = self.pc.match_files(args)
305
if len(matched_files) == 0:
306
print "ERROR: File(s) not found."
308
for f in matched_files:
313
def do_mv(self, args):
314
"""Move files off photocard"""
315
if self.pc.write_protect:
316
log.error("Photo card is write protected. mv not allowed.")
318
self.do_cp(args, True)
320
def do_lpwd(self, args):
321
"""Print name of local current/working directory."""
324
def do_lcd(self, args):
325
"""Change current local working directory."""
327
os.chdir(args.strip())
329
print utils.bold("ERROR: Directory not found.")
332
def do_pwd(self, args):
333
"""Print name of photo card current/working directory
338
def do_cd(self, args):
339
"""Change current working directory on photo card.
341
\tYou may only specify one directory level at a time.
345
args = args.lower().strip()
348
if self.pc.pwd() != '/':
358
matched_dirs = self.pc.match_dirs(args)
360
if len(matched_dirs) == 0:
361
print "Directory not found"
363
elif len(matched_dirs) > 1:
364
print "Pattern matches more than one directory"
367
self.pc.cd(matched_dirs[0])
369
self.prompt = utils.bold("pcard: %s > " % self.pc.pwd())
371
def do_cdup(self, args):
372
"""Change to parent directory."""
375
#def complete_cd( self, text, line, begidx, endidx ):
376
# print text, line, begidx, endidx
379
def do_cache(self, args):
380
"""Display current cache entries, or turn cache on/off.
384
\tTurn off: cache off
386
args = args.strip().lower()
389
self.pc.cache_control(True)
392
self.pc.cache_control(False)
395
if self.pc.cache_state():
396
cache_info = self.pc.cache_info()
398
t = cache_info.keys()
402
print "sector %d (%d hits)" % (s, cache_info[s])
404
print utils.bold("Total cache usage: %s (%s maximum)" % (utils.format_bytes(len(t)*512), utils.format_bytes(photocard.MAX_CACHE * 512)))
405
print utils.bold("Total cache sectors: %s of %s" % (utils.commafy(len(t)), utils.commafy(photocard.MAX_CACHE)))
407
print "Cache is off."
409
def do_sector(self, args):
410
"""Display sector data.
412
\tsector <sector num>
414
args = args.strip().lower()
419
print "Sector must be specified as a number"
422
if self.pc.cache_check(sector) > 0:
423
print "Cached sector"
425
print repr(self.pc.sector(sector))
428
def do_tree(self, args):
429
"""Display photo card directory tree."""
430
tree = self.pc.tree()
432
self.print_tree(tree)
434
def print_tree(self, tree, level=0):
436
if type(tree[d]) == type({}):
437
print ''.join([' '*level*4, d, '/'])
438
self.print_tree(tree[d], level+1)
441
def do_reset(self, args):
442
"""Reset the cache."""
443
self.pc.cache_reset()
446
def do_card(self, args):
447
"""Print info about photocard."""
449
print "Device URI = %s" % self.pc.device.device_uri
450
print "Model = %s" % self.pc.device.model_ui
451
print "Working dir = %s" % self.pc.pwd()
452
disk_info = self.pc.info()
453
print "OEM ID = %s" % disk_info[0]
454
print "Bytes/sector = %d" % disk_info[1]
455
print "Sectors/cluster = %d" % disk_info[2]
456
print "Reserved sectors = %d" % disk_info[3]
457
print "Root entries = %d" % disk_info[4]
458
print "Sectors/FAT = %d" % disk_info[5]
459
print "Volume label = %s" % disk_info[6]
460
print "System ID = %s" % disk_info[7]
461
print "Write protected = %d" % disk_info[8]
462
print "Cached sectors = %s" % utils.commafy(len(self.pc.cache_info()))
465
def do_display(self, args):
466
"""Display an image with ImageMagick.
468
\tdisplay <filename>"""
469
args = args.strip().lower()
470
matched_files = self.pc.match_files(args)
472
if len(matched_files) == 1:
474
typ = self.pc.classify_file(args).split('/')[0]
477
fd, temp_name = utils.make_temp_file()
478
self.pc.cp(args, temp_name)
479
os.system('display %s' % temp_name)
483
print "File is not an image."
485
elif len(matched_files) == 0:
486
print "File not found."
489
print "Only one file at a time may be specified for display."
491
def do_show(self, args):
492
"""Synonym for the display command."""
493
self.do_display(args)
495
def do_thumbnail(self, args):
496
"""Display an embedded thumbnail image with ImageMagick.
498
\tOnly works with JPEG/JFIF images with embedded JPEG/TIFF thumbnails
500
\tthumbnail <filename>"""
501
args = args.strip().lower()
502
matched_files = self.pc.match_files(args)
504
if len(matched_files) == 1:
505
typ, subtyp = self.pc.classify_file(args).split('/')
506
#print "'%s' '%s'" % (typ, subtyp)
508
if typ == 'image' and subtyp in ('jpeg', 'tiff'):
509
exif_info = self.pc.get_exif(args)
511
dir_name, file_name=os.path.split(args)
512
photo_name, photo_ext=os.path.splitext(args)
514
if 'JPEGThumbnail' in exif_info:
515
#print "JPEG thumbnail found."
516
temp_file_fd, temp_file_name = utils.make_temp_file()
517
#thumb_name = os.path.join( os.getcwd(), photo_name ) + '_thumb.jpg'
518
open(temp_file_name, 'wb').write(exif_info['JPEGThumbnail'])
519
os.system('display %s' % temp_file_name)
520
os.remove(temp_file_name)
522
elif 'TIFFThumbnail' in exif_info:
523
#print "TIFF thumbnail found."
524
#thumb_name = os.path.join( os.getcwd(), photo_name ) + '_thumb.tif'
525
temp_file_fd, temp_file_name = utils.make_temp_file()
526
open(temp_file_name, 'wb').write(exif_info['TIFFThumbnail'])
527
os.system('display %s' % temp_file_name)
528
os.remove(temp_file_name)
531
print "No thumbnail found."
534
print "Incorrect file type for thumbnail."
536
elif len(matched_files) == 0:
537
print "File not found."
539
print "Only one file at a time may be specified for thumbnail display."
541
def do_thumb(self, args):
542
"""Synonym for the thumbnail command."""
543
self.do_thumbnail(args)
545
def do_exif(self, args):
546
"""Display EXIF info for file.
549
args = args.strip().lower()
550
matched_files = self.pc.match_files(args)
552
if len(matched_files) == 1:
553
typ, subtyp = self.pc.classify_file(args).split('/')
554
#print "'%s' '%s'" % (typ, subtyp)
556
if typ == 'image' and subtyp in ('jpeg', 'tiff'):
557
exif_info = self.pc.get_exif(args)
559
formatter = utils.TextFormatter(
561
{'width': 40, 'margin' : 2},
562
{'width': 40, 'margin' : 2},
567
print utils.bold(formatter.compose(("Tag", "Value")))
569
ee = exif_info.keys()
572
if e not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename'):
573
#if e != 'EXIF MakerNote':
574
print formatter.compose((e, '%s' % exif_info[e]))
576
# print formatter.compose( ( e, ''.join( [ chr(x) for x in exif_info[e].values if chr(x) in string.printable ] ) ) )
578
print "Incorrect file type for thumbnail."
580
elif len(matched_files) == 0:
581
print "File not found."
583
print "Only one file at a time may be specified for thumbnail display."
585
def do_info(self, args):
586
"""Synonym for the exif command."""
595
opts, args = getopt.getopt(sys.argv[1:], 'p:d:hb:l:g',
596
['printer=', 'device=', 'help', 'help-rest', 'help-man',
598
except getopt.GetoptError:
603
bus = device.DEFAULT_PROBE_BUS
604
log_level = logger.DEFAULT_LOG_LEVEL
606
if os.getenv("HPLIP_DEBUG"):
607
log.set_level('debug')
610
if o in ('-h', '--help'):
613
elif o == '--help-rest':
616
elif o == '--help-man':
619
elif o in ('-p', '--printer'):
622
elif o in ('-d', '--device'):
625
elif o in ('-b', '--bus'):
626
bus = a.lower().strip()
627
if not device.validateBusList(bus):
630
elif o in ('-l', '--logging'):
631
log_level = a.lower().strip()
632
if not log.set_level(log_level):
636
log.set_level('debug')
639
if device_uri and printer_name:
640
log.error("You may not specify both a printer (-p) and a device (-d).")
643
utils.log_title(__title__, __version__)
646
printer_list = cups.getPrinters()
648
for p in printer_list:
649
if p.name == printer_name:
653
log.error("Unknown printer name: %s" % printer_name)
657
if not device_uri and not printer_name:
659
device_uri = device.getInteractiveDeviceURI(bus, 'pcard')
660
if device_uri is None:
663
log.error("Error occured during interative mode. Exiting.")
667
pc = photocard.PhotoCard( None, device_uri, printer_name )
669
log.error("Unable to start photocard session: %s" % e.msg)
672
pc.set_callback(update_spinner)
674
if pc.device.device_uri is None and printer_name:
675
log.error("Printer '%s' not found." % printer_name)
678
if pc.device.device_uri is None and device_uri:
679
log.error("Malformed/invalid device-uri: %s" % device_uri)
682
pc.device.sendEvent(EVENT_START_PCARD_JOB)
687
log.error("Unable to mount photo card on device. Check that device is powered on and photo card is correctly inserted.")
689
pc.device.sendEvent(EVENT_PCARD_UNABLE_TO_MOUNT, typ='error')
692
log.info(utils.bold("\nPhotocard on device %s mounted" % pc.device.device_uri))
693
log.info(utils.bold("DO NOT REMOVE PHOTO CARD UNTIL YOU EXIT THIS PROGRAM"))
695
console = Console(pc)
700
log.error("An error occured: %s" % e)
704
pc.device.sendEvent(EVENT_END_PCARD_JOB)