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"""
125 shell_not_found = 32512
128 if rsutil.common.run_command ('par2repair --help > /dev/null 2>&1') == shell_not_found:
129 needed.append ('par2repair')
131 if rsutil.common.run_command ('unrar --help > /dev/null 2>&1') == shell_not_found:
132 needed.append ('unrar')
134 if rsutil.common.run_command ('unzip --help > /dev/null 2>&1') == shell_not_found:
135 needed.append ('unzip')
139 print 'Needed program "%s" not found in $PATH' % (n, )
143 def run_options (options):
144 """Process all of the commandline options, doing thing such as printing the
145 version number, etc."""
148 options.work_dir = rsutil.common.full_abspath (options.work_dir)
150 # Make sure that the directory is valid
151 if not os.path.isdir (options.work_dir):
152 sys.stderr.write ('\"%s\" is not a valid directory. Use the \"-d\"\n' % options.work_dir)
153 sys.stderr.write ('option to override the working directory temporarily, or edit the\n')
154 sys.stderr.write ('configuration file to override the working directory permanently.\n')
157 if options.extract_dir != None:
158 options.extract_dir = rsutil.common.full_abspath (options.extract_dir)
161 print PROGRAM + ' - ' + VERSION
163 print 'Copyright (c) 2005,2006 Ira W. Snyder (devel@irasnyder.com)'
165 print 'This program comes with ABSOLUTELY NO WARRANTY.'
166 print 'This is free software, and you are welcome to redistribute it'
167 print 'under certain conditions. See the file COPYING for details.'
170 if options.check_progs:
171 check_required_progs ()
173 if options.write_def_config:
174 config.write_config (default=True)
177 if options.write_config:
178 config.write_config ()
181 def find_loglevel (options):
182 """Find the log level that should be printed by the logging class"""
184 loglevel = options.verbose - options.quiet
192 LEVELS = { 1 : logging.DEBUG,
199 return LEVELS [loglevel]
204 logger = DelayedLogger ()
205 logging.basicConfig (stream=logger, level=logging.WARNING, \
206 format='%(levelname)-8s %(message)s')
208 # Build the OptionParser
209 parser = optparse.OptionParser()
210 parser.add_option('-n', '--not-recursive', action='store_false', dest='recursive',
211 default=rsutil.common.config_get_value('options', 'recursive'),
212 help="Don't run recursively")
214 parser.add_option('-d', '--work-dir', dest='work_dir', type='string',
215 default=rsutil.common.config_get_value('directories', 'working_directory'),
216 help="Start running at DIR", metavar='DIR')
218 parser.add_option('-e', '--extract-dir', dest='extract_dir', type='string',
219 default=rsutil.common.config_get_value('directories', 'extract_directory'),
220 help="Extract to DIR", metavar='DIR')
222 parser.add_option('-p', '--check-required-programs',
223 action='store_true', dest='check_progs',
225 help="Check for required programs")
227 parser.add_option('-f', '--write-default-config',
228 action='store_true', dest='write_def_config',
229 default=False, help="Write out a new default config")
231 parser.add_option('-c', '--write-new-config',
232 action='store_true', dest='write_config',
233 default=False, help="Write out the current config")
235 parser.add_option('-i', '--interactive', dest='interactive', action='store_true',
236 default=rsutil.common.config_get_value('options', 'interactive'),
237 help="Confirm before removing files")
239 parser.add_option('-q', '--quiet', dest='quiet', action='count',
240 default=0, help="Output fatal messages only")
242 parser.add_option('-v', '--verbose', dest='verbose', action='count',
243 default=0, help="Output extra information")
245 parser.add_option('-V', '--version', dest='version', action='store_true',
246 default=False, help="Output version information")
248 parser.version = VERSION
250 # Parse the given options
252 (rsutil.globals.options, args) = parser.parse_args()
253 options = rsutil.globals.options
255 # Run any special actions that are needed on these options
256 run_options (options)
258 # Find the loglevel using the options given
259 logging.getLogger().setLevel (find_loglevel (options))
262 if options.recursive:
263 for (dir, subdirs, files) in os.walk (options.work_dir):
264 parsets = generate_all_parsets (dir)
265 for (p2dir, p2file) in parsets:
266 detector = RarslaveDetector.RarslaveDetector (p2dir, p2file)
267 ret = detector.runMatchingTypes ()
271 parsets = generate_all_parsets (options.work_dir)
272 for (p2dir, p2file) in parsets:
273 detector = RarslaveDetector.RarslaveDetector (p2dir, p2file)
274 ret = detector.runMatchingTypes ()
277 if logger.size () > 0:
278 print '\nLog\n' + '=' * 80
284 if __name__ == '__main__':