Package libxyz :: Package vfs :: Module tar

[hide private]
[frames] | no frames]

# Source Code for Module libxyz.vfs.tar

      1  #-*- coding: utf8 -*
      2  #
      3  # Max E. Kuznecov ~syhpoon <syhpoon@syhpoon.name> 2008-2009
      4  #
      5  # This file is part of XYZCommander.
      6  # XYZCommander is free software: you can redistribute it and/or modify
      7  # it under the terms of the GNU Lesser Public License as published by
      8  # the Free Software Foundation, either version 3 of the License, or
      9  # (at your option) any later version.
     10  # XYZCommander is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     13  # GNU Lesser Public License for more details.
     14  # You should have received a copy of the GNU Lesser Public License
     15  # along with XYZCommander. If not, see <http://www.gnu.org/licenses/>.
     16
     17  import os
     18  import stat
     19  import tarfile
     20  import time
     21
     22  from libxyz.exceptions import XYZRuntimeError
     23  from libxyz.core.utils import ustring
     24  from libxyz.vfs import types as vfstypes
     25  from libxyz.vfs import vfsobj
     26  from libxyz.vfs import util
     27  from libxyz.vfs import mode
     28
     29 -class TarVFSObject(vfsobj.VFSObject):
     30      """
     31      Tar archive interface
     32      """
     33
     34 -    def either(self, a, b):
     35          if self.root:
     36              return a
     37          else:
     38              return b()
     39
     40      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     41
     42      get_name = lambda self, x: os.path.basename(x.name.rstrip(os.path.sep))
     43      get_path = lambda self, x: os.path.join(self.ext_path, x.lstrip(os.sep))
     44
     45      file_type_map = {
     46          lambda obj: obj.isfile(): vfstypes.VFSTypeFile(),
     47          lambda obj: obj.isdir(): vfstypes.VFSTypeDir(),
     48          lambda obj: obj.issym(): vfstypes.VFSTypeLink(),
     49          lambda obj: obj.ischr(): vfstypes.VFSTypeChar(),
     50          lambda obj: obj.isblk(): vfstypes.VFSTypeBlock(),
     51          lambda obj: obj.isfifo(): vfstypes.VFSTypeFifo(),
     52          }
     53
     54 -    def __init__(self, *args, **kwargs):
     55          self.tarobj = kwargs.get('tarobj', None)
     56
     57          super(TarVFSObject, self).__init__(*args, **kwargs)
     58
     59      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     60
     61 -    def walk(self):
     62          """
     63          Directory tree walker
     64          @return: tuple (parent, dir, dirs, files) where:
     65          parent - parent dir *VFSObject instance
     66          dir - current dir TarVFSObject instance
     67          dirs - list of TarVFSObject objects of directories
     68          files - list of TarVFSObject objects of files
     69          """
     70
     71          tarobj = self._open_archive()
     72          entries = tarobj.getmembers()
     73
     74          _dirs = [x for x in entries if x.isdir() and
     75                   self.in_dir(self.path, x.name)]
     76          _files = [x for x in entries if not x.isdir() and
     77                    self.in_dir(self.path, x.name)]
     78
     79          _dirs.sort(cmp=lambda x, y: cmp(self.get_name(x),
     80                                          self.get_name(y)))
     81          _files.sort(cmp=lambda x, y: cmp(self.get_name(x),
     82                                           self.get_name(y)))
     83
     84          if self.path == os.sep:
     85              _parent = self.xyz.vfs.get_parent(self.parent.path, self.enc)
     86          else:
     87              _parent = self.xyz.vfs.dispatch(
     88                  self.get_path(os.path.dirname(self.path)), self.enc)
     89              _parent.name = ".."
     90
     91          return [
     92              _parent,
     93              self,
     94              [self.xyz.vfs.dispatch(self.get_path(x.name), self.enc)
     95               for x in _dirs],
     96              [self.xyz.vfs.dispatch(self.get_path(x.name), self.enc)
     97               for x in _files],
     98              ]
     99
    100      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    101
    102 -    def copy(self, path, existcb=None, errorcb=None, save_attrs=True,
    103               follow_links=False, cancel=None):
    104
    105          env = {
    106              'override': 'abort',
    107              'error': 'abort'
    108              }
    109
    110          tarobj = self._open_archive()
    111
    112          try:
    113              if self.is_dir():
    114                  f = self._copy_dir
    115              else:
    116                  f = self._copy_file
    117
    118              f(self.path, path, existcb, errorcb,
    119                save_attrs, follow_links, env, cancel, tarobj=tarobj)
    120          except XYZRuntimeError:
    121              # Aborted
    122              return False
    123          else:
    124              return True
    125
    126      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    127
    128 -    def _prepare(self):
    129          if self.path == os.sep:
    130              self.root = True
    131          else:
    132              self.root = False
    133
    134          self.tarobj = self.kwargs.get("tarobj", None)
    135
    136          if self.root:
    137              self.obj = None
    138          else:
    139              self.obj = self._init_obj()
    140
    141          self.ftype = self._find_type()
    142          self.vtype = self.ftype.vtype
    143
    144          self._set_attributes()
    145
    146          self.attributes = (
    147              (_(u"Name"), ustring(self.name)),
    148              (_(u"Type"), ustring(self.ftype)),
    149              (_(u"Modification time"), ustring(time.ctime(self.mtime))),
    150              (_(u"Size in bytes"), ustring(self.size)),
    151              (_(u"Owner"), ustring(self.uid)),
    152              (_(u"Group"), ustring(self.gid)),
    153              (_(u"Access mode"), ustring(self.mode)),
    154              (_(u"Type-specific data"), self.data),
    155              )
    156
    157      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    158
    159 -    def _normalize(self, path):
    160          """
    161          Normalize path
    162          """
    163
    164          if path.startswith(os.sep):
    165              return path.lstrip(os.sep)
    166          else:
    167              return path
    168
    169      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    170
    171 -    def __str__(self):
    172          return "<TarVFSObject object: %s>" % self.path
    173
    174      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    175
    176 -    def in_dir(self, d, e):
    177          """
    178          Filter only those archive entries which exist in the same
    179          directory level
    180          """
    181
    182          if e.startswith(d.lstrip(os.sep)) and \
    183             len(util.split_path(e)) == (len(util.split_path(d)) + 1):
    184              return True
    185          else:
    186              return False
    187
    188      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    189
    190 -    def _find_type(self):
    191          """
    192          Find out file type
    193          """
    194
    195          if self.root:
    196              return self.parent.ftype
    197
    198          for k, v in self.file_type_map.iteritems():
    199              if k(self.obj):
    200                  return v
    201
    202          return vfstypes.VFSTypeUnknown()
    203
    204      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    205
    206 -    def _set_attributes(self):
    207          """
    208          Set file attibutes
    209          """
    210
    211          def set_link_attributes():
    212              """
    213              Set appropriate soft link attibutes
    214              """
    215
    216              self.info = ""
    217              self.visual = "-> %s" % self.obj.linkname or ""
    218
    219          #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    220
    221          self.name = self.either(self.parent.name, lambda: self.name)
    222          self.mtime = self.either(self.parent.mtime, lambda: self.obj.mtime)
    223          self.size = self.either(self.parent.size, lambda: self.obj.size)
    224          self.uid = self.either(self.parent.uid, lambda: self.obj.uid)
    225          self.gid = self.either(self.parent.gid, lambda: self.obj.gid)
    226          self.mode = mode.Mode(self.either(self.parent.mode.raw,
    227                                            lambda: self.obj.mode), self.ftype)
    228          self.visual = "%s%s" % (self.vtype, self.name)
    229
    230          self.info = "%s %s" % (util.format_size(self.size), self.mode)
    231
    232          if self.is_link():
    233              set_link_attributes()
    234          elif self.is_file():
    235              _mode = stat.S_IMODE(self.mode.raw)
    236
    237              # Executable
    238              if _mode & 0111:
    239                  self.vtype = "*"
    240                  self.visual = "*%s" % self.name
    241
    242      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    243
    244 -    def _init_obj(self, altpath=None):
    245          tarobj = self._open_archive()
    246          path = (altpath or self.path).lstrip(os.sep)
    247
    248          try:
    249              obj = tarobj.getmember(path)
    250          except KeyError:
    251              obj = tarobj.getmember(path + os.sep)
    252
    253          return obj
    254
    255      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    256
    257 -    def _open_archive(self):
    258          if self.tarobj is None:
    259              _mode = "r"
    260
    261              if self.driver == "gztar":
    262                  _mode = "r:gz"
    263              elif self.driver == "bz2tar":
    264                  _mode = "r:bz2"
    265
    266              self.tarobj = tarfile.open(self.parent.path, mode=_mode)
    267              self.xyz.vfs.set_cache(self.parent.path, {'tarobj': self.tarobj})
    268
    269          return self.tarobj
    270
    271      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    272
    273 -    def _copy_file(self, src, dst, existcb, errorcb, save_attrs,
    274                     follow_links, env, cancel=None, tarobj=None):
    275          """
    276          File-to-file copy
    277          """
    278
    279          obj = self._init_obj(src)
    280
    281          if os.path.exists(dst) and os.path.isdir(dst):
    282              dstto = os.path.join(dst, os.path.basename(src))
    283          else:
    284              dstto = dst
    285
    286          if os.path.exists(dstto):
    287              if env['override'] not in ('override all', 'skip all'):
    288                  if existcb:
    289                      try:
    290                          env['override'] = existcb(
    291                              self.xyz.vfs.dispatch(dstto))
    292                      except Exception:
    293                          env['override'] = 'abort'
    294
    295              if env['override'] == 'abort':
    296                  raise XYZRuntimeError()
    297              elif env['override'] in ('skip', 'skip all'):
    298                  return False
    299
    300          try:
    301              if not follow_links and obj.issym():
    302                  os.symlink(obj.linkname, dstto)
    303              else:
    304                  if obj.issym():
    305                      objdir = os.path.dirname(obj.name)
    306                      src = os.path.join(objdir, obj.linkname)
    307
    308                  self._do_copy(src, dstto, save_attrs, tarobj, obj)
    309
    310              return True
    311          except Exception, e:
    312              if env['error'] != 'skip all':
    313                  if errorcb:
    314                      try:
    315                          env['error'] = errorcb(
    316                              self.xyz.vfs.dispatch(self.full_path), str(e))
    317                      except Exception:
    318                          env['error'] = 'abort'
    319
    320                  if env['error'] == 'abort':
    321                      raise XYZRuntimeError()
    322
    323          return False
    324
    325      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    326
    327 -    def _copy_dir(self, src, dst, existcb, errorcb, save_attrs,
    328                    follow_links, env, cancel=None, tarobj=None):
    329          """
    330          Dir-to-dir copy
    331          """
    332
    333          obj = self._init_obj(src)
    334
    335          if os.path.exists(dst) and os.path.isdir(dst) and \
    336                 os.path.basename(src) != os.path.basename(dst):
    337              dst = os.path.join(dst, os.path.basename(src))
    338
    339          if not follow_links and obj.issym():
    340              os.symlink(obj.linkname, dst)
    341
    342              return True
    343
    344          if obj.isdir() and not os.path.exists(dst):
    345              os.makedirs(dst)
    346
    347          files = [x for x in tarobj.getmembers() if
    348                   self.in_dir(obj.name, x.name)]
    349
    350          for f in files:
    351              if cancel is not None and cancel.isSet():
    352                  raise StopIteration()
    353
    354              srcobj = f.name
    355              dstobj = os.path.join(dst, self.get_name(f))
    356
    357              if self._init_obj(srcobj).isdir():
    358                  fun = self._copy_dir
    359              else:
    360                  fun = self._copy_file
    361
    362              fun(srcobj, dstobj, existcb, errorcb, save_attrs,
    363                  follow_links, env, cancel, tarobj)
    364
    365          if obj.isdir() and save_attrs:
    366              self._copystat(obj, dst)
    367
    368      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    369
    370 -    def _do_copy(self, src, dst, save_attrs, tarobj, obj):
    371          """
    372          Copy file from inside archive
    373          """
    374
    375          fsrc = tarobj.extractfile(self._normalize(src))
    376
    377          fdst = open(dst, "w")
    378
    379          while True:
    380              block = fsrc.read(4096)
    381
    382              # EOF
    383              if block == '':
    384                  break
    385
    386              fdst.write(block)
    387
    388          fsrc.close()
    389          fdst.close()
    390
    391          if save_attrs:
    392              self._copystat(obj, dst)
    393
    394      #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    395
    396 -    def _copystat(self, obj, dst):
    397          try:
    398              os.chown(dst, obj.uid, obj.gid)
    399          except Exception, e:
    400              xyzlog.warning(_(u"Unable to chown %s: %s") %
    401                             (ustring(dst), ustring(str(e))))
    402
    403          try:
    404              os.chmod(dst, obj.mode)
    405          except Exception, e:
    406              xyzlog.warning(_(u"Unable to chmod %s: %s") %
    407                             (ustring(dst), ustring(str(e))))
    408