[RARSLAVE] Refactoring of PAR2Set
[rarslave2.git] / rarslave.py
index 6ffe57f..ee8be7e 100644 (file)
@@ -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):