Skip to content

Commit b507210

Browse files
committed
improve the documentation
closes #26, closes #92, closes #112
1 parent 854ff62 commit b507210

3 files changed

Lines changed: 106 additions & 18 deletions

File tree

README.rst

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,87 @@ Import::
3636

3737
import libarchive
3838

39-
To extract an archive to the current directory::
39+
Extracting archives
40+
-------------------
4041

42+
To extract an archive, use the ``extract_file`` function::
43+
44+
os.chdir('/path/to/target/directory')
4145
libarchive.extract_file('test.zip')
4246

43-
``extract_memory`` extracts from a buffer instead, and ``extract_fd`` extracts
44-
from a file descriptor.
47+
Alternatively, the ``extract_memory`` function can be used to extract from a buffer,
48+
and ``extract_fd`` from a file descriptor.
49+
50+
The ``extract_*`` functions all have an integer ``flags`` argument which is passed
51+
directly to the C function ``archive_write_disk_set_options()``. You can import
52+
the ``EXTRACT_*`` constants from the ``libarchive.extract`` module and see the
53+
official description of each flag in the ``archive_write_disk(3)`` man page.
54+
55+
By default, when the ``flags`` argument is ``None``, the ``SECURE_NODOTDOT``,
56+
``SECURE_NOABSOLUTEPATHS`` and ``SECURE_SYMLINKS`` flags are passed to
57+
libarchive, unless the current directory is the root (``/``).
58+
59+
Reading archives
60+
----------------
4561

46-
To read an archive::
62+
To read an archive, use the ``file_reader`` function::
4763

4864
with libarchive.file_reader('test.7z') as archive:
4965
for entry in archive:
5066
for block in entry.get_blocks():
5167
...
5268

53-
``memory_reader`` reads from a memory buffer instead, and ``fd_reader`` reads
54-
from a file descriptor.
69+
Alternatively, the ``memory_reader`` function can be used to read from a buffer,
70+
``fd_reader`` from a file descriptor, ``stream_reader`` from a stream object
71+
(which must support the standard ``readinto`` method), and ``custom_reader``
72+
from anywhere using callbacks.
5573

56-
To create an archive::
74+
To learn about the attributes of the ``entry`` object, see the ``libarchive/entry.py``
75+
source code or run ``help(libarchive.entry.ArchiveEntry)`` in a Python shell.
5776

58-
with libarchive.file_writer('test.tar.gz', 'ustar', 'gzip') as archive:
59-
archive.add_files('libarchive/', 'README.rst')
77+
Displaying progress
78+
~~~~~~~~~~~~~~~~~~~
79+
80+
The ``stream_reader`` function and the standard `tell <https://docs.python.org/3/library/io.html#io.IOBase.tell>`_ method can be used to estimate how much of an archive has been read by your program. Here's an example of a progress bar using `tqdm <https://pypi.org/project/tqdm/>`_::
6081

61-
``memory_writer`` writes to a memory buffer instead, ``fd_writer`` writes to a
62-
file descriptor, and ``custom_writer`` sends the data to a callback function.
82+
with tqdm(total=os.stat(archive_path).st_size, unit='bytes') as pbar, \
83+
open(archive_path, 'rb') as file, \
84+
libarchive.stream_reader(file) as archive:
85+
for entry in archive:
86+
...
87+
pbar.update(file.tell() - pbar.n)
88+
89+
Of course this is only useful if your program processes large archives.
90+
91+
Creating archives
92+
-----------------
93+
94+
To create an archive, use the ``file_writer`` function::
6395

64-
You can also find more thorough examples in the ``tests/`` directory.
96+
from libarchive.entry import FileType
97+
98+
with libarchive.file_writer('test.tar.gz', 'ustar', 'gzip') as archive:
99+
# Add the `libarchive/` directory and everything in it (recursively),
100+
# then the `README.rst` file.
101+
archive.add_files('libarchive/', 'README.rst')
102+
# Add a regular file defined from scratch.
103+
data = b'foobar'
104+
archive.add_file_from_memory('../escape-test', len(data), data)
105+
# Add a directory defined from scratch.
106+
early_epoch = (42, 42) # 1970-01-01 00:00:42.000000042
107+
archive.add_file_from_memory(
108+
'metadata-test', 0, b'',
109+
filetype=FileType.DIRECTORY, permission=0o755, uid=4242, gid=4242,
110+
atime=early_epoch, mtime=early_epoch, ctime=early_epoch, birthtime=early_epoch,
111+
)
112+
113+
Alternatively, the ``memory_writer`` function can be used to write to a memory buffer,
114+
``fd_writer`` to a file descriptor, and ``custom_writer`` to a callback function.
115+
116+
For each of those functions, the mandatory second argument is the archive format,
117+
and the optional third argument is the compression format (called “filter” in
118+
libarchive). The acceptable values are listed in ``libarchive.ffi.WRITE_FORMATS``
119+
and ``libarchive.ffi.WRITE_FILTERS``.
65120

66121
License
67122
=======

libarchive/entry.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ def __init__(self, archive_p=None, **attributes):
4747
self.modify(**attributes)
4848

4949
def __del__(self):
50+
"""Free the C struct"""
5051
ffi.entry_free(self._entry_p)
5152

5253
def __str__(self):
54+
"""Returns the file's path"""
5355
return self.pathname
5456

5557
def modify(self, **attributes):
@@ -129,7 +131,16 @@ def gname(self, value):
129131
ffi.entry_update_gname_utf8(self._entry_p, value)
130132

