diff options
Diffstat (limited to 'unload.py')
-rwxr-xr-x | unload.py | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/unload.py b/unload.py new file mode 100755 index 0000000..da78a7e --- /dev/null +++ b/unload.py @@ -0,0 +1,760 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P. +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Author: Don Welch +# + +__version__ = '3.3' +__mod__ = 'hp-unload' +__title__ = 'Photo Card Access Utility' +__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." + +# Std Lib +import sys +import os +import os.path +import getopt +import re +import cmd +import time +import fnmatch +import string +import operator + +try: + import readline +except ImportError: + pass + +# Local +from base.g import * +from base import device, utils, tui, module +from prnt import cups +from pcard import photocard + + +# Console class (from ASPN Python Cookbook) +# Author: James Thiele +# Date: 27 April 2004 +# Version: 1.0 +# Location: http://www.eskimo.com/~jet/python/examples/cmd/ +# Copyright (c) 2004, James Thiele + +class Console(cmd.Cmd): + + def __init__(self, pc): + cmd.Cmd.__init__(self) + self.intro = "Type 'help' for a list of commands. Type 'exit' to quit." + self.pc = pc + disk_info = self.pc.info() + pc.write_protect = disk_info[8] + if pc.write_protect: + log.warning("Photo card is write protected.") + self.prompt = log.bold("pcard: %s > " % self.pc.pwd()) + + # Command definitions + def do_hist(self, args): + """Print a list of commands that have been entered""" + print self._hist + + def do_exit(self, args): + """Exits from the console""" + return -1 + + def do_quit(self, args): + """Exits from the console""" + return -1 + + # Command definitions to support Cmd object functionality + def do_EOF(self, args): + """Exit on system end of file character""" + return self.do_exit(args) + + def do_help(self, args): + """Get help on commands + 'help' or '?' with no arguments prints a list of commands for which help is available + 'help <command>' or '? <command>' gives help on <command> + """ + # The only reason to define this method is for the help text in the doc string + cmd.Cmd.do_help(self, args) + + # Override methods in Cmd object + def preloop(self): + """Initialization before prompting user for commands. + Despite the claims in the Cmd documentaion, Cmd.preloop() is not a stub. + """ + cmd.Cmd.preloop(self) # sets up command completion + self._hist = [] # No history yet + self._locals = {} # Initialize execution namespace for user + self._globals = {} + + def postloop(self): + """Take care of any unfinished business. + Despite the claims in the Cmd documentaion, Cmd.postloop() is not a stub. + """ + cmd.Cmd.postloop(self) # Clean up command completion + print "Exiting..." + + def precmd(self, line): + """ This method is called after the line has been input but before + it has been interpreted. If you want to modifdy the input line + before execution (for example, variable substitution) do it here. + """ + self._hist += [line.strip()] + return line + + def postcmd(self, stop, line): + """If you want to stop the console, return something that evaluates to true. + If you want to do some post command processing, do it here. + """ + return stop + + def emptyline(self): + """Do nothing on empty input line""" + pass + + def default(self, line): + print log.bold("ERROR: Unrecognized command. Use 'help' to list commands.") + + def do_ldir(self, args): + """ List local directory contents.""" + os.system('ls -l') + + def do_lls(self, args): + """ List local directory contents.""" + os.system('ls -l') + + def do_dir(self, args): + """Synonym for the ls command.""" + return self.do_ls(args) + + def do_ls(self, args): + """List photo card directory contents.""" + args = args.strip().lower() + files = self.pc.ls(True, args) + + total_size = 0 + formatter = utils.TextFormatter( + ( + {'width': 14, 'margin' : 2}, + {'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT}, + {'width': 30, 'margin' : 2}, + ) + ) + + print + print log.bold(formatter.compose(("Name", "Size", "Type"))) + + num_files = 0 + for d in self.pc.current_directories(): + if d[0] in ('.', '..'): + print formatter.compose((d[0], "", "directory")) + else: + print formatter.compose((d[0] + "/", "", "directory")) + + for f in self.pc.current_files(): + print formatter.compose((f[0], utils.format_bytes(f[2]), self.pc.classify_file(f[0]))) + num_files += 1 + total_size += f[2] + + print log.bold("% d files, %s" % (num_files, utils.format_bytes(total_size, True))) + + + def do_df(self, args): + """Display free space on photo card. + Options: + -h\tDisplay in human readable format + """ + freespace = self.pc.df() + + if args.strip().lower() == '-h': + fs = utils.format_bytes(freespace) + else: + fs = utils.commafy(freespace) + + print "Freespace = %s Bytes" % fs + + + def do_cp(self, args, remove_after_copy=False): + """Copy files from photo card to current local directory. + Usage: + \tcp FILENAME(S)|GLOB PATTERN(S) + Example: + \tCopy all JPEG and GIF files and a file named thumbs.db from photo card to local directory: + \tcp *.jpg *.gif thumbs.db + """ + args = args.strip().lower() + + matched_files = self.pc.match_files(args) + + if len(matched_files) == 0: + print "ERROR: File(s) not found." + else: + total, delta = self.pc.cp_multiple(matched_files, remove_after_copy, self.cp_status_callback, self.rm_status_callback) + + print log.bold("\n%s transfered in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/(delta))) + + def do_unload(self, args): + """Unload all image files from photocard to current local directory. + Note: + \tSubdirectories on photo card are not preserved + Options: + -x\tDon't remove files after copy + -p\tPrint unload list but do not copy or remove files""" + args = args.lower().strip().split() + dont_remove = False + if '-x' in args: + if self.pc.write_protect: + log.error("Photo card is write protected. -x not allowed.") + return + else: + dont_remove = True + + + unload_list = self.pc.get_unload_list() + print + + if len(unload_list) > 0: + if '-p' in args: + + max_len = 0 + for u in unload_list: + max_len = max(max_len, len(u[0])) + + formatter = utils.TextFormatter( + ( + {'width': max_len+2, 'margin' : 2}, + {'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT}, + {'width': 12, 'margin' : 2}, + ) + ) + + print + print log.bold(formatter.compose(("Name", "Size", "Type"))) + + total = 0 + for u in unload_list: + print formatter.compose(('%s' % u[0], utils.format_bytes(u[1]), '%s/%s' % (u[2], u[3]))) + total += u[1] + + + print log.bold("Found %d files to unload, %s" % (len(unload_list), utils.format_bytes(total, True))) + else: + print log.bold("Unloading %d files..." % len(unload_list)) + total, delta, was_cancelled = self.pc.unload(unload_list, self.cp_status_callback, self.rm_status_callback, dont_remove) + print log.bold("\n%s unloaded in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/delta)) + + else: + print "No image, audio, or video files found." + + + def cp_status_callback(self, src, trg, size): + if size == 1: + print + print log.bold("Copying %s..." % src) + else: + print "\nCopied %s to %s (%s)..." % (src, trg, utils.format_bytes(size)) + + def rm_status_callback(self, src): + print "Removing %s..." % src + + + + def do_rm(self, args): + """Remove files from photo card.""" + if self.pc.write_protect: + log.error("Photo card is write protected. rm not allowed.") + return + + args = args.strip().lower() + + matched_files = self.pc.match_files(args) + + if len(matched_files) == 0: + print "ERROR: File(s) not found." + else: + for f in matched_files: + self.pc.rm(f, False) + + self.pc.ls() + + def do_mv(self, args): + """Move files off photocard""" + if self.pc.write_protect: + log.error("Photo card is write protected. mv not allowed.") + return + self.do_cp(args, True) + + def do_lpwd(self, args): + """Print name of local current/working directory.""" + print os.getcwd() + + def do_lcd(self, args): + """Change current local working directory.""" + try: + os.chdir(args.strip()) + except OSError: + print log.bold("ERROR: Directory not found.") + print os.getcwd() + + def do_pwd(self, args): + """Print name of photo card current/working directory + Usage: + \t>pwd""" + print self.pc.pwd() + + def do_cd(self, args): + """Change current working directory on photo card. + Note: + \tYou may only specify one directory level at a time. + Usage: + \tcd <directory> + """ + args = args.lower().strip() + + if args == '..': + if self.pc.pwd() != '/': + self.pc.cdup() + + elif args == '.': + pass + + elif args == '/': + self.pc.cd('/') + + else: + matched_dirs = self.pc.match_dirs(args) + + if len(matched_dirs) == 0: + print "Directory not found" + + elif len(matched_dirs) > 1: + print "Pattern matches more than one directory" + + else: + self.pc.cd(matched_dirs[0]) + + self.prompt = log.bold("pcard: %s > " % self.pc.pwd()) + + def do_cdup(self, args): + """Change to parent directory.""" + self.do_cd('..') + + #def complete_cd( self, text, line, begidx, endidx ): + # print text, line, begidx, endidx + # #return "XXX" + + def do_cache(self, args): + """Display current cache entries, or turn cache on/off. + Usage: + \tDisplay: cache + \tTurn on: cache on + \tTurn off: cache off + """ + args = args.strip().lower() + + if args == 'on': + self.pc.cache_control(True) + + elif args == 'off': + self.pc.cache_control(False) + + else: + if self.pc.cache_state(): + cache_info = self.pc.cache_info() + + t = cache_info.keys() + t.sort() + print + for s in t: + print "sector %d (%d hits)" % (s, cache_info[s]) + + print log.bold("Total cache usage: %s (%s maximum)" % (utils.format_bytes(len(t)*512), utils.format_bytes(photocard.MAX_CACHE * 512))) + print log.bold("Total cache sectors: %s of %s" % (utils.commafy(len(t)), utils.commafy(photocard.MAX_CACHE))) + else: + print "Cache is off." + + def do_sector(self, args): + """Display sector data. + Usage: + \tsector <sector num> + """ + args = args.strip().lower() + cached = False + try: + sector = int(args) + except ValueError: + print "Sector must be specified as a number" + return + + if self.pc.cache_check(sector) > 0: + print "Cached sector" + + print repr(self.pc.sector(sector)) + + + def do_tree(self, args): + """Display photo card directory tree.""" + tree = self.pc.tree() + print + self.print_tree(tree) + + def print_tree(self, tree, level=0): + for d in tree: + if type(tree[d]) == type({}): + print ''.join([' '*level*4, d, '/']) + self.print_tree(tree[d], level+1) + + + def do_reset(self, args): + """Reset the cache.""" + self.pc.cache_reset() + + + def do_card(self, args): + """Print info about photocard.""" + print + print "Device URI = %s" % self.pc.device.device_uri + print "Model = %s" % self.pc.device.model_ui + print "Working dir = %s" % self.pc.pwd() + disk_info = self.pc.info() + print "OEM ID = %s" % disk_info[0] + print "Bytes/sector = %d" % disk_info[1] + print "Sectors/cluster = %d" % disk_info[2] + print "Reserved sectors = %d" % disk_info[3] + print "Root entries = %d" % disk_info[4] + print "Sectors/FAT = %d" % disk_info[5] + print "Volume label = %s" % disk_info[6] + print "System ID = %s" % disk_info[7] + print "Write protected = %d" % disk_info[8] + print "Cached sectors = %s" % utils.commafy(len(self.pc.cache_info())) + + + def do_display(self, args): + """Display an image with ImageMagick. + Usage: + \tdisplay <filename>""" + args = args.strip().lower() + matched_files = self.pc.match_files(args) + + if len(matched_files) == 1: + + typ = self.pc.classify_file(args).split('/')[0] + + if typ == 'image': + fd, temp_name = utils.make_temp_file() + self.pc.cp(args, temp_name) + os.system('display %s' % temp_name) + os.remove(temp_name) + + else: + print "File is not an image." + + elif len(matched_files) == 0: + print "File not found." + + else: + print "Only one file at a time may be specified for display." + + def do_show(self, args): + """Synonym for the display command.""" + self.do_display(args) + + def do_thumbnail(self, args): + """Display an embedded thumbnail image with ImageMagick. + Note: + \tOnly works with JPEG/JFIF images with embedded JPEG/TIFF thumbnails + Usage: + \tthumbnail <filename>""" + args = args.strip().lower() + matched_files = self.pc.match_files(args) + + if len(matched_files) == 1: + typ, subtyp = self.pc.classify_file(args).split('/') + + if typ == 'image' and subtyp in ('jpeg', 'tiff'): + exif_info = self.pc.get_exif(args) + + dir_name, file_name=os.path.split(args) + photo_name, photo_ext=os.path.splitext(args) + + if 'JPEGThumbnail' in exif_info: + temp_file_fd, temp_file_name = utils.make_temp_file() + open(temp_file_name, 'wb').write(exif_info['JPEGThumbnail']) + os.system('display %s' % temp_file_name) + os.remove(temp_file_name) + + elif 'TIFFThumbnail' in exif_info: + temp_file_fd, temp_file_name = utils.make_temp_file() + open(temp_file_name, 'wb').write(exif_info['TIFFThumbnail']) + os.system('display %s' % temp_file_name) + os.remove(temp_file_name) + + else: + print "No thumbnail found." + + else: + print "Incorrect file type for thumbnail." + + elif len(matched_files) == 0: + print "File not found." + else: + print "Only one file at a time may be specified for thumbnail display." + + def do_thumb(self, args): + """Synonym for the thumbnail command.""" + self.do_thumbnail(args) + + def do_exif(self, args): + """Display EXIF info for file. + Usage: + \texif <filename>""" + args = args.strip().lower() + matched_files = self.pc.match_files(args) + + if len(matched_files) == 1: + typ, subtyp = self.pc.classify_file(args).split('/') + #print "'%s' '%s'" % (typ, subtyp) + + if typ == 'image' and subtyp in ('jpeg', 'tiff'): + exif_info = self.pc.get_exif(args) + + formatter = utils.TextFormatter( + ( + {'width': 40, 'margin' : 2}, + {'width': 40, 'margin' : 2}, + ) + ) + + print + print log.bold(formatter.compose(("Tag", "Value"))) + + ee = exif_info.keys() + ee.sort() + for e in ee: + if e not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename'): + #if e != 'EXIF MakerNote': + print formatter.compose((e, '%s' % exif_info[e])) + #else: + # print formatter.compose( ( e, ''.join( [ chr(x) for x in exif_info[e].values if chr(x) in string.printable ] ) ) ) + else: + print "Incorrect file type for thumbnail." + + elif len(matched_files) == 0: + print "File not found." + else: + print "Only one file at a time may be specified for thumbnail display." + + def do_info(self, args): + """Synonym for the exif command.""" + self.do_exif(args) + + def do_about(self, args): + utils.log_title(__title__, __version__) + + +def status_callback(src, trg, size): + if size == 1: + print + print log.bold("Copying %s..." % src) + else: + print "\nCopied %s to %s (%s)..." % (src, trg, utils.format_bytes(size)) + + + +mod = module.Module(__mod__, __title__, __version__, __doc__, None, + (GUI_MODE, INTERACTIVE_MODE, NON_INTERACTIVE_MODE), + (UI_TOOLKIT_QT3,)) + +mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS, + extra_options=[("Output directory:", "-o<dir> or --output=<dir> (Defaults to current directory)(Only used for non-GUI modes)", "option", False)], + see_also_list=['hp-toolbox']) + +opts, device_uri, printer_name, mode, ui_toolkit, loc = \ + mod.parseStdOpts('o', ['output=']) + +output_dir = os.getcwd() + +for o, a in opts: + if o in ('-o', '--output'): + output_dir = a + +if mode == GUI_MODE: + if not utils.canEnterGUIMode(): + mode = INTERACTIVE_MODE + +if mode == GUI_MODE: + if ui_toolkit == 'qt4': + log.error("%s does not support Qt4. Please use Qt3 or run in -i or -n modes.") + sys.exit(1) + +if mode in (INTERACTIVE_MODE, NON_INTERACTIVE_MODE): + try: + device_uri = mod.getDeviceUri(device_uri, printer_name, + filter={'pcard-type' : (operator.eq, 1)}) + + try: + pc = photocard.PhotoCard( None, device_uri, printer_name ) + except Error, e: + log.error("Unable to start photocard session: %s" % e.msg) + sys.exit(1) + + pc.set_callback(update_spinner) + + try: + pc.mount() + except Error: + log.error("Unable to mount photo card on device. Check that device is powered on and photo card is correctly inserted.") + pc.umount() + # TODO: + #pc.device.sendEvent(EVENT_PCARD_UNABLE_TO_MOUNT, typ='error') + sys.exit(1) + + log.info(log.bold("\nPhotocard on device %s mounted" % pc.device.device_uri)) + log.info(log.bold("DO NOT REMOVE PHOTO CARD UNTIL YOU EXIT THIS PROGRAM")) + + output_dir = os.path.realpath(os.path.normpath(os.path.expanduser(output_dir))) + + try: + os.chdir(output_dir) + except OSError: + print log.bold("ERROR: Output directory %s not found." % output_dir) + sys.exit(1) + + + if mode == INTERACTIVE_MODE: # INTERACTIVE_MODE + console = Console(pc) + try: + try: + console . cmdloop() + except KeyboardInterrupt: + log.error("Aborted.") + except Exception, e: + log.error("An error occured: %s" % e) + finally: + pc.umount() + + # TODO: + #pc.device.sendEvent(EVENT_END_PCARD_JOB) + + + else: # NON_INTERACTIVE_MODE + print "Output directory is %s" % os.getcwd() + try: + unload_list = pc.get_unload_list() + print + + if len(unload_list) > 0: + + max_len = 0 + for u in unload_list: + max_len = max(max_len, len(u[0])) + + formatter = utils.TextFormatter( + ( + {'width': max_len+2, 'margin' : 2}, + {'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT}, + {'width': 12, 'margin' : 2}, + ) + ) + + print + print log.bold(formatter.compose(("Name", "Size", "Type"))) + + total = 0 + for u in unload_list: + print formatter.compose(('%s' % u[0], utils.format_bytes(u[1]), '%s/%s' % (u[2], u[3]))) + total += u[1] + + + print log.bold("Found %d files to unload, %s\n" % (len(unload_list), utils.format_bytes(total, True))) + print log.bold("Unloading files...\n") + total, delta, was_cancelled = pc.unload(unload_list, status_callback, None, True) + print log.bold("\n%s unloaded in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/delta)) + + + finally: + pc.umount() + + except KeyboardInterrupt: + log.error("User exit") + + +else: # GUI_MODE (qt3 only) + try: + from qt import * + from ui import unloadform + except ImportError: + log.error("Unable to load Qt3 support. Is it installed?") + sys.exit(1) + + app = QApplication(sys.argv) + QObject.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()")) + + if loc is None: + loc = user_conf.get('ui', 'loc', 'system') + if loc.lower() == 'system': + loc = str(QTextCodec.locale()) + log.debug("Using system locale: %s" % loc) + + if loc.lower() != 'c': + e = 'utf8' + try: + l, x = loc.split('.') + loc = '.'.join([l, e]) + except ValueError: + l = loc + loc = '.'.join([loc, e]) + + log.debug("Trying to load .qm file for %s locale." % loc) + trans = QTranslator(None) + + qm_file = 'hplip_%s.qm' % l + log.debug("Name of .qm file: %s" % qm_file) + loaded = trans.load(qm_file, prop.localization_dir) + + if loaded: + app.installTranslator(trans) + else: + loc = 'c' + + if loc == 'c': + log.debug("Using default 'C' locale") + else: + log.debug("Using locale: %s" % loc) + QLocale.setDefault(QLocale(loc)) + prop.locale = loc + try: + locale.setlocale(locale.LC_ALL, locale.normalize(loc)) + except locale.Error: + pass + + try: + w = unloadform.UnloadForm(['cups'], device_uri, printer_name) + except Error: + log.error("Unable to connect to HPLIP I/O. Please (re)start HPLIP and try again.") + sys.exit(1) + + app.setMainWidget(w) + w.show() + + app.exec_loop() + +log.info("") +log.info("Done.") |