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  from libxyz.ui import BlockEntries 
 29   
30 -class TarVFSObject(vfsobj.VFSObject):
31 """ 32 Tar archive interface 33 """ 34
35 - def either(self, a, b):
36 if self.root: 37 return a 38 else: 39 return b()
40 41 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 42 43 get_name = lambda self, x: os.path.basename(x.name.rstrip(os.path.sep)) 44 get_path = lambda self, x: os.path.join(self.ext_path, x.lstrip(os.sep)) 45 get_int_path = lambda self, x: x.lstrip(os.sep) 46 47 file_type_map = { 48 lambda obj: obj.isfile(): vfstypes.VFSTypeFile(), 49 lambda obj: obj.isdir(): vfstypes.VFSTypeDir(), 50 lambda obj: obj.issym(): vfstypes.VFSTypeLink(), 51 lambda obj: obj.ischr(): vfstypes.VFSTypeChar(), 52 lambda obj: obj.isblk(): vfstypes.VFSTypeBlock(), 53 lambda obj: obj.isfifo(): vfstypes.VFSTypeFifo(), 54 } 55
56 - def __init__(self, *args, **kwargs):
57 self.tarobj = None 58 self.members = None 59 60 super(TarVFSObject, self).__init__(*args, **kwargs)
61 62 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 63
64 - def walk(self):
65 """ 66 Directory tree walker 67 @return: tuple (parent, dir, objects) where: 68 parent - parent dir *VFSObject instance 69 dir - current dir TarVFSObject instance 70 objects - BlockEntries of TarVFSObject objects 71 """ 72 73 dirs, files = [], [] 74 self._open_archive() 75 76 for x in self.members.values(): 77 if self.in_dir(self.path, x.name): 78 if x.isdir(): 79 dirs.append(x) 80 else: 81 files.append(x) 82 83 dirs.sort(cmp=lambda x, y: cmp(self.get_name(x), 84 self.get_name(y))) 85 files.sort(cmp=lambda x, y: cmp(self.get_name(x), 86 self.get_name(y))) 87 88 if self.path == os.sep: 89 _parent = self.xyz.vfs.get_parent(self.parent.full_path, self.enc) 90 else: 91 _parent = self.xyz.vfs.dispatch( 92 self.get_path(os.path.dirname(self.path)), self.enc) 93 _parent.name = ".." 94 95 return [ 96 _parent, 97 self, 98 BlockEntries(self.xyz, dirs + files, 99 lambda x: self.get_path(x.name))]
100 101 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 102
103 - def copy(self, path, existcb=None, errorcb=None, save_attrs=True, 104 follow_links=False, cancel=None):
105 106 env = { 107 'override': 'abort', 108 'error': 'abort' 109 } 110 111 tarobj = self._open_archive() 112 113 try: 114 if self.is_dir(): 115 f = self._copy_dir 116 else: 117 f = self._copy_file 118 119 f(self.path, path, existcb, errorcb, 120 save_attrs, follow_links, env, cancel, tarobj=tarobj) 121 except XYZRuntimeError: 122 # Aborted 123 return False 124 else: 125 return True
126 127 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 128
129 - def open(self, mode='r'):
130 """ 131 Open self object in provided mode 132 """ 133 134 if self.fileobj: 135 return self 136 else: 137 self.fileobj = self.tarobj.extractfile( 138 self.get_int_path(self.path)) 139 140 return self
141 142 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 143
144 - def close(self):
145 """ 146 Close self object 147 """ 148 149 if self.fileobj is None: 150 return 151 else: 152 try: 153 self.fileobj.close() 154 finally: 155 self.fileobj = None
156 157 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 158
159 - def read(self, bytes=None):
160 """ 161 Read bytes from self object 162 """ 163 164 return self.fileobj.read(bytes)
165 166 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 167
168 - def tell(self):
169 """ 170 Tell file position 171 """ 172 173 return self.fileobj.tell()
174 175 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 176
177 - def seek(self, offset, whence=0):
178 """ 179 Perform seek() on object 180 """ 181 182 return self.fileobj.seek(offset, whence)
183 184 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 185
186 - def _prepare(self):
187 if self.path == os.sep: 188 self.root = True 189 else: 190 self.root = False 191 192 (self.tarobj, self.members) = self._find_cached() 193 194 if self.root: 195 self.obj = None 196 else: 197 self.obj = self._init_obj() 198 199 self.ftype = self._find_type() 200 self.vtype = self.ftype.vtype 201 202 self._set_attributes() 203 204 self.attributes = ( 205 (_(u"Name"), ustring(self.name)), 206 (_(u"Type"), ustring(self.ftype)), 207 (_(u"Modification time"), ustring(time.ctime(self.mtime))), 208 (_(u"Size in bytes"), ustring(self.size)), 209 (_(u"Owner"), ustring(self.uid)), 210 (_(u"Group"), ustring(self.gid)), 211 (_(u"Access mode"), ustring(self.mode)), 212 (_(u"Type-specific data"), self.data), 213 )
214 215 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 216
217 - def _normalize(self, path):
218 """ 219 Normalize path 220 """ 221 222 if path.startswith(os.sep): 223 return path.lstrip(os.sep) 224 else: 225 return path
226 227 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 228
229 - def __str__(self):
230 return "<TarVFSObject object: %s>" % self.path
231 232 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 233
234 - def in_dir(self, d, e):
235 """ 236 Filter only those archive entries which exist in the same 237 directory level 238 """ 239 240 if e.startswith(d.lstrip(os.sep)) and \ 241 len(util.split_path(e)) == (len(util.split_path(d)) + 1): 242 return True 243 else: 244 return False
245 246 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 247
248 - def _find_type(self):
249 """ 250 Find out file type 251 """ 252 253 if self.root: 254 return self.parent.ftype 255 256 for k, v in self.file_type_map.iteritems(): 257 if k(self.obj): 258 return v 259 260 return vfstypes.VFSTypeUnknown()
261 262 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 263
264 - def _set_attributes(self):
265 """ 266 Set file attibutes 267 """ 268 269 def set_link_attributes(): 270 """ 271 Set appropriate soft link attibutes 272 """ 273 274 self.info = "" 275 self.visual = "-> %s" % self.obj.linkname or ""
276 277 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 278 279 self.name = self.either(self.parent.name, lambda: self.name) 280 self.mtime = self.either(self.parent.mtime, lambda: self.obj.mtime) 281 self.size = self.either(self.parent.size, lambda: self.obj.size) 282 self.uid = self.either(self.parent.uid, lambda: self.obj.uid) 283 self.gid = self.either(self.parent.gid, lambda: self.obj.gid) 284 self.mode = mode.Mode(self.either(self.parent.mode.raw, 285 lambda: self.obj.mode), self.ftype) 286 self.visual = "%s%s" % (self.vtype, self.name) 287 288 self.info = "%s %s" % (util.format_size(self.size), self.mode) 289 290 if self.is_link(): 291 set_link_attributes() 292 elif self.is_file(): 293 _mode = stat.S_IMODE(self.mode.raw) 294 295 # Executable 296 if _mode & 0111: 297 self.vtype = "*" 298 self.visual = "*%s" % self.name
299 300 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 301
302 - def _init_obj(self, altpath=None):
303 self._open_archive() 304 path = (altpath or self.path).lstrip(os.sep) 305 306 try: 307 obj = self.members[path] 308 except KeyError: 309 obj = self.members(path + os.sep) 310 311 return obj
312 313 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 314
315 - def _open_archive(self):
316 if self.tarobj is None: 317 _mode = "r" 318 319 if self.driver == "gztar": 320 _mode = "r:gz" 321 elif self.driver == "bz2tar": 322 _mode = "r:bz2" 323 324 self.tarobj = tarfile.open(fileobj=self.parent.open(), mode=_mode) 325 self.members = self._init_members(self.tarobj) 326 327 self.xyz.vfs.set_cache(self.parent.full_path, (self.tarobj, 328 self.members)) 329 330 return self.tarobj
331 332 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 333
334 - def _copy_file(self, src, dst, existcb, errorcb, save_attrs, 335 follow_links, env, cancel=None, tarobj=None):
336 """ 337 File-to-file copy 338 """ 339 340 obj = self._init_obj(src) 341 342 if os.path.exists(dst) and os.path.isdir(dst): 343 dstto = os.path.join(dst, os.path.basename(src)) 344 else: 345 dstto = dst 346 347 if os.path.exists(dstto): 348 if env['override'] not in ('override all', 'skip all'): 349 if existcb: 350 try: 351 env['override'] = existcb( 352 self.xyz.vfs.dispatch(dstto)) 353 except Exception: 354 env['override'] = 'abort' 355 356 if env['override'] == 'abort': 357 raise XYZRuntimeError() 358 elif env['override'] in ('skip', 'skip all'): 359 return False 360 361 try: 362 if not follow_links and obj.issym(): 363 os.symlink(obj.linkname, dstto) 364 else: 365 if obj.issym(): 366 objdir = os.path.dirname(obj.name) 367 src = os.path.join(objdir, obj.linkname) 368 369 self._do_copy(src, dstto, save_attrs, tarobj, obj) 370 371 return True 372 except Exception, e: 373 if env['error'] != 'skip all': 374 if errorcb: 375 try: 376 env['error'] = errorcb( 377 self.xyz.vfs.dispatch(self.full_path), str(e)) 378 except Exception: 379 env['error'] = 'abort' 380 381 if env['error'] == 'abort': 382 raise XYZRuntimeError() 383 384 return False
385 386 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 387
388 - def _copy_dir(self, src, dst, existcb, errorcb, save_attrs, 389 follow_links, env, cancel=None, tarobj=None):
390 """ 391 Dir-to-dir copy 392 """ 393 394 obj = self._init_obj(src) 395 396 if os.path.exists(dst) and os.path.isdir(dst) and \ 397 os.path.basename(src) != os.path.basename(dst): 398 dst = os.path.join(dst, os.path.basename(src)) 399 400 if not follow_links and obj.issym(): 401 os.symlink(obj.linkname, dst) 402 403 return True 404 405 if obj.isdir() and not os.path.exists(dst): 406 os.makedirs(dst) 407 408 files = [x for x in tarobj.getmembers() if 409 self.in_dir(obj.name, x.name)] 410 411 for f in files: 412 if cancel is not None and cancel.isSet(): 413 raise StopIteration() 414 415 srcobj = f.name 416 dstobj = os.path.join(dst, self.get_name(f)) 417 418 if self._init_obj(srcobj).isdir(): 419 fun = self._copy_dir 420 else: 421 fun = self._copy_file 422 423 fun(srcobj, dstobj, existcb, errorcb, save_attrs, 424 follow_links, env, cancel, tarobj) 425 426 if obj.isdir() and save_attrs: 427 self._copystat(obj, dst)
428 429 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 430
431 - def _do_copy(self, src, dst, save_attrs, tarobj, obj):
432 """ 433 Copy file from inside archive 434 """ 435 436 fsrc = tarobj.extractfile(self._normalize(src)) 437 438 fdst = open(dst, "w") 439 440 while True: 441 block = fsrc.read(4096) 442 443 # EOF 444 if block == '': 445 break 446 447 fdst.write(block) 448 449 fsrc.close() 450 fdst.close() 451 452 if save_attrs: 453 self._copystat(obj, dst)
454 455 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 456
457 - def _copystat(self, obj, dst):
458 try: 459 os.chown(dst, obj.uid, obj.gid) 460 except Exception, e: 461 xyzlog.warning(_(u"Unable to chown %s: %s") % 462 (ustring(dst), unicode(e))) 463 464 try: 465 os.chmod(dst, obj.mode) 466 except Exception, e: 467 xyzlog.warning(_(u"Unable to chmod %s: %s") % 468 (ustring(dst), unicode(e)))
469 470 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 471
472 - def _find_cached(self):
473 """ 474 Find cached data 475 """ 476 477 return self.xyz.vfs.get_cache(self.parent.full_path) or (None, None)
478 479 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 480
481 - def _init_members(self, tarobj):
482 """ 483 Init members from archive contents 484 """ 485 486 members = {} 487 488 for name in tarobj.getnames(): 489 data = util.split_path(name) 490 current = [] 491 492 for obj in data: 493 current.append(obj) 494 495 path = os.path.join(*current) 496 497 if path not in members: 498 try: 499 item = tarobj.getmember(path) 500 except KeyError: 501 try: 502 item = tarobj.getmember(path + os.sep) 503 except KeyError: 504 # Create dummy dir 505 item = self._make_dummy_dir(path) 506 507 members[path] = item 508 509 return members
510 511 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 512
513 - def _make_dummy_dir(self, name):
514 """ 515 Make dummy directory object 516 """ 517 518 uid = os.getuid() 519 gid = os.getgid() 520 521 obj = tarfile.TarInfo(name) 522 obj.type = tarfile.DIRTYPE 523 obj.mode = 0755 524 obj.uid = uid 525 obj.gid = gid 526 obj.mtime = int(time.time()) 527 obj.uname = util.get_user(uid) 528 obj.gname = util.get_group(gid) 529 530 return obj
531