Replace 'tap' to 'spaces' to make gbs build succeed
[platform/upstream/hplip.git] / pcard / photocard.py
1 # -*- coding: utf-8 -*-
2 #
3 # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19
20 # Std Lib
21 import struct
22 import time
23 import fnmatch
24 import mimetypes
25 import array
26
27 # Local
28 from base.g import *
29 from base.codes import *
30 from base import device, utils, exif
31
32 try:
33     import pcardext
34 except ImportError:
35     if not os.getenv("HPLIP_BUILD"):
36         log.error("PCARDEXT could not be loaded. Please check HPLIP installation.")
37         sys.exit(1)
38
39 # Photocard command codes
40 ACK = 0x0100
41 NAK = 0x0101
42 READ_CMD = 0x0010
43 WRITE_CMD = 0x0020
44
45 SECTOR_SIZE = 512 # don't change this (TODO: impl. in pcardext)
46
47 # Photocard sector cache
48 MAX_CACHE = 512 # units = no. sectors 
49
50 # PhotoCardFile byte cache
51 # Used for thumbnails
52 INITIAL_PCARDFILE_BUFFER = 20*SECTOR_SIZE 
53 INCREMENTAL_PCARDFILE_BUFFER = 2*SECTOR_SIZE 
54
55 class PhotoCardFile:    
56     # File-like interface
57
58     def __init__(self, pc, name=None):
59         self.pos = 0
60         self.closed = True
61         self.file_size = 0
62         self.pc = pc
63         self.buffer = array.array('c') 
64
65         if name is not None:
66             self.open(name)
67
68         self.buffer_size = INITIAL_PCARDFILE_BUFFER
69         self.buffer.fromstring(pcardext.read(self.name, 0, self.buffer_size))
70
71
72     def open(self, name):
73         self.closed = False
74         self.name = name
75
76     def seek(self, offset, whence=0):
77         if whence == 0:
78             self.pos = offset
79         elif whence == 1:
80             self.pos += offset
81         elif whence == 2:
82             self.pos = self.file_size - offset
83         else:
84             return
85
86
87     def tell(self):
88         return self.pos
89
90
91     def read(self, size): 
92         if size > 0:
93             if self.pos + size < self.buffer_size:
94                 data = self.buffer[self.pos : self.pos + size].tostring()
95                 self.pos += size
96                 return data
97             else:
98                 # Read some more in from the card to satisfy the request
99                 while self.pos + size >= self.buffer_size:
100                     self.buffer.fromstring(pcardext.read(self.name, self.buffer_size, INCREMENTAL_PCARDFILE_BUFFER))
101                     self.buffer_size += INCREMENTAL_PCARDFILE_BUFFER
102                 return self.read(size)
103
104
105     def close(self):
106         self.closed = True
107         self.pos = 0
108
109
110 class PhotoCard:
111
112     def __init__(self, dev_obj=None, device_uri=None, printer_name=None):
113
114         if dev_obj is None:
115             self.device = device.Device(device_uri, printer_name)
116             self.device.open()
117             self.close_device = True
118         else:
119             self.device = dev_obj
120             self.close_device = False
121
122         self.dir_stack = utils.Stack()
123         self.current_dir = []
124         self.device_uri = self.device.device_uri
125         self.pcard_mounted = False
126         self.saved_pwd = []
127         self.sector_buffer = {}
128         self.sector_buffer_counts = {}
129         self.cache_flag = True
130         self.write_protect = False
131
132         self.callback = None
133
134         self.channel_opened = False
135
136
137     def START_OPERATION(self, name=''):
138         pass
139
140     def END_OPERATION(self, name='', flag=True):
141         if self.channel_opened and flag:
142             self.close_channel()
143
144     def set_callback(self, callback):
145         self.callback = callback
146
147     def _read(self, sector, nsector): 
148         log.debug("read pcard sector: sector=%d count=%d" % (sector, nsector))
149
150         if self.cache_flag:
151             for s in range(sector, sector+nsector):
152                 if s not in self.sector_buffer:
153                     break
154             else:
155                 buffer = ''
156                 for s in range(sector, sector+nsector):
157                     buffer = ''.join([buffer, self.sector_buffer[s]])
158                     log.debug("Cached sector read sector=%d" % s)
159                     count = self.sector_buffer_counts[s]
160                     self.sector_buffer_counts[s] = count+1
161
162                     if self.callback is not None:
163                         self.callback()
164
165                 #log.log_data(buffer)
166                 return buffer
167
168         if self.callback is not None:
169             self.callback()
170
171         if not self.channel_opened:
172             self.open_channel()
173
174         log.debug("Normal sector read sector=%d count=%d" % (sector, nsector))
175         sectors_to_read = range(sector, sector+nsector)
176         request = struct.pack('!HH' + 'I'*nsector, READ_CMD, nsector, *sectors_to_read)
177         #log.log_data(request)
178         
179         if self.callback is not None:
180             self.callback()
181
182         # send out request
183         bytes_written = self.device.writePCard(request)
184         log.debug("%d bytes written" % bytes_written)
185
186         # read return code
187         data = self.device.readPCard(2)
188         #log.log_data(data)
189         code = struct.unpack('!H', data)[0]
190
191         log.debug("Return code: %x" % code)
192
193         if code == 0x0110:
194
195             # read sector count and version
196             data = self.device.readPCard(6)
197             nsector_read, ver = struct.unpack('!IH', data)
198
199             log.debug("code=0x%x, nsector=%d, ver=%d" % (code, nsector_read, ver))
200
201             buffer, data_read, total_to_read = '', 0, nsector * SECTOR_SIZE
202
203             while (data_read < total_to_read):
204                 data = self.device.readPCard(total_to_read)
205
206                 data_read += len(data)
207                 buffer = ''.join([buffer, data])
208
209                 if self.callback is not None:
210                     self.callback()            
211
212             if self.cache_flag:
213                 i = 0
214
215                 for s in range(sector, sector + nsector_read):
216                     self.sector_buffer[s] = buffer[i : i+SECTOR_SIZE]
217                     #log.debug("Sector %d data=\n%s" % (s, repr(self.sector_buffer[s])))
218                     count = self.sector_buffer_counts.get(s, 0)
219                     self.sector_buffer_counts[s] = count+1
220                     i += SECTOR_SIZE
221
222                     if self.callback is not None:
223                         self.callback()            
224
225                 self._check_cache(nsector)
226
227             #log.log_data(buffer)
228             return buffer
229         else:
230             log.error("Error code: %d" % code)
231             return ''
232
233     def _write(self, sector, nsector, buffer):
234
235         #log.debug("write pcard sector: sector=%d count=%d len=%d data=\n%s" % (sector, nsector, len(buffer), repr(buffer)))
236         log.debug("write pcard sector: sector=%d count=%d len=%d" % (sector, nsector, len(buffer)))
237         
238         if not self.channel_opened:
239             self.open_channel()
240
241
242         sectors_to_write = range(sector, sector+nsector)
243         request = struct.pack('!HHH' + 'I'*nsector, WRITE_CMD, nsector, 0, *sectors_to_write)
244         request = ''.join([request, buffer])
245
246         if self.callback is not None:
247             self.callback()
248
249         self.device.writePCard(request)
250         data = self.device.readPCard(2)
251
252         if self.callback is not None:
253             self.callback()
254
255         code = struct.unpack('!H', data)[0]
256
257         if code != NAK:
258             if self.cache_flag:
259                 i = 0
260                 for s in range(sector, sector+nsector):
261                     log.debug("Caching sector %d" % sector)
262                     self.sector_buffer[s] = buffer[i:i+SECTOR_SIZE]
263                     self.sector_buffer_counts[s] = 1
264                     i += SECTOR_SIZE
265
266                 if self.callback is not None:
267                     self.callback()    
268
269                 self._check_cache(nsector)
270
271             return 0
272
273         else:    
274             if self.cache_flag:
275                 for s in range(sector, sector+nsector):
276                     try:
277                         del self.sector_buffer[s]
278                         del self.sector_buffer_counts[s]
279                     except KeyError:
280                         pass
281
282             log.error("Photo card write failed (Card may be write protected)")
283             self.close_channel()
284             return 1
285
286
287     def _check_cache(self, nsector):
288         if len(self.sector_buffer) > MAX_CACHE:
289             # simple minded: scan for first nsector sectors that has count of 1 and throw it away
290             t, n = self.sector_buffer.keys()[:], 0
291             for s in t:
292                 if self.sector_buffer_counts[s] == 1:
293                     del self.sector_buffer[s]
294                     del self.sector_buffer_counts[s]
295                     n += 1
296                     if n >= nsector:
297                         break
298                     if self.callback is not None:
299                         self.callback()
300
301
302
303     def cache_info(self):
304         return self.sector_buffer_counts
305
306     def cache_check(self, sector):
307         return self.sector_buffer_counts.get(sector, 0)
308
309     def cache_control(self, control):
310         self.cache_flag = control
311
312         if not self.cache_flag:
313             self.cache_reset()
314
315     def cache_state(self):
316         return self.cache_flag
317
318     def cache_reset(self):
319         self.sector_buffer.clear()
320         self.sector_buffer_counts.clear()
321
322     def df(self):
323         df = 0
324         self.START_OPERATION('df')
325         try:
326             df = pcardext.df()
327         finally:
328             self.END_OPERATION('df')
329             return df
330
331     def ls(self, force_read=True, glob_list='*', openclose=True):
332         if not glob_list:
333             glob_list = '*'
334         if force_read:
335             self.START_OPERATION('ls')
336             try:
337                 self.current_dir = pcardext.ls()
338             finally:
339                 self.END_OPERATION('ls', openclose)
340
341         self.current_dir = [(n.lower(),a,s) for (n,a,s) in self.current_dir]
342
343         if glob_list == '*':
344             return self.current_dir
345
346         return [fnmatch.filter(self.current_dir, x) for x in glob_list.strip().lower().split()][0]
347
348     def size(self, name):
349         for f in self.current_dir:
350             if f == name:
351                 return self.current_dir[f][2]
352         return 0
353
354     def current_files(self):
355         return [x for x in self.current_dir if x[1] != 'd']
356
357     def current_directories(self):
358         return [x for x in self.current_dir if x[1] == 'd']
359
360     def match_files(self, glob_list):
361         if len(glob_list) > 0:
362             current_files = [x[0] for x in self.current_files()]
363             return [fnmatch.filter(current_files, x) for x in glob_list.strip().lower().split()][0]
364         return []
365
366     def match_dirs(self, glob_list):
367         if len(glob_list) > 0:
368             current_dirs = [x[0] for x in self.current_directories()]
369             return [fnmatch.filter(current_dirs, x) for x in glob_list.strip().lower().split()][0]
370         return []
371
372     def classify_file(self, filename):
373         t = mimetypes.guess_type(filename)[0]
374         if t is None:
375             return 'unknown/unknown'
376         return t
377
378     # copy a single file fom pwd to lpwd
379     def cp(self, name, local_file, openclose=True):
380         self.START_OPERATION('cp')
381         total = 0
382         try:
383             f = file(local_file, 'w');
384             total = pcardext.cp(name, f.fileno())
385             f.close()
386         finally:
387             self.END_OPERATION('cp', openclose)
388             return total
389
390     # cp multiple files in the current working directory
391     def cp_multiple(self, filelist, remove_after_copy, cp_status_callback=None, rm_status_callback=None):
392         delta, total = 0, 0
393         self.START_OPERATION('cp_multiple')
394         t1 = time.time()
395         try:
396             for f in filelist:
397
398                 size = self.cp(f, f, False)
399
400                 if cp_status_callback:
401                     cp_status_callback(os.path.join(self.pwd(), f), os.path.join(os.getcwd(), f), size)
402
403                 total += size
404
405
406                 if remove_after_copy:
407                     pcardext.rm(f)
408
409             t2 = time.time()
410             delta = t2-t1
411         finally:
412             if remove_after_copy:
413                 self.ls(True, '*', False)
414             self.END_OPERATION('cp_multiple')
415             return (total, delta)
416
417     # cp multiple files with paths
418     def cp_list(self, filelist, remove_after_copy, cp_status_callback=None, rm_status_callback=None):
419         self.save_wd()
420         delta, total = 0, 0
421         self.START_OPERATION('cp_list')
422         t1 = time.time()
423         try:
424             for f in filelist:
425
426                 path_list = f.split('/')[:-1]
427                 filename = f.split('/')[-1]
428
429                 for p in path_list:
430                     self.cd(p, False)
431
432                 size = self.cp(filename, filename, False)
433
434                 if cp_status_callback is not None:
435                     cp_status_callback(f, os.path.join(os.getcwd(), filename), size)
436
437                 total += size    
438
439                 if remove_after_copy:
440                     pcardext.rm(filename)
441
442                     if rm_status_callback is not None:
443                         rm_status_callback(f)
444
445                 self.cd('/', False)
446
447             t2 = time.time()
448             delta = t2-t1
449         finally:
450             #if remove_after_copy:
451             #    self.ls( True, '*', False )
452             self.restore_wd()
453             self.END_OPERATION('cp_list')
454             return (total, delta)
455
456
457
458     def cp_fd(self, name, fd):
459         total = 0
460         self.START_OPERATION('cp_fd')
461         try:
462             total = pcardext.cp(name, fd)
463         finally:
464             self.END_OPERATION('cp_fd')
465             return total
466
467
468     def unload(self, unload_list, cp_status_callback=None, rm_status_callback=None, dont_remove=False):
469         was_cancelled = False
470         self.save_wd()
471         self.START_OPERATION('unload')
472         total = 0
473         t1 = time.time()
474
475         for f in unload_list:
476             if not was_cancelled:
477                 name, size, typ, subtyp = f
478
479                 p = name.split('/')
480                 dirs = p[:-1]
481                 filename = p[-1]
482                 self.cd('/', False)
483
484                 if cp_status_callback is not None:
485                     if cp_status_callback(os.path.join(self.pwd(), filename), 
486                                             os.path.join(os.getcwd(), filename), 0):
487                         was_cancelled = True
488                         break
489
490                 if len(dirs) > 0:
491                     for d in dirs:
492                         self.cd(d, False)
493
494                 if os.path.exists(os.path.join(os.getcwd(), filename)):
495                     i = 2
496
497                     while True:
498                         if not os.path.exists(os.path.join(os.getcwd(), filename + " (%d)" % i)):
499                             break
500
501                         i += 1
502
503                     total += self.cp(filename, filename + " (%d)" % i, False)
504
505                 else:    
506                     total += self.cp(filename, filename, False)
507
508                 if cp_status_callback is not None:
509                     if cp_status_callback(os.path.join(self.pwd(), filename), 
510                                             os.path.join(os.getcwd(), filename), size):
511                         was_cancelled = True
512                         break
513
514                 if not dont_remove:
515                     if rm_status_callback is not None:
516                         rm_status_callback(os.path.join(self.pwd(), filename))
517
518                     self.rm(filename, False, False)
519
520
521         t2 = time.time()
522         self.restore_wd(False)
523         self.ls(True, '*', False)
524         self.END_OPERATION('unload')
525
526         return total, (t2-t1), was_cancelled
527
528
529     def get_unload_list(self):
530         tree = self.tree()
531         return self.__build_unload_list(tree)
532
533
534     def __build_unload_list(self, tree, path=None, out=None): 
535         if path is None:
536             out = []
537             path = utils.Stack()
538         for d in tree:
539             if type(tree[d]) == type({}):
540                 path.push(d)
541                 self.__build_unload_list(tree[d], path, out) 
542                 path.pop()
543             else:
544                 typ, subtyp = self.classify_file(d).split('/')
545                 if typ in ['image', 'audio', 'video']:
546                     p = path.as_list()
547                     name = '/'.join(['/'.join(p), d])
548                     out.append((name, tree[d], typ, subtyp)) 
549
550         return out
551
552
553     def info(self):
554         return pcardext.info()
555
556
557     def cd(self, dirs, openclose=True):
558         self.START_OPERATION('cd')
559         try:
560             stat = pcardext.cd(dirs)
561             if stat:
562                 if dirs == '/':
563                     self.dir_stack.clear()
564
565                 else:
566                     dirs = dirs.split('/')
567                     for d in dirs:
568                         self.dir_stack.push(d)
569
570                 self.ls(True, '*', False)
571
572         finally:
573             self.END_OPERATION('cd', openclose)
574
575
576     def cdup(self, openclose=True):
577         if len(self.dir_stack.as_list()) == 0:
578             return self.cd('/', openclose)
579
580         self.dir_stack.pop()
581         self.START_OPERATION('cdup')
582         try:
583             pcardext.cd('/')
584
585             for d in self.dir_stack.as_list():
586                 pcardext.cd(d)
587
588             self.ls(True, '*', False)
589         finally:
590             self.END_OPERATION('cdup', openclose)
591
592     def rm(self, name, refresh_dir=True, openclose=True):
593         self.START_OPERATION()
594         try:
595             r = pcardext.rm(name)
596
597             if refresh_dir:
598                 self.ls(True, '*', False)
599         finally:
600             self.END_OPERATION(openclose)
601             return r
602
603     def mount(self):
604         log.debug("Mounting photocard...")
605         self.START_OPERATION('mount')
606         try:
607             stat = pcardext.mount(self._read, self._write)
608             disk_info = pcardext.info()
609             self.write_protect = disk_info[8]
610             log.debug("stat=%d" % stat)
611
612             if stat == 0:
613                 if self.write_protect:
614                     # if write_protect is True,
615                     # card write NAK'd and channel was 
616                     # closed. We have to reopen here.
617                     self.open_channel()
618
619                 self.pcard_mounted = True
620                 pcardext.cd('/')
621
622                 self.ls(True, '*', False)
623
624             else:
625                 self.pcard_mounted = False
626                 raise Error(ERROR_DEVICE_DOES_NOT_SUPPORT_OPERATION)
627         finally:
628             if self.pcard_mounted:
629                 self.END_OPERATION('mount')
630
631
632
633     def pwd(self):
634         return '/' + '/'.join(self.dir_stack.as_list())
635
636
637     def save_wd(self):
638         self.saved_pwd = self.dir_stack.as_list()[:]
639
640     def restore_wd(self, openclose=True):
641         self.cd('/', openclose)
642         for d in self.saved_pwd:
643             self.cd(d, openclose)
644
645
646     def tree(self):
647         self.START_OPERATION('tree')
648         dir_tree = {}
649         try:
650             self.save_wd()
651             dir_tree = self.__tree()
652             self.restore_wd(False)
653         finally:
654             self.END_OPERATION('tree')
655             return dir_tree
656
657     def __tree(self, __d=None):
658         if __d is None:
659             __d = {}
660             pcardext.cd('/')
661
662         for f in pcardext.ls(): # True, '*', False ):
663             fname = f[0].lower()
664
665             if self.callback is not None:
666                 self.callback()
667
668             if fname not in ('.', '..'):
669                 if f[1] == 'd':
670                     self.cd(fname, False)
671                     __d[fname] = {}
672                     __d[fname] = self.__tree(__d[fname])
673                     self.cdup(False)
674
675                 else:
676                     __d[fname] = f[2]
677
678         return __d
679
680
681     def get_exif(self, name):
682         exif_info = {}
683         self.START_OPERATION('get_exif')
684         pcf = None
685         try:
686             pcf = PhotoCardFile(self, name)
687             exif_info = exif.process_file(pcf)
688         finally:    
689             if pcf is not None:
690                 pcf.close()
691             self.END_OPERATION('get_exif')
692             return exif_info
693
694
695     def get_exif_path(self, name):
696         exif_info = {}
697         self.START_OPERATION('get_exif_path')
698         self.save_wd()
699         try:
700             path_list = name.split('/')[:-1]
701             filename = name.split('/')[-1]
702
703             for p in path_list:
704                 self.cd(p, False)
705
706             pcf = PhotoCardFile(self, filename)
707             exif_info = exif.process_file(pcf)
708
709         finally:    
710             self.restore_wd(False)
711             pcf.close()
712             self.END_OPERATION('get_exif_path')
713             return exif_info
714
715
716
717     def sector(self, sector):
718         self.START_OPERATION('sector')
719         try:
720             data = self._read(sector, 1)
721         finally:
722             self.END_OPERATION('sector')
723             return data
724
725     def umount(self):
726         pcardext.umount()
727         self.pcard_mounted = False
728
729     def open_channel(self):
730         self.channel_opened = True
731         self.device.openPCard()
732
733     def close_channel(self):
734         self.channel_opened = False
735         self.device.closePCard()
736
737
738
739
740
741