Skip to content

Commit 854ff62

Browse files
committed
support custom paths when adding files to an archive
closes #93
1 parent a48ddb8 commit 854ff62

3 files changed

Lines changed: 92 additions & 61 deletions

File tree

libarchive/entry.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,54 @@ class ArchiveEntry:
3636

3737
__slots__ = ('_archive_p', '_entry_p')
3838

39-
def __init__(self, archive_p):
39+
def __init__(self, archive_p=None, **attributes):
40+
"""Allocate memory for an `archive_entry` struct.
41+
42+
The attributes are passed to the `modify` method.
43+
"""
4044
self._archive_p = archive_p
4145
self._entry_p = ffi.entry_new()
46+
if attributes:
47+
self.modify(**attributes)
4248

4349
def __del__(self):
4450
ffi.entry_free(self._entry_p)
4551

4652
def __str__(self):
4753
return self.pathname
4854

55+
def modify(self, **attributes):
56+
"""Convenience method to modify the entry's attributes.
57+
58+
Args:
59+
filetype (int): the file's type, see the `FileType` class for values
60+
pathname (str): the file's path
61+
linkpath (str): the other path of the file, if the file is a link
62+
size (int | None): the file's size, in bytes
63+
perm (int): the file's permissions in standard Unix format, e.g. 0o640
64+
uid (int): the file owner's numerical identifier
65+
gid (int): the file group's numerical identifier
66+
uname (str | bytes): the file owner's name
67+
gname (str | bytes): the file group's name
68+
atime (int | Tuple[int, int] | float | None):
69+
the file's most recent access time,
70+
either in seconds or as a tuple (seconds, nanoseconds)
71+
mtime (int | Tuple[int, int] | float | None):
72+
the file's most recent modification time,
73+
either in seconds or as a tuple (seconds, nanoseconds)
74+
ctime (int | Tuple[int, int] | float | None):
75+
the file's most recent metadata change time,
76+
either in seconds or as a tuple (seconds, nanoseconds)
77+
birthtime (int | Tuple[int, int] | float | None):
78+
the file's creation time (for archive formats that support it),
79+
either in seconds or as a tuple (seconds, nanoseconds)
80+
rdev (int | Tuple[int, int]): device number, if the file is a device
81+
rdevmajor (int): major part of the device number
82+
rdevminor (int): minor part of the device number
83+
"""
84+
for name, value in attributes.items():
85+
setattr(self, name, value)
86+
4987
@property
5088
def filetype(self):
5189
return ffi.entry_filetype(self._entry_p)

libarchive/ffi.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
ARCHIVE_WARN = -20 # Partial success.
3535
ARCHIVE_FAILED = -25 # Current operation cannot complete.
3636
ARCHIVE_FATAL = -30 # No more operations are possible.
37-
REGULAR_FILE = 0o100000
38-
DEFAULT_UNIX_PERMISSION = 0o664
3937

4038

4139
# Callback types

libarchive/write.py

Lines changed: 53 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
from contextlib import contextmanager
22
from ctypes import byref, cast, c_char, c_size_t, c_void_p, POINTER
3+
from posixpath import join
34
import warnings
45

56
from . import ffi
6-
from .entry import ArchiveEntry
7+
from .entry import ArchiveEntry, FileType
78
from .ffi import (
89
OPEN_CALLBACK, WRITE_CALLBACK, CLOSE_CALLBACK, NO_OPEN_CB, NO_CLOSE_CB,
9-
REGULAR_FILE, DEFAULT_UNIX_PERMISSION, ARCHIVE_EOF,
10+
ARCHIVE_EOF,
1011
page_size, entry_sourcepath, entry_clear, read_disk_new, read_disk_open_w,
1112
read_next_header2, read_disk_descend, read_free, write_header, write_data,
12-
write_finish_entry, entry_set_size, entry_set_filetype, entry_set_perm,
13+
write_finish_entry,
1314
read_disk_set_behavior
1415
)
1516

@@ -42,26 +43,55 @@ def add_entries(self, entries):
4243
write_data(write_p, block, len(block))
4344
write_finish_entry(write_p)
4445

