Browse Source
Change post process to cleanup filenames with config/Post Processing/'Unpack downloads' enabled. Change post process to join incrementally named (i.e. file.001 to file.nnn) split files. Change replace unrar2 lib with rarfile 3.0 and UnRAR.exe 5.40 beta 4 freeware. Change post process "Copy" to delete redundant files after use.pull/757/head
24 changed files with 3229 additions and 944 deletions
Binary file not shown.
@ -0,0 +1 @@ |
|||||
|
|
File diff suppressed because it is too large
@ -1,18 +0,0 @@ |
|||||
The unrar.dll library is freeware. This means: |
|
||||
|
|
||||
1. All copyrights to RAR and the unrar.dll are exclusively |
|
||||
owned by the author - Alexander Roshal. |
|
||||
|
|
||||
2. The unrar.dll library may be used in any software to handle RAR |
|
||||
archives without limitations free of charge. |
|
||||
|
|
||||
3. THE RAR ARCHIVER AND THE UNRAR.DLL LIBRARY ARE DISTRIBUTED "AS IS". |
|
||||
NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT |
|
||||
YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, |
|
||||
DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING |
|
||||
OR MISUSING THIS SOFTWARE. |
|
||||
|
|
||||
Thank you for your interest in RAR and unrar.dll. |
|
||||
|
|
||||
|
|
||||
Alexander L. Roshal |
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,180 +0,0 @@ |
|||||
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov |
|
||||
# |
|
||||
# Permission is hereby granted, free of charge, to any person obtaining |
|
||||
# a copy of this software and associated documentation files (the |
|
||||
# "Software"), to deal in the Software without restriction, including |
|
||||
# without limitation the rights to use, copy, modify, merge, publish, |
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
# permit persons to whom the Software is furnished to do so, subject to |
|
||||
# the following conditions: |
|
||||
# |
|
||||
# The above copyright notice and this permission notice shall be |
|
||||
# included in all copies or substantial portions of the Software. |
|
||||
# |
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
# SOFTWARE. |
|
||||
|
|
||||
""" |
|
||||
pyUnRAR2 is a ctypes based wrapper around the free UnRAR.dll. |
|
||||
|
|
||||
It is an modified version of Jimmy Retzlaff's pyUnRAR - more simple, |
|
||||
stable and foolproof. |
|
||||
Notice that it has INCOMPATIBLE interface. |
|
||||
|
|
||||
It enables reading and unpacking of archives created with the |
|
||||
RAR/WinRAR archivers. There is a low-level interface which is very |
|
||||
similar to the C interface provided by UnRAR. There is also a |
|
||||
higher level interface which makes some common operations easier. |
|
||||
""" |
|
||||
|
|
||||
__version__ = '0.99.6' |
|
||||
|
|
||||
try: |
|
||||
WindowsError |
|
||||
in_windows = True |
|
||||
except NameError: |
|
||||
in_windows = False |
|
||||
|
|
||||
if in_windows: |
|
||||
from windows import RarFileImplementation |
|
||||
else: |
|
||||
from unix import RarFileImplementation |
|
||||
|
|
||||
|
|
||||
import fnmatch, time, weakref |
|
||||
|
|
||||
class RarInfo(object): |
|
||||
"""Represents a file header in an archive. Don't instantiate directly. |
|
||||
Use only to obtain information about file. |
|
||||
YOU CANNOT EXTRACT FILE CONTENTS USING THIS OBJECT. |
|
||||
USE METHODS OF RarFile CLASS INSTEAD. |
|
||||
|
|
||||
Properties: |
|
||||
index - index of file within the archive |
|
||||
filename - name of the file in the archive including path (if any) |
|
||||
datetime - file date/time as a struct_time suitable for time.strftime |
|
||||
isdir - True if the file is a directory |
|
||||
size - size in bytes of the uncompressed file |
|
||||
comment - comment associated with the file |
|
||||
|
|
||||
Note - this is not currently intended to be a Python file-like object. |
|
||||
""" |
|
||||
|
|
||||
def __init__(self, rarfile, data): |
|
||||
self.rarfile = weakref.proxy(rarfile) |
|
||||
self.index = data['index'] |
|
||||
self.filename = data['filename'] |
|
||||
self.isdir = data['isdir'] |
|
||||
self.size = data['size'] |
|
||||
self.datetime = data['datetime'] |
|
||||
self.comment = data['comment'] |
|
||||
|
|
||||
def __str__(self): |
|
||||
try : |
|
||||
arcName = self.rarfile.archiveName |
|
||||
except ReferenceError: |
|
||||
arcName = "[ARCHIVE_NO_LONGER_LOADED]" |
|
||||
return '<RarInfo "%s" in "%s">' % (self.filename, arcName) |
|
||||
|
|
||||
class RarFile(RarFileImplementation): |
|
||||
|
|
||||
def __init__(self, archiveName, password=None): |
|
||||
"""Instantiate the archive. |
|
||||
|
|
||||
archiveName is the name of the RAR file. |
|
||||
password is used to decrypt the files in the archive. |
|
||||
|
|
||||
Properties: |
|
||||
comment - comment associated with the archive |
|
||||
|
|
||||
>>> print RarFile('test.rar').comment |
|
||||
This is a test. |
|
||||
""" |
|
||||
self.archiveName = archiveName |
|
||||
RarFileImplementation.init(self, password) |
|
||||
|
|
||||
def __del__(self): |
|
||||
self.destruct() |
|
||||
|
|
||||
def infoiter(self): |
|
||||
"""Iterate over all the files in the archive, generating RarInfos. |
|
||||
|
|
||||
>>> import os |
|
||||
>>> for fileInArchive in RarFile('test.rar').infoiter(): |
|
||||
... print os.path.split(fileInArchive.filename)[-1], |
|
||||
... print fileInArchive.isdir, |
|
||||
... print fileInArchive.size, |
|
||||
... print fileInArchive.comment, |
|
||||
... print tuple(fileInArchive.datetime)[0:5], |
|
||||
... print time.strftime('%a, %d %b %Y %H:%M', fileInArchive.datetime) |
|
||||
test True 0 None (2003, 6, 30, 1, 59) Mon, 30 Jun 2003 01:59 |
|
||||
test.txt False 20 None (2003, 6, 30, 2, 1) Mon, 30 Jun 2003 02:01 |
|
||||
this.py False 1030 None (2002, 2, 8, 16, 47) Fri, 08 Feb 2002 16:47 |
|
||||
""" |
|
||||
for params in RarFileImplementation.infoiter(self): |
|
||||
yield RarInfo(self, params) |
|
||||
|
|
||||
def infolist(self): |
|
||||
"""Return a list of RarInfos, descripting the contents of the archive.""" |
|
||||
return list(self.infoiter()) |
|
||||
|
|
||||
def read_files(self, condition='*'): |
|
||||
"""Read specific files from archive into memory. |
|
||||
If "condition" is a list of numbers, then return files which have those positions in infolist. |
|
||||
If "condition" is a string, then it is treated as a wildcard for names of files to extract. |
|
||||
If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object |
|
||||
and returns boolean True (extract) or False (skip). |
|
||||
If "condition" is omitted, all files are returned. |
|
||||
|
|
||||
Returns list of tuples (RarInfo info, str contents) |
|
||||
""" |
|
||||
checker = condition2checker(condition) |
|
||||
return RarFileImplementation.read_files(self, checker) |
|
||||
|
|
||||
|
|
||||
def extract(self, condition='*', path='.', withSubpath=True, overwrite=True): |
|
||||
"""Extract specific files from archive to disk. |
|
||||
|
|
||||
If "condition" is a list of numbers, then extract files which have those positions in infolist. |
|
||||
If "condition" is a string, then it is treated as a wildcard for names of files to extract. |
|
||||
If "condition" is a function, it is treated as a callback function, which accepts a RarInfo object |
|
||||
and returns either boolean True (extract) or boolean False (skip). |
|
||||
DEPRECATED: If "condition" callback returns string (only supported for Windows) - |
|
||||
that string will be used as a new name to save the file under. |
|
||||
If "condition" is omitted, all files are extracted. |
|
||||
|
|
||||
"path" is a directory to extract to |
|
||||
"withSubpath" flag denotes whether files are extracted with their full path in the archive. |
|
||||
"overwrite" flag denotes whether extracted files will overwrite old ones. Defaults to true. |
|
||||
|
|
||||
Returns list of RarInfos for extracted files.""" |
|
||||
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]: |
|
||||
def smatcher(info): |
|
||||
return fnmatch.fnmatch(info.filename, condition) |
|
||||
return smatcher |
|
||||
elif type(condition) in [list, tuple] and type(condition[0]) in [int, long]: |
|
||||
def imatcher(info): |
|
||||
return info.index in condition |
|
||||
return imatcher |
|
||||
elif callable(condition): |
|
||||
return condition |
|
||||
else: |
|
||||
raise TypeError |
|
||||
|
|
||||
|
|
@ -1,21 +0,0 @@ |
|||||
Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining |
|
||||
a copy of this software and associated documentation files (the |
|
||||
"Software"), to deal in the Software without restriction, including |
|
||||
without limitation the rights to use, copy, modify, merge, publish, |
|
||||
distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
permit persons to whom the Software is furnished to do so, subject to |
|
||||
the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be |
|
||||
included in all copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
SOFTWARE. |
|
@ -1,30 +0,0 @@ |
|||||
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov |
|
||||
# |
|
||||
# Permission is hereby granted, free of charge, to any person obtaining |
|
||||
# a copy of this software and associated documentation files (the |
|
||||
# "Software"), to deal in the Software without restriction, including |
|
||||
# without limitation the rights to use, copy, modify, merge, publish, |
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
# permit persons to whom the Software is furnished to do so, subject to |
|
||||
# the following conditions: |
|
||||
# |
|
||||
# The above copyright notice and this permission notice shall be |
|
||||
# included in all copies or substantial portions of the Software. |
|
||||
# |
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
# SOFTWARE. |
|
||||
|
|
||||
# Low level interface - see UnRARDLL\UNRARDLL.TXT |
|
||||
|
|
||||
|
|
||||
class ArchiveHeaderBroken(Exception): pass |
|
||||
class InvalidRARArchive(Exception): pass |
|
||||
class FileOpenError(Exception): pass |
|
||||
class IncorrectRARPassword(Exception): pass |
|
||||
class InvalidRARArchiveUsage(Exception): pass |
|
@ -1,265 +0,0 @@ |
|||||
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov |
|
||||
# |
|
||||
# Permission is hereby granted, free of charge, to any person obtaining |
|
||||
# a copy of this software and associated documentation files (the |
|
||||
# "Software"), to deal in the Software without restriction, including |
|
||||
# without limitation the rights to use, copy, modify, merge, publish, |
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
# permit persons to whom the Software is furnished to do so, subject to |
|
||||
# the following conditions: |
|
||||
# |
|
||||
# The above copyright notice and this permission notice shall be |
|
||||
# included in all copies or substantial portions of the Software. |
|
||||
# |
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
# SOFTWARE. |
|
||||
|
|
||||
# Unix version uses unrar command line executable |
|
||||
|
|
||||
import subprocess |
|
||||
import gc |
|
||||
|
|
||||
import os, os.path |
|
||||
import time, re |
|
||||
|
|
||||
from rar_exceptions import * |
|
||||
|
|
||||
class UnpackerNotInstalled(Exception): pass |
|
||||
|
|
||||
rar_executable_cached = None |
|
||||
rar_executable_version = None |
|
||||
|
|
||||
def call_unrar(params): |
|
||||
"Calls rar/unrar command line executable, returns stdout pipe" |
|
||||
global rar_executable_cached |
|
||||
if rar_executable_cached is None: |
|
||||
for command in ('unrar', 'rar'): |
|
||||
try: |
|
||||
subprocess.Popen([command], stdout=subprocess.PIPE) |
|
||||
rar_executable_cached = command |
|
||||
break |
|
||||
except OSError: |
|
||||
pass |
|
||||
if rar_executable_cached is None: |
|
||||
raise UnpackerNotInstalled("No suitable RAR unpacker installed") |
|
||||
|
|
||||
assert type(params) == list, "params must be list" |
|
||||
args = [rar_executable_cached] + params |
|
||||
try: |
|
||||
gc.disable() # See http://bugs.python.org/issue1336 |
|
||||
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|
||||
finally: |
|
||||
gc.enable() |
|
||||
|
|
||||
class RarFileImplementation(object): |
|
||||
|
|
||||
def init(self, password=None): |
|
||||
global rar_executable_version |
|
||||
self.password = password |
|
||||
|
|
||||
|
|
||||
stdoutdata, stderrdata = self.call('v', []).communicate() |
|
||||
|
|
||||
for line in stderrdata.splitlines(): |
|
||||
if line.strip().startswith("Cannot open"): |
|
||||
raise FileOpenError |
|
||||
if line.find("CRC failed")>=0: |
|
||||
raise IncorrectRARPassword |
|
||||
accum = [] |
|
||||
source = iter(stdoutdata.splitlines()) |
|
||||
line = '' |
|
||||
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.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'): |
|
||||
raise InvalidRARArchive |
|
||||
line = source.next() |
|
||||
while not line.startswith('Pathname/Comment'): |
|
||||
accum.append(line.rstrip('\n')) |
|
||||
line = source.next() |
|
||||
if len(accum): |
|
||||
accum[0] = accum[0][9:] # strip out "Comment:" part |
|
||||
self.comment = '\n'.join(accum[:-1]) |
|
||||
else: |
|
||||
self.comment = None |
|
||||
elif signature.find("RAR 5") > -1: |
|
||||
rar_executable_version = 5 |
|
||||
line = source.next() |
|
||||
while not line.startswith('Archive:'): |
|
||||
if line.strip().endswith('is not RAR archive'): |
|
||||
raise InvalidRARArchive |
|
||||
accum.append(line.rstrip('\n')) |
|
||||
line = source.next() |
|
||||
if len(accum): |
|
||||
self.comment = '\n'.join(accum[:-1]).strip() |
|
||||
else: |
|
||||
self.comment = None |
|
||||
else: |
|
||||
raise UnpackerNotInstalled("Unsupported RAR version, expected 4.x or 5.x, found: " |
|
||||
+ signature.split(" ")[1]) |
|
||||
|
|
||||
|
|
||||
def escaped_password(self): |
|
||||
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] |
|
||||
return call_unrar([cmd]+soptions+['--',self.archiveName]+files) |
|
||||
|
|
||||
def infoiter(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 |
|
||||
|
|
||||
accum = [] |
|
||||
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() |
|
||||
i = 0 |
|
||||
re_spaces = re.compile(r"\s+") |
|
||||
if rar_executable_version == 4: |
|
||||
while not line.startswith('-----------'): |
|
||||
accum.append(line) |
|
||||
if len(accum)==2: |
|
||||
data = {} |
|
||||
data['index'] = i |
|
||||
# asterisks mark password-encrypted files |
|
||||
data['filename'] = accum[0].strip().lstrip("*") # asterisks marks password-encrypted files |
|
||||
fields = re_spaces.split(accum[1].strip()) |
|
||||
data['size'] = int(fields[0]) |
|
||||
attr = fields[5] |
|
||||
data['isdir'] = 'd' in attr.lower() |
|
||||
data['datetime'] = self.rarcmd_dt(fields[3], fields[4]) |
|
||||
data['comment'] = None |
|
||||
data['volume'] = None |
|
||||
yield data |
|
||||
accum = [] |
|
||||
i += 1 |
|
||||
line = source.next() |
|
||||
elif rar_executable_version == 5: |
|
||||
while not line.startswith('-----------'): |
|
||||
fields = line.strip().lstrip("*").split() |
|
||||
data = {} |
|
||||
data['index'] = i |
|
||||
data['filename'] = " ".join(fields[4:]) |
|
||||
data['size'] = int(fields[1]) |
|
||||
attr = fields[0] |
|
||||
data['isdir'] = 'd' in attr.lower() |
|
||||
data['datetime'] = self.rarcmd_dt(fields[2], fields[3]) |
|
||||
data['comment'] = None |
|
||||
data['volume'] = None |
|
||||
yield data |
|
||||
i += 1 |
|
||||
line = source.next() |
|
||||
|
|
||||
@staticmethod |
|
||||
def rarcmd_dt(param_date=time.strftime('%Y-%m-%d'), param_time=time.strftime('%H:%M')): |
|
||||
for str_fmt in '%Y-%m-%d %H:%M', '%d-%m-%y %H:%M': |
|
||||
try: |
|
||||
return time.strptime('%s %s' % (param_date, param_time), str_fmt) |
|
||||
except ValueError: |
|
||||
pass |
|
||||
return time.strptime('%s %s' % (time.strftime('%Y-%m-%d'), time.strftime('%H:%M')), '%Y-%m-%d %H:%M') |
|
||||
|
|
||||
def read_files(self, checker): |
|
||||
res = [] |
|
||||
for info in self.infoiter(): |
|
||||
checkres = checker(info) |
|
||||
if checkres==True and not info.isdir: |
|
||||
pipe = self.call('p', ['inul'], [info.filename]).stdout |
|
||||
res.append((info, pipe.read())) |
|
||||
return res |
|
||||
|
|
||||
|
|
||||
def extract(self, checker, path, withSubpath, overwrite): |
|
||||
res = [] |
|
||||
command = 'x' |
|
||||
if not withSubpath: |
|
||||
command = 'e' |
|
||||
options = [] |
|
||||
if overwrite: |
|
||||
options.append('o+') |
|
||||
else: |
|
||||
options.append('o-') |
|
||||
if not path.endswith(os.sep): |
|
||||
path += os.sep |
|
||||
names = [] |
|
||||
for info in self.infoiter(): |
|
||||
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: |
|
||||
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: |
|
||||
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 |
|
||||
|
|
||||
|
|
@ -1,332 +0,0 @@ |
|||||
# Copyright (c) 2003-2005 Jimmy Retzlaff, 2008 Konstantin Yegupov |
|
||||
# |
|
||||
# Permission is hereby granted, free of charge, to any person obtaining |
|
||||
# a copy of this software and associated documentation files (the |
|
||||
# "Software"), to deal in the Software without restriction, including |
|
||||
# without limitation the rights to use, copy, modify, merge, publish, |
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to |
|
||||
# permit persons to whom the Software is furnished to do so, subject to |
|
||||
# the following conditions: |
|
||||
# |
|
||||
# The above copyright notice and this permission notice shall be |
|
||||
# included in all copies or substantial portions of the Software. |
|
||||
# |
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
# SOFTWARE. |
|
||||
|
|
||||
# Low level interface - see UnRARDLL\UNRARDLL.TXT |
|
||||
|
|
||||
from __future__ import generators |
|
||||
|
|
||||
import ctypes, ctypes.wintypes |
|
||||
import os, os.path, sys, re |
|
||||
import Queue |
|
||||
import time |
|
||||
|
|
||||
from rar_exceptions import * |
|
||||
|
|
||||
ERAR_END_ARCHIVE = 10 |
|
||||
ERAR_NO_MEMORY = 11 |
|
||||
ERAR_BAD_DATA = 12 |
|
||||
ERAR_BAD_ARCHIVE = 13 |
|
||||
ERAR_UNKNOWN_FORMAT = 14 |
|
||||
ERAR_EOPEN = 15 |
|
||||
ERAR_ECREATE = 16 |
|
||||
ERAR_ECLOSE = 17 |
|
||||
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 |
|
||||
|
|
||||
RAR_SKIP = 0 |
|
||||
RAR_TEST = 1 |
|
||||
RAR_EXTRACT = 2 |
|
||||
|
|
||||
RAR_VOL_ASK = 0 |
|
||||
RAR_VOL_NOTIFY = 1 |
|
||||
|
|
||||
RAR_DLL_VERSION = 3 |
|
||||
|
|
||||
# enum UNRARCALLBACK_MESSAGES |
|
||||
UCM_CHANGEVOLUME = 0 |
|
||||
UCM_PROCESSDATA = 1 |
|
||||
UCM_NEEDPASSWORD = 2 |
|
||||
|
|
||||
architecture_bits = ctypes.sizeof(ctypes.c_voidp)*8 |
|
||||
dll_name = "unrar.dll" |
|
||||
if architecture_bits == 64: |
|
||||
dll_name = "x64\\unrar64.dll" |
|
||||
|
|
||||
volume_naming1 = re.compile("\.r([0-9]{2})$") |
|
||||
volume_naming2 = re.compile("\.([0-9]{3}).rar$") |
|
||||
volume_naming3 = re.compile("\.part([0-9]+).rar$") |
|
||||
|
|
||||
try: |
|
||||
unrar = ctypes.WinDLL(os.path.join(os.path.split(__file__)[0], 'UnRARDLL', dll_name)) |
|
||||
except WindowsError: |
|
||||
unrar = ctypes.WinDLL(dll_name) |
|
||||
|
|
||||
|
|
||||
class RAROpenArchiveDataEx(ctypes.Structure): |
|
||||
def __init__(self, ArcName=None, ArcNameW=u'', OpenMode=RAR_OM_LIST): |
|
||||
self.CmtBuf = ctypes.c_buffer(64*1024) |
|
||||
ctypes.Structure.__init__(self, ArcName=ArcName, ArcNameW=ArcNameW, OpenMode=OpenMode, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf)) |
|
||||
|
|
||||
_fields_ = [ |
|
||||
('ArcName', ctypes.c_char_p), |
|
||||
('ArcNameW', ctypes.c_wchar_p), |
|
||||
('OpenMode', ctypes.c_uint), |
|
||||
('OpenResult', ctypes.c_uint), |
|
||||
('_CmtBuf', ctypes.c_voidp), |
|
||||
('CmtBufSize', ctypes.c_uint), |
|
||||
('CmtSize', ctypes.c_uint), |
|
||||
('CmtState', ctypes.c_uint), |
|
||||
('Flags', ctypes.c_uint), |
|
||||
('Reserved', ctypes.c_uint*32), |
|
||||
] |
|
||||
|
|
||||
class RARHeaderDataEx(ctypes.Structure): |
|
||||
def __init__(self): |
|
||||
self.CmtBuf = ctypes.c_buffer(64*1024) |
|
||||
ctypes.Structure.__init__(self, _CmtBuf=ctypes.addressof(self.CmtBuf), CmtBufSize=ctypes.sizeof(self.CmtBuf)) |
|
||||
|
|
||||
_fields_ = [ |
|
||||
('ArcName', ctypes.c_char*1024), |
|
||||
('ArcNameW', ctypes.c_wchar*1024), |
|
||||
('FileName', ctypes.c_char*1024), |
|
||||
('FileNameW', ctypes.c_wchar*1024), |
|
||||
('Flags', ctypes.c_uint), |
|
||||
('PackSize', ctypes.c_uint), |
|
||||
('PackSizeHigh', ctypes.c_uint), |
|
||||
('UnpSize', ctypes.c_uint), |
|
||||
('UnpSizeHigh', ctypes.c_uint), |
|
||||
('HostOS', ctypes.c_uint), |
|
||||
('FileCRC', ctypes.c_uint), |
|
||||
('FileTime', ctypes.c_uint), |
|
||||
('UnpVer', ctypes.c_uint), |
|
||||
('Method', ctypes.c_uint), |
|
||||
('FileAttr', ctypes.c_uint), |
|
||||
('_CmtBuf', ctypes.c_voidp), |
|
||||
('CmtBufSize', ctypes.c_uint), |
|
||||
('CmtSize', ctypes.c_uint), |
|
||||
('CmtState', ctypes.c_uint), |
|
||||
('Reserved', ctypes.c_uint*1024), |
|
||||
] |
|
||||
|
|
||||
def DosDateTimeToTimeTuple(dosDateTime): |
|
||||
"""Convert an MS-DOS format date time to a Python time tuple. |
|
||||
""" |
|
||||
dosDate = dosDateTime >> 16 |
|
||||
dosTime = dosDateTime & 0xffff |
|
||||
day = dosDate & 0x1f |
|
||||
month = (dosDate >> 5) & 0xf |
|
||||
year = 1980 + (dosDate >> 9) |
|
||||
second = 2*(dosTime & 0x1f) |
|
||||
minute = (dosTime >> 5) & 0x3f |
|
||||
hour = dosTime >> 11 |
|
||||
return time.localtime(time.mktime((year, month, day, hour, minute, second, 0, 1, -1))) |
|
||||
|
|
||||
def _wrap(restype, function, argtypes): |
|
||||
result = function |
|
||||
result.argtypes = argtypes |
|
||||
result.restype = restype |
|
||||
return result |
|
||||
|
|
||||
RARGetDllVersion = _wrap(ctypes.c_int, unrar.RARGetDllVersion, []) |
|
||||
|
|
||||
RAROpenArchiveEx = _wrap(ctypes.wintypes.HANDLE, unrar.RAROpenArchiveEx, [ctypes.POINTER(RAROpenArchiveDataEx)]) |
|
||||
|
|
||||
RARReadHeaderEx = _wrap(ctypes.c_int, unrar.RARReadHeaderEx, [ctypes.wintypes.HANDLE, ctypes.POINTER(RARHeaderDataEx)]) |
|
||||
|
|
||||
_RARSetPassword = _wrap(ctypes.c_int, unrar.RARSetPassword, [ctypes.wintypes.HANDLE, ctypes.c_char_p]) |
|
||||
def RARSetPassword(*args, **kwargs): |
|
||||
_RARSetPassword(*args, **kwargs) |
|
||||
|
|
||||
RARProcessFile = _wrap(ctypes.c_int, unrar.RARProcessFile, [ctypes.wintypes.HANDLE, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p]) |
|
||||
|
|
||||
RARCloseArchive = _wrap(ctypes.c_int, unrar.RARCloseArchive, [ctypes.wintypes.HANDLE]) |
|
||||
|
|
||||
UNRARCALLBACK = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_uint, ctypes.c_long, ctypes.c_long, ctypes.c_long) |
|
||||
RARSetCallback = _wrap(ctypes.c_int, unrar.RARSetCallback, [ctypes.wintypes.HANDLE, UNRARCALLBACK, ctypes.c_long]) |
|
||||
|
|
||||
|
|
||||
|
|
||||
RARExceptions = { |
|
||||
ERAR_NO_MEMORY : MemoryError, |
|
||||
ERAR_BAD_DATA : ArchiveHeaderBroken, |
|
||||
ERAR_BAD_ARCHIVE : InvalidRARArchive, |
|
||||
ERAR_EOPEN : FileOpenError, |
|
||||
} |
|
||||
|
|
||||
class PassiveReader: |
|
||||
"""Used for reading files to memory""" |
|
||||
def __init__(self, usercallback = None): |
|
||||
self.buf = [] |
|
||||
self.ucb = usercallback |
|
||||
|
|
||||
def _callback(self, msg, UserData, P1, P2): |
|
||||
if msg == UCM_PROCESSDATA: |
|
||||
data = (ctypes.c_char*P2).from_address(P1).raw |
|
||||
if self.ucb!=None: |
|
||||
self.ucb(data) |
|
||||
else: |
|
||||
self.buf.append(data) |
|
||||
return 1 |
|
||||
|
|
||||
def get_result(self): |
|
||||
return ''.join(self.buf) |
|
||||
|
|
||||
class RarInfoIterator(object): |
|
||||
def __init__(self, arc): |
|
||||
self.arc = arc |
|
||||
self.index = 0 |
|
||||
self.headerData = RARHeaderDataEx() |
|
||||
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData)) |
|
||||
if self.res in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]: |
|
||||
raise IncorrectRARPassword |
|
||||
self.arc.lockStatus = "locked" |
|
||||
self.arc.needskip = False |
|
||||
|
|
||||
def __iter__(self): |
|
||||
return self |
|
||||
|
|
||||
def next(self): |
|
||||
if self.index>0: |
|
||||
if self.arc.needskip: |
|
||||
RARProcessFile(self.arc._handle, RAR_SKIP, None, None) |
|
||||
self.res = RARReadHeaderEx(self.arc._handle, ctypes.byref(self.headerData)) |
|
||||
|
|
||||
if self.res: |
|
||||
raise StopIteration |
|
||||
self.arc.needskip = True |
|
||||
|
|
||||
data = {} |
|
||||
data['index'] = self.index |
|
||||
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) |
|
||||
if self.headerData.CmtState == 1: |
|
||||
data['comment'] = self.headerData.CmtBuf.value |
|
||||
else: |
|
||||
data['comment'] = None |
|
||||
self.index += 1 |
|
||||
return data |
|
||||
|
|
||||
|
|
||||
def __del__(self): |
|
||||
self.arc.lockStatus = "finished" |
|
||||
|
|
||||
def generate_password_provider(password): |
|
||||
def password_provider_callback(msg, UserData, P1, P2): |
|
||||
if msg == UCM_NEEDPASSWORD and password!=None: |
|
||||
(ctypes.c_char*P2).from_address(P1).value = password |
|
||||
return 1 |
|
||||
return password_provider_callback |
|
||||
|
|
||||
class RarFileImplementation(object): |
|
||||
|
|
||||
def init(self, password=None): |
|
||||
self.password = password |
|
||||
archiveData = RAROpenArchiveDataEx(ArcNameW=self.archiveName, OpenMode=RAR_OM_EXTRACT) |
|
||||
self._handle = RAROpenArchiveEx(ctypes.byref(archiveData)) |
|
||||
self.c_callback = UNRARCALLBACK(generate_password_provider(self.password)) |
|
||||
RARSetCallback(self._handle, self.c_callback, 1) |
|
||||
|
|
||||
if archiveData.OpenResult != 0: |
|
||||
raise RARExceptions[archiveData.OpenResult] |
|
||||
|
|
||||
if archiveData.CmtState == 1: |
|
||||
self.comment = archiveData.CmtBuf.value |
|
||||
else: |
|
||||
self.comment = None |
|
||||
|
|
||||
if password: |
|
||||
RARSetPassword(self._handle, password) |
|
||||
|
|
||||
self.lockStatus = "ready" |
|
||||
|
|
||||
self.isVolume = archiveData.Flags & 1 |
|
||||
|
|
||||
|
|
||||
def destruct(self): |
|
||||
if self._handle and RARCloseArchive: |
|
||||
RARCloseArchive(self._handle) |
|
||||
|
|
||||
def make_sure_ready(self): |
|
||||
if self.lockStatus == "locked": |
|
||||
raise InvalidRARArchiveUsage("cannot execute infoiter() without finishing previous one") |
|
||||
if self.lockStatus == "finished": |
|
||||
self.destruct() |
|
||||
self.init(self.password) |
|
||||
|
|
||||
def infoiter(self): |
|
||||
self.make_sure_ready() |
|
||||
return RarInfoIterator(self) |
|
||||
|
|
||||
def read_files(self, checker): |
|
||||
res = [] |
|
||||
for info in self.infoiter(): |
|
||||
if checker(info) and not info.isdir: |
|
||||
reader = PassiveReader() |
|
||||
c_callback = UNRARCALLBACK(reader._callback) |
|
||||
RARSetCallback(self._handle, c_callback, 1) |
|
||||
tmpres = RARProcessFile(self._handle, RAR_TEST, None, None) |
|
||||
if tmpres in [ERAR_BAD_DATA, ERAR_MISSING_PASSWORD]: |
|
||||
raise IncorrectRARPassword |
|
||||
self.needskip = False |
|
||||
res.append((info, reader.get_result())) |
|
||||
return res |
|
||||
|
|
||||
|
|
||||
def extract(self, checker, path, withSubpath, overwrite): |
|
||||
res = [] |
|
||||
for info in self.infoiter(): |
|
||||
checkres = checker(info) |
|
||||
if checkres!=False and not info.isdir: |
|
||||
if checkres==True: |
|
||||
fn = info.filename |
|
||||
if not withSubpath: |
|
||||
fn = os.path.split(fn)[-1] |
|
||||
target = os.path.join(path, fn) |
|
||||
else: |
|
||||
raise DeprecationWarning, "Condition callbacks returning strings are deprecated and only supported in Windows" |
|
||||
target = checkres |
|
||||
if overwrite or (not os.path.exists(target)): |
|
||||
tmpres = RARProcessFile(self._handle, RAR_EXTRACT, None, target) |
|
||||
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 |
|
||||
|
|
||||
|
|
||||
|
|
Loading…
Reference in new issue