X-Git-Url: https://www.irasnyder.com/gitweb/?p=rarslave2.git;a=blobdiff_plain;f=rarslave.py;h=f40ac9cb3fcc35973bb84c150dff4b4d2d7c56b0;hp=285974d4188044de6824ca296b8c4af3fd829cb9;hb=6b8ee05cfd6f43df9f5bb66e3fbe6d59bff8c61b;hpb=2d8195e52490df0e0c3b16ffe179a6f5d1b02f87 diff --git a/rarslave.py b/rarslave.py index 285974d..f40ac9c 100644 --- a/rarslave.py +++ b/rarslave.py @@ -1,7 +1,10 @@ #!/usr/bin/env python # vim: set ts=4 sts=4 sw=4 textwidth=112 : -import re, os, sys +VERSION="2.0.0" +PROGRAM="rarslave2" + +import re, os, sys, optparse import par2parser import RarslaveConfig import RarslaveLogger @@ -12,6 +15,9 @@ import RarslaveLogger config = RarslaveConfig.RarslaveConfig() logger = RarslaveLogger.RarslaveLogger () +# Global options to be set / used later. +options = None + class RarslaveExtractor (object): def __init__ (self, type): @@ -20,10 +26,7 @@ class RarslaveExtractor (object): def addHead (self, dir, head): assert os.path.isdir (dir) - # REQUIRES that the dir is valid, but not that the file is valid, so that - # we can move a file that doesn't exist yet. - # FIXME: probably CAN add this back, since we should be running this AFTER repair. - #assert os.path.isfile (os.path.join (dir, head)) + assert os.path.isfile (os.path.join (dir, head)) full_head = os.path.join (dir, head) logger.addMessage ('Adding extraction head: %s' % full_head, RarslaveLogger.MessageType.Debug) @@ -97,6 +100,10 @@ class RarslaveExtractor (object): # FIXME: NOTE: mv will fail by itself if you're moving to the same dir! NOEXTRACT_CMD = config.get_value ('commands', 'noextract') + # Make sure that both files are not the same file. If they are, don't run at all. + if os.path.samefile (file, os.path.join (todir, file)): + return SUCCESS + cmd = NOEXTRACT_CMD % (file, todir) ret = run_command (cmd) @@ -127,7 +134,7 @@ class RarslaveRepairer (object): # Get set up basename = get_basename (self.file) - all_files = find_likely_files (basename, self.dir) + all_files = find_likely_files (self.dir, self.file) all_files.sort () par2_files = find_par2_files (all_files) @@ -163,12 +170,9 @@ def run_command (cmd, indir=None): assert os.path.isdir (indir) # MUST be a directory! os.chdir (indir) - # FIXME: re-enable this after testing - print 'RUNNING (%s): %s' % (indir, cmd) - return SUCCESS - - # return os.system (cmd) - + ret = os.system (cmd) + os.chdir (pwd) + return ret def full_abspath (p): return os.path.abspath (os.path.expanduser (p)) @@ -190,18 +194,23 @@ def get_basename (name): return name -def find_likely_files (name, dir): +def find_likely_files (dir, p2file): """Finds files which are likely to be part of the set corresponding to $name in the directory $dir""" - if not os.path.isdir (os.path.abspath (dir)): - raise ValueError # bad directory given + assert os.path.isdir (dir) + assert os.path.isfile (os.path.join (dir, p2file)) + + basename = get_basename (p2file) dir = os.path.abspath (dir) - ename = re.escape (name) + ename = re.escape (basename) regex = re.compile ('^%s.*$' % (ename, )) - return [f for f in os.listdir (dir) if regex.match (f)] + name_matches = [f for f in os.listdir (dir) if regex.match (f)] + parsed_matches = par2parser.get_protected_files (dir, p2file) + + return name_matches + parsed_matches def find_par2_files (files): """Find all par2 files in the list $files""" @@ -222,16 +231,6 @@ def find_all_par2_files (dir): return find_par2_files (files) -def has_extension (f, ext): - """Checks if f has the extension ext""" - - if ext[0] != '.': - ext = '.' + ext - - ext = re.escape (ext) - regex = re.compile ('^.*%s$' % (ext, ), re.IGNORECASE) - return regex.match (f) - def find_extraction_heads (dir, files): """Takes a list of possible files and finds likely heads of extraction.""" @@ -257,7 +256,7 @@ def find_extraction_heads (dir, files): if is_newrar (files): extractor = RarslaveExtractor (TYPE_NEWRAR) - regex = re.compile ('^.*\.part01.rar$', re.IGNORECASE) + regex = re.compile ('^.*\.part0*1.rar$', re.IGNORECASE) for f in files: if regex.match (f): extractor.addHead (dir, f) @@ -296,42 +295,41 @@ def find_extraction_heads (dir, files): # Make sure we found the type if extractor == None: logger.addMessage ('Not able to find an extractor for this type of set: %s' % p2files[0], - RarslaveLogger.MessageType.Fatal) + RarslaveLogger.MessageType.Verbose) # No-heads here, but it's better than failing completely extractor = RarslaveExtractor (TYPE_NOEXTRACT) return extractor -def is_oldrar (files): - for f in files: - if has_extension (f, '.r00'): - return True +def generic_matcher (files, regex, nocase=False): + """Run the regex over the files, and see if one matches or not. + NOTE: this does not return the matches, just if a match occurred.""" - return False + if nocase: + cregex = re.compile (regex, re.IGNORECASE) + else: + cregex = re.compile (regex) -def is_newrar (files): for f in files: - if has_extension (f, '.part01.rar'): + if cregex.match (f): return True return False -def is_zip (files): - for f in files: - if has_extension (f, '.zip'): - return True +def is_oldrar (files): + return generic_matcher (files, '^.*\.r00$') - return False +def is_newrar (files): + return generic_matcher (files, '^.*\.part0*1\.rar$') + +def is_zip (files): + return generic_matcher (files, '^.*\.zip$') def is_noextract (files): # Type that needs no extraction. # TODO: Add others ??? - for f in files: - if has_extension (f, '.001'): - return True - - return False + return generic_matcher (files, '^.*\.001$') def find_deleteable_files (files): # Deleteable types regex should come from the config @@ -359,7 +357,7 @@ class PAR2Set (object): self.file = file basename = get_basename (file) - self.likely_files = find_likely_files (basename, dir) + self.likely_files = find_likely_files (dir, file) def __list_eq (self, l1, l2): @@ -390,7 +388,7 @@ class PAR2Set (object): return -ECHECK # Extraction Stage - EXTRACT_DIR = config.get_value ('directories', 'extract_directory') + EXTRACT_DIR = options.extract_dir extractor = find_extraction_heads (self.dir, self.likely_files) ret = extractor.extract (EXTRACT_DIR) @@ -399,9 +397,9 @@ class PAR2Set (object): return -EEXTRACT # Deletion Stage - DELETE_INTERACTIVE = config.get_value ('options', 'interactive') + DELETE_INTERACTIVE = options.interactive deleteable_files = find_deleteable_files (self.likely_files) - ret = delete_list (deleteable_files, DELETE_INTERACTIVE) + ret = delete_list (self.dir, deleteable_files, DELETE_INTERACTIVE) if ret != SUCCESS: logger.addMessage ('Deletion stage failed for: %s' % par2head, RarslaveLogger.MessageType.Fatal) @@ -410,9 +408,11 @@ class PAR2Set (object): logger.addMessage ('Successfully completed: %s' % par2head) return SUCCESS -def delete_list (files, interactive=False): +def delete_list (dir, files, interactive=False): # Delete a list of files + assert os.path.isdir (dir) + done = False valid_y = ['Y', 'YES'] valid_n = ['N', 'NO'] @@ -429,9 +429,7 @@ def delete_list (files, interactive=False): return SUCCESS for f in files: - # FIXME: re-enable this in production - # os.remove (f) - print 'rm \"%s\"' % f + os.remove (os.path.join (dir, f)) return SUCCESS @@ -451,38 +449,177 @@ def generate_all_parsets (dir): return parsets -def main (): - TOPDIR = os.path.abspath ('test_material') +def check_required_progs(): + """Check if the required programs are installed""" - for (dir, subdirs, files) in os.walk (TOPDIR): - parsets = generate_all_parsets (dir) - for p in parsets: - p.run_all () + shell_not_found = 32512 + needed = [] + + if run_command ('par2repair --help > /dev/null 2>&1') == shell_not_found: + needed.append ('par2repair') + + if run_command ('unrar --help > /dev/null 2>&1') == shell_not_found: + needed.append ('unrar') + + if run_command ('unzip --help > /dev/null 2>&1') == shell_not_found: + needed.append ('unzip') + + if needed: + for n in needed: + print 'Needed program "%s" not found in $PATH' % (n, ) - print '\nRARSLAVE STATUS\n' + sys.exit(1) + +def run_options (options): + + # Fix directories + options.work_dir = full_abspath (options.work_dir) + + # Make sure that the directory is valid + if not os.path.isdir (options.work_dir): + sys.stderr.write ('\"%s\" is not a valid directory. Use the \"-d\"\n' % options.work_dir) + sys.stderr.write ('option to override the working directory temporarily, or edit the\n') + sys.stderr.write ('configuration file to override the working directory permanently.\n') + sys.exit (1) + + if options.extract_dir != None: + options.extract_dir = full_abspath (options.extract_dir) + + if options.version: + print PROGRAM + ' - ' + VERSION + print + print 'Copyright (c) 2005,2006 Ira W. Snyder (devel@irasnyder.com)' + print + print 'This program comes with ABSOLUTELY NO WARRANTY.' + print 'This is free software, and you are welcome to redistribute it' + print 'under certain conditions. See the file COPYING for details.' + sys.exit (0) + + if options.check_progs: + check_required_progs () + + if options.write_def_config: + config.write_config (default=True) + + if options.write_config: + config.write_config () + +def find_loglevel (options): + + loglevel = options.verbose - options.quiet + + if loglevel < RarslaveLogger.MessageType.Fatal: + loglevel = RarslaveLogger.MessageType.Fatal + + if loglevel > RarslaveLogger.MessageType.Debug: + loglevel = RarslaveLogger.MessageType.Debug + + return loglevel + +def printMessageTable (loglevel): - # Used in '--quiet' mode if logger.hasFatalMessages (): print '\nFatal Messages\n' + '=' * 80 logger.printLoglevel (RarslaveLogger.MessageType.Fatal) - # Used in no options mode + if loglevel == RarslaveLogger.MessageType.Fatal: + return + if logger.hasNormalMessages (): print '\nNormal Messages\n' + '=' * 80 logger.printLoglevel (RarslaveLogger.MessageType.Normal) - # Used in --verbose mode + if loglevel == RarslaveLogger.MessageType.Normal: + return + if logger.hasVerboseMessages (): print '\nVerbose Messages\n' + '=' * 80 logger.printLoglevel (RarslaveLogger.MessageType.Verbose) - # Used in --debug mode + if loglevel == RarslaveLogger.MessageType.Verbose: + return + if logger.hasDebugMessages (): print '\nDebug Messages\n' + '=' * 80 logger.printLoglevel (RarslaveLogger.MessageType.Debug) - print '\n\nALL MESSAGES:' - logger.printAllMessages () + return + +def main (): + + # Build the OptionParser + parser = optparse.OptionParser() + parser.add_option('-n', '--not-recursive', + action='store_false', dest='recursive', + default=config.get_value('options', 'recursive'), + help="Don't run recursively") + + parser.add_option('-d', '--work-dir', + dest='work_dir', type='string', + default=config.get_value('directories', 'working_directory'), + help="Start running at DIR", metavar='DIR') + + parser.add_option('-e', '--extract-dir', + dest='extract_dir', type='string', + default=config.get_value('directories', 'extract_directory'), + help="Extract to DIR", metavar='DIR') + + parser.add_option('-p', '--check-required-programs', + action='store_true', dest='check_progs', + default=False, + help="Check for required programs") + + parser.add_option('-f', '--write-default-config', + action='store_true', dest='write_def_config', + default=False, help="Write out a new default config") + + parser.add_option('-c', '--write-new-config', + action='store_true', dest='write_config', + default=False, help="Write out the current config") + + parser.add_option('-i', '--interactive', dest='interactive', action='store_true', + default=config.get_value('options', 'interactive'), + help="Confirm before removing files") + + parser.add_option('-q', '--quiet', dest='quiet', action='count', + default=0, help="Output fatal messages only") + + parser.add_option('-v', '--verbose', dest='verbose', action='count', + default=0, help="Output extra information") + + parser.add_option('-V', '--version', dest='version', action='store_true', + default=False, help="Output version information") + + parser.version = VERSION + + # Parse the given options + global options + (options, args) = parser.parse_args() + + # Run any special actions that are needed on these options + run_options (options) + + # Find the loglevel using the options given + loglevel = find_loglevel (options) + + # Run recursively + if options.recursive: + for (dir, subdirs, files) in os.walk (options.work_dir): + parsets = generate_all_parsets (dir) + for p in parsets: + p.run_all () + + # Non-recursive + else: + parsets = generate_all_parsets (options.work_dir) + for p in parsets: + p.run_all () + + # Print the results + printMessageTable (loglevel) + + # Done! + return 0 if __name__ == '__main__': main ()