2 # vim: set ts=4 sts=4 sw=4 textwidth=92:
5 The main program of the rarslave project.
7 This handles all of the commandline, configuration file, and option
8 work. It gets the environment set up for a run using the RarslaveDetector
12 __author__ = "Ira W. Snyder (devel@irasnyder.com)"
13 __copyright__ = "Copyright (c) 2006,2007 Ira W. Snyder (devel@irasnyder.com)"
14 __license__ = "GNU GPL v2 (or, at your option, any later version)"
16 # rarslave.py -- a usenet autorepair and autoextract utility
18 # Copyright (C) 2006,2007 Ira W. Snyder (devel@irasnyder.com)
20 # This program is free software; you can redistribute it and/or modify
21 # it under the terms of the GNU General Public License as published by
22 # the Free Software Foundation; either version 2 of the License, or
23 # (at your option) any later version.
25 # This program is distributed in the hope that it will be useful,
26 # but WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 # GNU General Public License for more details.
30 # You should have received a copy of the GNU General Public License
31 # along with this program; if not, write to the Free Software
32 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
37 import os, sys, optparse, logging
39 import RarslaveDetector
41 # Global options from the rsutil.globals class
42 options = rsutil.globals.options
43 config = rsutil.globals.config
45 # A tiny class to hold logging output until we're finished
46 class DelayedLogger (object):
48 """A small class to hold logging output until the program is finished running.
49 It emulates sys.stdout in the needed ways for the logging module."""
51 def __init__ (self, output=sys.stdout.write):
53 self.__output = output
55 def write (self, msg):
56 self.__messages.append (msg)
62 """Returns the number of messages queued for printing"""
63 return len (self.__messages)
66 """Print all messages, clear the queue"""
67 for m in self.__messages:
72 # A tiny class used to find unique PAR2 sets
73 class CompareSet (object):
75 """A small class used to find unique PAR2 sets"""
77 def __init__ (self, dir, p2file):
81 self.basename = rsutil.common.get_basename (self.p2file)
82 self.name_matches = rsutil.common.find_name_matches (self.dir, self.basename)
84 def __eq__ (self, rhs):
85 return (self.dir == rhs.dir) \
86 and (self.basename == rhs.basename) \
87 and rsutil.common.list_eq (self.name_matches, rhs.name_matches)
90 def find_all_par2_files (dir):
91 """Finds all par2 files in the given directory.
93 dir -- the directory in which to search for PAR2 files
95 NOTE: does not return absolute paths"""
97 if not os.path.isdir (os.path.abspath (dir)):
98 raise ValueError # bad directory given
100 dir = os.path.abspath (dir)
101 files = os.listdir (dir)
103 return rsutil.common.find_par2_files (files)
105 def generate_all_parsets (dir):
106 """Generate all parsets in the given directory
108 dir -- the directory in which to search"""
110 assert os.path.isdir (dir) # Directory MUST be valid
113 p2files = find_all_par2_files (dir)
116 p = CompareSet (dir, f)
120 return [(p.dir, p.p2file) for p in parsets]
122 def check_required_progs():
123 """Check if the required programs are installed"""
128 rsutil.common.run_command(['par2repair', '--help'])
130 needed.append('par2repair')
135 rsutil.common.run_command(['unrar', '--help'])
137 needed.append('unrar')
142 rsutil.common.run_command(['unzip', '--help'])
144 needed.append('unzip')
150 print 'Needed program "%s" not found in $PATH' % (n, )
154 def run_options (options):
155 """Process all of the commandline options, doing thing such as printing the
156 version number, etc."""
159 options.work_dir = rsutil.common.full_abspath (options.work_dir)
161 # Make sure that the directory is valid
162 if not os.path.isdir (options.work_dir):
163 sys.stderr.write ('\"%s\" is not a valid directory. Use the \"-d\"\n' % options.work_dir)
164 sys.stderr.write ('option to override the working directory temporarily, or edit the\n')
165 sys.stderr.write ('configuration file to override the working directory permanently.\n')
168 if options.extract_dir != None:
169 options.extract_dir = rsutil.common.full_abspath (options.extract_dir)
172 print PROGRAM + ' - ' + VERSION
174 print 'Copyright (c) 2005,2006 Ira W. Snyder (devel@irasnyder.com)'
176 print 'This program comes with ABSOLUTELY NO WARRANTY.'
177 print 'This is free software, and you are welcome to redistribute it'
178 print 'under certain conditions. See the file COPYING for details.'
181 if options.check_progs:
182 check_required_progs ()
185 if options.write_def_config:
186 config.write_config (default=True)
189 if options.write_config:
190 config.write_config ()
193 def find_loglevel (options):
194 """Find the log level that should be printed by the logging class"""
196 loglevel = options.verbose - options.quiet
204 LEVELS = { 1 : logging.DEBUG,
211 return LEVELS [loglevel]
216 logger = DelayedLogger ()
217 logging.basicConfig (stream=logger, level=logging.WARNING, \
218 format='%(levelname)-8s %(message)s')
220 # Build the OptionParser
221 parser = optparse.OptionParser()
222 parser.add_option('-n', '--not-recursive', action='store_false', dest='recursive',
223 default=rsutil.common.config_get_value('options', 'recursive'),
224 help="Don't run recursively")
226 parser.add_option('-d', '--work-dir', dest='work_dir', type='string',
227 default=rsutil.common.config_get_value('directories', 'working_directory'),
228 help="Start running at DIR", metavar='DIR')
230 parser.add_option('-e', '--extract-dir', dest='extract_dir', type='string',
231 default=rsutil.common.config_get_value('directories', 'extract_directory'),
232 help="Extract to DIR", metavar='DIR')
234 parser.add_option('-p', '--check-required-programs',
235 action='store_true', dest='check_progs',
237 help="Check for required programs")
239 parser.add_option('-f', '--write-default-config',
240 action='store_true', dest='write_def_config',
241 default=False, help="Write out a new default config")
243 parser.add_option('-c', '--write-new-config',
244 action='store_true', dest='write_config',
245 default=False, help="Write out the current config")
247 parser.add_option('-i', '--interactive', dest='interactive', action='store_true',
248 default=rsutil.common.config_get_value('options', 'interactive'),
249 help="Confirm before removing files")
251 parser.add_option('-q', '--quiet', dest='quiet', action='count',
252 default=0, help="Output fatal messages only")
254 parser.add_option('-v', '--verbose', dest='verbose', action='count',
255 default=0, help="Output extra information")
257 parser.add_option('-V', '--version', dest='version', action='store_true',
258 default=False, help="Output version information")
260 parser.version = VERSION
262 # Parse the given options
264 (rsutil.globals.options, args) = parser.parse_args()
265 options = rsutil.globals.options
267 # Run any special actions that are needed on these options
268 run_options (options)
270 # Find the loglevel using the options given
271 logging.getLogger().setLevel (find_loglevel (options))
274 if options.recursive:
275 for (dir, subdirs, files) in os.walk (options.work_dir):
276 parsets = generate_all_parsets (dir)
277 for (p2dir, p2file) in parsets:
278 detector = RarslaveDetector.RarslaveDetector (p2dir, p2file)
279 detector.runMatchingTypes ()
283 parsets = generate_all_parsets (options.work_dir)
284 for (p2dir, p2file) in parsets:
285 detector = RarslaveDetector.RarslaveDetector (p2dir, p2file)
286 detector.runMatchingTypes ()
289 if logger.size () > 0:
290 print '\nLog\n' + '=' * 80
296 if __name__ == '__main__':