@@ -4,4 +4,5 @@ venv | |||
build | |||
libarchive/__libarchive.so | |||
libarchive/_libarchive_wrap.o | |||
python_libarchive.egg-info |
@@ -1,14 +1,16 @@ | |||
language: python | |||
python: | |||
- "2.7" | |||
env: | |||
- DJANGO=1.3 | |||
- DJANGO=1.4 | |||
jobs: | |||
include: | |||
- python: "2.7" | |||
env: PYVER=2.7 | |||
- python: "3.6" | |||
env: PYVER=3.6 | |||
before_install: | |||
- sudo apt-get update -qq | |||
- sudo apt-get install -qq libarchive-dev | |||
- pyenv shell $(python -c 'import platform; print(platform.python_version())') | |||
install: | |||
- pip install . | |||
- make build | |||
script: | |||
- make test | |||
notifications: | |||
@@ -1,3 +1,6 @@ | |||
build: | |||
make -C libarchive | |||
test: | |||
python tests.py | |||
@@ -11,3 +14,6 @@ install: | |||
publish: | |||
python setup.py register | |||
python setup.py sdist upload | |||
clean: | |||
make -C libarchive clean |
@@ -40,8 +40,8 @@ source_suffix = '.rst' | |||
master_doc = 'index' | |||
# General information about the project. | |||
project = u'python-libarchive' | |||
copyright = u'2012, Ben Timby' | |||
project = 'python-libarchive' | |||
copyright = '2012, Ben Timby' | |||
# The version info for the project you're documenting, acts as replacement for | |||
# |version| and |release|, also used in various other places throughout the | |||
@@ -178,8 +178,8 @@ htmlhelp_basename = 'python-libarchivedoc' | |||
# Grouping the document tree into LaTeX files. List of tuples | |||
# (source start file, target name, title, author, documentclass [howto/manual]). | |||
latex_documents = [ | |||
('index', 'python-libarchive.tex', u'python-libarchive Documentation', | |||
u'Ben Timby', 'manual'), | |||
('index', 'python-libarchive.tex', 'python-libarchive Documentation', | |||
'Ben Timby', 'manual'), | |||
] | |||
# The name of an image file (relative to this directory) to place at the top of | |||
@@ -211,6 +211,6 @@ latex_documents = [ | |||
# One entry per manual page. List of tuples | |||
# (source start file, name, description, authors, manual section). | |||
man_pages = [ | |||
('index', 'python-libarchive', u'python-libarchive Documentation', | |||
[u'Ben Timby'], 1) | |||
('index', 'python-libarchive', 'python-libarchive Documentation', | |||
['Ben Timby'], 1) | |||
] |
@@ -1,12 +1,8 @@ | |||
CFLAGS = -g | |||
INCLUDE = -I/usr/include -I. | |||
LIBS = -L/usr/local/lib -l:libarchive.so.13.1.2 | |||
LIBS = -larchive | |||
#if PYTHON_VERSION | |||
PYVER = $(PYTHON_VERSION) | |||
#else | |||
PYVER = 2.7 | |||
#endif | |||
PYVER ?= 2.7 | |||
all: __libarchive.so | |||
@@ -30,10 +30,9 @@ import time | |||
import warnings | |||
from libarchive import _libarchive | |||
try: | |||
from cStringIO import StringIO | |||
except ImportError: | |||
from StringIO import StringIO | |||
from io import StringIO | |||
PY3 = sys.version_info[0] == 3 | |||
# Suggested block size for libarchive. Libarchive may adjust it. | |||
BLOCK_SIZE = 10240 | |||
@@ -134,7 +133,7 @@ def is_archive_name(filename, formats=None): | |||
This function will return the name of the most likely archive format, None if the file is | |||
unlikely to be an archive.''' | |||
if formats is None: | |||
formats = FORMAT_EXTENSIONS.values() | |||
formats = list(FORMAT_EXTENSIONS.values()) | |||
format, filter = guess_format(filename) | |||
if format in formats: | |||
return format | |||
@@ -153,8 +152,8 @@ 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).''' | |||
if isinstance(f, basestring): | |||
f = file(f, 'r') | |||
if isinstance(f, str): | |||
f = open(f, 'r') | |||
a = _libarchive.archive_read_new() | |||
for format in formats: | |||
format = get_func(format, FORMATS, 0) | |||
@@ -175,6 +174,7 @@ def is_archive(f, formats=(None, ), filters=(None, )): | |||
finally: | |||
_libarchive.archive_read_close(a) | |||
_libarchive.archive_read_free(a) | |||
f.close() | |||
class EntryReadStream(object): | |||
@@ -271,7 +271,7 @@ class EntryWriteStream(object): | |||
if self.buffer: | |||
self.buffer.write(data) | |||
else: | |||
_libarchive.archive_write_data_from_str(self.archive._a, data) | |||
_libarchive.archive_write_data_from_str(self.archive._a, data.encode('utf-8')) | |||
self.bytes += len(data) | |||
def close(self): | |||
@@ -280,7 +280,7 @@ class EntryWriteStream(object): | |||
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()) | |||
_libarchive.archive_write_data_from_str(self.archive._a, self.buffer.getvalue().encode('utf-8')) | |||
_libarchive.archive_write_finish_entry(self.archive._a) | |||
# Call archive.close() with _defer True to let it know we have been | |||
@@ -312,8 +312,13 @@ class Entry(object): | |||
call_and_check(_libarchive.archive_read_next_header2, archive._a, archive._a, e) | |||
mode = _libarchive.archive_entry_filetype(e) | |||
mode |= _libarchive.archive_entry_perm(e) | |||
entry = cls( | |||
if PY3: | |||
pathname=_libarchive.archive_entry_pathname(e) | |||
else: | |||
pathname=_libarchive.archive_entry_pathname(e).decode(encoding), | |||
entry = cls( | |||
pathname=pathname, | |||
size=_libarchive.archive_entry_size(e), | |||
mtime=_libarchive.archive_entry_mtime(e), | |||
mode=mode, | |||
@@ -330,7 +335,7 @@ class Entry(object): | |||
if entry is None: | |||
entry = cls(encoding=encoding) | |||
if entry.pathname is None: | |||
if isinstance(f, basestring): | |||
if isinstance(f, str): | |||
st = os.stat(f) | |||
entry.pathname = f | |||
entry.size = st.st_size | |||
@@ -353,7 +358,10 @@ class Entry(object): | |||
'''Creates an archive header and writes it to the given archive.''' | |||
e = _libarchive.archive_entry_new() | |||
try: | |||
_libarchive.archive_entry_set_pathname(e, self.pathname.encode(self.encoding)) | |||
if PY3: | |||
_libarchive.archive_entry_set_pathname(e, self.pathname) | |||
else: | |||
_libarchive.archive_entry_set_pathname(e, self.pathname.encode(self.encoding)) | |||
_libarchive.archive_entry_set_filetype(e, stat.S_IFMT(self.mode)) | |||
_libarchive.archive_entry_set_perm(e, stat.S_IMODE(self.mode)) | |||
_libarchive.archive_entry_set_size(e, self.size) | |||
@@ -390,9 +398,9 @@ class Archive(object): | |||
self._stream = None | |||
self.encoding = encoding | |||
self.blocksize = blocksize | |||
if isinstance(f, basestring): | |||
if isinstance(f, str): | |||
self.filename = f | |||
f = file(f, mode) | |||
f = open(f, mode) | |||
# Only close it if we opened it... | |||
self._defer_close = True | |||
elif hasattr(f, 'fileno'): | |||
@@ -520,11 +528,11 @@ class Archive(object): | |||
def readpath(self, f): | |||
'''Write current archive entry contents to file. f can be a file-like object or | |||
a path.''' | |||
if isinstance(f, basestring): | |||
if isinstance(f, str): | |||
basedir = os.path.basename(f) | |||
if not os.path.exists(basedir): | |||
os.makedirs(basedir) | |||
f = file(f, 'w') | |||
f = open(f, 'w') | |||
return _libarchive.archive_read_data_into_fd(self._a, f.fileno()) | |||
def readstream(self, size): | |||
@@ -534,23 +542,26 @@ class Archive(object): | |||
def write(self, member, data=None): | |||
'''Writes a string buffer to the archive as the given entry.''' | |||
if isinstance(member, basestring): | |||
if isinstance(member, str): | |||
member = self.entry_class(pathname=member, encoding=self.encoding) | |||
if data: | |||
member.size = len(data) | |||
member.to_archive(self) | |||
if data: | |||
_libarchive.archive_write_data_from_str(self._a, data) | |||
if PY3: | |||
result = _libarchive.archive_write_data_from_str(self._a, data.encode('utf8')) | |||
else: | |||
result = _libarchive.archive_write_data_from_str(self._a, data) | |||
_libarchive.archive_write_finish_entry(self._a) | |||
def writepath(self, f, pathname=None, folder=False): | |||
'''Writes a file to the archive. f can be a file-like object or a path. Uses | |||
write() to do the actual writing.''' | |||
member = self.entry_class.from_file(f, encoding=self.encoding) | |||
if isinstance(f, basestring): | |||
if isinstance(f, str): | |||
if os.path.isfile(f): | |||
f = file(f, 'r') | |||
f = open(f, 'r') | |||
if pathname: | |||
member.pathname = pathname | |||
if folder and not member.isdir(): | |||
@@ -587,8 +598,8 @@ class SeekableArchive(Archive): | |||
self._stream = None | |||
# Convert file to open file. We need this to reopen the archive. | |||
mode = kwargs.setdefault('mode', 'r') | |||
if isinstance(f, basestring): | |||
f = file(f, mode) | |||
if isinstance(f, str): | |||
f = open(f, mode) | |||
super(SeekableArchive, self).__init__(f, **kwargs) | |||
self.entries = [] | |||
self.eof = False | |||
@@ -614,7 +625,11 @@ class SeekableArchive(Archive): | |||
def getentry(self, pathname): | |||
'''Take a name or entry object and returns an entry object.''' | |||
for entry in self: | |||
if entry.pathname == pathname: | |||
if PY3: | |||
entry_pathname = entry.pathname | |||
if not PY3: | |||
entry_pathname = entry.pathname[0] | |||
if entry_pathname == pathname: | |||
return entry | |||
raise KeyError(pathname) | |||
@@ -360,7 +360,7 @@ extern const char *archive_error_string(struct archive *); | |||
%inline %{ | |||
PyObject *archive_read_data_into_str(struct archive *archive, int len) { | |||
PyObject *str = NULL; | |||
if (!(str = PyString_FromStringAndSize(NULL, len))) { | |||
if (!(str = PyUnicode_FromStringAndSize(NULL, len))) { | |||
PyErr_SetString(PyExc_MemoryError, "could not allocate string."); | |||
return NULL; | |||
} | |||
@@ -739,7 +739,7 @@ SWIG_UnpackDataName(const char *c, void *ptr, size_t sz, const char *name) { | |||
#define PyString_Size(str) PyBytes_Size(str) | |||
#define PyString_InternFromString(key) PyUnicode_InternFromString(key) | |||
#define Py_TPFLAGS_HAVE_CLASS Py_TPFLAGS_BASETYPE | |||
#define PyString_AS_STRING(x) PyUnicode_AS_STRING(x) | |||
#define PyString_AS_STRING(x) PyBytes_AsString(x) | |||
#define _PyLong_FromSsize_t(x) PyLong_FromSsize_t(x) | |||
#endif | |||
@@ -3342,10 +3342,17 @@ SWIG_AsVal_unsigned_SS_short (PyObject * obj, unsigned short *val) | |||
PyObject *archive_read_data_into_str(struct archive *archive, int len) { | |||
PyObject *str = NULL; | |||
if (!(str = PyString_FromStringAndSize(NULL, len))) { | |||
#if PY_VERSION_HEX >= 0x03000000 | |||
if (!(str = PyBytes_FromStringAndSize(NULL, len))) { | |||
PyErr_SetString(PyExc_MemoryError, "could not allocate string."); | |||
return NULL; | |||
} | |||
#else | |||
if (!(str = PyString_FromStringAndSize(NULL, len))) { | |||
PyErr_SetString(PyExc_MemoryError, "could not allocate string."); | |||
return NULL; | |||
} | |||
#endif | |||
if (len != archive_read_data(archive, PyString_AS_STRING(str), len)) { | |||
PyErr_SetString(PyExc_RuntimeError, "could not read requested data."); | |||
return NULL; | |||
@@ -76,14 +76,14 @@ class TarFile(SeekableArchive): | |||
def getnames(self): | |||
return list(self.iterpaths) | |||
def next(self): | |||
def __next__(self): | |||
raise NotImplementedError | |||
pass # TODO: how to do this? | |||
def extract(self, member, path=None): | |||
if path is None: | |||
path = os.getcwd() | |||
if isinstance(member, basestring): | |||
if isinstance(member, str): | |||
f = os.path.join(path, member) | |||
else: | |||
f = os.path.join(path, member.pathname) | |||
@@ -23,7 +23,7 @@ class ZipEntry(Entry): | |||
return self.size | |||
def set_file_size(self, value): | |||
assert isinstance(value, (int, long)), 'Please provide size as int or long.' | |||
assert isinstance(value, int), 'Please provide size as int or long.' | |||
self.size = value | |||
file_size = property(get_file_size, set_file_size) | |||
@@ -42,7 +42,7 @@ versrel = version + '-' + release | |||
readme = 'README.rst' | |||
download_url = "http://" + name + ".googlecode.com/files/" + name + "-" + \ | |||
versrel + ".tar.gz" | |||
long_description = file(readme).read() | |||
long_description = open(readme).read() | |||
class build_ext_extra(build_ext, object): | |||
""" | |||
@@ -26,11 +26,13 @@ | |||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | |||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
import os, unittest, tempfile, random, string, subprocess | |||
import os, unittest, tempfile, random, string, subprocess, sys | |||
from libarchive import is_archive_name, is_archive | |||
from libarchive.zip import is_zipfile, ZipFile, ZipEntry | |||
PY3 = sys.version_info[0] == 3 | |||
TMPDIR = tempfile.mkdtemp() | |||
ZIPCMD = '/usr/bin/zip' | |||
ZIPFILE = 'test.zip' | |||
@@ -47,7 +49,8 @@ FILENAMES = [ | |||
def make_temp_files(): | |||
if not os.path.exists(ZIPPATH): | |||
for name in FILENAMES: | |||
file(os.path.join(TMPDIR, name), 'w').write(''.join(random.sample(string.printable, 10))) | |||
with open(os.path.join(TMPDIR, name), 'w') as f: | |||
f.write(''.join(random.sample(string.ascii_letters, 10))) | |||
def make_temp_archive(): | |||
@@ -93,14 +96,17 @@ class TestIsArchiveTar(unittest.TestCase): | |||
class TestZipRead(unittest.TestCase): | |||
def setUp(self): | |||
make_temp_archive() | |||
self.f = open(ZIPPATH, mode='r') | |||
def tearDown(self): | |||
self.f.close() | |||
def test_iszipfile(self): | |||
self.assertEqual(is_zipfile('/dev/null'), False) | |||
self.assertEqual(is_zipfile(ZIPPATH), True) | |||
def test_iterate(self): | |||
f = file(ZIPPATH, mode='r') | |||
z = ZipFile(f, 'r') | |||
z = ZipFile(self.f, 'r') | |||
count = 0 | |||
for e in z: | |||
count += 1 | |||
@@ -108,8 +114,7 @@ class TestZipRead(unittest.TestCase): | |||
def test_deferred_close_by_archive(self): | |||
""" Test archive deferred close without a stream. """ | |||
f = file(ZIPPATH, mode='r') | |||
z = ZipFile(f, 'r') | |||
z = ZipFile(self.f, 'r') | |||
self.assertIsNotNone(z._a) | |||
self.assertIsNone(z._stream) | |||
z.close() | |||
@@ -117,8 +122,7 @@ class TestZipRead(unittest.TestCase): | |||
def test_deferred_close_by_stream(self): | |||
""" Ensure archive closes self if stream is closed first. """ | |||
f = file(ZIPPATH, mode='r') | |||
z = ZipFile(f, 'r') | |||
z = ZipFile(self.f, 'r') | |||
stream = z.readstream(FILENAMES[0]) | |||
stream.close() | |||
# Make sure archive stays open after stream is closed. | |||
@@ -131,8 +135,7 @@ class TestZipRead(unittest.TestCase): | |||
def test_close_stream_first(self): | |||
""" Ensure that archive stays open after being closed if a stream is | |||
open. Further, ensure closing the stream closes the archive. """ | |||
f = file(ZIPPATH, mode='r') | |||
z = ZipFile(f, 'r') | |||
z = ZipFile(self.f, 'r') | |||
stream = z.readstream(FILENAMES[0]) | |||
z.close() | |||
try: | |||
@@ -146,11 +149,13 @@ class TestZipRead(unittest.TestCase): | |||
self.assertIsNone(z._stream) | |||
def test_filenames(self): | |||
f = file(ZIPPATH, mode='r') | |||
z = ZipFile(f, 'r') | |||
z = ZipFile(self.f, 'r') | |||
names = [] | |||
for e in z: | |||
names.append(e.filename) | |||
if PY3: | |||
names.append(e.filename) | |||
else: | |||
names.append(e.filename[0]) | |||
self.assertEqual(names, FILENAMES, 'File names differ in archive.') | |||
#~ def test_non_ascii(self): | |||
@@ -163,25 +168,27 @@ class TestZipRead(unittest.TestCase): | |||
class TestZipWrite(unittest.TestCase): | |||
def setUp(self): | |||
make_temp_files() | |||
self.f = open(ZIPPATH, mode='w') | |||
def tearDown(self): | |||
self.f.close() | |||
def test_writepath(self): | |||
f = file(ZIPPATH, mode='w') | |||
z = ZipFile(f, 'w') | |||
z = ZipFile(self.f, 'w') | |||
for fname in FILENAMES: | |||
z.writepath(file(os.path.join(TMPDIR, fname), 'r')) | |||
with open(os.path.join(TMPDIR, fname), 'r') as f: | |||
z.writepath(f) | |||
z.close() | |||
def test_writepath_directory(self): | |||
""" Test writing a directory. """ | |||
f = file(ZIPPATH, mode='w') | |||
z = ZipFile(f, 'w') | |||
z = ZipFile(self.f, 'w') | |||
z.writepath(None, pathname='/testdir', folder=True) | |||
z.writepath(None, pathname='/testdir/testinside', folder=True) | |||
z.close() | |||
f.close() | |||
self.f.close() | |||
f = file(ZIPPATH, mode='r') | |||
f = open(ZIPPATH, mode='r') | |||
z = ZipFile(f, 'r') | |||
entries = z.infolist() | |||
@@ -192,46 +199,52 @@ class TestZipWrite(unittest.TestCase): | |||
f.close() | |||
def test_writestream(self): | |||
f = file(ZIPPATH, mode='w') | |||
z = ZipFile(f, 'w') | |||
z = ZipFile(self.f, 'w') | |||
for fname in FILENAMES: | |||
full_path = os.path.join(TMPDIR, fname) | |||
i = file(full_path) | |||
i = open(full_path) | |||
o = z.writestream(fname) | |||
while True: | |||
data = i.read(1) | |||
if not data: | |||
break | |||
o.write(data) | |||
if PY3: | |||
o.write(data) | |||
else: | |||
o.write(unicode(data)) | |||
o.close() | |||
i.close() | |||
z.close() | |||
def test_writestream_unbuffered(self): | |||
f = file(ZIPPATH, mode='w') | |||
z = ZipFile(f, 'w') | |||
z = ZipFile(self.f, 'w') | |||
for fname in FILENAMES: | |||
full_path = os.path.join(TMPDIR, fname) | |||
i = file(full_path) | |||
i = open(full_path) | |||
o = z.writestream(fname, os.path.getsize(full_path)) | |||
while True: | |||
data = i.read(1) | |||
if not data: | |||
break | |||
o.write(data) | |||
if PY3: | |||
o.write(data) | |||
else: | |||
o.write(unicode(data)) | |||
o.close() | |||
i.close() | |||
z.close() | |||
def test_deferred_close_by_archive(self): | |||
""" Test archive deferred close without a stream. """ | |||
f = file(ZIPPATH, mode='w') | |||
z = ZipFile(f, 'w') | |||
z = ZipFile(self.f, 'w') | |||
o = z.writestream(FILENAMES[0]) | |||
z.close() | |||
self.assertIsNotNone(z._a) | |||
self.assertIsNotNone(z._stream) | |||
o.write('testdata') | |||
if PY3: | |||
o.write('testdata') | |||
else: | |||
o.write(unicode('testdata')) | |||
o.close() | |||
self.assertIsNone(z._a) | |||
self.assertIsNone(z._stream) | |||