[go: up one dir, main page]

Menu

[r1440]: / mcomix / archive / rar.py  Maximize  Restore  History

Download this file

342 lines (300 with data), 13.4 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# -*- coding: utf-8 -*-
""" Glue around libunrar.so/unrar.dll to extract RAR files without having to
resort to calling rar/unrar manually. """
import sys, os
import ctypes, ctypes.util
from mcomix import constants
from mcomix.archive import archive_base
from mcomix import log
if sys.platform == 'win32':
UNRARCALLBACK = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_uint,
ctypes.c_long, ctypes.c_long, ctypes.c_long)
else:
UNRARCALLBACK = ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_uint,
ctypes.c_long, ctypes.c_long, ctypes.c_long)
class RarArchive(archive_base.BaseArchive):
""" Wrapper class for libunrar. All string values passed to this class must be unicode objects.
In turn, all values returned are also unicode. """
# Nope! Not a good idea...
support_concurrent_extractions = False
class _OpenMode(object):
""" Rar open mode """
RAR_OM_LIST = 0
RAR_OM_EXTRACT = 1
class _ProcessingMode(object):
""" Rar file processing mode """
RAR_SKIP = 0
RAR_EXTRACT = 2
class _ErrorCode(object):
""" Rar error codes """
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
class _RAROpenArchiveDataEx(ctypes.Structure):
""" Archive header structure. Used by DLL calls. """
_pack_ = 1
_fields_ = [("ArcName", ctypes.c_char_p),
("ArcNameW", ctypes.c_wchar_p),
("OpenMode", ctypes.c_uint),
("OpenResult", ctypes.c_uint),
("CmtBuf", ctypes.c_char_p),
("CmtBufSize", ctypes.c_uint),
("CmtSize", ctypes.c_uint),
("CmtState", ctypes.c_uint),
("Flags", ctypes.c_uint),
("Callback", UNRARCALLBACK),
("UserData", ctypes.c_long),
("Reserved", ctypes.c_uint * 28)]
class _RARHeaderDataEx(ctypes.Structure):
""" Archive file structure. Used by DLL calls. """
_pack_ = 1
_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_char_p),
("CmtBufSize", ctypes.c_uint),
("CmtSize", ctypes.c_uint),
("CmtState", ctypes.c_uint),
("Reserved", ctypes.c_uint * 1024)]
@staticmethod
def is_available():
""" Returns True if unrar.dll can be found, False otherwise. """
return bool(_get_unrar_dll())
def __init__(self, archive):
""" Initialize Unrar.dll. """
super(RarArchive, self).__init__(archive)
self._unrar = _get_unrar_dll()
self._handle = None
self._callback_function = None
self._is_solid = False
# Information about the current file will be stored in this structure
self._headerdata = RarArchive._RARHeaderDataEx()
self._current_filename = None
# Set up function prototypes.
# Mandatory since pointers get truncated on x64 otherwise!
self._unrar.RAROpenArchiveEx.restype = ctypes.c_void_p
self._unrar.RAROpenArchiveEx.argtypes = \
[ctypes.POINTER(RarArchive._RAROpenArchiveDataEx)]
self._unrar.RARCloseArchive.restype = ctypes.c_int
self._unrar.RARCloseArchive.argtypes = \
[ctypes.c_void_p]
self._unrar.RARReadHeaderEx.restype = ctypes.c_int
self._unrar.RARReadHeaderEx.argtypes = \
[ctypes.c_void_p, ctypes.POINTER(RarArchive._RARHeaderDataEx)]
self._unrar.RARProcessFileW.restype = ctypes.c_int
self._unrar.RARProcessFileW.argtypes = \
[ctypes.c_void_p, ctypes.c_int, ctypes.c_wchar_p, ctypes.c_wchar_p]
self._unrar.RARSetCallback.argtypes = \
[ctypes.c_void_p, UNRARCALLBACK, ctypes.c_long]
def is_solid(self):
return self._is_solid
def iter_contents(self):
""" List archive contents. """
self._close()
self._open()
try:
while True:
self._read_header()
if 0 != (0x10 & self._headerdata.Flags):
self._is_solid = True
filename = self._current_filename
yield filename
# Skip to the next entry if we're still on the same name
# (extract may have been called by iter_extract).
if filename == self._current_filename:
self._process()
except UnrarException as exc:
log.error('Error while listing contents: %s', str(exc))
except EOFError:
# End of archive reached.
pass
finally:
self._close()
def extract(self, filename, destination_dir):
""" Extract <filename> from the archive to <destination_dir>. """
if not self._handle:
self._open()
looped = False
while True:
# Check if the current entry matches the requested file.
if self._current_filename is not None:
if (self._current_filename == filename):
# It's the entry we're looking for, extract it.
dest = ctypes.c_wchar_p(os.path.join(destination_dir, filename))
self._process(dest)
break
# Not the right entry, skip it.
self._process()
try:
self._read_header()
except EOFError:
# Archive end was reached, this might be due to out-of-order
# extraction while the handle was still open. Close the
# archive and jump back to archive start and try to extract
# file again. Do this only once; if the file isn't found after
# a second full pass, it probably doesn't even exist in the
# archive.
if looped:
break
self._open()
# After the method returns, the RAR handler is still open and pointing
# to the next archive file. This will improve extraction speed for sequential file reads.
# After all files have been extracted, close() should be called to free the handler resources.
def close(self):
""" Close the archive handle """
self._close()
def _open(self):
""" Open rar handle for extraction. """
self._callback_function = UNRARCALLBACK(self._password_callback)
archivedata = RarArchive._RAROpenArchiveDataEx(ArcNameW=self.archive,
OpenMode=RarArchive._OpenMode.RAR_OM_EXTRACT,
Callback=self._callback_function,
UserData=0)
handle = self._unrar.RAROpenArchiveEx(ctypes.byref(archivedata))
if not handle:
errormessage = UnrarException.get_error_message(archivedata.OpenResult)
raise UnrarException("Couldn't open archive: %s" % errormessage)
self._unrar.RARSetCallback(handle, self._callback_function, 0)
self._handle = handle
def _check_errorcode(self, errorcode):
if 0 == errorcode:
# No error.
return
self._close()
if RarArchive._ErrorCode.ERAR_END_ARCHIVE == errorcode:
# End of archive reached.
exc = EOFError()
else:
errormessage = UnrarException.get_error_message(errorcode)
exc = UnrarException(errormessage)
raise exc
def _read_header(self):
self._current_filename = None
errorcode = self._unrar.RARReadHeaderEx(self._handle, ctypes.byref(self._headerdata))
self._check_errorcode(errorcode)
self._current_filename = self._headerdata.FileNameW
def _process(self, dest=None):
""" Process current entry: extract or skip it. """
if dest is None:
mode = RarArchive._ProcessingMode.RAR_SKIP
else:
mode = RarArchive._ProcessingMode.RAR_EXTRACT
errorcode = self._unrar.RARProcessFileW(self._handle, mode, None, dest)
self._current_filename = None
self._check_errorcode(errorcode)
def _close(self):
""" Close the rar handle previously obtained by open. """
if self._handle is None:
return
errorcode = self._unrar.RARCloseArchive(self._handle)
if errorcode != 0:
errormessage = UnrarException.get_error_message(errorcode)
raise UnrarException("Couldn't close archive: %s" % errormessage)
self._handle = None
def _password_callback(self, msg, userdata, buffer_address, buffer_size):
""" Called by the unrar library in case of missing password. """
if msg == 2: # UCM_NEEDPASSWORD
self._get_password()
if len(self._password) == 0:
# Abort extraction
return -1
password = ctypes.create_string_buffer(self._password)
copy_size = min(buffer_size, len(password))
ctypes.memmove(buffer_address, password, copy_size)
return 0
else:
# Continue operation
return 1
class UnrarException(Exception):
""" Exception class for RarArchive. """
_exceptions = {
RarArchive._ErrorCode.ERAR_END_ARCHIVE: "End of archive",
RarArchive._ErrorCode.ERAR_NO_MEMORY:" Not enough memory to initialize data structures",
RarArchive._ErrorCode.ERAR_BAD_DATA: "Bad data, CRC mismatch",
RarArchive._ErrorCode.ERAR_BAD_ARCHIVE: "Volume is not valid RAR archive",
RarArchive._ErrorCode.ERAR_UNKNOWN_FORMAT: "Unknown archive format",
RarArchive._ErrorCode.ERAR_EOPEN: "Volume open error",
RarArchive._ErrorCode.ERAR_ECREATE: "File create error",
RarArchive._ErrorCode.ERAR_ECLOSE: "File close error",
RarArchive._ErrorCode.ERAR_EREAD: "Read error",
RarArchive._ErrorCode.ERAR_EWRITE: "Write error",
RarArchive._ErrorCode.ERAR_SMALL_BUF: "Buffer too small",
RarArchive._ErrorCode.ERAR_UNKNOWN: "Unknown error",
RarArchive._ErrorCode.ERAR_MISSING_PASSWORD: "Password missing"
}
@staticmethod
def get_error_message(errorcode):
if errorcode in UnrarException._exceptions:
return UnrarException._exceptions[errorcode]
else:
return "Unkown error"
# Filled on-demand by _get_unrar_dll
_unrar_dll = -1
def _get_unrar_dll():
""" Tries to load libunrar and will return a handle of it.
Returns None if an error occured or the library couldn't be found. """
global _unrar_dll
if _unrar_dll != -1:
return _unrar_dll
# Load unrar.dll on win32
if sys.platform == 'win32':
# First, search for unrar.dll in PATH
unrar_path = ctypes.util.find_library("unrar.dll")
if unrar_path:
try:
return ctypes.windll.LoadLibrary(unrar_path)
except WindowsError:
pass
# The file wasn't found in PATH, try MComix' root directory
try:
return ctypes.windll.LoadLibrary(os.path.join(constants.BASE_PATH, "unrar.dll"))
except WindowsError:
pass
# Last attempt, just use the current directory
try:
_unrar_dll = ctypes.windll.LoadLibrary("unrar.dll")
except WindowsError:
_unrar_dll = None
return _unrar_dll
# Load libunrar.so on UNIX
else:
# find_library on UNIX uses various mechanisms to determine the path
# of a library, so one could assume the library is not installed
# when find_library fails
unrar_path = ctypes.util.find_library("unrar") or \
'/usr/lib/libunrar.so'
if unrar_path:
try:
_unrar_dll = ctypes.cdll.LoadLibrary(unrar_path)
return _unrar_dll
except OSError:
pass
# Last attempt, try the current directory
try:
_unrar_dll = ctypes.cdll.LoadLibrary(os.path.join(os.getcwd(), "libunrar.so"))
except OSError:
_unrar_dll = None
return _unrar_dll
# vim: expandtab:sw=4:ts=4