X-Git-Url: https://www.irasnyder.com/gitweb/?p=rarslave2.git;a=blobdiff_plain;f=rarslave.py;h=2e9861a78e097f3e4e888ea261a5c2d16d0527e2;hp=6532de57de81ce6d34a5a88f5f42018450d66be0;hb=66eefc5c771ec365dd165aa42cd7e27e18fade0d;hpb=f4ae5ea4515b9a7d8a3198e13cd4472da748a99c diff --git a/rarslave.py b/rarslave.py index 6532de5..2e9861a 100644 --- a/rarslave.py +++ b/rarslave.py @@ -2,6 +2,159 @@ # vim: set ts=4 sts=4 sw=4 textwidth=112 : import re, os, sys +import par2parser + +# Global Variables +(TYPE_OLDRAR, TYPE_NEWRAR, TYPE_ZIP, TYPE_NOEXTRACT) = range (4) +(ECHECK, EEXTRACT, EDELETE) = range(1,4) + +class RarslaveExtractor (object): + + def __init__ (self, type): + self.type = type + self.heads = [] + + 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)) + + self.heads.append (os.path.join (dir, head)) + + def extract (self, todir=None): + # Extract all heads of this set + + # Create the directory $todir if it doesn't exist + if todir != None and not os.path.isdir (todir): + # TODO: LOGGER + try: + os.makedirs (todir) + except OSError: + # TODO: LOGGER + return -EEXTRACT + + # Extract all heads + extraction_func = \ + { TYPE_OLDRAR : self.__extract_rar, + TYPE_NEWRAR : self.__extract_rar, + TYPE_ZIP : self.__extract_zip, + TYPE_NOEXTRACT : self.__extract_noextract }[self.type] + + # Call the extraction function on each head + for h in self.heads: + if todir == None: + # Run in the head's directory + extraction_func (h, os.path.dirname (h)) + else: + extraction_func (h, todir) + + def __extract_rar (self, file, todir): + assert os.path.isfile (file) + assert os.path.isdir (todir) + + RAR_CMD = 'unrar x -o+ -- ' + + cmd = '%s \"%s\"' % (RAR_CMD, file) + ret = run_command (cmd, todir) + + # Check error code + if ret != 0: + return -EEXTRACT + + def __extract_zip (self, file, todir): + ZIP_CMD = 'unzip \"%s\" -d \"%s\"' + + cmd = ZIP_CMD % (file, todir) + ret = run_command (cmd) + + # Check error code + if ret != 0: + return -EEXTRACT + + def __extract_noextract (self, file, todir): + # Just move this file to the $todir, since no extraction is needed + # FIXME: NOTE: mv will fail by itself if you're moving to the same dir! + cmd = 'mv \"%s\" \"%s\"' % (file, todir) + ret = run_command (cmd) + + # Check error code + if ret != 0: + return -EEXTRACT + + + +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 = 'par2repair -- ' + + # Get set up + basename = get_basename (self.file) + all_files = find_likely_files (basename, self.dir) + 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\" " % get_filename(f) + + if self.join: + for f in all_files: + if f not in par2_files: + command += "\"%s\" " % get_filename(f) + + # run the command + ret = run_command (command, self.dir) + + # check the result + if ret != 0: + # TODO: logger + print 'error during checkAndRepair()' + return -ECHECK + +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. + + pwd = os.getcwd () + + if 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 0 + + # return os.system (cmd) + + +def full_abspath (p): + return os.path.abspath (os.path.expanduser (p)) + +def get_filename (f): + # TODO: I don't think that we should enforce this... + # TODO: ... because I think we should be able to get the filename, regardless + # TODO: of whether this is a legit filename RIGHT NOW or not. + # assert os.path.isfile (f) + return os.path.split (f)[1] def get_basename (name): """Strips most kinds of endings from a filename""" @@ -33,17 +186,23 @@ def find_likely_files (name, dir): return [f for f in os.listdir (dir) if regex.match (f)] +def find_par2_files (files): + """Find all par2 files in the list $files""" + + regex = re.compile ('^.*\.par2$', re.IGNORECASE) + return [f for f in files if regex.match (f)] + def find_all_par2_files (dir): """Finds all par2 files in a directory""" + # NOTE: does NOT return absolute paths if not os.path.isdir (os.path.abspath (dir)): raise ValueError # bad directory given dir = os.path.abspath (dir) - regex = re.compile ('^.*\.par2$', re.IGNORECASE) + files = os.listdir (dir) - # Find all files - return [f for f in os.listdir (dir) if regex.match (f)] + return find_par2_files (files) def has_extension (f, ext): """Checks if f has the extension ext""" @@ -55,7 +214,7 @@ def has_extension (f, ext): regex = re.compile ('^.*%s$' % (ext, ), re.IGNORECASE) return regex.match (f) -def find_extraction_heads (files): +def find_extraction_heads (dir, files): """Takes a list of possible files and finds likely heads of extraction.""" @@ -67,36 +226,59 @@ def find_extraction_heads (files): # 2) post rar-3.0: .part01.rar .part02.rar # 3) zip all ver: .zip - heads = [] + extractor = None + p2files = find_par2_files (files) # Old RAR type, find all files ending in .rar if is_oldrar (files): + extractor = RarslaveExtractor (TYPE_OLDRAR) regex = re.compile ('^.*\.rar$', re.IGNORECASE) for f in files: if regex.match (f): - heads.append (f) - - return heads + extractor.addHead (dir, f) if is_newrar (files): + extractor = RarslaveExtractor (TYPE_NEWRAR) regex = re.compile ('^.*\.part01.rar$', re.IGNORECASE) for f in files: if regex.match (f): - heads.append (f) - - return heads + extractor.addHead (dir, f) if is_zip (files): + extractor = RarslaveExtractor (TYPE_ZIP) regex = re.compile ('^.*\.zip$', re.IGNORECASE) for f in files: if regex.match (f): - heads.append (f) + extractor.addHead (dir, f) - return heads + if is_noextract (files): + # Use the Par2 Parser (from cfv) here to find out what files are protected. + # Since these are not being extracted, they will be mv'd to another directory + # later. + extractor = RarslaveExtractor (TYPE_NOEXTRACT) - # Not a type we know yet - raise ValueError + for f in p2files: + done = False + try: + prot_files = par2parser.get_protected_files (dir, f) + done = True + except: #FIXME: add the actual exceptions + print 'ERROR PARSING P2FILE ...', f + continue + + if done: + break + + if done: + for f in prot_files: + extractor.addHead (dir, f) + else: + print 'BADNESS' + # Make sure we found the type + assert extractor != None + + return extractor def is_oldrar (files): for f in files: @@ -113,9 +295,124 @@ def is_zip (files): if has_extension (f, '.zip'): return True +def is_noextract (files): + # Type that needs no extraction. + # TODO: Add others ??? + for f in files: + if has_extension (f, '.001'): + return True + +def find_deleteable_files (files): + # Deleteable types regex should come from the config + dfiles = [] + dregex = re.compile ('^.*\.(par2|\d|\d\d\d|rar|r\d\d|zip)$', re.IGNORECASE) + + return [f for f in files if dregex.match (f)] + +def printlist (li): + for f in li: + print f + +class PAR2Set (object): + + dir = None + file = None + likely_files = [] + + def __init__ (self, dir, file): + assert os.path.isdir (dir) + assert os.path.isfile (os.path.join (dir, file)) + + self.dir = dir + self.file = file + + basename = get_basename (file) + self.likely_files = find_likely_files (basename, dir) + + def __list_eq (self, l1, l2): + + if len(l1) != len(l2): + return False + + for e in l1: + if e not in l2: + return False + + return True + + def __eq__ (self, rhs): + return self.__list_eq (self.likely_files, rhs.likely_files) + + def run_all (self): + par2files = find_par2_files (self.likely_files) + par2head = par2files[0] + + join = is_noextract (self.likely_files) + + # Repair Stage + repairer = RarslaveRepairer (self.dir, par2head, join) + ret = repairer.checkAndRepair () # FIXME: Check return value + + if ret: # FAILURE + return -ECHECK + + # Extraction Stage + extractor = find_extraction_heads (self.dir, self.likely_files) + ret = extractor.extract ('extract_dir') # FIXME: Get it from the config + + if ret: # FAILURE + return -EEXTRACT + + # Deletion Stage + deleteable_files = find_deleteable_files (self.likely_files) + ret = delete_list (deleteable_files) + + if ret: # FAILURE + return -EDELETE + + return 0 + +def delete_list (files, interactive=False): + # Delete a list of files + # TODO: Add the ability to confirm deletion, like in the original rarslave + + if interactive: + # TODO: prompt here + # prompt -> OK_TO_DELETE -> do nothing, fall through + # prompt -> NOT_OK -> return immediately + pass + + for f in files: + # FIXME: re-enable this in production + # os.remove (f) + print 'rm', f + + return 0 + + +def generate_all_parsets (dir): + # Generate all parsets in the given directory. + + assert os.path.isdir (dir) # Directory MUST be valid + + parsets = [] + p2files = find_all_par2_files (dir) + + for f in p2files: + p = PAR2Set (dir, f) + if p not in parsets: + parsets.append (p) + + return parsets def main (): - print find_all_par2_files ('/home/irasnyd/downloads/test_material/01/') + TOPDIR = os.path.abspath ('test_material') + + for (dir, subdirs, files) in os.walk (TOPDIR): + print 'DEBUG: IN DIRECTORY:', dir + parsets = generate_all_parsets (dir) + for p in parsets: + p.run_all () if __name__ == '__main__': main ()