Home | History | Annotate | Download | only in modulegraph
      1 """
      2 A helper module that can work with paths
      3 that can refer to data inside a zipfile
      4 
      5 XXX: Need to determine if isdir("zipfile.zip")
      6 should return True or False. Currently returns
      7 True, but that might do the wrong thing with
      8 data-files that are zipfiles.
      9 """
     10 import os as _os
     11 import zipfile as _zipfile
     12 import errno as _errno
     13 import time as _time
     14 import sys as _sys
     15 import stat as _stat
     16 
     17 _DFLT_DIR_MODE = (
     18       _stat.S_IFDIR
     19     | _stat.S_IXOTH
     20     | _stat.S_IXGRP
     21     | _stat.S_IXUSR
     22     | _stat.S_IROTH
     23     | _stat.S_IRGRP
     24     | _stat.S_IRUSR)
     25 
     26 _DFLT_FILE_MODE = (
     27       _stat.S_IFREG
     28     | _stat.S_IROTH
     29     | _stat.S_IRGRP
     30     | _stat.S_IRUSR)
     31 
     32 
     33 if _sys.version_info[0] == 2:
     34     from  StringIO import StringIO as _BaseStringIO
     35     from  StringIO import StringIO as _BaseBytesIO
     36 
     37     class _StringIO (_BaseStringIO):
     38         def __enter__(self):
     39             return self
     40 
     41         def __exit__(self, exc_type, exc_value, traceback):
     42             self.close()
     43             return False
     44 
     45     class _BytesIO (_BaseBytesIO):
     46         def __enter__(self):
     47             return self
     48 
     49         def __exit__(self, exc_type, exc_value, traceback):
     50             self.close()
     51             return False
     52 
     53 else:
     54     from io import StringIO as _StringIO
     55     from io import BytesIO as _BytesIO
     56 
     57 
     58 
     59 
     60 def _locate(path):
     61     full_path = path
     62     if _os.path.exists(path):
     63         return path, None
     64 
     65     else:
     66         rest = []
     67         root = _os.path.splitdrive(path)
     68         while path and path != root:
     69             path, bn = _os.path.split(path)
     70             rest.append(bn)
     71             if _os.path.exists(path):
     72                 break
     73 
     74         if path == root:
     75             raise IOError(
     76                 _errno.ENOENT, full_path,
     77                 "No such file or directory")
     78 
     79         if not _os.path.isfile(path):
     80             raise IOError(
     81                 _errno.ENOENT, full_path,
     82                 "No such file or directory")
     83 
     84         rest.reverse()
     85         return path, '/'.join(rest).strip('/')
     86 
     87 _open = open
     88 def open(path, mode='r'):
     89     if 'w' in mode or 'a' in mode:
     90         raise IOError(
     91             _errno.EINVAL, path, "Write access not supported")
     92     elif 'r+' in mode:
     93         raise IOError(
     94             _errno.EINVAL, path, "Write access not supported")
     95 
     96     full_path = path
     97     path, rest = _locate(path)
     98     if not rest:
     99         return _open(path, mode)
    100 
    101     else:
    102         try:
    103             zf = _zipfile.ZipFile(path, 'r')
    104 
    105         except _zipfile.error:
    106             raise IOError(
    107                 _errno.ENOENT, full_path,
    108                 "No such file or directory")
    109 
    110         try:
    111             data = zf.read(rest)
    112         except (_zipfile.error, KeyError):
    113             zf.close()
    114             raise IOError(
    115                 _errno.ENOENT, full_path,
    116                 "No such file or directory")
    117         zf.close()
    118 
    119         if mode == 'rb':
    120             return _BytesIO(data)
    121 
    122         else:
    123             if _sys.version_info[0] == 3:
    124                 data = data.decode('ascii')
    125 
    126             return _StringIO(data)
    127 
    128 def listdir(path):
    129     full_path = path
    130     path, rest = _locate(path)
    131     if not rest and not _os.path.isfile(path):
    132         return _os.listdir(path)
    133 
    134     else:
    135         try:
    136             zf = _zipfile.ZipFile(path, 'r')
    137 
    138         except _zipfile.error:
    139             raise IOError(
    140                 _errno.ENOENT, full_path,
    141                 "No such file or directory")
    142 
    143         result = set()
    144         seen = False
    145         try:
    146             for nm in zf.namelist():
    147                 if rest is None:
    148                     seen = True
    149                     value = nm.split('/')[0]
    150                     if value:
    151                         result.add(value)
    152 
    153                 elif nm.startswith(rest):
    154                     if nm == rest:
    155                         seen = True
    156                         value = ''
    157                         pass
    158                     elif nm[len(rest)] == '/':
    159                         seen = True
    160                         value = nm[len(rest)+1:].split('/')[0]
    161                     else:
    162                         value = None
    163 
    164                     if value:
    165                         result.add(value)
    166         except _zipfile.error:
    167             zf.close()
    168             raise IOError(
    169                 _errno.ENOENT, full_path,
    170                 "No such file or directory")
    171 
    172         zf.close()
    173 
    174         if not seen:
    175             raise IOError(
    176                 _errno.ENOENT, full_path,
    177                 "No such file or directory")
    178 
    179         return list(result)
    180 
    181 def isfile(path):
    182     full_path = path
    183     path, rest = _locate(path)
    184     if not rest:
    185         ok =  _os.path.isfile(path)
    186         if ok:
    187             try:
    188                 zf = _zipfile.ZipFile(path, 'r')
    189                 return False
    190             except (_zipfile.error, IOError):
    191                 return True
    192         return False
    193 
    194     zf = None
    195     try:
    196         zf = _zipfile.ZipFile(path, 'r')
    197         info = zf.getinfo(rest)
    198         zf.close()
    199         return True
    200     except (KeyError, _zipfile.error):
    201         if zf is not None:
    202             zf.close()
    203 
    204         # Check if this is a directory
    205         try:
    206             info = zf.getinfo(rest + '/')
    207         except KeyError:
    208             pass
    209         else:
    210             return False
    211 
    212         rest = rest + '/'
    213         for nm in zf.namelist():
    214             if nm.startswith(rest):
    215                 # Directory
    216                 return False
    217 
    218         # No trace in zipfile
    219         raise IOError(
    220             _errno.ENOENT, full_path,
    221             "No such file or directory")
    222 
    223 
    224 
    225 
    226 def isdir(path):
    227     full_path = path
    228     path, rest = _locate(path)
    229     if not rest:
    230         ok =  _os.path.isdir(path)
    231         if not ok:
    232             try:
    233                 zf = _zipfile.ZipFile(path, 'r')
    234             except (_zipfile.error, IOError):
    235                 return False
    236             return True
    237         return True
    238 
    239     zf = None
    240     try:
    241         try:
    242             zf = _zipfile.ZipFile(path)
    243         except _zipfile.error:
    244             raise IOError(
    245                 _errno.ENOENT, full_path,
    246                 "No such file or directory")
    247 
    248         try:
    249             info = zf.getinfo(rest)
    250         except KeyError:
    251             pass
    252         else:
    253             # File found
    254             return False
    255 
    256         rest = rest + '/'
    257         try:
    258             info = zf.getinfo(rest)
    259         except KeyError:
    260             pass
    261         else:
    262             # Directory entry found
    263             return True
    264 
    265         for nm in zf.namelist():
    266             if nm.startswith(rest):
    267                 return True
    268 
    269         raise IOError(
    270             _errno.ENOENT, full_path,
    271             "No such file or directory")
    272     finally:
    273         if zf is not None:
    274             zf.close()
    275 
    276 
    277 def islink(path):
    278     full_path = path
    279     path, rest = _locate(path)
    280     if not rest:
    281         return _os.path.islink(path)
    282 
    283     try:
    284         zf = _zipfile.ZipFile(path)
    285     except _zipfile.error:
    286         raise IOError(
    287             _errno.ENOENT, full_path,
    288             "No such file or directory")
    289     try:
    290 
    291 
    292         try:
    293             info = zf.getinfo(rest)
    294         except KeyError:
    295             pass
    296         else:
    297             # File
    298             return False
    299 
    300         rest += '/'
    301         try:
    302             info = zf.getinfo(rest)
    303         except KeyError:
    304             pass
    305         else:
    306             # Directory
    307             return False
    308 
    309         for nm in zf.namelist():
    310             if nm.startswith(rest):
    311                 # Directory without listing
    312                 return False
    313 
    314         raise IOError(
    315             _errno.ENOENT, full_path,
    316             "No such file or directory")
    317 
    318     finally:
    319         zf.close()
    320 
    321 
    322 def readlink(path):
    323     full_path = path
    324     path, rest = _locate(path)
    325     if rest:
    326         # No symlinks inside zipfiles
    327         raise OSError(
    328             _errno.ENOENT, full_path,
    329             "No such file or directory")
    330 
    331     return _os.readlink(path)
    332 
    333 def getmode(path):
    334     full_path = path
    335     path, rest = _locate(path)
    336     if not rest:
    337         return _os.stat(path).st_mode
    338 
    339     zf = None
    340     try:
    341         zf = _zipfile.ZipFile(path)
    342         info = None
    343 
    344         try:
    345             info = zf.getinfo(rest)
    346         except KeyError:
    347             pass
    348 
    349         if info is None:
    350             try:
    351                 info = zf.getinfo(rest + '/')
    352             except KeyError:
    353                 pass
    354 
    355         if info is None:
    356             rest = rest + '/'
    357             for nm in zf.namelist():
    358                 if nm.startswith(rest):
    359                     break
    360             else:
    361                 raise IOError(
    362                     _errno.ENOENT, full_path,
    363                     "No such file or directory")
    364 
    365             # Directory exists, but has no entry of its own.
    366             return _DFLT_DIR_MODE
    367 
    368         # The mode is stored without file-type in external_attr.
    369         if (info.external_attr >> 16) != 0:
    370             return _stat.S_IFREG | (info.external_attr >> 16)
    371         else:
    372             return _DFLT_FILE_MODE
    373 
    374 
    375     except KeyError:
    376         if zf is not None:
    377             zf.close()
    378         raise IOError(
    379             _errno.ENOENT, full_path,
    380             "No such file or directory")
    381 
    382 def getmtime(path):
    383     full_path = path
    384     path, rest = _locate(path)
    385     if not rest:
    386         return _os.path.getmtime(path)
    387 
    388     zf = None
    389     try:
    390         zf = _zipfile.ZipFile(path)
    391         info = None
    392 
    393         try:
    394             info = zf.getinfo(rest)
    395         except KeyError:
    396             pass
    397 
    398         if info is None:
    399             try:
    400                 info = zf.getinfo(rest + '/')
    401             except KeyError:
    402                 pass
    403 
    404         if info is None:
    405             rest = rest + '/'
    406             for nm in zf.namelist():
    407                 if nm.startswith(rest):
    408                     break
    409             else:
    410                 raise IOError(
    411                     _errno.ENOENT, full_path,
    412                     "No such file or directory")
    413 
    414             # Directory exists, but has no entry of its
    415             # own, fake mtime by using the timestamp of
    416             # the zipfile itself.
    417             return _os.path.getmtime(path)
    418 
    419         return _time.mktime(info.date_time + (0, 0, -1))
    420 
    421     except KeyError:
    422         if zf is not None:
    423             zf.close()
    424         raise IOError(
    425             _errno.ENOENT, full_path,
    426             "No such file or directory")
    427