Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
2cffae8
symlink
richnetdesign Nov 16, 2021
e9597ea
additional api
richyo-codes Nov 16, 2021
2182fa0
Merge pull request #1 from richnetdesign/richnetdesign-patch-1
richnetdesign Nov 16, 2021
1d1a212
hacking in some more api
richyo-codes Nov 16, 2021
08704fd
wip
richyo-codes Nov 16, 2021
7cc1f0f
Merge branch 'master' of https://github.com/richnetdesign/python-liba…
richyo-codes Nov 16, 2021
22d911a
symlink api
richyo-codes Nov 16, 2021
e3ccbaa
update version
richyo-codes Nov 16, 2021
4729258
Change default python version to the version I'm using
richyo-codes Nov 16, 2021
a5f43e4
Attempt to fix API for 3.10 compatibility
richyo-codes Nov 16, 2021
08db5d3
fixes
richyo-codes Nov 16, 2021
f35ce39
stuff
richyo-codes Nov 16, 2021
8aded1a
work
richyo-codes Nov 16, 2021
f78b671
stuff
richyo-codes Nov 16, 2021
32a2fa4
switched to python 3.9
richyo-codes Nov 17, 2021
9bb3133
Add password support for read access
Vadiml1024 Jul 18, 2022
731e119
upgrade archive write support
Vadiml1024 Jul 18, 2022
b95ad6f
Fixes for wraper rule
Vadiml1024 Jul 18, 2022
0c5afe4
Fixes to handle password protected archives
Vadiml1024 Jul 18, 2022
e66c2a2
Add SONAME to .so file
Vadiml1024 Jul 18, 2022
35a412a
Fix SWIG helper functions
Vadiml1024 Jul 18, 2022
84f25fe
Fix tests
Vadiml1024 Jul 18, 2022
fa0e5fe
python package rename
Vadiml1024 Jul 18, 2022
d6b55c7
package description fix
Vadiml1024 Jul 20, 2022
9c0f639
fixes archive_read_data_into_str to return python byte array
Vadiml1024 Jul 21, 2022
f38459c
Fix archive element closing
Vadiml1024 Jul 21, 2022
855dc86
is_archive: Avoid closing a file not opened by is_archive
Vadiml1024 Jul 22, 2022
546d898
open archive in binary mode
Vadiml1024 Jul 23, 2022
c5e562d
Fix exception text
Vadiml1024 Jul 23, 2022
72fdfa4
Adding pyproject.toml
Vadiml1024 Jul 24, 2022
794ef1c
cosmetics
Vadiml1024 Jul 25, 2022
8118196
retrive version number from SWIG generated .C file
Vadiml1024 Jul 25, 2022
5453c15
Adding githib workflow
Vadiml1024 Aug 1, 2022
5aa1916
Fixes to build instruction
Vadiml1024 Aug 1, 2022
e58fb75
Build instructions fixes
Vadiml1024 Aug 1, 2022
44d6dd8
User correct libarchive branch
Vadiml1024 Aug 1, 2022
86c7797
workflow syntax error fix
Vadiml1024 Aug 1, 2022
2575c1d
workflow fix
Vadiml1024 Aug 1, 2022
2230c1f
workflow syntax fix
Vadiml1024 Aug 1, 2022
345f03b
Workflow syntax
Vadiml1024 Aug 1, 2022
d4d0368
fix depended package name
Vadiml1024 Aug 1, 2022
b986afd
fixed dependencies
Vadiml1024 Aug 1, 2022
bba64b2
more dependencies fixe
Vadiml1024 Aug 1, 2022
e3c90a5
adding epel dependency
Vadiml1024 Aug 1, 2022
a0c02a2
syntax error
Vadiml1024 Aug 1, 2022
0544fed
use correct git clone command
Vadiml1024 Aug 1, 2022
a982dc4
fix cmake instructions
Vadiml1024 Aug 1, 2022
5cebb52
syntax error
Vadiml1024 Aug 1, 2022
9d78f86
Fix environemnt for wheel building
Vadiml1024 Aug 1, 2022
ce813dd
Implement arifact upload
Vadiml1024 Aug 1, 2022
7fc18df
Generate a release
Vadiml1024 Aug 2, 2022
d7107fd
Fix version step
Vadiml1024 Aug 2, 2022
c987fc4
Fix version step
Vadiml1024 Aug 2, 2022
bbb73ab
Adding mini test step
Vadiml1024 Aug 2, 2022
a18fbc1
syntax error fix
Vadiml1024 Aug 2, 2022
13180b3
Run minitest from /tmp
Vadiml1024 Aug 2, 2022
42a487e
Debugging workflow
Vadiml1024 Aug 2, 2022
e11c498
Fixes for version number verification
Vadiml1024 Aug 2, 2022
e2a0d6e
Compile flag fixes
Vadiml1024 Aug 2, 2022
5f94da7
Fix version function
Vadiml1024 Aug 2, 2022
33bbd69
Remove mini test step
Vadiml1024 Aug 2, 2022
4faf9b4
syntax error
Vadiml1024 Aug 2, 2022
e607d7b
Fix release production
Vadiml1024 Aug 2, 2022
1598bc8
Add missing dependecies
Aug 3, 2022
104b2c2
Optimize workflow
Aug 3, 2022
8194803
Fix python copmile step
Aug 3, 2022
c865efb
Siwtch to libachive v3.6.1
Vadiml1024 Aug 5, 2022
000fcf4
Merge branch 'extended' of https://github.com/Vadiml1024/python-libar…
Vadiml1024 Aug 5, 2022
cc26e02
Syntax error
Vadiml1024 Aug 5, 2022
8805124
remove printing if symlink data
Vadiml1024 Aug 5, 2022
0810f77
Merge branch 'master' into extended
Vadiml1024 Aug 8, 2022
5307ed6
Fix for Python2 compatibility
Vadiml1024 Aug 8, 2022
f334049
More Fixes for Python2 compatiblity
Vadiml1024 Aug 8, 2022
2f7a7d6
More Python 2 compatibility fixes
Vadiml1024 Aug 8, 2022
70a29e4
Fix python version check
Vadiml1024 Aug 8, 2022
c94a053
Unicode - related fixes
Vadiml1024 Aug 8, 2022
4cf8f94
Cosmetics
Vadiml1024 Aug 9, 2022
e9c1df8
Cleanup unused code
Vadiml1024 Aug 9, 2022
1f55eb4
Fixes to setup script to merge with parent
Vadiml1024 Aug 9, 2022
825687d
Fixest for merging with parent
Vadiml1024 Aug 9, 2022
f18e798
Fix to test for password protection
Vadiml1024 Aug 9, 2022
b565ace
Implemnet correct password protection test
Vadiml1024 Aug 9, 2022
8d96aa6
Remove pyproject.toml
Vadiml1024 Aug 9, 2022
9a3680d
Fix tests to wokr in python2 and Python3 environments
Vadiml1024 Aug 9, 2022
0b924d9
Fix writing unicod data in python2
Vadiml1024 Aug 9, 2022
0566cec
Fixes for python3
Vadiml1024 Aug 9, 2022
be4531f
More python3 fixes
Vadiml1024 Aug 9, 2022
aa419e6
String and unicode handling fixes
Vadiml1024 Aug 9, 2022
0f1190d
Fix password protected ZIP creation and reading
Vadiml1024 Aug 10, 2022
4dafc26
Implement testing of password protected zip creation
Vadiml1024 Aug 10, 2022
4864f77
Restore formatting
Vadiml1024 Aug 11, 2022
f1a4504
Fix creation of password protected ZIP files
Vadiml1024 Aug 11, 2022
c5658c7
Fixes to create password protected ZIP files
Vadiml1024 Aug 11, 2022
7ae17e9
Cleanup
Vadiml1024 Aug 12, 2022
9d5895f
Fix release number
Vadiml1024 Aug 12, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ verify:
install:
python setup.py install


