Skip to content

Commit 0c0c0eb

Browse files
authored
Merge pull request #60 from versity/nic/custom-reader-clean
Add custom_reader for archives
2 parents 2fefe20 + 6e0ed18 commit 0c0c0eb

4 files changed

Lines changed: 57 additions & 16 deletions

File tree

libarchive/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from .entry import ArchiveEntry
22
from .exception import ArchiveError
33
from .extract import extract_fd, extract_file, extract_memory
4-
from .read import fd_reader, file_reader, memory_reader
4+
from .read import custom_reader, fd_reader, file_reader, memory_reader, stream_reader
55
from .write import custom_writer, fd_writer, file_writer, memory_writer
66

77
__all__ = [
88
ArchiveEntry,
99
ArchiveError,
1010
extract_fd, extract_file, extract_memory,
11-
fd_reader, file_reader, memory_reader,
11+
custom_reader, fd_reader, file_reader, memory_reader, stream_reader,
1212
custom_writer, fd_writer, file_writer, memory_writer
1313
]

libarchive/ffi.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
WRITE_CALLBACK = CFUNCTYPE(
4545
c_ssize_t, c_void_p, c_void_p, POINTER(c_void_p), c_size_t
4646
)
47+
READ_CALLBACK = CFUNCTYPE(
48+
c_ssize_t, c_void_p, c_void_p, POINTER(c_void_p)
49+
)
4750
OPEN_CALLBACK = CFUNCTYPE(c_int, c_void_p, c_void_p)
4851
CLOSE_CALLBACK = CFUNCTYPE(c_int, c_void_p, c_void_p)
4952
VOID_CB = lambda *_: ARCHIVE_OK
@@ -170,6 +173,9 @@ def ffi(name, argtypes, restype, errcheck=None):
170173
logger.warning('read filter "%s" is not supported' % f_name)
171174
READ_FILTERS.remove(f_name)
172175

176+
ffi('read_open',
177+
[c_archive_p, c_void_p, OPEN_CALLBACK, READ_CALLBACK, CLOSE_CALLBACK],
178+
c_int, check_int)
173179
ffi('read_open_fd', [c_archive_p, c_int, c_size_t], c_int, check_int)
174180
ffi('read_open_filename_w', [c_archive_p, c_wchar_p, c_size_t],
175181
c_int, check_int)

libarchive/read.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from __future__ import division, print_function, unicode_literals
22

33
from contextlib import contextmanager
4-
from ctypes import cast, c_void_p
4+
from ctypes import cast, c_void_p, POINTER, create_string_buffer
55
from os import fstat, stat
66

77
from . import ffi
8-
from .ffi import ARCHIVE_EOF
8+
from .ffi import (ARCHIVE_EOF, OPEN_CALLBACK, READ_CALLBACK, CLOSE_CALLBACK,
9+
VOID_CB, page_size)
910
from .entry import ArchiveEntry, new_archive_entry
1011

1112

@@ -43,6 +44,22 @@ def new_archive_read(format_name='all', filter_name='all'):
4344
ffi.read_free(archive_p)
4445

4546

47+
@contextmanager
48+
def custom_reader(
49+
read_func, format_name='all', filter_name='all',
50+
open_func=VOID_CB, close_func=VOID_CB, block_size=page_size,
51+
archive_read_class=ArchiveRead
52+
):
53+
"""Read an archive using a custom function.
54+
"""
55+
open_cb = OPEN_CALLBACK(open_func)
56+
read_cb = READ_CALLBACK(read_func)
57+
close_cb = CLOSE_CALLBACK(close_func)
58+
with new_archive_read(format_name, filter_name) as archive_p:
59+
ffi.read_open(archive_p, None, open_cb, read_cb, close_cb)
60+
yield archive_read_class(archive_p)
61+
62+
4663
@contextmanager
4764
def fd_reader(fd, format_name='all', filter_name='all', block_size=4096):
4865
"""Read an archive from a file descriptor.
@@ -76,3 +93,27 @@ def memory_reader(buf, format_name='all', filter_name='all'):
7693
with new_archive_read(format_name, filter_name) as archive_p:
7794
ffi.read_open_memory(archive_p, cast(buf, c_void_p), len(buf))
7895
yield ArchiveRead(archive_p)
96+
97+
98+
@contextmanager
99+
def stream_reader(stream, format_name='all', filter_name='all', block_size=page_size):
100+
"""Read an archive from a stream (an object supporting the `readinto` method).
101+
"""
102+
buf = create_string_buffer(block_size)
103+
buf_p = cast(buf, c_void_p)
104+
105+
def read_func(archive_p, context, ptrptr):
106+
# readinto the buffer, returns number of bytes read
107+
length = stream.readinto(buf)
108+
# write the address of the buffer into the pointer
109+
ptrptr = cast(ptrptr, POINTER(c_void_p))
110+
ptrptr[0] = buf_p
111+
# tell libarchive how much data was written into the buffer
112+
return length
113+
114+
open_cb = OPEN_CALLBACK(VOID_CB)
115+
read_cb = READ_CALLBACK(read_func)
116+
close_cb = CLOSE_CALLBACK(VOID_CB)
117+
with new_archive_read(format_name, filter_name) as archive_p:
118+
ffi.read_open(archive_p, None, open_cb, read_cb, close_cb)
119+
yield ArchiveRead(archive_p)

tests/test_rwx.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Test reading, writing and extracting archives."""
22

33
from __future__ import division, print_function, unicode_literals
4+
import io
45

56
import libarchive
67
from libarchive.extract import EXTRACT_OWNER, EXTRACT_PERM, EXTRACT_TIME
@@ -79,25 +80,18 @@ def test_files(tmpdir):
7980
assert tree2 == tree
8081

8182

82-
def test_custom_writer():
83-
83+
def test_custom_writer_and_stream_reader():
8484
# Collect information on what should be in the archive
8585
tree = treestat('libarchive')
8686

8787
# Create an archive of our libarchive/ directory
88-
blocks = []
89-
90-
def write_cb(data):
91-
blocks.append(data[:])
92-
return len(data)
93-
94-
with libarchive.custom_writer(write_cb, 'zip') as archive:
88+
stream = io.BytesIO()
89+
with libarchive.custom_writer(stream.write, 'zip') as archive:
9590
archive.add_files('libarchive/')
96-
pass
91+
stream.seek(0)
9792

9893
# Read the archive and check that the data is correct
99-
buf = b''.join(blocks)
100-
with libarchive.memory_reader(buf) as archive:
94+
with libarchive.stream_reader(stream, 'zip') as archive:
10195
check_archive(archive, tree)
10296

10397

0 commit comments

Comments
 (0)