diff --git a/libs/unrar2/__init__.py b/libs/unrar2/__init__.py index 41b0d71..3bbbc46 100644 --- a/libs/unrar2/__init__.py +++ b/libs/unrar2/__init__.py @@ -33,7 +33,7 @@ similar to the C interface provided by UnRAR. There is also a higher level interface which makes some common operations easier. """ -__version__ = '0.99.3' +__version__ = '0.99.6' try: WindowsError @@ -159,6 +159,12 @@ class RarFile(RarFileImplementation): checker = condition2checker(condition) return RarFileImplementation.extract(self, checker, path, withSubpath, overwrite) + def get_volume(self): + """Determine which volume is it in a multi-volume archive. Returns None if it's not a + multi-volume archive, 0-based volume number otherwise.""" + return RarFileImplementation.get_volume(self) + + def condition2checker(condition): """Converts different condition types to callback""" if type(condition) in [str, unicode]: diff --git a/libs/unrar2/unix.py b/libs/unrar2/unix.py index 62c0df2..02705c0 100644 --- a/libs/unrar2/unix.py +++ b/libs/unrar2/unix.py @@ -23,13 +23,14 @@ # Unix version uses unrar command line executable import platform import stat + import subprocess import gc -import os -import os.path -import time -import re +import os, os.path +import time, re + +from rar_exceptions import * from dateutil.parser import parse from rar_exceptions import * @@ -54,7 +55,7 @@ def call_unrar(params, custom_path = None): for command in (custom_path, 'unrar', 'rar', osx_unrar): if not command: continue try: - subprocess.Popen([command], stdout = subprocess.PIPE) + subprocess.Popen([command], stdout=subprocess.PIPE) rar_executable_cached = command break except OSError: @@ -66,7 +67,7 @@ def call_unrar(params, custom_path = None): args = [rar_executable_cached] + params try: gc.disable() # See http://bugs.python.org/issue1336 - return subprocess.Popen(args, stdout = subprocess.PIPE, stderr = subprocess.PIPE) + return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) finally: gc.enable() @@ -82,18 +83,18 @@ class RarFileImplementation(object): for line in stderrdata.splitlines(): if line.strip().startswith("Cannot open"): raise FileOpenError - if line.find("CRC failed") >= 0: + if line.find("CRC failed")>=0: raise IncorrectRARPassword accum = [] source = iter(stdoutdata.splitlines()) line = '' - while not (line.startswith('UNRAR')): + while (line.find('RAR ') == -1): line = source.next() signature = line # The code below is mighty flaky # and will probably crash on localized versions of RAR # but I see no safe way to rewrite it using a CLI tool - if signature.startswith("UNRAR 4"): + if signature.find("RAR 4") > -1: rar_executable_version = 4 while not (line.startswith('Comment:') or line.startswith('Pathname/Comment')): if line.strip().endswith('is not RAR archive'): @@ -107,7 +108,7 @@ class RarFileImplementation(object): self.comment = '\n'.join(accum[:-1]) else: self.comment = None - elif signature.startswith("UNRAR 5"): + elif signature.find("RAR 5") > -1: rar_executable_version = 5 line = source.next() while not line.startswith('Archive:'): @@ -128,9 +129,9 @@ class RarFileImplementation(object): return '-' if self.password == None else self.password - def call(self, cmd, options = [], files = []): - options2 = options + ['p' + self.escaped_password()] - soptions = ['-' + x for x in options2] + def call(self, cmd, options=[], files=[]): + options2 = options + ['p'+self.escaped_password()] + soptions = ['-'+x for x in options2] return call_unrar([cmd] + soptions + ['--', self.archiveName] + files, self.custom_path) def infoiter(self): @@ -157,7 +158,7 @@ class RarFileImplementation(object): if rar_executable_version == 4: while not line.startswith('-----------'): accum.append(line) - if len(accum) == 2: + if len(accum)==2: data = {} data['index'] = i # asterisks mark password-encrypted files @@ -166,8 +167,9 @@ class RarFileImplementation(object): data['size'] = int(fields[0]) attr = fields[5] data['isdir'] = 'd' in attr.lower() - data['datetime'] = time.strptime(fields[3] + " " + fields[4], '%d-%m-%y %H:%M') + data['datetime'] = time.strptime(fields[3]+" "+fields[4], '%d-%m-%y %H:%M') data['comment'] = None + data['volume'] = None yield data accum = [] i += 1 @@ -183,6 +185,7 @@ class RarFileImplementation(object): data['isdir'] = 'd' in attr.lower() data['datetime'] = parse(fields[2] + " " + fields[3]).timetuple() data['comment'] = None + data['volume'] = None yield data i += 1 line = source.next() @@ -192,7 +195,7 @@ class RarFileImplementation(object): res = [] for info in self.infoiter(): checkres = checker(info) - if checkres == True and not info.isdir: + if checkres==True and not info.isdir: pipe = self.call('p', ['inul'], [info.filename]).stdout res.append((info, pipe.read())) return res @@ -215,17 +218,54 @@ class RarFileImplementation(object): checkres = checker(info) if type(checkres) in [str, unicode]: raise NotImplementedError("Condition callbacks returning strings are deprecated and only supported in Windows") - if checkres == True and not info.isdir: + if checkres==True and not info.isdir: names.append(info.filename) res.append(info) names.append(path) proc = self.call(command, options, names) stdoutdata, stderrdata = proc.communicate() - if stderrdata.find("CRC failed") >= 0 or stderrdata.find("Checksum error") >= 0: + if stderrdata.find("CRC failed")>=0 or stderrdata.find("Checksum error")>=0: raise IncorrectRARPassword return res def destruct(self): pass + def get_volume(self): + command = "v" if rar_executable_version == 4 else "l" + stdoutdata, stderrdata = self.call(command, ['c-']).communicate() + + for line in stderrdata.splitlines(): + if line.strip().startswith("Cannot open"): + raise FileOpenError + + source = iter(stdoutdata.splitlines()) + line = '' + while not line.startswith('-----------'): + if line.strip().endswith('is not RAR archive'): + raise InvalidRARArchive + if line.startswith("CRC failed") or line.startswith("Checksum error"): + raise IncorrectRARPassword + line = source.next() + line = source.next() + if rar_executable_version == 4: + while not line.startswith('-----------'): + line = source.next() + line = source.next() + items = line.strip().split() + if len(items)>4 and items[4]=="volume": + return int(items[5]) - 1 + else: + return None + + elif rar_executable_version == 5: + while not line.startswith('-----------'): + line = source.next() + line = source.next() + items = line.strip().split() + if items[1]=="volume": + return int(items[2]) - 1 + else: + return None + diff --git a/libs/unrar2/windows.py b/libs/unrar2/windows.py index e3d920f..572dcee 100644 --- a/libs/unrar2/windows.py +++ b/libs/unrar2/windows.py @@ -25,8 +25,9 @@ from __future__ import generators from couchpotato.environment import Env from shutil import copyfile -import ctypes.wintypes -import os.path + +import ctypes, ctypes.wintypes +import os, os.path, re import time from rar_exceptions import * @@ -43,6 +44,7 @@ ERAR_EREAD = 18 ERAR_EWRITE = 19 ERAR_SMALL_BUF = 20 ERAR_UNKNOWN = 21 +ERAR_MISSING_PASSWORD = 22 RAR_OM_LIST = 0 RAR_OM_EXTRACT = 1 @@ -75,8 +77,12 @@ if os.path.isfile(dll_copy): copyfile(dll_file, dll_copy) -unrar = ctypes.WinDLL(dll_copy) +volume_naming1 = re.compile("\.r([0-9]{2})$") +volume_naming2 = re.compile("\.([0-9]{3}).rar$") +volume_naming3 = re.compile("\.part([0-9]+).rar$") + +unrar = ctypes.WinDLL(dll_copy) class RAROpenArchiveDataEx(ctypes.Structure): def __init__(self, ArcName=None, ArcNameW=u'', OpenMode=RAR_OM_LIST): @@ -193,7 +199,7 @@ class RarInfoIterator(object): self.index = 0 self.headerData = RARHeaderDataEx() self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData)) - if self.res==ERAR_BAD_DATA: + if self.res in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]: raise IncorrectRARPassword self.arc.lockStatus = "locked" self.arc.needskip = False @@ -213,7 +219,7 @@ class RarInfoIterator(object): data = {} data['index'] = self.index - data['filename'] = self.headerData.FileName + data['filename'] = self.headerData.FileNameW data['datetime'] = DosDateTimeToTimeTuple(self.headerData.FileTime) data['isdir'] = ((self.headerData.Flags & 0xE0) == 0xE0) data['size'] = self.headerData.UnpSize + (self.headerData.UnpSizeHigh << 32) @@ -257,6 +263,7 @@ class RarFileImplementation(object): self.lockStatus = "ready" + self.isVolume = archiveData.Flags & 1 def destruct(self): @@ -282,7 +289,7 @@ class RarFileImplementation(object): c_callback = UNRARCALLBACK(reader._callback) RARSetCallback(self._handle, c_callback, 1) tmpres = RARProcessFile(self._handle, RAR_TEST, None, None) - if tmpres==ERAR_BAD_DATA: + if tmpres in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]: raise IncorrectRARPassword self.needskip = False res.append((info, reader.get_result())) @@ -304,11 +311,29 @@ class RarFileImplementation(object): target = checkres if overwrite or (not os.path.exists(target)): tmpres = RARProcessFile(self._handle, RAR_EXTRACT, None, target) - if tmpres==ERAR_BAD_DATA: + if tmpres in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]: raise IncorrectRARPassword self.needskip = False res.append(info) return res + def get_volume(self): + if not self.isVolume: + return None + headerData = RARHeaderDataEx() + res = RARReadHeaderEx(self._handle, ctypes.byref(headerData)) + arcName = headerData.ArcNameW + match3 = volume_naming3.search(arcName) + if match3 != None: + return int(match3.group(1)) - 1 + match2 = volume_naming3.search(arcName) + if match2 != None: + return int(match2.group(1)) + match1 = volume_naming1.search(arcName) + if match1 != None: + return int(match1.group(1)) + 1 + return 0 + +