publish:
python setup.py register
python setup.py sdist upload
Expand Down
14 changes: 7 additions & 7 deletions libarchive/Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
CFLAGS = -g
INCLUDE = -I/usr/include -I.
LIBS = -larchive
INCLUDE = -I/usr/local/include -I/usr/include -I.
LIBS = -L /usr/local/lib -larchive

PYVER ?= 2.7
PYVER ?= 3.9

all: __libarchive.so

_libarchive_wrap.c: _libarchive.i
swig -python -shadow _libarchive.i
swig -python -Wall -shadow _libarchive.i

_libarchive_wrap.o: _libarchive_wrap.c
${CC} -c ${CFLAGS} -fPIC ${INCLUDE} $$(python${PYVER}-config --cflags) _libarchive_wrap.c
${CC} -c ${CFLAGS} -fPIC $$(python${PYVER}-config --cflags) _libarchive_wrap.c

__libarchive.so: _libarchive_wrap.o
${CC} _libarchive_wrap.o -shared $$(python${PYVER}-config --ldflags) -o __libarchive.so ${LIBS}
${CC} _libarchive_wrap.o -shared $$(python${PYVER}-config --ldflags) -Wl,-soname=__libarchive.so -o __libarchive.so ${LIBS}

clean:
rm -f *.o *.so *.pyc
rm -f *.o *.so *.pyc
92 changes: 76 additions & 16 deletions libarchive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@
import sys
import time
import warnings

from libarchive import _libarchive
from io import StringIO

PY3 = sys.version_info[0] == 3
PY3 = sys.version_info[0] >= 3

