2 # vim: set ts=4 sts=4 sw=4 textwidth=112 :
9 (TYPE_OLDRAR, TYPE_NEWRAR, TYPE_ZIP, TYPE_NOEXTRACT) = range (4)
10 (ECHECK, EEXTRACT, EDELETE) = range(1,4)
11 config = RarslaveConfig.RarslaveConfig()
13 class RarslaveExtractor (object):
15 def __init__ (self, type):
19 def addHead (self, dir, head):
20 assert os.path.isdir (dir)
21 # REQUIRES that the dir is valid, but not that the file is valid, so that
22 # we can move a file that doesn't exist yet.
23 # FIXME: probably CAN add this back, since we should be running this AFTER repair.
24 #assert os.path.isfile (os.path.join (dir, head))
26 self.heads.append (os.path.join (dir, head))
28 def extract (self, todir=None):
29 # Extract all heads of this set
31 # Create the directory $todir if it doesn't exist
32 if todir != None and not os.path.isdir (todir):
42 { TYPE_OLDRAR : self.__extract_rar,
43 TYPE_NEWRAR : self.__extract_rar,
44 TYPE_ZIP : self.__extract_zip,
45 TYPE_NOEXTRACT : self.__extract_noextract }[self.type]
47 # Call the extraction function on each head
50 # Run in the head's directory
51 extraction_func (h, os.path.dirname (h))
53 extraction_func (h, todir)
55 def __extract_rar (self, file, todir):
56 assert os.path.isfile (file)
57 assert os.path.isdir (todir)
59 RAR_CMD = config.get_value ('commands', 'unrar')
61 cmd = '%s \"%s\"' % (RAR_CMD, file)
62 ret = run_command (cmd, todir)
68 def __extract_zip (self, file, todir):
69 ZIP_CMD = config.get_value ('commands', 'unzip')
71 cmd = ZIP_CMD % (file, todir)
72 ret = run_command (cmd)
78 def __extract_noextract (self, file, todir):
79 # Just move this file to the $todir, since no extraction is needed
80 # FIXME: NOTE: mv will fail by itself if you're moving to the same dir!
81 NOEXTRACT_CMD = config.get_value ('commands', 'noextract')
83 cmd = NOEXTRACT_CMD % (file, todir)
84 ret = run_command (cmd)
92 class RarslaveRepairer (object):
93 # Verify (and repair) the set
94 # Make sure it worked, otherwise clean up and return failure
96 def __init__ (self, dir, file, join=False):
97 self.dir = dir # the directory containing the par2 file
98 self.file = file # the par2 file
99 self.join = join # True if the par2 set is 001 002 ...
101 assert os.path.isdir (dir)
102 assert os.path.isfile (os.path.join (dir, file))
104 def checkAndRepair (self):
106 # par2repair -- PAR2 PAR2_EXTRA [JOIN_FILES]
107 PAR2_CMD = config.get_value ('commands', 'par2repair')
110 basename = get_basename (self.file)
111 all_files = find_likely_files (basename, self.dir)
113 par2_files = find_par2_files (all_files)
115 # assemble the command
116 command = "%s \"%s\" " % (PAR2_CMD, self.file)
120 command += "\"%s\" " % get_filename(f)
124 if f not in par2_files:
125 command += "\"%s\" " % get_filename(f)
128 ret = run_command (command, self.dir)
133 print 'error during checkAndRepair()'
136 def run_command (cmd, indir=None):
137 # Runs the specified command-line in the directory given (or, in the current directory
138 # if none is given). It returns the status code given by the application.
143 assert os.path.isdir (indir) # MUST be a directory!
146 # FIXME: re-enable this after testing
147 print 'RUNNING (%s): %s' % (indir, cmd)
150 # return os.system (cmd)
153 def full_abspath (p):
154 return os.path.abspath (os.path.expanduser (p))
156 def get_filename (f):
157 # TODO: I don't think that we should enforce this...
158 # TODO: ... because I think we should be able to get the filename, regardless
159 # TODO: of whether this is a legit filename RIGHT NOW or not.
160 # assert os.path.isfile (f)
161 return os.path.split (f)[1]
163 def get_basename (name):
164 """Strips most kinds of endings from a filename"""
166 regex = config.get_value ('regular expressions', 'basename_regex')
167 r = re.compile (regex, re.IGNORECASE)
174 g = r.match (name).groups()
180 def find_likely_files (name, dir):
181 """Finds files which are likely to be part of the set corresponding
182 to $name in the directory $dir"""
184 if not os.path.isdir (os.path.abspath (dir)):
185 raise ValueError # bad directory given
187 dir = os.path.abspath (dir)
188 ename = re.escape (name)
189 regex = re.compile ('^%s.*$' % (ename, ))
191 return [f for f in os.listdir (dir) if regex.match (f)]
193 def find_par2_files (files):
194 """Find all par2 files in the list $files"""
196 PAR2_REGEX = config.get_value ('regular expressions', 'par2_regex')
197 regex = re.compile (PAR2_REGEX, re.IGNORECASE)
198 return [f for f in files if regex.match (f)]
200 def find_all_par2_files (dir):
201 """Finds all par2 files in a directory"""
202 # NOTE: does NOT return absolute paths
204 if not os.path.isdir (os.path.abspath (dir)):
205 raise ValueError # bad directory given
207 dir = os.path.abspath (dir)
208 files = os.listdir (dir)
210 return find_par2_files (files)
212 def has_extension (f, ext):
213 """Checks if f has the extension ext"""
218 ext = re.escape (ext)
219 regex = re.compile ('^.*%s$' % (ext, ), re.IGNORECASE)
220 return regex.match (f)
222 def find_extraction_heads (dir, files):
223 """Takes a list of possible files and finds likely heads of
226 # NOTE: perhaps this should happen AFTER repair is
227 # NOTE: successful. That way all files would already exist
229 # According to various sources online:
230 # 1) pre rar-3.0: .rar .r00 .r01 ...
231 # 2) post rar-3.0: .part01.rar .part02.rar
232 # 3) zip all ver: .zip
235 p2files = find_par2_files (files)
237 # Old RAR type, find all files ending in .rar
238 if is_oldrar (files):
239 extractor = RarslaveExtractor (TYPE_OLDRAR)
240 regex = re.compile ('^.*\.rar$', re.IGNORECASE)
243 extractor.addHead (dir, f)
245 if is_newrar (files):
246 extractor = RarslaveExtractor (TYPE_NEWRAR)
247 regex = re.compile ('^.*\.part01.rar$', re.IGNORECASE)
250 extractor.addHead (dir, f)
253 extractor = RarslaveExtractor (TYPE_ZIP)
254 regex = re.compile ('^.*\.zip$', re.IGNORECASE)
257 extractor.addHead (dir, f)
259 if is_noextract (files):
260 # Use the Par2 Parser (from cfv) here to find out what files are protected.
261 # Since these are not being extracted, they will be mv'd to another directory
263 extractor = RarslaveExtractor (TYPE_NOEXTRACT)
268 prot_files = par2parser.get_protected_files (dir, f)
270 except: #FIXME: add the actual exceptions
271 print 'ERROR PARSING P2FILE ...', f
279 extractor.addHead (dir, f)
283 # Make sure we found the type
284 assert extractor != None
288 def is_oldrar (files):
290 if has_extension (f, '.r00'):
293 def is_newrar (files):
295 if has_extension (f, '.part01.rar'):
300 if has_extension (f, '.zip'):
303 def is_noextract (files):
304 # Type that needs no extraction.
305 # TODO: Add others ???
307 if has_extension (f, '.001'):
310 def find_deleteable_files (files):
311 # Deleteable types regex should come from the config
313 DELETE_REGEX = config.get_value ('regular expressions', 'delete_regex')
314 dregex = re.compile (DELETE_REGEX, re.IGNORECASE)
316 return [f for f in files if dregex.match (f)]
322 class PAR2Set (object):
328 def __init__ (self, dir, file):
329 assert os.path.isdir (dir)
330 assert os.path.isfile (os.path.join (dir, file))
335 basename = get_basename (file)
336 self.likely_files = find_likely_files (basename, dir)
338 def __list_eq (self, l1, l2):
340 if len(l1) != len(l2):
349 def __eq__ (self, rhs):
350 return self.__list_eq (self.likely_files, rhs.likely_files)
353 par2files = find_par2_files (self.likely_files)
354 par2head = par2files[0]
356 join = is_noextract (self.likely_files)
359 repairer = RarslaveRepairer (self.dir, par2head, join)
360 ret = repairer.checkAndRepair () # FIXME: Check return value
366 EXTRACT_DIR = config.get_value ('directories', 'extract_directory')
367 extractor = find_extraction_heads (self.dir, self.likely_files)
368 ret = extractor.extract (EXTRACT_DIR)
374 DELETE_INTERACTIVE = config.get_value ('options', 'interactive')
375 deleteable_files = find_deleteable_files (self.likely_files)
376 ret = delete_list (deleteable_files, DELETE_INTERACTIVE)
383 def delete_list (files, interactive=False):
384 # Delete a list of files
385 # TODO: Add the ability to confirm deletion, like in the original rarslave
389 # prompt -> OK_TO_DELETE -> do nothing, fall through
390 # prompt -> NOT_OK -> return immediately
394 # FIXME: re-enable this in production
401 def generate_all_parsets (dir):
402 # Generate all parsets in the given directory.
404 assert os.path.isdir (dir) # Directory MUST be valid
407 p2files = find_all_par2_files (dir)
417 TOPDIR = os.path.abspath ('test_material')
419 for (dir, subdirs, files) in os.walk (TOPDIR):
420 print 'DEBUG: IN DIRECTORY:', dir
421 parsets = generate_all_parsets (dir)
425 if __name__ == '__main__':