Replace 'tap' to 'spaces' to make gbs build succeed
[platform/upstream/hplip.git] / fab.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__ = '6.0'
24 __title__ = "Fax Address Book"
25 __mod__ = 'hp-fab'
26 __doc__ = "A simple fax address book for HPLIP."
27
28 # Std Lib
29 import cmd
30 import getopt
31 import os
32
33 # Local
34 from base.g import *
35 from base import utils, tui, module
36
37
38 # Console class (from ASPN Python Cookbook)
39 # Author:   James Thiele
40 # Date:     27 April 2004
41 # Version:  1.0
42 # Location: http://www.eskimo.com/~jet/python/examples/cmd/
43 # Copyright (c) 2004, James Thiele
44 class Console(cmd.Cmd):
45
46     def __init__(self):
47         cmd.Cmd.__init__(self)
48         self.intro  = "Type 'help' for a list of commands. Type 'exit' or 'quit' to quit."
49         self.db =  fax.FaxAddressBook() # database instance
50         self.prompt = log.bold("hp-fab > ")
51
52     # Command definitions
53     def do_hist(self, args):
54         """Print a list of commands that have been entered"""
55         print self._hist
56
57     def do_exit(self, args):
58         """Exits from the console"""
59         return -1
60
61     def do_quit(self, args):
62         """Exits from the console"""
63         return -1
64
65     # Command definitions to support Cmd object functionality
66     def do_EOF(self, args):
67         """Exit on system end of file character"""
68         return self.do_exit(args)
69
70     def do_help(self, args):
71         """Get help on commands
72            'help' or '?' with no arguments prints a list of commands for which help is available
73            'help <command>' or '? <command>' gives help on <command>
74         """
75         # The only reason to define this method is for the help text in the doc string
76         cmd.Cmd.do_help(self, args)
77
78     # Override methods in Cmd object
79     def preloop(self):
80         """Initialization before prompting user for commands.
81            Despite the claims in the Cmd documentaion, Cmd.preloop() is not a stub.
82         """
83         cmd.Cmd.preloop(self)   # sets up command completion
84         self._hist    = []      # No history yet
85         self._locals  = {}      # Initialize execution namespace for user
86         self._globals = {}
87
88         self.do_list('')
89
90     def postloop(self):
91         """Take care of any unfinished business.
92            Despite the claims in the Cmd documentaion, Cmd.postloop() is not a stub.
93         """
94         cmd.Cmd.postloop(self)   # Clean up command completion
95         print "Exiting..."
96
97     def precmd(self, line):
98         """ This method is called after the line has been input but before
99             it has been interpreted. If you want to modifdy the input line
100             before execution (for example, variable substitution) do it here.
101         """
102         self._hist += [line.strip()]
103         return line
104
105     def postcmd(self, stop, line):
106         """If you want to stop the console, return something that evaluates to true.
107            If you want to do some post command processing, do it here.
108         """
109         return stop
110
111     def emptyline(self):
112         """Do nothing on empty input line"""
113         pass
114
115     def default(self, line):
116         log.error("Unrecognized command. Use 'help' to list commands.")
117
118     def get_nickname(self, args, fail_if_match=True, alt_text=False):
119         if not args:
120             while True:
121                 if alt_text:
122                     nickname = raw_input(log.bold("Enter the name to add to the group (<enter>=done*, c=cancel) ? ")).strip()
123                 else:
124                     nickname = raw_input(log.bold("Enter name (c=cancel) ? ")).strip()
125
126                 if nickname.lower() == 'c':
127                     print log.red("Canceled")
128                     return ''
129
130                 if not nickname:
131                     if alt_text:
132                         return ''
133                     else:
134                         log.error("Name must not be blank.")
135                         continue
136
137
138                 if fail_if_match:
139                     if self.db.get(nickname) is not None:
140                         log.error("Name already exists. Please choose a different name.")
141                         continue
142
143                 else:
144                     if self.db.get(nickname) is None:
145                         log.error("Name not found. Please enter a different name.")
146                         continue
147
148                 break
149
150         else:
151             nickname = args.strip()
152
153             if fail_if_match:
154                 if self.db.get(nickname) is not None:
155                     log.error("Name already exists. Please choose a different name.")
156                     return ''
157
158             else:
159                 if self.db.get(nickname) is None:
160                     log.error("Name not found. Please enter a different name.")
161                     return ''
162
163         return nickname
164
165
166     def get_groupname(self, args, fail_if_match=True, alt_text=False):
167         all_groups = self.db.get_all_groups()
168
169         if not args:
170             while True:
171                 if alt_text:
172                     groupname = raw_input(log.bold("Enter the group to join (<enter>=done*, c=cancel) ? ")).strip()
173                 else:
174                     groupname = raw_input(log.bold("Enter the group (c=cancel) ? ")).strip()
175
176
177                 if groupname.lower() == 'c':
178                     print log.red("Canceled")
179                     return ''
180
181                 if not groupname:
182                     if alt_text:
183                         return ''
184                     else:
185                         log.error("The group name must not be blank.")
186                         continue
187
188                 if groupname == 'All':
189                     print "Cannot specify group 'All'. Please choose a different group."
190                     return ''
191
192                 if fail_if_match:
193                     if groupname in all_groups:
194                         log.error("Group already exists. Please choose a different group.")
195                         continue
196
197                 else:
198                     if groupname not in all_groups:
199                         log.error("Group not found. Please enter a different group.")
200                         continue
201
202                 break
203
204         else:
205             groupname = args.strip()
206
207             if fail_if_match:
208                 if groupname in all_groups:
209                     log.error("Group already exists. Please choose a different group.")
210                     return ''
211
212             else:
213                 if groupname not in all_groups:
214                     log.error("Group not found. Please enter a different group.")
215                     return ''
216
217         return groupname
218
219     def do_list(self, args):
220         """
221         List names and/or groups.
222         list [names|groups|all|]
223         dir [names|groups|all|]
224         """
225
226         if args:
227             scope = args.strip().split()[0]
228
229             if args.startswith('nam'):
230                 self.do_names('')
231                 return
232
233             elif args.startswith('gro'):
234                 self.do_groups('')
235                 return
236
237         self.do_names('')
238         self.do_groups('')
239
240     do_dir = do_list
241
242     def do_names(self, args):
243         """
244         List names.
245         names
246         """
247         all_entries = self.db.get_all_records()
248         log.debug(all_entries)
249
250         print log.bold("\nNames:\n")
251         if len(all_entries) > 0:
252
253             f = tui.Formatter()
254             f.header = ("Name", "Fax Number", "Notes", "Member of Group(s)")
255             for name, e in all_entries.items():
256                 if not name.startswith('__'):
257                     f.add((name, e['fax'], e['notes'], ', '.join(e['groups'])))
258
259             f.output()
260
261         else:
262             print "(None)"
263
264         print
265
266     def do_groups(self, args):
267         """
268         List groups.
269         groups
270         """
271         all_groups = self.db.get_all_groups()
272         log.debug(all_groups)
273
274         print log.bold("\nGroups:\n")
275         if len(all_groups):
276
277             f = tui.Formatter()
278             f.header = ("Group", "Members")
279             for group in all_groups:
280                 f.add((group, ', '.join([x for x in self.db.group_members(group) if not x.startswith('__')])))
281             f.output()
282
283         else:
284             print "(None)"
285
286         print
287
288
289     def do_edit(self, args):
290         """
291         Edit an name.
292         edit [name]
293         modify [name]
294         """
295         nickname = self.get_nickname(args, fail_if_match=False)
296         if not nickname: return
297
298         e = self.db.get(nickname)
299         log.debug(e)
300
301         print log.bold("\nEdit/modify information for %s:\n" % nickname)
302
303 #        save_title = e['title']
304 #        title = raw_input(log.bold("Title (<enter>='%s', c=cancel) ? " % save_title)).strip()
305 #
306 #        if title.lower() == 'c':
307 #            print log.red("Canceled")
308 #            return
309 #
310 #        if not title:
311 #            title = save_title
312 #
313 #        save_firstname = e['firstname']
314 #        firstname = raw_input(log.bold("First name (<enter>='%s', c=cancel) ? " % save_firstname)).strip()
315 #
316 #        if firstname.lower() == 'c':
317 #            print log.red("Canceled")
318 #            return
319 #
320 #        if not firstname:
321 #            firstname = save_firstname
322 #
323 #        save_lastname = e['lastname']
324 #        lastname = raw_input(log.bold("Last name (<enter>='%s', c=cancel) ? " % save_lastname)).strip()
325 #
326 #        if lastname.lower() == 'c':
327 #            print log.red("Canceled")
328 #            return
329 #
330 #        if not lastname:
331 #            lastname = save_lastname
332
333         lastname = ''
334         firstname = ''
335         title = ''
336
337         save_faxnum = e['fax']
338         while True:
339             faxnum = raw_input(log.bold("Fax Number (<enter>='%s', c=cancel) ? " % save_faxnum)).strip()
340
341             if faxnum.lower() == 'c':
342                 print log.red("Canceled")
343                 return
344
345             if not faxnum and not save_faxnum:
346                 log.error("Fax number must not be empty.")
347                 continue
348
349             if not faxnum:
350                 faxnum = save_faxnum
351
352             ok = True
353             for c in faxnum:
354                 if c not in '0123456789-(+) *#':
355                     log.error("Invalid characters in fax number. Fax number may only contain '0123456789-(+) '")
356                     ok = False
357                     break
358
359
360             if ok: break
361
362         save_notes = e['notes']
363         notes = raw_input(log.bold("Notes (<enter>='%s', c=cancel) ? " % save_notes)).strip()
364
365         if notes.lower() == 'c':
366             print log.red("Canceled")
367             return
368
369         if not notes:
370             notes = save_notes
371
372         if e['groups']:
373             print "\nLeave or Stay in a Group:\n"
374
375         new_groups = []
376         for g in e['groups']:
377             if g == 'All':
378                 continue
379
380             ok, ans = tui.enter_yes_no("Stay in group %s " % g,
381                 choice_prompt="(y=yes* (stay), n=no (leave), c=cancel) ? ")
382
383             if not ok:
384                 print log.red("Canceled")
385                 return
386
387             if ans:
388                 new_groups.append(g)
389
390         print "\nJoin New Group(s):\n"
391
392         while True:
393             add_group = self.get_groupname('', fail_if_match=False, alt_text=True)
394
395             if add_group.lower() == 'c':
396                 print log.red("Canceled")
397                 return
398
399             if not add_group:
400                 break
401
402             all_groups = self.db.get_all_groups()
403
404             if add_group not in all_groups:
405                 log.warn("Group not found.")
406                 ok, ans = tui.enter_yes_no("Is this a new group",
407                     choice_prompt="(y=yes* (new), n=no, c=cancel) ? ")
408
409                 if not ok:
410                     print log.red("Canceled")
411                     return
412
413                 if not ans:
414                     continue
415
416             if add_group in e['groups']:
417                 log.error("Group already specified. Choose a different group name or press <enter> to continue.")
418                 continue
419
420             new_groups.append(add_group)
421
422         self.db.set(nickname, title, firstname, lastname, faxnum, new_groups, notes)
423         self.do_show(nickname)
424
425         print
426
427     do_modify = do_edit
428
429
430     def do_editgrp(self, args):
431         """
432         Edit a group.
433         editgrp [group]
434         modifygrp [group]
435         """
436         group = self.get_groupname(args, fail_if_match=False)
437         if not group: return
438
439         old_entries = self.db.group_members(group)
440
441         new_entries = []
442
443         print "\nExisting Names in Group:\n"
444
445         for e in old_entries:
446             if not e.startswith('__'):
447                 ok, ans = tui.enter_yes_no("Should '%s' stay in this group " % e,
448                     choice_prompt="(y=yes* (stay), n=no (leave), c=cancel) ? ")
449             else:
450                 continue
451
452             if not ok:
453                 print log.red("Canceled")
454                 return
455
456             if ans:
457                 new_entries.append(e)
458
459         print "\nAdd New Names to Group:\n"
460
461         while True:
462             nickname = self.get_nickname('', fail_if_match=False, alt_text=True)
463
464             if nickname.lower() == 'c':
465                 print log.red("Canceled")
466                 return
467
468             if not nickname.lower():
469                 break
470
471             new_entries.append(nickname)
472
473         self.db.update_groups(group, new_entries)
474
475         print
476
477     do_modifygrp = do_editgrp
478
479
480     def do_add(self, args):
481         """
482         Add an name.
483         add [name]
484         new [name]
485         """
486         nickname = self.get_nickname(args, fail_if_match=True)
487         if not nickname: return
488
489         print log.bold("\nEnter information for %s:\n" % nickname)
490
491 #        title = raw_input(log.bold("Title (c=cancel) ? ")).strip()
492 #
493 #        if title.lower() == 'c':
494 #            print log.red("Canceled")
495 #            return
496 #
497 #        firstname = raw_input(log.bold("First name (c=cancel) ? ")).strip()
498 #
499 #        if firstname.lower() == 'c':
500 #            print log.red("Canceled")
501 #            return
502 #
503 #        lastname = raw_input(log.bold("Last name (c=cancel) ? ")).strip()
504 #
505 #        if lastname.lower() == 'c':
506 #            print log.red("Canceled")
507 #            return
508
509         title = ''
510         firstname = ''
511         lastname = ''
512
513         while True:
514             faxnum = raw_input(log.bold("Fax Number (c=cancel) ? ")).strip()
515
516             if faxnum.lower() == 'c':
517                 print log.red("Canceled")
518                 return
519
520             if not faxnum:
521                 log.error("Fax number must not be empty.")
522                 continue
523
524             ok = True
525             for c in faxnum:
526                 if c not in '0123456789-(+) *#':
527                     log.error("Invalid characters in fax number. Fax number may only contain '0123456789-(+) *#'")
528                     ok = False
529                     break
530
531
532             if ok: break
533
534         notes = raw_input(log.bold("Notes (c=cancel) ? ")).strip()
535
536         if notes.strip().lower() == 'c':
537             print log.red("Canceled")
538             return
539
540         groups = []
541         all_groups = self.db.get_all_groups()
542         while True:
543             add_group = raw_input(log.bold("Member of group (<enter>=done*, c=cancel) ? " )).strip()
544
545             if add_group.lower() == 'c':
546                 print log.red("Canceled")
547                 return
548
549             if not add_group:
550                 break
551
552             if add_group == 'All':
553                 print log.red("Cannot specify 'All'.")
554                 continue
555
556             if add_group not in all_groups:
557                 log.warn("Group not found.")
558
559                 while True:
560                     user_input = raw_input(log.bold("Is this a new group (y=yes*, n=no) ? ")).lower().strip()
561
562                     if user_input not in ['', 'n', 'y']:
563                         log.error("Please enter 'y', 'n' or press <enter> for 'yes'.")
564                         continue
565
566                     break
567
568                 if user_input == 'n':
569                     continue
570
571             if add_group in groups:
572                 log.error("Group already specified. Choose a different group name or press <enter> to continue.")
573                 continue
574
575             groups.append(add_group)
576
577         groups.append('All')
578
579         self.db.set(nickname, title, firstname, lastname, faxnum, groups, notes)
580         self.do_show(nickname)
581
582
583     do_new = do_add
584
585
586     def do_addgrp(self, args):
587         """
588         Add a group.
589         addgrp [group]
590         newgrp [group]
591         """
592         group = self.get_groupname(args, fail_if_match=True)
593         if not group: return
594
595         entries = []
596         while True:
597             nickname = self.get_nickname('', fail_if_match=False, alt_text=True)
598
599             if nickname.lower() == 'c':
600                 print log.red("Canceled")
601                 return
602
603             if not nickname.lower():
604                 break
605
606             entries.append(nickname)
607
608         self.db.update_groups(group, entries)
609
610         print
611
612     do_newgrp = do_addgrp
613
614
615     def do_view(self, args):
616         """
617         View all name data.
618         view
619         """
620         all_entries = self.db.get_all_records()
621         log.debug(all_entries)
622
623         print log.bold("\nView all Data:\n")
624         if len(all_entries) > 0:
625
626             f = tui.Formatter()
627             f.header = ("Name", "Fax", "Notes", "Member of Group(s)")
628
629             for name, e in all_entries.items():
630                 if not name.startswith('__'):
631                     f.add((name, e['fax'], e['notes'], ', '.join(e['groups'])))
632
633             f.output()
634
635         print
636
637
638
639     def do_show(self, args):
640         """
641         Show a name (all details).
642         show [name]
643         details [name]
644         """
645         name = self.get_nickname(args, fail_if_match=False)
646         if not name: return
647
648         e = self.db.get(name)
649         if e:
650             f = tui.Formatter()
651             f.header = ("Key", "Value")
652             f.add(("Name:", name))
653             #f.add(("Title:", e['title']))
654             #f.add(("First Name:", e['firstname']))
655             #f.add(("Last Name:", e['lastname']))
656             f.add(("Fax Number:", e['fax']))
657             f.add(("Notes:", e['notes']))
658             f.add(("Member of Group(s):", ', '.join(e['groups'])))
659
660             f.output()
661
662         else:
663             log.error("Name not found. Use the 'names' command to view all names.")
664
665         print
666
667     do_details = do_show
668
669     def do_rm(self, args):
670         """
671         Remove a name.
672         rm [name]
673         del [name]
674         """
675         nickname = self.get_nickname(args, fail_if_match=False)
676         if not nickname: return
677
678         self.db.delete(nickname)
679
680         print
681
682     do_del = do_rm
683
684     def do_rmgrp(self, args):
685         """
686         Remove a group.
687         rmgrp [group]
688         delgrp [group]
689         """
690         group = self.get_groupname(args, fail_if_match=False)
691         if not group: return
692
693         self.db.delete_group(group)
694
695         print
696
697     do_delgrp = do_rmgrp
698
699
700     def do_about(self, args):
701         """About fab."""
702         utils.log_title(__title__, __version__)
703
704     def do_import(self, args):
705         """
706         Import LDIF
707         import <filename> [type]
708         [type] = vcf|ldif|auto
709         """
710         args = args.strip().split()
711
712         if not args:
713             log.error("You must specify a filename to import from.")
714             return
715
716         filename = args[0]
717
718         if len(args) > 1:
719             typ = args[1].lower()
720         else:
721             typ = 'auto'
722
723         if typ not in ('auto', 'ldif', 'vcf', 'vcard'):
724             log.error("Invalid type: %s" % typ)
725             return
726
727         if not os.path.exists(filename):
728             log.error("File %s not found." % filename)
729             return
730
731         if typ == 'auto':
732             ext = os.path.splitext(filename)[1].lower()
733             if ext == '.vcf':
734                 typ = 'vcf'
735             elif ext == '.ldif':
736                 typ = 'ldif'
737             else:
738                 head = file(filename, 'r').read(1024).lower()
739                 if 'begin:vcard' in head:
740                     typ = 'vcf'
741                 else:
742                     typ = 'ldif'
743
744         if typ == 'ldif':
745             print "Importing from LDIF file %s..." % filename
746             ok, error_str = self.db.import_ldif(filename)
747
748         elif typ in ('vcard', 'vcf'):
749             print "Importing from VCF file %s..." % filename
750             ok, error_str = self.db.import_vcard(filename)
751
752         if not ok:
753             log.error(error_str)
754         else:
755             self.do_list('')
756
757         print
758
759
760
761
762 mod = module.Module(__mod__, __title__, __version__, __doc__, None,
763                     (GUI_MODE, INTERACTIVE_MODE),
764                     (UI_TOOLKIT_QT3, UI_TOOLKIT_QT4))
765
766 mod.setUsage(module.USAGE_FLAG_NONE)
767
768 opts, device_uri, printer_name, mode, ui_toolkit, loc = \
769     mod.parseStdOpts(handle_device_printer=False)
770
771 if ui_toolkit == 'qt3':
772     if not utils.canEnterGUIMode():
773         log.error("%s GUI mode requires GUI support (try running with --qt4). Entering interactive mode." % __mod__)
774         mode = INTERACTIVE_MODE
775 else:
776     if not utils.canEnterGUIMode4():
777         log.error("%s GUI mode requires GUI support (try running with --qt3). Entering interactive mode." % __mod__)
778         mode = INTERACTIVE_MODE
779
780
781 if mode == GUI_MODE:
782     if ui_toolkit == 'qt3':
783         log.set_module("hp-fab(qt3)")
784         try:
785             from qt import *
786             from ui.faxaddrbookform import FaxAddrBookForm
787         except ImportError:
788             log.error("Unable to load Qt3 support. Is it installed?")
789             sys.exit(1)
790
791         app = None
792         addrbook = None
793         # create the main application object
794         app = QApplication(sys.argv)
795
796         if loc is None:
797             loc = user_conf.get('ui', 'loc', 'system')
798             if loc.lower() == 'system':
799                 loc = str(QTextCodec.locale())
800                 log.debug("Using system locale: %s" % loc)
801
802         if loc.lower() != 'c':
803             e = 'utf8'
804             try:
805                 l, x = loc.split('.')
806                 loc = '.'.join([l, e])
807             except ValueError:
808                 l = loc
809                 loc = '.'.join([loc, e])
810
811             log.debug("Trying to load .qm file for %s locale." % loc)
812             trans = QTranslator(None)
813
814             qm_file = 'hplip_%s.qm' % l
815             log.debug("Name of .qm file: %s" % qm_file)
816             loaded = trans.load(qm_file, prop.localization_dir)
817
818             if loaded:
819                 app.installTranslator(trans)
820             else:
821                 loc = 'c'
822
823         if loc == 'c':
824             log.debug("Using default 'C' locale")
825         else:
826             log.debug("Using locale: %s" % loc)
827             QLocale.setDefault(QLocale(loc))
828             prop.locale = loc
829             try:
830                 locale.setlocale(locale.LC_ALL, locale.normalize(loc))
831             except locale.Error:
832                 pass
833
834         addrbook = FaxAddrBookForm()
835         addrbook.show()
836         app.setMainWidget(addrbook)
837
838         try:
839             log.debug("Starting GUI loop...")
840             app.exec_loop()
841         except KeyboardInterrupt:
842             pass
843
844         sys.exit(0)
845
846     else: # qt4
847         try:
848             from PyQt4.QtGui import QApplication
849             from ui4.fabwindow import FABWindow
850         except ImportError:
851             log.error("Unable to load Qt4 support. Is it installed?")
852             sys.exit(1)
853
854         log.set_module("hp-fab(qt4)")
855
856         if 1:
857             app = QApplication(sys.argv)
858
859             fab = FABWindow(None)
860             fab.show()
861
862             try:
863                 log.debug("Starting GUI loop...")
864                 app.exec_()
865             except KeyboardInterrupt:
866                 sys.exit(0)
867
868
869
870 else: # INTERACTIVE_MODE
871     try:
872         from fax import fax
873     except ImportError:
874         # This can fail on Python < 2.3 due to the datetime module
875         log.error("Fax address book disabled - Python 2.3+ required.")
876         sys.exit(1)
877
878     console = Console()
879
880     try:
881         console.cmdloop()
882     except KeyboardInterrupt:
883         log.error("User exit.")
884
885     log.info("")
886     log.info("Done.")
887