2 # vim: set ts=4 sts=4 sw=4 textwidth=112 :
8 (TYPE_OLDRAR, TYPE_NEWRAR, TYPE_ZIP, TYPE_NOEXTRACT) = range (4)
10 class RarslaveExtractor (object):
12 def __init__ (self, type):
16 def addHead (self, dir, head):
17 assert os.path.isdir (dir)
18 # REQUIRES that the dir is valid, but not that the file is valid, so that
19 # we can move a file that doesn't exist yet.
20 # FIXME: probably CAN add this back, since we should be running this AFTER repair.
21 #assert os.path.isfile (os.path.join (dir, head))
23 self.heads.append (os.path.join (dir, head))
25 def extract (self, todir):
26 # Extract all heads of this set
28 # Create the directory $todir if it doesn't exist
29 if not os.path.isdir (todir):
35 # Failed mkdir -p, clean up time ...
36 pass # FIXME: temporary for syntax
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
47 extraction_func (h, todir)
49 def __extract_rar (self, file, todir):
50 assert os.path.isfile (file)
51 assert os.path.isdir (todir)
53 RAR_CMD = 'unrar x -o+ -- '
55 #file = full_abspath (file)
56 #todir = full_abspath (todir)
58 cmd = '%s \"%s\"' % (RAR_CMD, file)
59 ret = run_command (cmd, todir)
61 def __extract_zip (self, file, todir):
62 ZIP_CMD = 'unzip \"%s\" -d \"%s\"'
64 cmd = ZIP_CMD % (file, todir)
65 ret = run_command (cmd)
67 def __extract_noextract (self, file, todir):
68 # Just move this file to the $todir, since no extraction is needed
69 # FIXME: NOTE: mv will fail by itself if you're moving to the same dir!
70 cmd = 'mv \"%s\" \"%s\"' % (file, todir)
71 ret = run_command (cmd)
75 class RarslaveRepairer (object):
76 # Verify (and repair) the set
77 # Make sure it worked, otherwise clean up and return failure
79 def __init__ (self, dir, file, join=False):
80 self.dir = dir # the directory containing the par2 file
81 self.file = file # the par2 file
82 self.join = join # True if the par2 set is 001 002 ...
84 assert os.path.isdir (dir)
85 assert os.path.isfile (os.path.join (dir, file))
87 def checkAndRepair (self):
89 # par2repair -- PAR2 PAR2_EXTRA [JOIN_FILES]
90 PAR2_CMD = 'par2repair -- '
93 basename = get_basename (self.file)
94 all_files = find_likely_files (basename, self.dir)
96 par2_files = find_par2_files (all_files)
98 # assemble the command
99 command = "%s \"%s\" " % (PAR2_CMD, self.file)
103 command += "\"%s\" " % get_filename(f)
107 if f not in par2_files:
108 command += "\"%s\" " % get_filename(f)
111 ret = run_command (command, self.dir)
113 def run_command (cmd, indir=None):
114 # Runs the specified command-line in the directory given (or, in the current directory
115 # if none is given). It returns the status code given by the application.
120 assert os.path.isdir (indir) # MUST be a directory!
123 # FIXME: re-enable this after testing
124 print 'RUNNING (%s): %s' % (indir, cmd)
125 # return os.system (cmd)
128 def full_abspath (p):
129 return os.path.abspath (os.path.expanduser (p))
131 def get_filename (f):
132 # TODO: I don't think that we should enforce this...
133 # TODO: ... because I think we should be able to get the filename, regardless
134 # TODO: of whether this is a legit filename RIGHT NOW or not.
135 # assert os.path.isfile (f)
136 return os.path.split (f)[1]
138 def get_basename (name):
139 """Strips most kinds of endings from a filename"""
141 regex = '^(.+)\.(par2|vol\d+\+\d+|\d\d\d|part\d+|rar|zip|avi|mp4|mkv|ogm)$'
142 r = re.compile (regex, re.IGNORECASE)
149 g = r.match (name).groups()
155 def find_likely_files (name, dir):
156 """Finds files which are likely to be part of the set corresponding
157 to $name in the directory $dir"""
159 if not os.path.isdir (os.path.abspath (dir)):
160 raise ValueError # bad directory given
162 dir = os.path.abspath (dir)
163 ename = re.escape (name)
164 regex = re.compile ('^%s.*$' % (ename, ))
166 return [f for f in os.listdir (dir) if regex.match (f)]
168 def find_par2_files (files):
169 """Find all par2 files in the list $files"""
171 regex = re.compile ('^.*\.par2$', re.IGNORECASE)
172 return [f for f in files if regex.match (f)]
174 def find_all_par2_files (dir):
175 """Finds all par2 files in a directory"""
176 # NOTE: does NOT return absolute paths
178 if not os.path.isdir (os.path.abspath (dir)):
179 raise ValueError # bad directory given
181 dir = os.path.abspath (dir)
182 files = os.listdir (dir)
184 return find_par2_files (files)
186 def has_extension (f, ext):
187 """Checks if f has the extension ext"""
192 ext = re.escape (ext)
193 regex = re.compile ('^.*%s$' % (ext, ), re.IGNORECASE)
194 return regex.match (f)
196 def find_extraction_heads (dir, files):
197 """Takes a list of possible files and finds likely heads of
200 # NOTE: perhaps this should happen AFTER repair is
201 # NOTE: successful. That way all files would already exist
203 # According to various sources online:
204 # 1) pre rar-3.0: .rar .r00 .r01 ...
205 # 2) post rar-3.0: .part01.rar .part02.rar
206 # 3) zip all ver: .zip
209 p2files = find_par2_files (files)
211 # Old RAR type, find all files ending in .rar
212 if is_oldrar (files):
213 extractor = RarslaveExtractor (TYPE_OLDRAR)
214 regex = re.compile ('^.*\.rar$', re.IGNORECASE)
217 extractor.addHead (dir, f)
219 if is_newrar (files):
220 extractor = RarslaveExtractor (TYPE_NEWRAR)
221 regex = re.compile ('^.*\.part01.rar$', re.IGNORECASE)
224 extractor.addHead (dir, f)
227 extractor = RarslaveExtractor (TYPE_ZIP)
228 regex = re.compile ('^.*\.zip$', re.IGNORECASE)
231 extractor.addHead (dir, f)
233 if is_noextract (files):
234 # Use the Par2 Parser (from cfv) here to find out what files are protected.
235 # Since these are not being extracted, they will be mv'd to another directory
237 extractor = RarslaveExtractor (TYPE_NOEXTRACT)
242 prot_files = par2parser.get_protected_files (dir, f)
244 except: #FIXME: add the actual exceptions
245 print 'ERROR PARSING P2FILE ...', f
253 extractor.addHead (dir, f)
257 # Make sure we found the type
258 assert extractor != None
262 def is_oldrar (files):
264 if has_extension (f, '.r00'):
267 def is_newrar (files):
269 if has_extension (f, '.part01.rar'):
274 if has_extension (f, '.zip'):
277 def is_noextract (files):
278 # Type that needs no extraction.
279 # TODO: Add others ???
281 if has_extension (f, '.001'):
284 def find_deleteable_files (files):
285 # Deleteable types regex should come from the config
287 dregex = re.compile ('^.*\.(par2|\d|\d\d\d|rar|r\d\d|zip)$', re.IGNORECASE)
289 return [f for f in files if dregex.match (f)]
295 class PAR2Set (object):
301 def __init__ (self, dir, file):
302 assert os.path.isdir (dir)
303 assert os.path.isfile (os.path.join (dir, file))
308 basename = get_basename (file)
309 self.likely_files = find_likely_files (basename, dir)
311 def __list_eq (self, l1, l2):
313 if len(l1) != len(l2):
322 def __eq__ (self, rhs):
323 return self.__list_eq (self.likely_files, rhs.likely_files)
325 def generate_all_parsets (dir):
326 # Generate all parsets in the given directory.
328 assert os.path.isdir (dir) # Directory MUST be valid
331 p2files = find_all_par2_files (dir)
342 print '\nSETUP STAGE'
343 DIR = os.path.abspath ('test_material/01/')
344 p2files = find_all_par2_files (DIR)
348 print '\nREPAIR STAGE'
349 repairer = RarslaveRepairer (DIR, p2file)
350 repairer.checkAndRepair ()
353 print '\nEXTRACTION STAGE'
354 files = find_likely_files (get_basename (p2file), DIR)
355 extractor = find_extraction_heads (DIR, files)
356 extractor.extract('extract_dir')
359 print '\nDELETION STAGE'
360 printlist ( find_deleteable_files (files) )
365 print '\nSETUP STAGE'
366 DIR = os.path.abspath ('test_material/13/')
367 p2files = find_all_par2_files (DIR)
371 print '\nREPAIR STAGE'
372 RarslaveRepairer (DIR, p2file, join=True).checkAndRepair ()
375 print '\nEXTRACTION STAGE'
376 files = find_likely_files (get_basename (p2file), DIR)
377 find_extraction_heads (DIR, files).extract ('extract_dir')
380 print '\nDELETION STAGE'
381 printlist ( find_deleteable_files (files) )
386 print '\nSETUP STAGE'
387 DIR = os.path.abspath ('test_material/14/')
388 p2files = find_all_par2_files (DIR)
392 print '\nREPAIR STAGE'
393 RarslaveRepairer (DIR, p2file, join=True).checkAndRepair ()
396 print '\nEXTRACTION STAGE'
397 files = find_likely_files (get_basename (p2file), DIR)
398 find_extraction_heads (DIR, files).extract ('extract_dir')
401 print '\nDELETEION STAGE'
402 printlist ( find_deleteable_files (files) )
405 parsets = generate_all_parsets ('test_material/02/')
406 print '\n\nPARSETS LEN:', len(parsets)
408 print p.likely_files[0]
411 if __name__ == '__main__':