# Suggested block size for libarchive. Libarchive may adjust it.
BLOCK_SIZE = 10240
Expand Down Expand Up @@ -88,8 +87,13 @@
class EOF(Exception):
'''Raised by ArchiveInfo.from_archive() when unable to read the next
archive header.'''

pass

def version():
'''Returns the version of the libarchive library.'''
return _libarchive.archive_version_string().split()[1]


def get_error(archive):
'''Retrieves the last error description for the given archive instance.'''
Expand All @@ -106,7 +110,7 @@ def call_and_check(func, archive, *args):
elif ret == _libarchive.ARCHIVE_EOF:
raise EOF()
else:
raise Exception('Fatal error executing function, message is: %s.' % get_error(archive))
raise Exception('Problem executing function, message is: %s.' % get_error(archive))


def get_func(name, items, index):
Expand Down Expand Up @@ -142,7 +146,7 @@ def is_archive_name(filename, formats=None):
return format


def is_archive(f, formats=(None, ), filters=(None, )):
def is_archive(f, formats=(None,), filters=(None,)):
'''Check to see if the given file is actually an archive. The format parameter
can be used to specify which archive format is acceptable. If ommitted, all supported
archive formats will be checked. It opens the file using libarchive. If no error is
Expand All @@ -155,8 +159,10 @@ def is_archive(f, formats=(None, ), filters=(None, )):

This function will return True if the file can be opened as an archive using the given
format(s)/filter(s).'''
need_close = False
if isinstance(f, str):
f = open(f, 'r')
f = open(f, 'rb')
need_close = True
a = _libarchive.archive_read_new()
for format in formats:
format = get_func(format, FORMATS, 0)
Expand All @@ -177,11 +183,13 @@ def is_archive(f, formats=(None, ), filters=(None, )):
finally:
_libarchive.archive_read_close(a)
_libarchive.archive_read_free(a)
f.close()
if need_close:
f.close()


class EntryReadStream(object):
'''A file-like object for reading an entry from the archive.'''

def __init__(self, archive, size):
self.archive = archive
self.closed = False
Expand Down Expand Up @@ -241,6 +249,7 @@ class EntryWriteStream(object):
If the size is known ahead of time and provided, then the file contents
are not buffered but flushed directly to the archive. If size is omitted,
then the file contents are buffered and flushed in the close() method.'''

def __init__(self, archive, pathname, size=None):
self.archive = archive
self.entry = Entry(pathname=pathname, mtime=time.time(), mode=stat.S_IFREG)
Expand Down Expand Up @@ -274,7 +283,7 @@ def write(self, data):
if self.buffer:
self.buffer.write(data)
else:
_libarchive.archive_write_data_from_str(self.archive._a, data.encode('utf-8'))
_libarchive.archive_write_data_from_str(self.archive._a, data.encode(ENCODING))
self.bytes += len(data)

def close(self):
Expand All @@ -283,7 +292,7 @@ def close(self):
if self.buffer:
self.entry.size = self.buffer.tell()
self.entry.to_archive(self.archive)
_libarchive.archive_write_data_from_str(self.archive._a, self.buffer.getvalue().encode('utf-8'))
_libarchive.archive_write_data_from_str(self.archive._a, self.buffer.getvalue().encode(ENCODING))
_libarchive.archive_write_finish_entry(self.archive._a)

# Call archive.close() with _defer True to let it know we have been
Expand All @@ -295,14 +304,18 @@ def close(self):

class Entry(object):
'''An entry within an archive. Represents the header data and it's location within the archive.'''

def __init__(self, pathname=None, size=None, mtime=None, mode=None, hpos=None, encoding=ENCODING):

self.pathname = pathname
self.size = size
self.mtime = mtime
self.mode = mode
self.hpos = hpos
self.encoding = encoding

self.symlink = ""

@property
def header_position(self):
return self.hpos
Expand All @@ -328,6 +341,11 @@ def from_archive(cls, archive, encoding=ENCODING):
mode=mode,
hpos=archive.header_position,
)

if entry.issym():
symLinkPath = _libarchive.archive_entry_symlink(e)
entry.symlink = symLinkPath

finally:
_libarchive.archive_entry_free(e)
return entry
Expand Down Expand Up @@ -356,6 +374,8 @@ def from_file(cls, f, entry=None, encoding=ENCODING):
entry.size = getattr(f, 'size', 0)
entry.mtime = getattr(f, 'mtime', time.time())
entry.mode = stat.S_IFREG


return entry

def to_archive(self, archive):
Expand All @@ -370,8 +390,12 @@ def to_archive(self, archive):
_libarchive.archive_entry_set_perm(e, stat.S_IMODE(self.mode))
_libarchive.archive_entry_set_size(e, self.size)
_libarchive.archive_entry_set_mtime(e, self.mtime, 0)

