Skip to content

Commit

Permalink
Misc Progress
Browse files Browse the repository at this point in the history
* updated README to be more accurate.
* added support for askExtractMode
* preliminary support for FILETIMEs on PROPVARIANTS (like mtime)
* slightly more data/data/debugging/logging info
  • Loading branch information
harvimt committed Oct 16, 2013
1 parent e731beb commit 54e6225
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 21 deletions.
12 changes: 6 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
python-lib7zip
==============
Python bindings for lib7zip_
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Python bindings for 7-Zip
~~~~~~~~~~~~~~~~~~~~~~~~~
:author: Mark Harviston <[email protected]>
:version: 0.1

pylib7zip is a direct binding to 7z.dll from the 7-zip project (7zip.org)

7z.dll uses Windows COM+ calling conventions with an over-engineered slightly pathological
OOP API.
7z.dll uses Windows COM+ calling conventions without registering itself with the COM server
and has an over-engineered slightly pathological OOP API.

Currently only works on Windows with Python 3.3
Currently only works on Windows with Python 3.3.

This provides roughly the same functionality as lib7zip does for C++ and SevenZipSharp does for C#
but with a clean Pythonic API.

Like lib7zip getting metadata and extracting files are the only operations supported, creating archives, or updating them in-place is not supported.

This is beta software and may crash if used in an unusual way.
This is beta software and may crash if used in an unusual (or even a usual) way.

Dependencies
------------
Expand Down
12 changes: 12 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import logging
#logger = logging.getLogger('lib7zip')
logging.basicConfig(
level=logging.DEBUG,
#level=logging.INFO,
format='%(asctime)s - %(name)s::%(funcName)s @ %(filename)s:%(lineno)d - %(levelname)s - %(message)s',
datefmt='%H:%M',
)
logger = logging.getLogger('lib7zip.simplecom')
logger.setLevel(logging.INFO)
logger = logging.getLogger('lib7zip.stream')
logger.setLevel(logging.INFO)
1 change: 1 addition & 0 deletions lib7zip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
HRESULT SetLargePageMode(); /* Unused */
void* calloc(size_t, size_t);
void* malloc(size_t);
void* memset(void*, int, size_t);
void free(void*);
Expand Down
35 changes: 26 additions & 9 deletions lib7zip/extract_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

from .py7ziptypes import IID_ICryptoGetTextPassword, IID_IArchiveExtractCallback, \
IID_ISequentialOutStream, IID_ICompressProgressInfo, IID_ICryptoGetTextPassword2, \
OperationResult
OperationResult, AskMode

from .wintypes import HRESULT
from . import log, ffi, py7ziptypes
from . import log, ffi, C, py7ziptypes
from .simplecom import IUnknownImpl
from .stream import FileOutStream

Expand All @@ -24,6 +24,14 @@ def __init__(self, password=''):
#self.out_file = FileOutStream(file)
#self.password = ffi.new('char[]', (password or '').encode('ascii'))
self.password = ffi.new('wchar_t[]', password or '')
#password = password or ''
'''
self._password = ffi.gc(C.malloc(ffi.sizeof('wchar_t') * len(password) + 1), C.free)
self.password = ffi.cast('wchar_t*', self._password)
self.password[0:len(password)] = password
self.password[len(password)] = '\0'
'''

super().__init__()

def cleanup(self):
Expand Down Expand Up @@ -68,20 +76,19 @@ def CryptoGetTextPassword(self, me, password):
assert password[0] == ffi.NULL
#log.debug('passowrd?=%s', ffi.string(password[0]))
#password = ffi.cast('wchar_t**', password)
#password[0] = self.password
password[0] = self.password
#password[0] = ffi.NULL
#log.debug('CryptoGetTextPassword returning, password=%s', ffi.string(password[0]))
log.debug('CryptoGetTextPassword returning, password=%s', ffi.string(password[0]))
return HRESULT.S_OK.value
#return len(self.password)

def CryptoGetTextPassword2(self, me, isdefined, password):
log.debug('CryptoGetTextPassword2 me=%r password=%r%r', me, ffi.string(self.password), self.password)
isdefined[0] = 1
isdefined[0] = bool(self.password)
password[0] = self.password
log.debug('CryptoGetTextPassword returning, password=%s', ffi.string(password[0]))
return HRESULT.S_OK.value


#STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize) PURE;
def SetRatioInfo(self, me, in_size, out_size):
log.debug('SetRatioInfo: in_size=%d, out_size=%d' % (int(in_size[0]), int(out_size[0])))
Expand All @@ -100,12 +107,17 @@ def __init__(self, archive, directory='', password=''):
super().__init__(password)

def GetStream(self, me, index, outStream, askExtractMode):
log.debug('GetStream [%d]', index)
askExtractMode = AskMode(askExtractMode)
log.debug('GetStream(%d, -, %d)', index, askExtractMode)

if askExtractMode != AskMode.kExtract:
return HRESULT.S_OK.value


path = os.path.join(self.directory, self.archive[index].path)
dirname = os.path.dirname(path)
log.debug('extracting to: %s', path)


