From f2f961bc4b0dcedb3a6108b14506e4416a6628bf Mon Sep 17 00:00:00 2001 From: "Ira W. Snyder" Date: Thu, 28 Dec 2006 11:40:17 -0800 Subject: [PATCH] [RARSLAVE] Refactoring of PAR2Set This patch moves much of the functionality required only by the PAR2Set class from the global scope into the PAR2Set class itself. It is now much more careful about only generating sets of data once (where possible). Also, it is much more careful to re-generate a set of data when it is possible (and likely) that it has changed. Fixes: Failure to remove original versions (.1 extension) of repaired files. Signed-off-by: Ira W. Snyder --- rarslave.py | 277 ++++++++++++++++++++++++++++------------------------ 1 file changed, 152 insertions(+), 125 deletions(-) diff --git a/rarslave.py b/rarslave.py index 6ffe57f..ee8be7e 100644 --- a/rarslave.py +++ b/rarslave.py @@ -113,53 +113,6 @@ class RarslaveExtractor (object): return SUCCESS - - -class RarslaveRepairer (object): - # Verify (and repair) the set - # Make sure it worked, otherwise clean up and return failure - - def __init__ (self, dir, file, join=False): - self.dir = dir # the directory containing the par2 file - self.file = file # the par2 file - self.join = join # True if the par2 set is 001 002 ... - - assert os.path.isdir (dir) - assert os.path.isfile (os.path.join (dir, file)) - - def checkAndRepair (self): - # Form the command: - # par2repair -- PAR2 PAR2_EXTRA [JOIN_FILES] - PAR2_CMD = config.get_value ('commands', 'par2repair') - - # Get set up - basename = get_basename (self.file) - all_files = find_likely_files (self.dir, self.file) - all_files.sort () - par2_files = find_par2_files (all_files) - - # assemble the command - command = "%s \"%s\" " % (PAR2_CMD, self.file) - - for f in par2_files: - if f != self.file: - command += "\"%s\" " % os.path.split (f)[1] - - if self.join: - for f in all_files: - if f not in par2_files: - command += "\"%s\" " % os.path.split (f)[1] - - # run the command - ret = run_command (command, self.dir) - - # check the result - if ret != 0: - logger.addMessage ('PAR2 Check / Repair failed: %s' % self.file, RarslaveLogger.MessageType.Fatal) - return -ECHECK - - return SUCCESS - def run_command (cmd, indir=None): # Runs the specified command-line in the directory given (or, in the current directory # if none is given). It returns the status code given by the application. @@ -194,28 +147,6 @@ def get_basename (name): return name -def find_likely_files (dir, p2file): - """Finds files which are likely to be part of the set corresponding - to $name in the directory $dir""" - - 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 (basename) - regex = re.compile ('^%s.*$' % (ename, )) - - name_matches = [f for f in os.listdir (dir) if regex.match (f)] - try: - parsed_matches = Par2Parser.get_protected_files (dir, p2file) - except (EnvironmentError, OSError, OverflowError): - parsed_matches = [] - logger.addMessage ('Bad par2 file: %s' % p2file, RarslaveLogger.MessageType.Fatal) - - return name_matches + parsed_matches - def find_par2_files (files): """Find all par2 files in the list $files""" @@ -335,15 +266,6 @@ def is_noextract (files): # TODO: Add others ??? return generic_matcher (files, '^.*\.001$') -def find_deleteable_files (dir, p2file): - likely = find_likely_files (dir, p2file) - DELETE_REGEX = config.get_value ('regular expressions', 'delete_regex') - dregex = re.compile (DELETE_REGEX, re.IGNORECASE) - - dfiles = [f for f in likely if dregex.match (f)] - dset = set(dfiles) # to eliminate dupes - return list(dset) - def printlist (li): for f in li: print f @@ -351,18 +273,28 @@ def printlist (li): class PAR2Set (object): dir = None - file = None - likely_files = [] + p2file = None # The starting par2 + basename = None # The p2file's basename + all_p2files = [] + name_matched_files = [] # Files that match by basename of the p2file + prot_matched_files = [] # Files that match by being protected members - def __init__ (self, dir, file): + def __init__ (self, dir, p2file): assert os.path.isdir (dir) - assert os.path.isfile (os.path.join (dir, file)) + assert os.path.isfile (os.path.join (dir, p2file)) self.dir = dir - self.file = file + self.p2file = p2file + self.basename = get_basename (p2file) + + # Find files that match by name only + self.name_matched_files = self.__find_name_matches (self.dir, self.basename) - basename = get_basename (file) - self.likely_files = find_likely_files (dir, file) + # Find all par2 files for this set using name matches + self.all_p2files = find_par2_files (self.name_matched_files) + + # Try to get the protected files for this set + self.prot_matched_files = self.__parse_all_par2 () def __list_eq (self, l1, l2): @@ -376,68 +308,163 @@ class PAR2Set (object): return True def __eq__ (self, rhs): - return self.__list_eq (self.likely_files, rhs.likely_files) + return (self.dir == rhs.dir) and (self.basename == rhs.basename) and \ + self.__list_eq (self.name_matched_files, rhs.name_matched_files) and \ + self.__list_eq (self.prot_matched_files, rhs.prot_matched_files) - def run_all (self): - par2files = find_par2_files (self.likely_files) - par2head = par2files[0] + def __parse_all_par2 (self): + """Searches though self.all_p2files and tries to parse at least one of them""" + done = False + files = [] + + for f in self.all_p2files: + + # Exit early if we've found a good file + if done: + break + + try: + files = Par2Parser.get_protected_files (self.dir, f) + done = True + except (EnvironmentError, OSError, OverflowError): + logger.addMessage ('Corrupt PAR2 file: %s' % f, RarslaveLogger.MessageType.Fatal) + + # Now that we're out of the loop, check if we really finished + if not done: + logger.addMessage ('All PAR2 files corrupt for: %s' % self.p2file, RarslaveLogger.MessageType.Fatal) + + # Return whatever we've got, empty or not + return files + + def __find_name_matches (self, dir, basename): + """Finds files which are likely to be part of the set corresponding + to $name in the directory $dir""" + + assert os.path.isdir (dir) + + ename = re.escape (basename) + regex = re.compile ('^%s.*$' % (ename, )) + + return [f for f in os.listdir (dir) if regex.match (f)] - join = is_noextract (self.likely_files) + def __update_name_matches (self): + """Updates the self.name_matched_files variable with the most current information. + This should be called after the directory contents are likely to change.""" + + self.name_matched_files = self.__find_name_matches (self.dir, self.basename) + + def runCheckAndRepair (self): + PAR2_CMD = config.get_value ('commands', 'par2repair') + + # Get set up + all_files = self.name_matched_files + self.prot_matched_files + join = is_noextract (all_files) + + # assemble the command + # par2repair -- PAR2 PAR2_EXTRA [JOIN_FILES] + command = "%s \"%s\" " % (PAR2_CMD, self.p2file) + + for f in self.all_p2files: + if f != self.p2file: + command += "\"%s\" " % os.path.split (f)[1] + + if join: + for f in all_files: + if f not in self.p2files: + command += "\"%s\" " % os.path.split (f)[1] + + # run the command + ret = run_command (command, self.dir) + + # check the result + if ret != 0: + logger.addMessage ('PAR2 Check / Repair failed: %s' % self.p2file, RarslaveLogger.MessageType.Fatal) + return -ECHECK + + return SUCCESS + + def __find_deleteable_files (self): + all_files = self.name_matched_files + self.prot_matched_files + DELETE_REGEX = config.get_value ('regular expressions', 'delete_regex') + dregex = re.compile (DELETE_REGEX, re.IGNORECASE) + + dfiles = [f for f in all_files if dregex.match (f)] + dset = set(dfiles) # to eliminate dupes + return list(dset) + + def __delete_list_of_files (self, dir, files, interactive=False): + # Delete a list of files + + assert os.path.isdir (dir) + + done = False + valid_y = ['Y', 'YES'] + valid_n = ['N', 'NO', ''] + + if interactive: + while not done: + print 'Do you want to delete the following?:' + printlist (files) + s = raw_input ('Delete [y/N]: ').upper() + + if s in valid_y + valid_n: + done = True + + if s in valid_n: + return SUCCESS + + for f in files: + try: + os.remove (os.path.join (dir, f)) + logger.addMessage ('Deleteing: %s' % os.path.join (dir, f), RarslaveLogger.MessageType.Debug) + except: + logger.addMessage ('Failed to delete: %s' % os.path.join (dir, f), + RarslaveLogger.MessageType.Fatal) + return -EDELETE + + return SUCCESS + + def runDelete (self): + deleteable_files = self.__find_deleteable_files () + ret = self.__delete_list_of_files (self.dir, deleteable_files, options.interactive) + + return ret + + def run_all (self): + all_files = self.name_matched_files + self.prot_matched_files # Repair Stage - repairer = RarslaveRepairer (self.dir, par2head, join) - ret = repairer.checkAndRepair () + ret = self.runCheckAndRepair () if ret != SUCCESS: - logger.addMessage ('Repair stage failed for: %s' % par2head, RarslaveLogger.MessageType.Fatal) + logger.addMessage ('Repair stage failed for: %s' % self.p2file, RarslaveLogger.MessageType.Fatal) return -ECHECK + self.__update_name_matches () + all_files = self.name_matched_files + self.prot_matched_files + # Extraction Stage EXTRACT_DIR = options.extract_dir - extractor = find_extraction_heads (self.dir, self.likely_files) + extractor = find_extraction_heads (self.dir, all_files) ret = extractor.extract (EXTRACT_DIR) if ret != SUCCESS: - logger.addMessage ('Extraction stage failed for: %s' % par2head, RarslaveLogger.MessageType.Fatal) + logger.addMessage ('Extraction stage failed for: %s' % self.p2file, RarslaveLogger.MessageType.Fatal) return -EEXTRACT + self.__update_name_matches () + all_files = self.name_matched_files + self.prot_matched_files + # Deletion Stage - DELETE_INTERACTIVE = options.interactive - deleteable_files = find_deleteable_files (self.dir, par2head) - ret = delete_list (self.dir, deleteable_files, DELETE_INTERACTIVE) + ret = self.runDelete () if ret != SUCCESS: - logger.addMessage ('Deletion stage failed for: %s' % par2head, RarslaveLogger.MessageType.Fatal) + logger.addMessage ('Deletion stage failed for: %s' % self.p2file, RarslaveLogger.MessageType.Fatal) return -EDELETE - logger.addMessage ('Successfully completed: %s' % par2head) + logger.addMessage ('Successfully completed: %s' % self.p2file) return SUCCESS -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', ''] - - if interactive: - while not done: - print 'Do you want to delete the following?:' - printlist (files) - s = raw_input ('Delete [y/N]: ').upper() - - if s in valid_y + valid_n: - done = True - - if s in valid_n: - return SUCCESS - - for f in files: - os.remove (os.path.join (dir, f)) - - return SUCCESS def generate_all_parsets (dir): -- 2.25.1