"""
|
A helper module that can work with paths
|
that can refer to data inside a zipfile
|
|
XXX: Need to determine if isdir("zipfile.zip")
|
should return True or False. Currently returns
|
True, but that might do the wrong thing with
|
data-files that are zipfiles.
|
"""
|
import os as _os
|
import zipfile as _zipfile
|
import errno as _errno
|
import time as _time
|
import sys as _sys
|
import stat as _stat
|
|
_DFLT_DIR_MODE = (
|
_stat.S_IXOTH
|
| _stat.S_IXGRP
|
| _stat.S_IXUSR
|
| _stat.S_IROTH
|
| _stat.S_IRGRP
|
| _stat.S_IRUSR)
|
|
_DFLT_FILE_MODE = (
|
_stat.S_IROTH
|
| _stat.S_IRGRP
|
| _stat.S_IRUSR)
|
|
|
if _sys.version_info[0] == 2:
|
from StringIO import StringIO as _BaseStringIO
|
from StringIO import StringIO as _BaseBytesIO
|
|
class _StringIO (_BaseStringIO):
|
def __enter__(self):
|
return self
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
self.close()
|
return False
|
|
class _BytesIO (_BaseBytesIO):
|
def __enter__(self):
|
return self
|
|
def __exit__(self, exc_type, exc_value, traceback):
|
self.close()
|
return False
|
|
else:
|
from io import StringIO as _StringIO
|
from io import BytesIO as _BytesIO
|
|
|
def _locate(path):
|
full_path = path
|
if _os.path.exists(path):
|
return path, None
|
|
else:
|
rest = []
|
root = _os.path.splitdrive(path)
|
while path and path != root:
|
path, bn = _os.path.split(path)
|
rest.append(bn)
|
if _os.path.exists(path):
|
break
|
|
if path == root:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
if not _os.path.isfile(path):
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
rest.reverse()
|
return path, '/'.join(rest).strip('/')
|
|
|
_open = open
|
|
|
def open(path, mode='r'):
|
if 'w' in mode or 'a' in mode:
|
raise IOError(
|
_errno.EINVAL, path, "Write access not supported")
|
elif 'r+' in mode:
|
raise IOError(
|
_errno.EINVAL, path, "Write access not supported")
|
|
full_path = path
|
path, rest = _locate(path)
|
if not rest:
|
return _open(path, mode)
|
|
else:
|
try:
|
zf = _zipfile.ZipFile(path, 'r')
|
|
except _zipfile.BadZipFile:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
try:
|
data = zf.read(rest)
|
except (_zipfile.BadZipFile, KeyError):
|
zf.close()
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
zf.close()
|
|
if mode == 'rb':
|
return _BytesIO(data)
|
|
else:
|
if _sys.version_info[0] == 3:
|
data = data.decode('ascii')
|
|
return _StringIO(data)
|
|
|
def listdir(path):
|
full_path = path
|
path, rest = _locate(path)
|
if not rest and not _os.path.isfile(path):
|
return _os.listdir(path)
|
|
else:
|
try:
|
zf = _zipfile.ZipFile(path, 'r')
|
|
except _zipfile.BadZipFile:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
result = set()
|
seen = False
|
try:
|
for nm in zf.namelist():
|
if rest is None:
|
seen = True
|
value = nm.split('/')[0]
|
if value:
|
result.add(value)
|
|
elif nm.startswith(rest):
|
if nm == rest:
|
seen = True
|
value = ''
|
pass
|
elif nm[len(rest)] == '/':
|
seen = True
|
value = nm[len(rest)+1:].split('/')[0]
|
else:
|
value = None
|
|
if value:
|
result.add(value)
|
except _zipfile.BadZipFile:
|
zf.close()
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
zf.close()
|
|
if not seen:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
return list(result)
|
|
|
def isfile(path):
|
full_path = path
|
path, rest = _locate(path)
|
if not rest:
|
ok = _os.path.isfile(path)
|
if ok:
|
try:
|
zf = _zipfile.ZipFile(path, 'r')
|
return False
|
except (_zipfile.BadZipFile, IOError):
|
return True
|
return False
|
|
zf = None
|
try:
|
zf = _zipfile.ZipFile(path, 'r')
|
zf.getinfo(rest)
|
zf.close()
|
return True
|
except (KeyError, _zipfile.BadZipFile):
|
if zf is not None:
|
zf.close()
|
|
# Check if this is a directory
|
try:
|
zf.getinfo(rest + '/')
|
except KeyError:
|
pass
|
else:
|
return False
|
|
rest = rest + '/'
|
for nm in zf.namelist():
|
if nm.startswith(rest):
|
# Directory
|
return False
|
|
# No trace in zipfile
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
|
def isdir(path):
|
full_path = path
|
path, rest = _locate(path)
|
if not rest:
|
ok = _os.path.isdir(path)
|
if not ok:
|
try:
|
zf = _zipfile.ZipFile(path, 'r')
|
except (_zipfile.BadZipFile, IOError):
|
return False
|
return True
|
return True
|
|
zf = None
|
try:
|
try:
|
zf = _zipfile.ZipFile(path)
|
except _zipfile.BadZipFile:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
try:
|
zf.getinfo(rest)
|
except KeyError:
|
pass
|
else:
|
# File found
|
return False
|
|
rest = rest + '/'
|
try:
|
zf.getinfo(rest)
|
except KeyError:
|
pass
|
else:
|
# Directory entry found
|
return True
|
|
for nm in zf.namelist():
|
if nm.startswith(rest):
|
return True
|
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
finally:
|
if zf is not None:
|
zf.close()
|
|
|
def islink(path):
|
full_path = path
|
path, rest = _locate(path)
|
if not rest:
|
return _os.path.islink(path)
|
|
try:
|
zf = _zipfile.ZipFile(path)
|
except _zipfile.BadZipFile:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
try:
|
try:
|
zf.getinfo(rest)
|
except KeyError:
|
pass
|
else:
|
# File
|
return False
|
|
rest += '/'
|
try:
|
zf.getinfo(rest)
|
except KeyError:
|
pass
|
else:
|
# Directory
|
return False
|
|
for nm in zf.namelist():
|
if nm.startswith(rest):
|
# Directory without listing
|
return False
|
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
finally:
|
zf.close()
|
|
|
def readlink(path):
|
full_path = path
|
path, rest = _locate(path)
|
if rest:
|
# No symlinks inside zipfiles
|
raise OSError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
return _os.readlink(path)
|
|
|
def getmode(path):
|
full_path = path
|
path, rest = _locate(path)
|
if not rest:
|
return _stat.S_IMODE(_os.stat(path).st_mode)
|
|
zf = None
|
try:
|
zf = _zipfile.ZipFile(path)
|
info = None
|
|
try:
|
info = zf.getinfo(rest)
|
except KeyError:
|
pass
|
|
if info is None:
|
try:
|
info = zf.getinfo(rest + '/')
|
except KeyError:
|
pass
|
|
if info is None:
|
rest = rest + '/'
|
for nm in zf.namelist():
|
if nm.startswith(rest):
|
break
|
else:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
# Directory exists, but has no entry of its own.
|
return _DFLT_DIR_MODE
|
|
# The mode is stored without file-type in external_attr.
|
if (info.external_attr >> 16) != 0:
|
return _stat.S_IMODE(info.external_attr >> 16)
|
else:
|
return _DFLT_FILE_MODE
|
|
finally:
|
if zf is not None:
|
zf.close()
|
|
|
def getmtime(path):
|
full_path = path
|
path, rest = _locate(path)
|
if not rest:
|
return _os.path.getmtime(path)
|
|
zf = None
|
try:
|
zf = _zipfile.ZipFile(path)
|
info = None
|
|
try:
|
info = zf.getinfo(rest)
|
except KeyError:
|
pass
|
|
if info is None:
|
try:
|
info = zf.getinfo(rest + '/')
|
except KeyError:
|
pass
|
|
if info is None:
|
rest = rest + '/'
|
for nm in zf.namelist():
|
if nm.startswith(rest):
|
break
|
else:
|
raise IOError(
|
_errno.ENOENT, full_path,
|
"No such file or directory")
|
|
# Directory exists, but has no entry of its
|
# own, fake mtime by using the timestamp of
|
# the zipfile itself.
|
return _os.path.getmtime(path)
|
|
return _time.mktime(info.date_time + (0, 0, -1))
|
|
finally:
|
if zf is not None:
|
zf.close()
|