if self.archive[index].isdir:
os.makedirs(path, exist_ok=True)
outStream[0] = ffi.NULL
Expand Down Expand Up @@ -140,7 +152,12 @@ def __init__(self, stream, index, password=''):
super().__init__(password)

def GetStream(self, me, index, outStream, askExtractMode):
log.debug('GetStream')
askExtractMode = AskMode(askExtractMode)
log.debug('GetStream(%d, -, %d)', index, askExtractMode)

if askExtractMode != AskMode.kExtract:
return HRESULT.S_OK.value

if self.index == index:
outStream[0] = self.stream.instances[py7ziptypes.IID_ISequentialOutStream]
return HRESULT.S_OK.value
Expand Down
7 changes: 6 additions & 1 deletion lib7zip/py7ziptypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def createIID(yy, xx):

#PropID.h

class ArchiveProps:
class ArchiveProps(IntEnum):
"""Archive and Archive Item Propertys"""
noproperty = 0 # kpidNoProperty
mainsubfile = 1 # kpidMainSubfile
Expand Down Expand Up @@ -326,3 +326,8 @@ class OperationResult(Enum):
kUnSupportedMethod = 1
kDataError = 2
kCRCError = 3

class AskMode(Enum):
kExtract = 0
kTest = 1
kSkip = 2
20 changes: 19 additions & 1 deletion lib7zip/winhelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .wintypes import *
#from bitstring import BitArray
#import warnings
from datetime import datetime, timedelta

def guidp2uuid(guid):
"""GUID* -> uuid.UUID"""
Expand Down Expand Up @@ -93,7 +94,7 @@ def get_prop_val(fn, forcetype=None, checktype=None):
ptr = alloc_propvariant()
RNOK(fn(ptr))
pvar = ffi.cast('PROPVARIANT*', ptr)
vt = forcetype or pvar.vt
vt = forcetype or VARTYPE(pvar.vt)
if pvar.vt in (VARTYPE.VT_EMPTY, VARTYPE.VT_NULL):
# always check for null, pvar.vt is not a bug
return None
Expand All @@ -113,5 +114,22 @@ def get_prop_val(fn, forcetype=None, checktype=None):
return guidp2uu(pvar.puuid)
elif vt == VARTYPE.VT_BSTR:
return ffi.string(pvar.bstrVal)
elif False: #vt == VARTYPE.VT_FILETIME:
#FIXME This should work but it totally doesn't
timestamp = int(pvar.filetime.dwLowDateTime)
timestamp += int(pvar.filetime.dwLowDateTime) << 32
#timestamp is in 100-nanosecond intervals, convert to nanoseconds
timestamp *= 100
#convert to seconds
timestamp /= 1e9

#timestamp is now the number of seconds since Jan 1, 1601 CE
jan01_1601 = datetime(year=1601, month=1, day=1)
delta = timedelta(seconds=timestamp)
import pdb; pdb.set_trace()
return jan01_1601 + delta

# timestamp isn't guaranteed to be UTC (it isn't on FAT for example)
# it is however recommended, hope 7zip enforces that.
else:
raise TypeError("type code %d not supported" % vt)
10 changes: 8 additions & 2 deletions lib7zip/wintypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
typedef uint8_t GUID[16];
typedef struct PROPVARIANT {
typedef struct {
uint32_t dwLowDateTime;
uint32_t dwHighDateTime;
} FILETIME;
typedef struct {
VARTYPE vt;
unsigned short wReserved1;
unsigned short wReserved2;
Expand All @@ -26,9 +31,10 @@
wchar_t* bstrVal;
uint64_t uhVal;
GUID* puuid;
FILETIME filetime;
/* snip */
};
} PROPVARIANT;
} PROPVARIANT;
typedef uint32_t HRESULT;
Expand Down
Binary file modified tests/complex.7z
Binary file not shown.
Binary file added tests/simple_crypt.7z
Binary file not shown.
Binary file added tests/simple_crypt.zip
Binary file not shown.
Binary file added tests/simple_crypt_filename.7z
Binary file not shown.
7 changes: 5 additions & 2 deletions tests/test_archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def IX(isdir=False, crc=None, contents=''):
#return _IX(isdir, crc, contents.encode('utf8') if contents is not None else None)
return _IX(isdir, crc, contents)
J = os.path.join

COMPLEX_MD = {
J('complex','articles'): IX(True),
J('complex','articles','the definate article.txt'): IX(False, 0x3C456DE6, 'the'),
Expand All @@ -40,7 +41,6 @@ def test_complex():
with Archive('tests/complex.7z') as archive:
for item in archive:
log.debug(item.path)
#print(item.path)
try:
md = COMPLEX_MD[item.path]
except KeyError as ex:
Expand All @@ -64,7 +64,10 @@ def test_extract_dir_complex(tmp_dir):
for path, md in COMPLEX_MD.items():
if md.contents:
with open(os.path.join(tmp_dir, path), encoding='utf-8') as f:
assert f.read() == md.contents
file_contents = f.read()
#if 'unicode' in path:
# import pdb; pdb.set_trace()
assert file_contents == md.contents

@pytest.mark.parametrize('path', simple_archives)
def test_extract_stream(path):
Expand Down

0 comments on commit 54e6225

Please sign in to comment.