summaryrefslogtreecommitdiff
path: root/unload.py
diff options
context:
space:
mode:
Diffstat (limited to 'unload.py')
-rwxr-xr-xunload.py760
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.")