# -*- coding: utf-8 -*-
import os
import re
import string
import subprocess
from module.plugins.internal.Extractor import Extractor, ArchiveError, CRCError, PasswordError
from module.plugins.internal.utils import decode, fs_join, renice
class UnRar(Extractor):
__name__ = "UnRar"
__type__ = "extractor"
__version__ = "1.29"
__status__ = "testing"
__description__ = """Rar extractor plugin"""
__license__ = "GPLv3"
__authors__ = [("RaNaN" , "RaNaN@pyload.org" ),
("Walter Purcaro", "vuolter@gmail.com"),
("Immenz" , "immenz@gmx.net" )]
CMD = "unrar"
EXTENSIONS = [".rar"]
re_multipart = re.compile(r'\.(part|r)(\d+)(?:\.rar)?(\.rev|\.bad)?', re.I)
re_filefixed = re.compile(r'Building (.+)')
re_filelist = re.compile(r'^(.)(\s*[\w\-.]+)\s+(\d+\s+)+(?:\d+\%\s+)?[\d\-]{8}\s+[\d\:]{5}', re.I | re.M)
re_wrongpwd = re.compile(r'password', re.I)
re_wrongcrc = re.compile(r'encrypted|damaged|CRC failed|checksum error|corrupt', re.I)
re_version = re.compile(r'(?:UN)?RAR\s(\d+\.\d+)', re.I)
@classmethod
def find(cls):
try:
if os.name is "nt":
cls.CMD = os.path.join(pypath, "RAR.exe")
else:
cls.CMD = "rar"
p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
# cls.__name__ = "RAR"
cls.REPAIR = True
except OSError:
try:
if os.name is "nt":
cls.CMD = os.path.join(pypath, "UnRAR.exe")
else:
cls.CMD = "unrar"
p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
except OSError:
return False
m = cls.re_version.search(out)
if m is not None:
cls.VERSION = m.group(1)
return True
@classmethod
def ismultipart(cls, filename):
return True if cls.re_multipart.search(filename) else False
def verify(self, password=None):
p = self.call_cmd("l", "-v", self.target, password=password)
out, err = p.communicate()
if self.re_wrongpwd.search(err):
raise PasswordError
if self.re_wrongcrc.search(err):
raise CRCError(err)
#: Output only used to check if passworded files are present
for attr in self.re_filelist.findall(out):
if attr[0].startswith("*"):
raise PasswordError
def repair(self):
p = self.call_cmd("rc", self.target)
#: Communicate and retrieve stderr
self._progress(p)
err = p.stderr.read().strip()
if err or p.returncode:
p = self.call_cmd("r", self.target)
# communicate and retrieve stderr
self._progress(p)
err = p.stderr.read().strip()
if err or p.returncode:
return False
else:
dir = os.path.dirname(filename)
name = re_filefixed.search(out).group(1)
self.filename = os.path.join(dir, name)
return True
def _progress(self, process):
s = ""
while True:
c = process.stdout.read(1)
#: Quit loop on eof
if not c:
break
#: Reading a percentage sign -> set progress and restart
if c == "%":
self.notify_progress(int(s))
s = ""
#: Not reading a digit -> therefore restart
elif c not in string.digits:
s = ""
#: Add digit to progressstring
else:
s += c
def extract(self, password=None):
command = "x" if self.fullpath else "e"
p = self.call_cmd(command, self.target, self.out, password=password)
#: Communicate and retrieve stderr
self._progress(p)
err = p.stderr.read().strip()
if err:
if self.re_wrongpwd.search(err):
raise PasswordError
elif self.re_wrongcrc.search(err):
raise CRCError(err)
else: #: Raise error if anything is on stderr
raise ArchiveError(err)
if p.returncode:
raise ArchiveError(_("Process return code: %d") % p.returncode)
self.files = self.list(password)
def items(self):
dir, name = os.path.split(self.filename)
#: Actually extracted file
files = [self.filename]
#: eventually Multipart Files
files.extend(fs_join(dir, os.path.basename(file)) for file in filter(self.ismultipart, os.listdir(dir))
if re.sub(self.re_multipart, ".rar", name) == re.sub(self.re_multipart, ".rar", file))
return files
def list(self, password=None):
command = "vb" if self.fullpath else "lb"
p = self.call_cmd(command, "-v", self.target, password=password)
out, err = p.communicate()
if "Cannot open" in err:
raise ArchiveError(_("Cannot open file"))
if err.strip(): #: Only log error at this point
self.log_error(err.strip())
result = set()
if not self.fullpath and self.VERSION.startswith('5'):
#@NOTE: Unrar 5 always list full path
for f in decode(out).splitlines():
f = fs_join(self.out, os.path.basename(f.strip()))
if os.path.isfile(f):
result.add(fs_join(self.out, os.path.basename(f)))
else:
for f in decode(out).splitlines():
result.add(fs_join(self.out, f.strip()))
return list(result)
def call_cmd(self, command, *xargs, **kwargs):
args = []
#: Overwrite flag
if self.overwrite:
args.append("-o+")
else:
args.append("-o-")
args.append("-or")
for word in self.excludefiles:
args.append("-x'%s'" % word.strip())
#: Assume yes on all queries
args.append("-y")
#: Set a password
if kwargs.get('password'):
args.append("-p%s" % kwargs['password'])
else:
args.append("-p-")
if self.keepbroken:
args.append("-kb")
#@NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue
call = [self.CMD, command] + args + list(xargs)
self.log_debug(" ".join(call))
p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
renice(p.pid, self.priority)
return p