a18e6a3fd7f518ed30fcbc35d78b1b4f63547752
[rarslave2.git] / rarslave.py
1 #!/usr/bin/env python
2 # vim: set ts=4 sts=4 sw=4 textwidth=112 :
3
4 VERSION="2.0.0"
5 PROGRAM="rarslave2"
6
7 import os, sys, optparse, logging
8 import RarslaveDetector
9 import RarslaveGlobals
10 from RarslaveCommon import *
11
12 # Global options from the RarslaveGlobals class
13 options = RarslaveGlobals.options
14 config = RarslaveGlobals.config
15
16 # A tiny class to hold logging output until we're finished
17 class DelayedLogger (object):
18         def __init__ (self, output=sys.stdout.write):
19                 self.__messages = []
20                 self.__output = output
21
22         def write (self, msg):
23                 self.__messages.append (msg)
24
25         def flush (self):
26                 pass
27
28         def size (self):
29                 """Returns the number of messages queued for printing"""
30                 return len (self.__messages)
31
32         def close (self):
33                 """Print all messages, clear the queue"""
34                 for m in self.__messages:
35                         self.__output (m)
36
37                 self.__messages = []
38
39 # A tiny class used to find unique PAR2 sets
40 class CompareSet (object):
41
42         def __init__ (self, dir, p2file):
43                 self.dir = dir
44                 self.p2file = p2file
45
46                 self.basename = get_basename (self.p2file)
47                 self.name_matches = find_name_matches (self.dir, self.basename)
48
49         def __eq__ (self, rhs):
50                 return (self.dir == rhs.dir) \
51                                 and (self.basename == rhs.basename) \
52                                 and list_eq (self.name_matches, rhs.name_matches)
53
54
55 def find_all_par2_files (dir):
56         """Finds all par2 files in a directory"""
57         # NOTE: does NOT return absolute paths
58
59         if not os.path.isdir (os.path.abspath (dir)):
60                 raise ValueError # bad directory given
61
62         dir = os.path.abspath (dir)
63         files = os.listdir (dir)
64
65         return find_par2_files (files)
66
67 def generate_all_parsets (dir):
68         # Generate all parsets in the given directory.
69
70         assert os.path.isdir (dir) # Directory MUST be valid
71
72         parsets = []
73         p2files = find_all_par2_files (dir)
74
75         for f in p2files:
76                 p = CompareSet (dir, f)
77                 if p not in parsets:
78                         parsets.append (p)
79
80         return [(p.dir, p.p2file) for p in parsets]
81
82 def check_required_progs():
83         """Check if the required programs are installed"""
84
85         shell_not_found = 32512
86         needed = []
87
88         if run_command ('par2repair --help > /dev/null 2>&1') == shell_not_found:
89                 needed.append ('par2repair')
90
91         if run_command ('unrar --help > /dev/null 2>&1') == shell_not_found:
92                 needed.append ('unrar')
93
94         if run_command ('unzip --help > /dev/null 2>&1') == shell_not_found:
95                 needed.append ('unzip')
96
97         if needed:
98                 for n in needed:
99                         print 'Needed program "%s" not found in $PATH' % (n, )
100
101                 sys.exit(1)
102
103 def run_options (options):
104
105         # Fix directories
106         options.work_dir = full_abspath (options.work_dir)
107
108         # Make sure that the directory is valid
109         if not os.path.isdir (options.work_dir):
110                 sys.stderr.write ('\"%s\" is not a valid directory. Use the \"-d\"\n' % options.work_dir)
111                 sys.stderr.write ('option to override the working directory temporarily, or edit the\n')
112                 sys.stderr.write ('configuration file to override the working directory permanently.\n')
113                 sys.exit (1)
114
115         if options.extract_dir != None:
116                 options.extract_dir = full_abspath (options.extract_dir)
117
118         if options.version:
119                 print PROGRAM + ' - ' + VERSION
120                 print
121                 print 'Copyright (c) 2005,2006 Ira W. Snyder (devel@irasnyder.com)'
122                 print
123                 print 'This program comes with ABSOLUTELY NO WARRANTY.'
124                 print 'This is free software, and you are welcome to redistribute it'
125                 print 'under certain conditions. See the file COPYING for details.'
126                 sys.exit (0)
127
128         if options.check_progs:
129                 check_required_progs ()
130
131         if options.write_def_config:
132                 config.write_config (default=True)
133
134         if options.write_config:
135                 config.write_config ()
136
137 def find_loglevel (options):
138
139         loglevel = options.verbose - options.quiet
140
141         if loglevel > 1:
142                 loglevel = 1
143
144         if loglevel < -3:
145                 loglevel = -3
146
147         LEVELS = {      1 : logging.DEBUG,
148                                 0 : logging.INFO,
149                                 -1: logging.WARNING,
150                                 -2: logging.ERROR,
151                                 -3: logging.CRITICAL
152         }
153
154         return LEVELS [loglevel]
155
156 def main ():
157
158         # Setup the logger
159         logger = DelayedLogger ()
160         logging.basicConfig (stream=logger, level=logging.WARNING, \
161                         format='%(levelname)-8s %(message)s')
162
163         # Build the OptionParser
164         parser = optparse.OptionParser()
165         parser.add_option('-n', '--not-recursive',
166                                                 action='store_false', dest='recursive',
167                                                 default=config_get_value('options', 'recursive'),
168                                                 help="Don't run recursively")
169
170         parser.add_option('-d', '--work-dir',
171                                                 dest='work_dir', type='string',
172                                                 default=config_get_value('directories', 'working_directory'),
173                                                 help="Start running at DIR", metavar='DIR')
174
175         parser.add_option('-e', '--extract-dir',
176                                                 dest='extract_dir', type='string',
177                                                 default=config_get_value('directories', 'extract_directory'),
178                                                 help="Extract to DIR", metavar='DIR')
179
180         parser.add_option('-p', '--check-required-programs',
181                                                 action='store_true', dest='check_progs',
182                                                 default=False,
183                                                 help="Check for required programs")
184
185         parser.add_option('-f', '--write-default-config',
186                                                 action='store_true', dest='write_def_config',
187                                                 default=False, help="Write out a new default config")
188
189         parser.add_option('-c', '--write-new-config',
190                                                 action='store_true', dest='write_config',
191                                                 default=False, help="Write out the current config")
192
193         parser.add_option('-i', '--interactive', dest='interactive', action='store_true',
194                                                 default=config_get_value('options', 'interactive'),
195                                                 help="Confirm before removing files")
196
197         parser.add_option('-q', '--quiet', dest='quiet', action='count',
198                                                 default=0, help="Output fatal messages only")
199
200         parser.add_option('-v', '--verbose', dest='verbose', action='count',
201                                                 default=0, help="Output extra information")
202
203         parser.add_option('-V', '--version', dest='version', action='store_true',
204                                                 default=False, help="Output version information")
205
206         parser.version = VERSION
207
208         # Parse the given options
209         global options
210         (RarslaveGlobals.options, args) = parser.parse_args()
211         options = RarslaveGlobals.options
212
213         # Run any special actions that are needed on these options
214         run_options (options)
215
216         # Find the loglevel using the options given 
217         logging.getLogger().setLevel (find_loglevel (options))
218
219         # Run recursively
220         if options.recursive:
221                 for (dir, subdirs, files) in os.walk (options.work_dir):
222                         parsets = generate_all_parsets (dir)
223                         for (p2dir, p2file) in parsets:
224                                 detector = RarslaveDetector.RarslaveDetector (p2dir, p2file)
225                                 ret = detector.runMatchingTypes ()
226
227         # Non-recursive
228         else:
229                 parsets = generate_all_parsets (options.work_dir)
230                 for (p2dir, p2file) in parsets:
231                         detector = RarslaveDetector.RarslaveDetector (p2dir, p2file)
232                         ret = detector.runMatchingTypes ()
233
234         # Print the results
235         if logger.size () > 0:
236                 print 'Log\n' + '=' * 80
237                 logger.close ()
238
239         # Done!
240         return 0
241
242 if __name__ == '__main__':
243         main ()
244