[RSUTIL] Fix directory handling
[rarslave2.git] / PAR2Set / Base.py
index 012c01a..533ff4f 100644 (file)
@@ -1,13 +1,38 @@
 #!/usr/bin/env python
 # vim: set ts=4 sts=4 sw=4 textwidth=92:
 
-from RarslaveCommon import *
-import Par2Parser
+"""
+Holds the PAR2Set base class
+"""
+
+__author__    = "Ira W. Snyder (devel@irasnyder.com)"
+__copyright__ = "Copyright (c) 2006,2007 Ira W. Snyder (devel@irasnyder.com)"
+__license__   = "GNU GPL v2 (or, at your option, any later version)"
+
+#    Base.py
+#
+#    Copyright (C) 2006,2007  Ira W. Snyder (devel@irasnyder.com)
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 2 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 import re
 import os
 import logging
 
+import rsutil.common
+
 # This is a fairly generic class which does all of the major things that a PAR2
 # set will need to have done to be verified and extracted. For most "normal" types
 # you won't need to override hardly anything.
@@ -15,11 +40,12 @@ import logging
 # It is ok to override other functions if the need arises, just make sure that you
 # understand why things are done the way that they are in the original versions.
 #
-# Assumptions made about each of the run*() functions:
+# Assumptions made in the runVerifyAndRepair(), runExtract() and runDelete() functions:
 # ==============================================================================
-# The state of self.name_matched_files and self.prot_matched_files will be consistent
-# with the real, in-filesystem state at the time that they are called.
-# (This is why runAll() calls update_matches() all the time.)
+# The state of self.name_matched_files, self.prot_matched_files, and self.all_files
+# will be consistent with the real, in-filesystem state at the time that they are
+# called. This is the reason that runAll() calls update_matches() after running each
+# operation that will possibly change the filesystem.
 #
 # Required overrides:
 # ==============================================================================
@@ -28,6 +54,7 @@ import logging
 #
 
 class Base (object):
+       """Base class for all PAR2Set types"""
 
        # Instance Variables
        # ==========================================================================
@@ -39,40 +66,50 @@ class Base (object):
        # prot_matched_files    -- Files in this set, guessed by parsing the PAR2 only
 
        def __init__ (self, dir, p2file):
+               """Default constructor for all PAR2Set types
+
+                  dir -- a directory
+                  p2file -- a PAR2 file inside the given directory"""
+
                assert os.path.isdir (dir)
                assert os.path.isfile (os.path.join (dir, p2file))
 
                # The real "meat" of the class
                self.dir = dir
                self.p2file = p2file
-               self.basename = get_basename (p2file)
+               self.basename = rsutil.common.get_basename (p2file)
 
                # Find files that match by name only
-               self.name_matched_files = find_name_matches (self.dir, self.basename)
+               self.name_matched_files = rsutil.common.find_name_matches (self.dir, self.basename)
 
                # Find all par2 files for this set using name matches
-               self.all_p2files = find_par2_files (self.name_matched_files)
+               self.all_p2files = rsutil.common.find_par2_files (self.name_matched_files)
 
                # Try to get the protected files for this set
-               self.prot_matched_files = parse_all_par2 (self.dir, self.p2file, self.all_p2files)
+               self.prot_matched_files = rsutil.common.parse_all_par2 (self.dir, self.p2file, self.all_p2files)
 
                # Setup the all_files combined set (for convenience only)
-               self.all_files = no_duplicates (self.name_matched_files + self.prot_matched_files)
+               self.all_files = rsutil.common.no_duplicates (self.name_matched_files + self.prot_matched_files)
 
        def __eq__ (self, rhs):
+               """Check for equality between PAR2Set types"""
+
                return (self.dir == rhs.dir) and (self.basename == rhs.basename) and \
-                               list_eq (self.name_matched_files, rhs.name_matched_files) and \
-                               list_eq (self.prot_matched_files, rhs.prot_matched_files)
+                               rsutil.common.list_eq (self.name_matched_files, rhs.name_matched_files) and \
+                               rsutil.common.list_eq (self.prot_matched_files, rhs.prot_matched_files)
 
        def update_matches (self):
-               """Updates the contents of instance variables which are likely to change after
-               running an operation, usually one which will create new files."""
+               """Updates the contents of instance variables with the current in-filesystem state.
+                  This should be run after any operation which can create or delete files."""
 
-               self.name_matched_files = find_name_matches (self.dir, self.basename)
-               self.all_files = no_duplicates (self.name_matched_files + self.prot_matched_files)
+               self.name_matched_files = rsutil.common.find_name_matches (self.dir, self.basename)
+               self.all_files = rsutil.common.no_duplicates (self.name_matched_files + self.prot_matched_files)
 
        def runVerifyAndRepair (self):
-               PAR2_CMD = config_get_value ('commands', 'par2repair')
+               """Verify and Repair a PAR2Set. This is done using the par2repair command by
+                  default"""
+
+               PAR2_CMD = rsutil.common.config_get_value ('commands', 'par2repair')
 
                # assemble the command
                # par2repair -- PAR2 PAR2_EXTRA [JOIN_FILES]
@@ -83,23 +120,30 @@ class Base (object):
                                command += "\"%s\" " % os.path.split (f)[1]
 
                # run the command
-               ret = run_command (command, self.dir)
+               ret = rsutil.common.run_command (command, self.dir)
 
                # check the result
                if ret != 0:
                        logging.critical ('PAR2 Check / Repair failed: %s' % self.p2file)
-                       return -ECHECK
+                       return -rsutil.common.ECHECK
 
