Replace 'tap' to 'spaces' to make gbs build succeed
[platform/upstream/hplip.git] / unload.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
5 #
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.
10 #
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.
15 #
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
19 #
20 # Author: Don Welch
21 #
22
23 __version__ = '3.3'
24 __mod__ = 'hp-unload'
25 __title__ = 'Photo Card Access Utility'
26 __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."
27
28 # Std Lib
29 import sys
30 import os
31 import os.path
32 import getopt
33 import re
34 import cmd
35 import time
36 import fnmatch
37 import string
38 import operator
39
40 try:
41     import readline
42 except ImportError:
43     pass
44
45 # Local
46 from base.g import *
47 from base import device, utils, tui, module
48 from prnt import cups
49 from pcard import photocard
50
51
52 # Console class (from ASPN Python Cookbook)
53 # Author:   James Thiele
54 # Date:     27 April 2004
55 # Version:  1.0
56 # Location: http://www.eskimo.com/~jet/python/examples/cmd/
57 # Copyright (c) 2004, James Thiele
58
59 class Console(cmd.Cmd):
60
61     def __init__(self, pc):
62         cmd.Cmd.__init__(self)
63         self.intro  = "Type 'help' for a list of commands. Type 'exit' to quit."
64         self.pc = pc
65         disk_info = self.pc.info()
66         pc.write_protect = disk_info[8]
67         if pc.write_protect:
68             log.warning("Photo card is write protected.")
69         self.prompt = log.bold("pcard: %s > " % self.pc.pwd())
70
71     # Command definitions
72     def do_hist(self, args):
73         """Print a list of commands that have been entered"""
74         print self._hist
75
76     def do_exit(self, args):
77         """Exits from the console"""
78         return -1
79
80     def do_quit(self, args):
81         """Exits from the console"""
82         return -1
83
84     # Command definitions to support Cmd object functionality
85     def do_EOF(self, args):
86         """Exit on system end of file character"""
87         return self.do_exit(args)
88
89     def do_help(self, args):
90         """Get help on commands
91            'help' or '?' with no arguments prints a list of commands for which help is available
92            'help <command>' or '? <command>' gives help on <command>
93         """
94         # The only reason to define this method is for the help text in the doc string
95         cmd.Cmd.do_help(self, args)
96
97     # Override methods in Cmd object
98     def preloop(self):
99         """Initialization before prompting user for commands.
100            Despite the claims in the Cmd documentaion, Cmd.preloop() is not a stub.
101         """
102         cmd.Cmd.preloop(self)   # sets up command completion
103         self._hist    = []      # No history yet
104         self._locals  = {}      # Initialize execution namespace for user
105         self._globals = {}
106
107     def postloop(self):
108         """Take care of any unfinished business.
109            Despite the claims in the Cmd documentaion, Cmd.postloop() is not a stub.
110         """
111         cmd.Cmd.postloop(self)   # Clean up command completion
112         print "Exiting..."
113
114     def precmd(self, line):
115         """ This method is called after the line has been input but before
116             it has been interpreted. If you want to modifdy the input line
117             before execution (for example, variable substitution) do it here.
118         """
119         self._hist += [line.strip()]
120         return line
121
122     def postcmd(self, stop, line):
123         """If you want to stop the console, return something that evaluates to true.
124            If you want to do some post command processing, do it here.
125         """
126         return stop
127
128     def emptyline(self):
129         """Do nothing on empty input line"""
130         pass
131
132     def default(self, line):
133         print log.bold("ERROR: Unrecognized command. Use 'help' to list commands.")
134
135     def do_ldir(self, args):
136         """ List local directory contents."""
137         os.system('ls -l')
138
139     def do_lls(self, args):
140         """ List local directory contents."""
141         os.system('ls -l')
142
143     def do_dir(self, args):
144         """Synonym for the ls command."""
145         return self.do_ls(args)
146
147     def do_ls(self, args):
148         """List photo card directory contents."""
149         args = args.strip().lower()
150         files = self.pc.ls(True, args)
151
152         total_size = 0
153         formatter = utils.TextFormatter(
154                 (
155                     {'width': 14, 'margin' : 2},
156                     {'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT},
157                     {'width': 30, 'margin' : 2},
158                 )
159             )
160
161         print
162         print log.bold(formatter.compose(("Name", "Size", "Type")))
163
164         num_files = 0
165         for d in self.pc.current_directories():
166             if d[0] in ('.', '..'):
167                 print formatter.compose((d[0], "", "directory"))
168             else:
169                 print formatter.compose((d[0] + "/", "", "directory"))
170
171         for f in self.pc.current_files():
172             print formatter.compose((f[0], utils.format_bytes(f[2]), self.pc.classify_file(f[0])))
173             num_files += 1
174             total_size += f[2]
175
176         print log.bold("% d files, %s" % (num_files, utils.format_bytes(total_size, True)))
177
178
179     def do_df(self, args):
180         """Display free space on photo card.
181         Options:
182         -h\tDisplay in human readable format
183         """
184         freespace = self.pc.df()
185
186         if args.strip().lower() == '-h':
187             fs = utils.format_bytes(freespace)
188         else:
189             fs = utils.commafy(freespace)
190
191         print "Freespace = %s Bytes" % fs
192
193
194     def do_cp(self, args, remove_after_copy=False):
195         """Copy files from photo card to current local directory.
196         Usage:
197         \tcp FILENAME(S)|GLOB PATTERN(S)
198         Example:
199         \tCopy all JPEG and GIF files and a file named thumbs.db from photo card to local directory:
200         \tcp *.jpg *.gif thumbs.db
201         """
202         args = args.strip().lower()
203
204         matched_files = self.pc.match_files(args)
205
206         if len(matched_files) == 0:
207             print "ERROR: File(s) not found."
208         else:
209             total, delta = self.pc.cp_multiple(matched_files, remove_after_copy, self.cp_status_callback, self.rm_status_callback)
210
211             print log.bold("\n%s transfered in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/(delta)))
212
213     def do_unload(self, args):
214         """Unload all image files from photocard to current local directory.
215         Note:
216         \tSubdirectories on photo card are not preserved
217         Options:
218         -x\tDon't remove files after copy
219         -p\tPrint unload list but do not copy or remove files"""
220         args = args.lower().strip().split()
221         dont_remove = False
222         if '-x' in args:
223             if self.pc.write_protect:
224                 log.error("Photo card is write protected. -x not allowed.")
225                 return
226             else:
227                 dont_remove = True
228
229
230         unload_list = self.pc.get_unload_list()
231         print
232
233         if len(unload_list) > 0:
234             if '-p' in args:
235
236                 max_len = 0
237                 for u in unload_list:
238                     max_len = max(max_len, len(u[0]))
239
240                 formatter = utils.TextFormatter(
241                         (
242                             {'width': max_len+2, 'margin' : 2},
243                             {'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT},
244                             {'width': 12, 'margin' : 2},
245                         )
246                     )
247
248                 print
249                 print log.bold(formatter.compose(("Name", "Size", "Type")))
250
251                 total = 0
252                 for u in unload_list:
253                      print formatter.compose(('%s' % u[0], utils.format_bytes(u[1]), '%s/%s' % (u[2], u[3])))
254                      total += u[1]
255
256
257                 print log.bold("Found %d files to unload, %s" % (len(unload_list), utils.format_bytes(total, True)))
258             else:
259                 print log.bold("Unloading %d files..." % len(unload_list))
260                 total, delta, was_cancelled = self.pc.unload(unload_list, self.cp_status_callback, self.rm_status_callback, dont_remove)
261                 print log.bold("\n%s unloaded in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/delta))
262
263         else:
264             print "No image, audio, or video files found."
265
266
267     def cp_status_callback(self, src, trg, size):
268         if size == 1:
269             print
270             print log.bold("Copying %s..." % src)
271         else:
272             print "\nCopied %s to %s (%s)..." % (src, trg, utils.format_bytes(size))
273
274     def rm_status_callback(self, src):
275         print "Removing %s..." % src
276
277
278
279     def do_rm(self, args):
280         """Remove files from photo card."""
281         if self.pc.write_protect:
282             log.error("Photo card is write protected. rm not allowed.")
283             return
284
285         args = args.strip().lower()
286
287         matched_files = self.pc.match_files(args)
288
289         if len(matched_files) == 0:
290             print "ERROR: File(s) not found."
291         else:
292             for f in matched_files:
293                 self.pc.rm(f, False)
294
295         self.pc.ls()
296
297     def do_mv(self, args):
298         """Move files off photocard"""
299         if self.pc.write_protect:
300             log.error("Photo card is write protected. mv not allowed.")
301             return
302         self.do_cp(args, True)
303
304     def do_lpwd(self, args):
305         """Print name of local current/working directory."""
306         print os.getcwd()
307
308     def do_lcd(self, args):
309         """Change current local working directory."""
310         try:
311             os.chdir(args.strip())
312         except OSError:
313             print log.bold("ERROR: Directory not found.")
314         print os.getcwd()
315
316     def do_pwd(self, args):
317         """Print name of photo card current/working directory
318         Usage:
319         \t>pwd"""
320         print self.pc.pwd()
321
322     def do_cd(self, args):
323         """Change current working directory on photo card.
324         Note:
325         \tYou may only specify one directory level at a time.
326         Usage:
327         \tcd <directory>
328         """
329         args = args.lower().strip()
330
331         if args == '..':
332             if self.pc.pwd() != '/':
333                 self.pc.cdup()
334
335         elif args == '.':
336             pass
337
338         elif args == '/':
339             self.pc.cd('/')
340
341         else:
342             matched_dirs = self.pc.match_dirs(args)
343
344             if len(matched_dirs) == 0:
345                 print "Directory not found"
346
347             elif len(matched_dirs) > 1:
348                 print "Pattern matches more than one directory"
349
350             else:
351                 self.pc.cd(matched_dirs[0])
352
353         self.prompt = log.bold("pcard: %s > " % self.pc.pwd())
354
355     def do_cdup(self, args):
356         """Change to parent directory."""
357         self.do_cd('..')
358
359     #def complete_cd( self, text, line, begidx, endidx ):
360     #    print text, line, begidx, endidx
361     #    #return "XXX"
362
363     def do_cache(self, args):
364         """Display current cache entries, or turn cache on/off.
365         Usage:
366         \tDisplay: cache
367         \tTurn on: cache on
368         \tTurn off: cache off
369         """
370         args = args.strip().lower()
371
372         if args == 'on':
373             self.pc.cache_control(True)
374
375         elif args == 'off':
376             self.pc.cache_control(False)
377
378         else:
379             if self.pc.cache_state():
380                 cache_info = self.pc.cache_info()
381
382                 t = cache_info.keys()
383                 t.sort()
384                 print
385                 for s in t:
386                     print "sector %d (%d hits)" % (s, cache_info[s])
387
388                 print log.bold("Total cache usage: %s (%s maximum)" % (utils.format_bytes(len(t)*512), utils.format_bytes(photocard.MAX_CACHE * 512)))
389                 print log.bold("Total cache sectors: %s of %s" % (utils.commafy(len(t)), utils.commafy(photocard.MAX_CACHE)))
390             else:
391                 print "Cache is off."
392
393     def do_sector(self, args):
394         """Display sector data.
395         Usage:
396         \tsector <sector num>
397         """
398         args = args.strip().lower()
399         cached = False
400         try:
401             sector = int(args)
402         except ValueError:
403             print "Sector must be specified as a number"
404             return
405
406         if self.pc.cache_check(sector) > 0:
407             print "Cached sector"
408
409         print repr(self.pc.sector(sector))
410
411
412     def do_tree(self, args):
413         """Display photo card directory tree."""
414         tree = self.pc.tree()
415         print
416         self.print_tree(tree)
417
418     def print_tree(self, tree, level=0):
419         for d in tree:
420             if type(tree[d]) == type({}):
421                 print ''.join([' '*level*4, d, '/'])
422                 self.print_tree(tree[d], level+1)
423
424
425     def do_reset(self, args):
426         """Reset the cache."""
427         self.pc.cache_reset()
428
429
430     def do_card(self, args):
431         """Print info about photocard."""
432         print
433         print "Device URI = %s" % self.pc.device.device_uri
434         print "Model = %s" % self.pc.device.model_ui
435         print "Working dir = %s" % self.pc.pwd()
436         disk_info = self.pc.info()
437         print "OEM ID = %s" % disk_info[0]
438         print "Bytes/sector = %d" % disk_info[1]
439         print "Sectors/cluster = %d" % disk_info[2]
440         print "Reserved sectors = %d" % disk_info[3]
441         print "Root entries = %d" % disk_info[4]
442         print "Sectors/FAT = %d" % disk_info[5]
443         print "Volume label = %s" % disk_info[6]
444         print "System ID = %s" % disk_info[7]
445         print "Write protected = %d" % disk_info[8]
446         print "Cached sectors = %s" % utils.commafy(len(self.pc.cache_info()))
447
448
449     def do_display(self, args):
450         """Display an image with ImageMagick.
451         Usage:
452         \tdisplay <filename>"""
453         args = args.strip().lower()
454         matched_files = self.pc.match_files(args)
455
456         if len(matched_files) == 1:
457
458             typ = self.pc.classify_file(args).split('/')[0]
459
460             if typ == 'image':
461                 fd, temp_name = utils.make_temp_file()
462                 self.pc.cp(args, temp_name)
463                 os.system('display %s' % temp_name)
464                 os.remove(temp_name)
465
466             else:
467                 print "File is not an image."
468
469         elif len(matched_files) == 0:
470             print "File not found."
471
472         else:
473             print "Only one file at a time may be specified for display."
474
475     def do_show(self, args):
476         """Synonym for the display command."""
477         self.do_display(args)
478
479     def do_thumbnail(self, args):
480         """Display an embedded thumbnail image with ImageMagick.
481         Note:
482         \tOnly works with JPEG/JFIF images with embedded JPEG/TIFF thumbnails
483         Usage:
484         \tthumbnail <filename>"""
485         args = args.strip().lower()
486         matched_files = self.pc.match_files(args)
487
488         if len(matched_files) == 1:
489             typ, subtyp = self.pc.classify_file(args).split('/')
490
491             if typ == 'image' and subtyp in ('jpeg', 'tiff'):
492                 exif_info = self.pc.get_exif(args)
493
494                 dir_name, file_name=os.path.split(args)
495                 photo_name, photo_ext=os.path.splitext(args)
496
497                 if 'JPEGThumbnail' in exif_info:
498                     temp_file_fd, temp_file_name = utils.make_temp_file()
499                     open(temp_file_name, 'wb').write(exif_info['JPEGThumbnail'])
500                     os.system('display %s' % temp_file_name)
501                     os.remove(temp_file_name)
502
503                 elif 'TIFFThumbnail' in exif_info:
504                     temp_file_fd, temp_file_name = utils.make_temp_file()
505                     open(temp_file_name, 'wb').write(exif_info['TIFFThumbnail'])
506                     os.system('display %s' % temp_file_name)
507                     os.remove(temp_file_name)
508
509                 else:
510                     print "No thumbnail found."
511
512             else:
513                 print "Incorrect file type for thumbnail."
514
515         elif len(matched_files) == 0:
516             print "File not found."
517         else:
518             print "Only one file at a time may be specified for thumbnail display."
519
520     def do_thumb(self, args):
521         """Synonym for the thumbnail command."""
522         self.do_thumbnail(args)
523
524     def do_exif(self, args):
525         """Display EXIF info for file.
526         Usage:
527         \texif <filename>"""
528         args = args.strip().lower()
529         matched_files = self.pc.match_files(args)
530
531         if len(matched_files) == 1:
532             typ, subtyp = self.pc.classify_file(args).split('/')
533             #print "'%s' '%s'" % (typ, subtyp)
534
535             if typ == 'image' and subtyp in ('jpeg', 'tiff'):
536                 exif_info = self.pc.get_exif(args)
537
538                 formatter = utils.TextFormatter(
539                         (
540                             {'width': 40, 'margin' : 2},
541                             {'width': 40, 'margin' : 2},
542                         )
543                     )
544
545                 print
546                 print log.bold(formatter.compose(("Tag", "Value")))
547
548                 ee = exif_info.keys()
549                 ee.sort()
550                 for e in ee:
551                     if e not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename'):
552                         #if e != 'EXIF MakerNote':
553                         print formatter.compose((e, '%s' % exif_info[e]))
554                         #else:
555                         #    print formatter.compose( ( e, ''.join( [ chr(x) for x in exif_info[e].values if chr(x) in string.printable ] ) ) )
556             else:
557                 print "Incorrect file type for thumbnail."
558
559         elif len(matched_files) == 0:
560             print "File not found."
561         else:
562             print "Only one file at a time may be specified for thumbnail display."
563
564     def do_info(self, args):
565         """Synonym for the exif command."""
566         self.do_exif(args)
567
568     def do_about(self, args):
569         utils.log_title(__title__, __version__)
570
571
572 def status_callback(src, trg, size):
573     if size == 1:
574         print
575         print log.bold("Copying %s..." % src)
576     else:
577         print "\nCopied %s to %s (%s)..." % (src, trg, utils.format_bytes(size))
578
579
580
581 mod = module.Module(__mod__, __title__, __version__, __doc__, None,
582                     (GUI_MODE, INTERACTIVE_MODE, NON_INTERACTIVE_MODE),
583                     (UI_TOOLKIT_QT3,))
584
585 mod.setUsage(module.USAGE_FLAG_DEVICE_ARGS,
586     extra_options=[("Output directory:", "-o<dir> or --output=<dir> (Defaults to current directory)(Only used for non-GUI modes)", "option", False)],
587     see_also_list=['hp-toolbox'])
588
589 opts, device_uri, printer_name, mode, ui_toolkit, loc = \
590     mod.parseStdOpts('o', ['output='])
591
592 output_dir = os.getcwd()
593
594 for o, a in opts:
595     if o in ('-o', '--output'):
596         output_dir = a
597
598 if mode == GUI_MODE:
599     if not utils.canEnterGUIMode():
600         mode = INTERACTIVE_MODE
601
602 if mode == GUI_MODE:
603     if ui_toolkit == 'qt4':
604         log.error("%s does not support Qt4. Please use Qt3 or run in -i or -n modes.")
605         sys.exit(1)
606
607 if mode in (INTERACTIVE_MODE, NON_INTERACTIVE_MODE):
608     try:
609         device_uri = mod.getDeviceUri(device_uri, printer_name,
610             filter={'pcard-type' : (operator.eq, 1)})
611
612         try:
613             pc = photocard.PhotoCard( None, device_uri, printer_name )
614         except Error, e:
615             log.error("Unable to start photocard session: %s" % e.msg)
616             sys.exit(1)
617
618         pc.set_callback(update_spinner)
619
620         try:
621             pc.mount()
622         except Error:
623             log.error("Unable to mount photo card on device. Check that device is powered on and photo card is correctly inserted.")
624             pc.umount()
625             # TODO:
626             #pc.device.sendEvent(EVENT_PCARD_UNABLE_TO_MOUNT, typ='error')
627             sys.exit(1)
628
629         log.info(log.bold("\nPhotocard on device %s mounted" % pc.device.device_uri))
630         log.info(log.bold("DO NOT REMOVE PHOTO CARD UNTIL YOU EXIT THIS PROGRAM"))
631
632         output_dir = os.path.realpath(os.path.normpath(os.path.expanduser(output_dir)))
633
634         try:
635             os.chdir(output_dir)
636         except OSError:
637             print log.bold("ERROR: Output directory %s not found." % output_dir)
638             sys.exit(1)
639
640
641         if mode == INTERACTIVE_MODE: # INTERACTIVE_MODE
642             console = Console(pc)
643             try:
644                 try:
645                     console . cmdloop()
646                 except KeyboardInterrupt:
647                     log.error("Aborted.")
648                 except Exception, e:
649                     log.error("An error occured: %s" % e)
650             finally:
651                 pc.umount()
652
653             # TODO:
654             #pc.device.sendEvent(EVENT_END_PCARD_JOB)
655
656
657         else: # NON_INTERACTIVE_MODE
658             print "Output directory is %s" % os.getcwd()
659             try:
660                 unload_list = pc.get_unload_list()
661                 print
662
663                 if len(unload_list) > 0:
664
665                     max_len = 0
666                     for u in unload_list:
667                         max_len = max(max_len, len(u[0]))
668
669                     formatter = utils.TextFormatter(
670                             (
671                                 {'width': max_len+2, 'margin' : 2},
672                                 {'width': 12, 'margin' : 2, 'alignment' : utils.TextFormatter.RIGHT},
673                                 {'width': 12, 'margin' : 2},
674                             )
675                         )
676
677                     print
678                     print log.bold(formatter.compose(("Name", "Size", "Type")))
679
680                     total = 0
681                     for u in unload_list:
682                          print formatter.compose(('%s' % u[0], utils.format_bytes(u[1]), '%s/%s' % (u[2], u[3])))
683                          total += u[1]
684
685
686                     print log.bold("Found %d files to unload, %s\n" % (len(unload_list), utils.format_bytes(total, True)))
687                     print log.bold("Unloading files...\n")
688                     total, delta, was_cancelled = pc.unload(unload_list, status_callback, None, True)
689                     print log.bold("\n%s unloaded in %d sec (%d KB/sec)" % (utils.format_bytes(total), delta, (total/1024)/delta))
690
691
692             finally:
693                 pc.umount()
694
695     except KeyboardInterrupt:
696         log.error("User exit")
697
698
699 else: # GUI_MODE (qt3 only)
700     try:
701         from qt import *
702         from ui import unloadform
703     except ImportError:
704         log.error("Unable to load Qt3 support. Is it installed?")
705         sys.exit(1)
706
707     app = QApplication(sys.argv)
708     QObject.connect(app, SIGNAL("lastWindowClosed()"), app, SLOT("quit()"))
709
710     if loc is None:
711         loc = user_conf.get('ui', 'loc', 'system')
712         if loc.lower() == 'system':
713             loc = str(QTextCodec.locale())
714             log.debug("Using system locale: %s" % loc)
715
716     if loc.lower() != 'c':
717         e = 'utf8'
718         try:
719             l, x = loc.split('.')
720             loc = '.'.join([l, e])
721         except ValueError:
722             l = loc
723             loc = '.'.join([loc, e])
724
725         log.debug("Trying to load .qm file for %s locale." % loc)
726         trans = QTranslator(None)
727
728         qm_file = 'hplip_%s.qm' % l
729         log.debug("Name of .qm file: %s" % qm_file)
730         loaded = trans.load(qm_file, prop.localization_dir)
731
732         if loaded:
733             app.installTranslator(trans)
734         else:
735             loc = 'c'
736
737     if loc == 'c':
738         log.debug("Using default 'C' locale")
739     else:
740         log.debug("Using locale: %s" % loc)
741         QLocale.setDefault(QLocale(loc))
742         prop.locale = loc
743         try:
744             locale.setlocale(locale.LC_ALL, locale.normalize(loc))
745         except locale.Error:
746             pass
747
748     try:
749         w = unloadform.UnloadForm(['cups'], device_uri, printer_name)
750     except Error:
751         log.error("Unable to connect to HPLIP I/O. Please (re)start HPLIP and try again.")
752         sys.exit(1)
753
754     app.setMainWidget(w)
755     w.show()
756
757     app.exec_loop()
758
759 log.info("")
760 log.info("Done.")