2 # vim: set ts=4 sts=4 sw=4 textwidth=112 :
8 (TYPE_OLDRAR, TYPE_NEWRAR, TYPE_ZIP, TYPE_NOEXTRACT) = range (4)
9 (ECHECK, EEXTRACT, EDELETE) = range(1,4)
11 class RarslaveExtractor (object):
13 def __init__ (self, type):
17 def addHead (self, dir, head):
18 assert os.path.isdir (dir)
19 # REQUIRES that the dir is valid, but not that the file is valid, so that
20 # we can move a file that doesn't exist yet.
21 # FIXME: probably CAN add this back, since we should be running this AFTER repair.
22 #assert os.path.isfile (os.path.join (dir, head))
24 self.heads.append (os.path.join (dir, head))
26 def extract (self, todir=None):
27 # Extract all heads of this set
29 # Create the directory $todir if it doesn't exist
30 if todir != None and not os.path.isdir (todir):
40 { TYPE_OLDRAR : self.__extract_rar,
41 TYPE_NEWRAR : self.__extract_rar,
42 TYPE_ZIP : self.__extract_zip,
43 TYPE_NOEXTRACT : self.__extract_noextract }[self.type]
45 # Call the extraction function on each head
48 # Run in the head's directory
49 extraction_func (h, os.path.dirname (h))
51 extraction_func (h, todir)
53 def __extract_rar (self, file, todir):
54 assert os.path.isfile (file)
55 assert os.path.isdir (todir)
57 RAR_CMD = 'unrar x -o+ -- '
59 cmd = '%s \"%s\"' % (RAR_CMD, file)
60 ret = run_command (cmd, todir)
66 def __extract_zip (self, file, todir):
67 ZIP_CMD = 'unzip \"%s\" -d \"%s\"'
69 cmd = ZIP_CMD % (file, todir)
70 ret = run_command (cmd)
76 def __extract_noextract (self, file, todir):
77 # Just move this file to the $todir, since no extraction is needed
78 # FIXME: NOTE: mv will fail by itself if you're moving to the same dir!
79 cmd = 'mv \"%s\" \"%s\"' % (file, todir)
80 ret = run_command (cmd)
88 class RarslaveRepairer (object):
89 # Verify (and repair) the set
90 # Make sure it worked, otherwise clean up and return failure
92 def __init__ (self, dir, file, join=False):
93 self.dir = dir # the directory containing the par2 file
94 self.file = file # the par2 file
95 self.join = join # True if the par2 set is 001 002 ...
97 assert os.path.isdir (dir)
98 assert os.path.isfile (os.path.join (dir, file))
100 def checkAndRepair (self):
102 # par2repair -- PAR2 PAR2_EXTRA [JOIN_FILES]
103 PAR2_CMD = 'par2repair -- '
106 basename = get_basename (self.file)
107 all_files = find_likely_files (basename, self.dir)
109 par2_files = find_par2_files (all_files)
111 # assemble the command
112 command = "%s \"%s\" " % (PAR2_CMD, self.file)
116 command += "\"%s\" " % get_filename(f)
120 if f not in par2_files:
121 command += "\"%s\" " % get_filename(f)
124 ret = run_command (command, self.dir)
129 print 'error during checkAndRepair()'
132 def run_command (cmd, indir=None):
133 # Runs the specified command-line in the directory given (or, in the current directory
134 # if none is given). It returns the status code given by the application.
139 assert os.path.isdir (indir) # MUST be a directory!
142 # FIXME: re-enable this after testing
143 print 'RUNNING (%s): %s' % (indir, cmd)
146 # return os.system (cmd)
149 def full_abspath (p):
150 return os.path.abspath (os.path.expanduser (p))
152 def get_filename (f):
153 # TODO: I don't think that we should enforce this...
154 # TODO: ... because I think we should be able to get the filename, regardless
155 # TODO: of whether this is a legit filename RIGHT NOW or not.
156 # assert os.path.isfile (f)
157 return os.path.split (f)[1]
159 def get_basename (name):
160 """Strips most kinds of endings from a filename"""
162 regex = '^(.+)\.(par2|vol\d+\+\d+|\d\d\d|part\d+|rar|zip|avi|mp4|mkv|ogm)$'
163 r = re.compile (regex, re.IGNORECASE)
170 g = r.match (name).groups()
176 def find_likely_files (name, dir):
177 """Finds files which are likely to be part of the set corresponding
178 to $name in the directory $dir"""
180 if not os.path.isdir (os.path.abspath (dir)):
181 raise ValueError # bad directory given
183 dir = os.path.abspath (dir)
184 ename = re.escape (name)
185 regex = re.compile ('^%s.*$' % (ename, ))
187 return [f for f in os.listdir (dir) if regex.match (f)]
189 def find_par2_files (files):
190 """Find all par2 files in the list $files"""
192 regex = re.compile ('^.*\.par2$', re.IGNORECASE)
193 return [f for f in files if regex.match (f)]
195 def find_all_par2_files (dir):
196 """Finds all par2 files in a directory"""
197 # NOTE: does NOT return absolute paths
199 if not os.path.isdir (os.path.abspath (dir)):
200 raise ValueError # bad directory given
202 dir = os.path.abspath (dir)
203 files = os.listdir (dir)
205 return find_par2_files (files)
207 def has_extension (f, ext):
208 """Checks if f has the extension ext"""
213 ext = re.escape (ext)
214 regex = re.compile ('^.*%s$' % (ext, ), re.IGNORECASE)
215 return regex.match (f)
217 def find_extraction_heads (dir, files):
218 """Takes a list of possible files and finds likely heads of
221 # NOTE: perhaps this should happen AFTER repair is
222 # NOTE: successful. That way all files would already exist
224 # According to various sources online:
225 # 1) pre rar-3.0: .rar .r00 .r01 ...
226 # 2) post rar-3.0: .part01.rar .part02.rar
227 # 3) zip all ver: .zip
230 p2files = find_par2_files (files)
232 # Old RAR type, find all files ending in .rar
233 if is_oldrar (files):
234 extractor = RarslaveExtractor (TYPE_OLDRAR)
235 regex = re.compile ('^.*\.rar$', re.IGNORECASE)
238 extractor.addHead (dir, f)
240 if is_newrar (files):
241 extractor = RarslaveExtractor (TYPE_NEWRAR)
242 regex = re.compile ('^.*\.part01.rar$', re.IGNORECASE)
245 extractor.addHead (dir, f)
248 extractor = RarslaveExtractor (TYPE_ZIP)
249 regex = re.compile ('^.*\.zip$', re.IGNORECASE)
252 extractor.addHead (dir, f)
254 if is_noextract (files):
255 # Use the Par2 Parser (from cfv) here to find out what files are protected.
256 # Since these are not being extracted, they will be mv'd to another directory
258 extractor = RarslaveExtractor (TYPE_NOEXTRACT)
263 prot_files = par2parser.get_protected_files (dir, f)
265 except: #FIXME: add the actual exceptions
266 print 'ERROR PARSING P2FILE ...', f
274 extractor.addHead (dir, f)
278 # Make sure we found the type
279 assert extractor != None
283 def is_oldrar (files):
285 if has_extension (f, '.r00'):
288 def is_newrar (files):
290 if has_extension (f, '.part01.rar'):
295 if has_extension (f, '.zip'):
298 def is_noextract (files):
299 # Type that needs no extraction.
300 # TODO: Add others ???
302 if has_extension (f, '.001'):
305 def find_deleteable_files (files):
306 # Deleteable types regex should come from the config
308 dregex = re.compile ('^.*\.(par2|\d|\d\d\d|rar|r\d\d|zip)$', re.IGNORECASE)
310 return [f for f in files if dregex.match (f)]
316 class PAR2Set (object):
322 def __init__ (self, dir, file):
323 assert os.path.isdir (dir)
324 assert os.path.isfile (os.path.join (dir, file))
329 basename = get_basename (file)
330 self.likely_files = find_likely_files (basename, dir)
332 def __list_eq (self, l1, l2):
334 if len(l1) != len(l2):
343 def __eq__ (self, rhs):
344 return self.__list_eq (self.likely_files, rhs.likely_files)
347 par2files = find_par2_files (self.likely_files)
348 par2head = par2files[0]
350 join = is_noextract (self.likely_files)
353 repairer = RarslaveRepairer (self.dir, par2head, join)
354 ret = repairer.checkAndRepair () # FIXME: Check return value
360 extractor = find_extraction_heads (self.dir, self.likely_files)
361 ret = extractor.extract ('extract_dir') # FIXME: Get it from the config
367 deleteable_files = find_deleteable_files (self.likely_files)
368 ret = delete_list (deleteable_files)
375 def delete_list (files, interactive=False):
376 # Delete a list of files
377 # TODO: Add the ability to confirm deletion, like in the original rarslave
381 # prompt -> OK_TO_DELETE -> do nothing, fall through
382 # prompt -> NOT_OK -> return immediately
386 # FIXME: re-enable this in production
393 def generate_all_parsets (dir):
394 # Generate all parsets in the given directory.
396 assert os.path.isdir (dir) # Directory MUST be valid
399 p2files = find_all_par2_files (dir)
409 TOPDIR = os.path.abspath ('test_material')
411 for (dir, subdirs, files) in os.walk (TOPDIR):
412 print 'DEBUG: IN DIRECTORY:', dir
413 parsets = generate_all_parsets (dir)
417 if __name__ == '__main__':