-               return SUCCESS
+               return rsutil.common.SUCCESS
 
        def find_deleteable_files (self):
-               DELETE_REGEX = config_get_value ('regular expressions', 'delete_regex')
+               """Find all files which are deletable by using the regular expression from the
+                  configuration file"""
+
+               DELETE_REGEX = rsutil.common.config_get_value ('regular expressions', 'delete_regex')
                dregex = re.compile (DELETE_REGEX, re.IGNORECASE)
 
                return [f for f in self.all_files if dregex.match (f)]
 
        def delete_list_of_files (self, dir, files, interactive=False):
-               # Delete a list of files
+               """Attempt to delete all files given
+
+                  dir -- the directory where the files live
+                  files -- the filenames themselves
+                  interactive -- prompt before deleteion"""
 
                assert os.path.isdir (dir)
 
@@ -118,7 +162,7 @@ class Base (object):
                                        done = True
 
                        if s in valid_n:
-                               return SUCCESS
+                               return rsutil.common.SUCCESS
 
                for f in files:
                        try:
@@ -126,65 +170,72 @@ class Base (object):
                                logging.debug ('Deleteing: %s' % os.path.join (dir, f))
                        except:
                                logging.error ('Failed to delete: %s' % os.path.join (dir, f))
-                               return -EDELETE
+                               return -rsutil.common.EDELETE
 
-               return SUCCESS
+               return rsutil.common.SUCCESS
 
        def runDelete (self):
+               """Run the delete operation and return the result"""
+
                deleteable_files = self.find_deleteable_files ()
                ret = self.delete_list_of_files (self.dir, deleteable_files, \
-                               options_get_value ('interactive'))
+                               rsutil.common.options_get_value ('interactive'))
 
                return ret
 
        def runAll (self):
+               """Run all of the major sections in the class: repair, extraction, and deletion."""
 
                # Repair Stage
                ret = self.runVerifyAndRepair ()
 
-               if ret != SUCCESS:
+               if ret != rsutil.common.SUCCESS:
                        logging.critical ('Repair stage failed for: %s' % self.p2file)
-                       return -ECHECK
+                       return -rsutil.common.ECHECK
 
                self.update_matches ()
 
                # Extraction Stage
                ret = self.runExtract ()
 
-               if ret != SUCCESS:
+               if ret != rsutil.common.SUCCESS:
                        logging.critical ('Extraction stage failed for: %s' % self.p2file)
-                       return -EEXTRACT
+                       return -rsutil.common.EEXTRACT
 
                self.update_matches ()
 
                # Deletion Stage
                ret = self.runDelete ()
 
-               if ret != SUCCESS:
+               if ret != rsutil.common.SUCCESS:
                        logging.critical ('Deletion stage failed for: %s' % self.p2file)
-                       return -EDELETE
+                       return -rsutil.common.EDELETE
 
                logging.info ('Successfully completed: %s' % self.p2file)
-               return SUCCESS
+               return rsutil.common.SUCCESS
 
        def safe_create_directory (self, dir):
+               """Safely create a directory, logging the result.
+
+                  dir -- the directory to create (None is ignored)"""
+
                if dir == None:
-                       return SUCCESS
+                       return rsutil.common.SUCCESS
 
                if os.path.isdir (dir):
-                       return SUCCESS
+                       return rsutil.common.SUCCESS
 
                try:
                        os.makedirs (dir)
                        logging.info ('Created directory: %s' % dir)
                except OSError:
                        logging.critical ('FAILED to create directory: %s' % dir)
-                       return -ECREATE
+                       return -rsutil.common.ECREATE
 
-               return SUCCESS
+               return rsutil.common.SUCCESS
 
        def runExtract (self, todir=None):
-               """Extract all heads of this set"""
+               """Extract all heads of this set and return the result"""
 
                # Extract to the head's dir if we don't care where to extract
                if todir == None:
@@ -193,31 +244,33 @@ class Base (object):
                # Create the directory $todir if it doesn't exist
                ret = self.safe_create_directory (todir)
 
-               if ret != SUCCESS:
-                       return -EEXTRACT
+               if ret != rsutil.common.SUCCESS:
+                       return -rsutil.common.EEXTRACT
 
                # Call the extraction function on each head
                for h in self.find_extraction_heads ():
-                       full_head = full_abspath (os.path.join (self.dir, h))
+                       full_head = rsutil.common.full_abspath (os.path.join (self.dir, h))
                        ret = self.extraction_function (full_head, todir)
                        logging.debug ('Extraction Function returned: %d' % ret)
 
                        # Check error code
-                       if ret != SUCCESS:
+                       if ret != rsutil.common.SUCCESS:
                                logging.critical ('Failed extracting: %s' % h)
-                               return -EEXTRACT
+                               return -rsutil.common.EEXTRACT
 
-               return SUCCESS
+               return rsutil.common.SUCCESS
 
        def find_extraction_heads (self):
+               """Find all extraction heads associated with this set. This must be
+                  overridden for the associated PAR2Set derived class to work."""
+
                assert False # You MUST override this on a per-type basis
 
        def extraction_function (self, file, todir):
-               # NOTE: Please keep the prototype the same for all overridden functions.
-               # Doing so will guarantee that your life is made much easier.
-               #
-               # Also note that the todir given will always be valid for the current directory
-               # when the function is called.
+               """Extract a single head of this PAR2Set's type.
+
+                  file -- the full path to the file to be extracted
+                  todir -- the directory to extract to"""
 
                assert False # You MUST override this on a per-type basis