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