if stat.S_ISLNK(self.mode):
_libarchive.archive_entry_set_symlink(e, self.symlink)

call_and_check(_libarchive.archive_write_header, archive._a, archive._a, e)
#self.hpos = archive.header_position

finally:
_libarchive.archive_entry_free(e)

Expand All @@ -397,11 +421,23 @@ def isblk(self):
class Archive(object):
'''A low-level archive reader which provides forward-only iteration. Consider
this a light-weight pythonic libarchive wrapper.'''
def __init__(self, f, mode='r', format=None, filter=None, entry_class=Entry, encoding=ENCODING, blocksize=BLOCK_SIZE):

def __init__(
self,
f,
mode='r',
format=None,
filter=None,
entry_class=Entry,
encoding=ENCODING,
blocksize=BLOCK_SIZE,
password=None,
):
assert mode in ('r', 'w', 'wb', 'a'), 'Mode should be "r", "w", "wb", or "a".'
self._stream = None
self.encoding = encoding
self.blocksize = blocksize
self.password = password
if isinstance(f, str):
self.filename = f
f = open(f, mode)
Expand Down Expand Up @@ -462,16 +498,28 @@ def __exit__(self, type, value, traceback):
def __del__(self):
self.close()

def set_initial_options(self):
pass

def init(self):
if self.mode == 'r':
self._a = _libarchive.archive_read_new()
else:
self._a = _libarchive.archive_write_new()
self.format_func(self._a)
self.filter_func(self._a)
self.set_initial_options()
if self.mode == 'r':
if self.password:
if isinstance(self.password, list):
for pwd in self.password:
self.add_passphrase(pwd)
else:
self.add_passphrase(self.password)
call_and_check(_libarchive.archive_read_open_fd, self._a, self._a, self.f.fileno(), self.blocksize)
else:
if self.password:
self.set_passphrase(self.password)
call_and_check(_libarchive.archive_write_open_fd, self._a, self._a, self.f.fileno())

def denit(self):
Expand Down Expand Up @@ -509,9 +557,11 @@ def close(self, _defer=False):
if getattr(self.f, 'closed', False):
return
# Flush it if not read-only...
if self.f.mode != 'r' and self.f.mode != 'rb':
self.f.flush()
os.fsync(self.f.fileno())
if hasattr(self.f, "mode") and self.f.mode != 'r' and self.f.mode != 'rb':
if hasattr(self.f, "flush"):
self.f.flush()
if hasattr(self.f, "fileno"):
os.fsync(self.f.fileno())
# and then close it, if we opened it...
if getattr(self, '_close', None):
self.f.close()
Expand All @@ -533,7 +583,7 @@ def readpath(self, f):
'''Write current archive entry contents to file. f can be a file-like object or
a path.'''
if isinstance(f, str):
basedir = os.path.basename(f)
basedir = os.path.dirname(f)
if not os.path.exists(basedir):
os.makedirs(basedir)
f = open(f, 'w')
Expand All @@ -547,7 +597,8 @@ def readstream(self, size):
def write(self, member, data=None):
'''Writes a string buffer to the archive as the given entry.'''
if isinstance(member, str):
member = self.entry_class(pathname=member, encoding=self.encoding)
member = self.entry_class(pathname=member, encoding=self.encoding,
mtime=time.time(), mode=stat.S_IFREG)
if data:
member.size = len(data)
member.to_archive(self)
Expand All @@ -557,7 +608,7 @@ def write(self, member, data=None):
if isinstance(data, bytes):
result = _libarchive.archive_write_data_from_str(self._a, data)
else:
result = _libarchive.archive_write_data_from_str(self._a, data.encode('utf8'))
result = _libarchive.archive_write_data_from_str(self._a, data.encode(self.encoding))
else:
result = _libarchive.archive_write_data_from_str(self._a, data)
_libarchive.archive_write_finish_entry(self._a)
Expand Down Expand Up @@ -594,13 +645,22 @@ def printlist(self, s=sys.stdout):
s.write(entry.pathname)
s.flush()

def add_passphrase(self, password):
'''Adds a password to the archive.'''
_libarchive.archive_read_add_passphrase(self._a, password)

def set_passphrase(self, password):
'''Sets a password for the archive.'''
_libarchive.archive_write_set_passphrase(self._a, password)


class SeekableArchive(Archive):
'''A class that provides random-access to archive entries. It does this by using one
or many Archive instances to seek to the correct location. The best performance will
occur when reading archive entries in the order in which they appear in the archive.
Reading out of order will cause the archive to be closed and opened each time a
reverse seek is needed.'''

def __init__(self, f, **kwargs):
self._stream = None
# Convert file to open file. We need this to reopen the archive.
Expand Down
Loading