131133
def get_blocks(self, block_size=ffi.page_size):
134+
"""Read the file's content, keeping only one chunk in memory at a time.
135+
136+
Don't do anything like `list(entry.get_blocks())`, it would silently fail.
137+
138+
Args:
139+
block_size (int): the buffer's size, in bytes
140+
"""
132141
archive_p = self._archive_p
142+
if not archive_p:
143+
raise TypeError("this entry isn't linked to any content")
133144
buf = create_string_buffer(block_size)
134145
read = ffi.read_data
135146
while 1:
@@ -200,8 +211,8 @@ def atime(self, value):
200211
self.set_atime(int(seconds), int(fraction * 1_000_000_000))
201212

202213
def set_atime(self, timestamp_sec, timestamp_nsec):
203-
return ffi.entry_set_atime(self._entry_p,
204-
timestamp_sec, timestamp_nsec)
214+
"Kept for backward compatibility. `entry.atime = ...` is supported now."
215+
return ffi.entry_set_atime(self._entry_p, timestamp_sec, timestamp_nsec)
205216

206217
@property
207218
def mtime(self):
@@ -224,8 +235,8 @@ def mtime(self, value):
224235
self.set_mtime(int(seconds), int(fraction * 1_000_000_000))
225236

226237
def set_mtime(self, timestamp_sec, timestamp_nsec):
227-
return ffi.entry_set_mtime(self._entry_p,
228-
timestamp_sec, timestamp_nsec)
238+
"Kept for backward compatibility. `entry.mtime = ...` is supported now."
239+
return ffi.entry_set_mtime(self._entry_p, timestamp_sec, timestamp_nsec)
229240

230241
@property
231242
def ctime(self):
@@ -248,8 +259,8 @@ def ctime(self, value):
248259
self.set_ctime(int(seconds), int(fraction * 1_000_000_000))
249260

250261
def set_ctime(self, timestamp_sec, timestamp_nsec):
251-
return ffi.entry_set_ctime(self._entry_p,
252-
timestamp_sec, timestamp_nsec)
262+
"Kept for backward compatibility. `entry.ctime = ...` is supported now."
263+
return ffi.entry_set_ctime(self._entry_p, timestamp_sec, timestamp_nsec)
253264

254265
@property
255266
def birthtime(self):
@@ -272,6 +283,7 @@ def birthtime(self, value):
272283
self.set_birthtime(int(seconds), int(fraction * 1_000_000_000))
273284

274285
def set_birthtime(self, timestamp_sec, timestamp_nsec=0):
286+
"Kept for backward compatibility. `entry.birthtime = ...` is supported now."
275287
return ffi.entry_set_birthtime(
276288
self._entry_p, timestamp_sec, timestamp_nsec
277289
)
@@ -331,6 +343,7 @@ def mode(self, value):
331343

332344
@property
333345
def strmode(self):
346+
"""The file's mode as a string, e.g. '?rwxrwx---'"""
334347
# note we strip the mode because archive_entry_strmode
335348
# returns a trailing space: strcpy(bp, "?rwxrwxrwx ");
336349
return ffi.entry_strmode(self._entry_p).strip()

libarchive/write.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,11 @@ def custom_writer(
191191
open_func=None, close_func=None, block_size=page_size,
192192
archive_write_class=ArchiveWrite, options='', passphrase=None,
193193
):
194+
"""Create an archive and send it in chunks to the `write_func` function.
195+
196+
For formats and filters, see `WRITE_FORMATS` and `WRITE_FILTERS` in the
197+
`libarchive.ffi` module.
198+
"""
194199

195200
def write_cb_internal(archive_p, context, buffer_, length):
196201
data = cast(buffer_, POINTER(c_char * length))[0]
@@ -213,6 +218,11 @@ def fd_writer(
213218
fd, format_name, filter_name=None,
214219
archive_write_class=ArchiveWrite, options='', passphrase=None,
215220
):
221+
"""Create an archive and write it into a file descriptor.
222+
223+
For formats and filters, see `WRITE_FORMATS` and `WRITE_FILTERS` in the
224+
`libarchive.ffi` module.
225+
"""
216226
with new_archive_write(format_name, filter_name, options,
217227
passphrase) as archive_p:
218228
ffi.write_open_fd(archive_p, fd)
@@ -224,6 +234,11 @@ def file_writer(
224234
filepath, format_name, filter_name=None,
225235
archive_write_class=ArchiveWrite, options='', passphrase=None,
226236
):
237+
"""Create an archive and write it into a file.
238+
239+
For formats and filters, see `WRITE_FORMATS` and `WRITE_FILTERS` in the
240+
`libarchive.ffi` module.
241+
"""
227242
with new_archive_write(format_name, filter_name, options,
228243
passphrase) as archive_p:
229244
ffi.write_open_filename_w(archive_p, filepath)
@@ -235,6 +250,11 @@ def memory_writer(
235250
buf, format_name, filter_name=None,
236251
archive_write_class=ArchiveWrite, options='', passphrase=None,
237252
):
253+
"""Create an archive and write it into a buffer.
254+
255+
For formats and filters, see `WRITE_FORMATS` and `WRITE_FILTERS` in the
256+
`libarchive.ffi` module.
257+
"""
238258
with new_archive_write(format_name, filter_name, options,
239259
passphrase) as archive_p:
240260
used = byref(c_size_t())

0 commit comments

Comments
 (0)