45-
def add_files(self, *paths, **kw):
46-
"""Read the given paths from disk and add them to the archive.
46+
def add_files(
47+
self, *paths, flags=0, lookup=False, pathname=None, **attributes
48+
):
49+
"""Read files through the OS and add them to the archive.
4750
48-
The keyword arguments (`**kw`) are passed to `new_archive_read_disk`.
51+
Args:
52+
paths (str): the paths of the files to add to the archive
53+
flags (int):
54+
passed to the C function `archive_read_disk_set_behavior`;
55+
use the `libarchive.flags.READDISK_*` constants
56+
lookup (bool):
57+
when True, the C function `archive_read_disk_set_standard_lookup`
58+
is called to enable the lookup of user and group names
59+
pathname (str | None):
60+
the path of the file in the archive, defaults to the source path
61+
attributes (dict): passed to `ArchiveEntry.modify()`
62+
63+
Raises:
64+
ArchiveError: if a file doesn't exist or can't be accessed, or if
65+
adding it to the archive fails
4966
"""
5067
write_p = self._pointer
5168

5269
block_size = ffi.write_get_bytes_per_block(write_p)
5370
if block_size <= 0:
5471
block_size = 10240 # pragma: no cover
5572

56-
entry = ArchiveEntry(None)
73+
entry = ArchiveEntry()
5774
entry_p = entry._entry_p
75+
destination_path = attributes.pop('pathname', None)
5876
for path in paths:
59-
with new_archive_read_disk(path, **kw) as read_p:
77+
with new_archive_read_disk(path, flags, lookup) as read_p:
6078
while 1:
6179
r = read_next_header2(read_p, entry_p)
6280
if r == ARCHIVE_EOF:
6381
break
64-
entry.pathname = entry.pathname.lstrip('/')
82+
entry_path = entry.pathname
83+
if destination_path:
84+
if entry_path == path:
85+
entry_path = destination_path
86+
else:
87+
assert entry_path.startswith(path)
88+
entry_path = join(
89+
destination_path,
90+
entry_path[len(path):].lstrip('/')
91+
)
92+
entry.pathname = entry_path.lstrip('/')
93+
if attributes:
94+
entry.modify(**attributes)
6595
read_disk_descend(read_p)
6696
write_header(write_p, entry_p)
6797
if entry.isreg:
@@ -74,34 +104,24 @@ def add_files(self, *paths, **kw):
74104
write_finish_entry(write_p)
75105
entry_clear(entry_p)
76106

107+
def add_file(self, path, **kw):
108+
"Single-path alias of `add_files()`"
109+
return self.add_files(path, **kw)
110+
77111
def add_file_from_memory(
78112
self, entry_path, entry_size, entry_data,
79-
filetype=REGULAR_FILE, permission=DEFAULT_UNIX_PERMISSION,
80-
atime=None, mtime=None, ctime=None, birthtime=None,
81-
uid=None, gid=None,
113+
filetype=FileType.REGULAR_FILE, permission=0o664,
114+
**other_attributes
82115
):
83116
""""Add file from memory to archive.
84117
85118
Args:
86119
entry_path (str): the file's path
87120
entry_size (int): the file's size, in bytes
88121
entry_data (bytes | Iterable[bytes]): the file's content
89-
filetype (int): the file's type (normal, symlink, etc.)
90-
permission (int): the file's permissions
91-
atime (int | Tuple[int]):
92-
the file's most recent access time,
93-
in seconds or as a tuple (seconds, nanoseconds)
94-
mtime (int | Tuple[int]):
95-
the file's most recent modification time,
96-
in seconds or as a tuple (seconds, nanoseconds)
97-
ctime (int | Tuple[int]):
98-
the file's creation time,
99-
in seconds or as a tuple (seconds, nanoseconds)
100-
birthtime (int | Tuple[int]):
101-
the file's birth time (for archive formats that support it),
102-
in seconds or as a tuple (seconds, nanoseconds)
103-
uid (int): the file owner's identifier
104-
gid (int): the file group's identifier
122+
filetype (int): see `libarchive.entry.ArchiveEntry.modify()`
123+
permission (int): see `libarchive.entry.ArchiveEntry.modify()`
124+
other_attributes: see `libarchive.entry.ArchiveEntry.modify()`
105125
"""
106126
archive_pointer = self._pointer
107127

@@ -112,36 +132,11 @@ def add_file_from_memory(
112132
"entry_data: expected bytes, got %r" % type(entry_data)
113133
)
114134

115-
archive_entry = ArchiveEntry(None)
116-
archive_entry_pointer = archive_entry._entry_p
117-
118-
archive_entry.pathname = entry_path
119-
entry_set_size(archive_entry_pointer, entry_size)
120-
entry_set_filetype(archive_entry_pointer, filetype)
121-
entry_set_perm(archive_entry_pointer, permission)
122-
123-
if uid is not None:
124-
archive_entry.uid = uid
125-
if gid is not None:
126-
archive_entry.gid = gid
127-
128-
if atime is not None:
129-
if not isinstance(atime, tuple):
130-
atime = (atime, 0)
131-
archive_entry.set_atime(*atime)
132-
if mtime is not None:
133-
if not isinstance(mtime, tuple):
134-
mtime = (mtime, 0)
135-
archive_entry.set_mtime(*mtime)
136-
if ctime is not None:
137-
if not isinstance(ctime, tuple):
138-
ctime = (ctime, 0)
139-
archive_entry.set_ctime(*ctime)
140-
if birthtime is not None:
141-
if not isinstance(birthtime, tuple):
142-
birthtime = (birthtime, 0)
143-
archive_entry.set_birthtime(*birthtime)
144-
write_header(archive_pointer, archive_entry_pointer)
135+
entry = ArchiveEntry(
136+
pathname=entry_path, size=entry_size, filetype=filetype,
137+
perm=permission, **other_attributes
138+
)
139+
write_header(archive_pointer, entry._entry_p)
145140

146141
for chunk in entry_data:
147142
if not chunk:

0 commit comments

Comments
 (0)