1""" 2fs.wrapfs 3========= 4 5A class for wrapping an existing FS object with additional functionality. 6 7This module provides the class WrapFS, a base class for objects that wrap 8another FS object and provide some transformation of its contents. It could 9be very useful for implementing e.g. transparent encryption or compression 10services. 11 12For a simple example of how this class could be used, see the 'HideDotFilesFS' 13class in the module fs.wrapfs.hidedotfilesfs. This wrapper implements the 14standard unix shell functionality of hiding dot-files in directory listings. 15 16""" 17 18import re 19import sys 20import fnmatch 21import threading 22 23from fs.base import FS, threading, synchronize, NoDefaultMeta 24from fs.errors import * 25from fs.path import * 26from fs.local_functools import wraps 27 28 29def rewrite_errors(func): 30 """Re-write paths in errors raised by wrapped FS objects.""" 31 @wraps(func) 32 def wrapper(self,*args,**kwds): 33 try: 34 return func(self,*args,**kwds) 35 except ResourceError, e: 36 (exc_type,exc_inst,tb) = sys.exc_info() 37 try: 38 e.path = self._decode(e.path) 39 except (AttributeError, ValueError, TypeError): 40 raise e, None, tb 41 raise 42 return wrapper 43 44 45class WrapFS(FS): 46 """FS that wraps another FS, providing translation etc. 47 48 This class allows simple transforms to be applied to the names 49 and/or contents of files in an FS. It could be used to implement 50 e.g. compression or encryption in a relatively painless manner. 51 52 The following methods can be overridden to control how files are 53 accessed in the underlying FS object: 54 55 * _file_wrap(file, mode): called for each file that is opened from 56 the underlying FS; may return a modified 57 file-like object. 58 59 * _encode(path): encode a path for access in the underlying FS 60 61 * _decode(path): decode a path from the underlying FS 62 63 If the required path translation proceeds one component at a time, 64 it may be simpler to override the _encode_name() and _decode_name() 65 methods. 66 """ 67 68 def __init__(self, fs): 69 super(WrapFS, self).__init__() 70 try: 71 self._lock = fs._lock 72 except (AttributeError,FSError): 73 self._lock = self._lock = threading.RLock() 74 self.wrapped_fs = fs 75 76 def _file_wrap(self, f, mode): 77 """Apply wrapping to an opened file.""" 78 return f 79 80 def _encode_name(self, name): 81 """Encode path component for the underlying FS.""" 82 return name 83 84 def _decode_name(self, name): 85 """Decode path component from the underlying FS.""" 86 return name 87 88 def _encode(self, path): 89 """Encode path for the underlying FS.""" 90 e_names = [] 91 for name in iteratepath(path): 92 if name == "": 93 e_names.append("") 94 else: 95 e_names.append(self._encode_name(name)) 96 return "/".join(e_names) 97 98 def _decode(self, path): 99 """Decode path from the underlying FS.""" 100 d_names = [] 101 for name in iteratepath(path): 102 if name == "": 103 d_names.append("") 104 else: 105 d_names.append(self._decode_name(name)) 106 return "/".join(d_names) 107 108 def _adjust_mode(self, mode): 109 """Adjust the mode used to open a file in the underlying FS. 110 111 This method takes the mode given when opening a file, and should 112 return a two-tuple giving the mode to be used in this FS as first 113 item, and the mode to be used in the underlying FS as the second. 114 115 An example of why this is needed is a WrapFS subclass that does 116 transparent file compression - in this case files from the wrapped 117 FS cannot be opened in append mode. 118 """ 119 return (mode, mode) 120 121 def __unicode__(self): 122 return u"<%s: %s>" % (self.__class__.__name__,self.wrapped_fs,) 123 124 #def __str__(self): 125 # return unicode(self).encode(sys.getdefaultencoding(),"replace") 126 127 128 @rewrite_errors 129 def getmeta(self, meta_name, default=NoDefaultMeta): 130 return self.wrapped_fs.getmeta(meta_name, default) 131 132 @rewrite_errors 133 def hasmeta(self, meta_name): 134 return self.wrapped_fs.hasmeta(meta_name) 135 136 @rewrite_errors 137 def validatepath(self, path): 138 return self.wrapped_fs.validatepath(self._encode(path)) 139 140 @rewrite_errors 141 def getsyspath(self, path, allow_none=False): 142 return self.wrapped_fs.getsyspath(self._encode(path), allow_none) 143 144 @rewrite_errors 145 def getpathurl(self, path, allow_none=False): 146 return self.wrapped_fs.getpathurl(self._encode(path), allow_none) 147 148 @rewrite_errors 149 def hassyspath(self, path): 150 return self.wrapped_fs.hassyspath(self._encode(path)) 151 152 @rewrite_errors 153 def open(self, path, mode='r', **kwargs): 154 (mode, wmode) = self._adjust_mode(mode) 155 f = self.wrapped_fs.open(self._encode(path), wmode, **kwargs) 156 return self._file_wrap(f, mode) 157 158 @rewrite_errors 159 def setcontents(self, path, data, encoding=None, errors=None, chunk_size=64*1024): 160 # We can't pass setcontents() through to the wrapped FS if the 161 # wrapper has defined a _file_wrap method, as it would bypass 162 # the file contents wrapping. 163 #if self._file_wrap.im_func is WrapFS._file_wrap.im_func: 164 if getattr(self.__class__, '_file_wrap', None) is getattr(WrapFS, '_file_wrap', None): 165 return self.wrapped_fs.setcontents(self._encode(path), data, encoding=encoding, errors=errors, chunk_size=chunk_size) 166 else: 167 return super(WrapFS, self).setcontents(path, data, encoding=encoding, errors=errors, chunk_size=chunk_size) 168 169 @rewrite_errors 170 def createfile(self, path, wipe=False): 171 return self.wrapped_fs.createfile(self._encode(path), wipe=wipe) 172 173 @rewrite_errors 174 def exists(self, path): 175 return self.wrapped_fs.exists(self._encode(path)) 176 177 @rewrite_errors 178 def isdir(self, path): 179 return self.wrapped_fs.isdir(self._encode(path)) 180 181 @rewrite_errors 182 def isfile(self, path): 183 return self.wrapped_fs.isfile(self._encode(path)) 184 185 @rewrite_errors 186 def listdir(self, path="", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): 187 kwds = dict(wildcard=wildcard, 188 full=full, 189 absolute=absolute, 190 dirs_only=dirs_only, 191 files_only=files_only) 192 full = kwds.pop("full",False) 193 absolute = kwds.pop("absolute",False) 194 wildcard = kwds.pop("wildcard",None) 195 if wildcard is None: 196 wildcard = lambda fn:True 197 elif not callable(wildcard): 198 wildcard_re = re.compile(fnmatch.translate(wildcard)) 199 wildcard = lambda fn:bool (wildcard_re.match(fn)) 200 entries = [] 201 enc_path = self._encode(path) 202 for e in self.wrapped_fs.listdir(enc_path,**kwds): 203 e = basename(self._decode(pathcombine(enc_path,e))) 204 if not wildcard(e): 205 continue 206 if full: 207 e = pathcombine(path,e) 208 elif absolute: 209 e = abspath(pathcombine(path,e)) 210 entries.append(e) 211 return entries 212 213 @rewrite_errors 214 def ilistdir(self, path="", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): 215 kwds = dict(wildcard=wildcard, 216 full=full, 217 absolute=absolute, 218 dirs_only=dirs_only, 219 files_only=files_only) 220 full = kwds.pop("full",False) 221 absolute = kwds.pop("absolute",False) 222 wildcard = kwds.pop("wildcard",None) 223 if wildcard is None: 224 wildcard = lambda fn:True 225 elif not callable(wildcard): 226 wildcard_re = re.compile(fnmatch.translate(wildcard)) 227 wildcard = lambda fn:bool (wildcard_re.match(fn)) 228 enc_path = self._encode(path) 229 for e in self.wrapped_fs.ilistdir(enc_path,**kwds): 230 e = basename(self._decode(pathcombine(enc_path,e))) 231 if not wildcard(e): 232 continue 233 if full: 234 e = pathcombine(path,e) 235 elif absolute: 236 e = abspath(pathcombine(path,e)) 237 yield e 238 239 @rewrite_errors 240 def listdirinfo(self, path="", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): 241 kwds = dict(wildcard=wildcard, 242 full=full, 243 absolute=absolute, 244 dirs_only=dirs_only, 245 files_only=files_only) 246 full = kwds.pop("full",False) 247 absolute = kwds.pop("absolute",False) 248 wildcard = kwds.pop("wildcard",None) 249 if wildcard is None: 250 wildcard = lambda fn:True 251 elif not callable(wildcard): 252 wildcard_re = re.compile(fnmatch.translate(wildcard)) 253 wildcard = lambda fn:bool (wildcard_re.match(fn)) 254 entries = [] 255 enc_path = self._encode(path) 256 for (nm,info) in self.wrapped_fs.listdirinfo(enc_path,**kwds): 257 nm = basename(self._decode(pathcombine(enc_path,nm))) 258 if not wildcard(nm): 259 continue 260 if full: 261 nm = pathcombine(path,nm) 262 elif absolute: 263 nm = abspath(pathcombine(path,nm)) 264 entries.append((nm,info)) 265 return entries 266 267 @rewrite_errors 268 def ilistdirinfo(self, path="", wildcard=None, full=False, absolute=False, dirs_only=False, files_only=False): 269 kwds = dict(wildcard=wildcard, 270 full=full, 271 absolute=absolute, 272 dirs_only=dirs_only, 273 files_only=files_only) 274 full = kwds.pop("full",False) 275 absolute = kwds.pop("absolute",False) 276 wildcard = kwds.pop("wildcard",None) 277 if wildcard is None: 278 wildcard = lambda fn:True 279 elif not callable(wildcard): 280 wildcard_re = re.compile(fnmatch.translate(wildcard)) 281 wildcard = lambda fn:bool (wildcard_re.match(fn)) 282 enc_path = self._encode(path) 283 for (nm,info) in self.wrapped_fs.ilistdirinfo(enc_path,**kwds): 284 nm = basename(self._decode(pathcombine(enc_path,nm))) 285 if not wildcard(nm): 286 continue 287 if full: 288 nm = pathcombine(path,nm) 289 elif absolute: 290 nm = abspath(pathcombine(path,nm)) 291 yield (nm,info) 292 293 @rewrite_errors 294 def walk(self,path="/",wildcard=None,dir_wildcard=None,search="breadth",ignore_errors=False): 295 if dir_wildcard is not None: 296 # If there is a dir_wildcard, fall back to the default impl 297 # that uses listdir(). Otherwise we run the risk of enumerating 298 # lots of directories that will just be thrown away. 299 for item in super(WrapFS,self).walk(path,wildcard,dir_wildcard,search,ignore_errors): 300 yield item 301 # Otherwise, the wrapped FS may provide a more efficient impl 302 # which we can use directly. 303 else: 304 if wildcard is not None and not callable(wildcard): 305 wildcard_re = re.compile(fnmatch.translate(wildcard)) 306 wildcard = lambda fn:bool (wildcard_re.match(fn)) 307 for (dirpath,filepaths) in self.wrapped_fs.walk(self._encode(path),search=search,ignore_errors=ignore_errors): 308 filepaths = [basename(self._decode(pathcombine(dirpath,p))) 309 for p in filepaths] 310 dirpath = abspath(self._decode(dirpath)) 311 if wildcard is not None: 312 filepaths = [p for p in filepaths if wildcard(p)] 313 yield (dirpath,filepaths) 314 315 @rewrite_errors 316 def walkfiles(self,path="/",wildcard=None,dir_wildcard=None,search="breadth",ignore_errors=False): 317 if dir_wildcard is not None: 318 # If there is a dir_wildcard, fall back to the default impl 319 # that uses listdir(). Otherwise we run the risk of enumerating 320 # lots of directories that will just be thrown away. 321 for item in super(WrapFS,self).walkfiles(path,wildcard,dir_wildcard,search,ignore_errors): 322 yield item 323 # Otherwise, the wrapped FS may provide a more efficient impl 324 # which we can use directly. 325 else: 326 if wildcard is not None and not callable(wildcard): 327 wildcard_re = re.compile(fnmatch.translate(wildcard)) 328 wildcard = lambda fn:bool (wildcard_re.match(fn)) 329 for filepath in self.wrapped_fs.walkfiles(self._encode(path),search=search,ignore_errors=ignore_errors): 330 filepath = abspath(self._decode(filepath)) 331 if wildcard is not None: 332 if not wildcard(basename(filepath)): 333 continue 334 yield filepath 335 336 @rewrite_errors 337 def walkdirs(self,path="/",wildcard=None,search="breadth",ignore_errors=False): 338 if wildcard is not None: 339 # If there is a wildcard, fall back to the default impl 340 # that uses listdir(). Otherwise we run the risk of enumerating 341 # lots of directories that will just be thrown away. 342 for item in super(WrapFS,self).walkdirs(path,wildcard,search,ignore_errors): 343 yield item 344 # Otherwise, the wrapped FS may provide a more efficient impl 345 # which we can use directly. 346 else: 347 for dirpath in self.wrapped_fs.walkdirs(self._encode(path),search=search,ignore_errors=ignore_errors): 348 yield abspath(self._decode(dirpath)) 349 350 351 @rewrite_errors 352 def makedir(self, path, *args, **kwds): 353 return self.wrapped_fs.makedir(self._encode(path),*args,**kwds) 354 355 @rewrite_errors 356 def remove(self, path): 357 return self.wrapped_fs.remove(self._encode(path)) 358 359 @rewrite_errors 360 def removedir(self, path, *args, **kwds): 361 return self.wrapped_fs.removedir(self._encode(path),*args,**kwds) 362 363 @rewrite_errors 364 def rename(self, src, dst): 365 return self.wrapped_fs.rename(self._encode(src),self._encode(dst)) 366 367 @rewrite_errors 368 def getinfo(self, path): 369 return self.wrapped_fs.getinfo(self._encode(path)) 370 371 @rewrite_errors 372 def settimes(self, path, *args, **kwds): 373 return self.wrapped_fs.settimes(self._encode(path), *args,**kwds) 374 375 @rewrite_errors 376 def desc(self, path): 377 return self.wrapped_fs.desc(self._encode(path)) 378 379 @rewrite_errors 380 def copy(self, src, dst, **kwds): 381 return self.wrapped_fs.copy(self._encode(src),self._encode(dst),**kwds) 382 383 @rewrite_errors 384 def move(self, src, dst, **kwds): 385 return self.wrapped_fs.move(self._encode(src),self._encode(dst),**kwds) 386 387 @rewrite_errors 388 def movedir(self, src, dst, **kwds): 389 return self.wrapped_fs.movedir(self._encode(src),self._encode(dst),**kwds) 390 391 @rewrite_errors 392 def copydir(self, src, dst, **kwds): 393 return self.wrapped_fs.copydir(self._encode(src),self._encode(dst),**kwds) 394 395 @rewrite_errors 396 def getxattr(self, path, name, default=None): 397 try: 398 return self.wrapped_fs.getxattr(self._encode(path),name,default) 399 except AttributeError: 400 raise UnsupportedError("getxattr") 401 402 @rewrite_errors 403 def setxattr(self, path, name, value): 404 try: 405 return self.wrapped_fs.setxattr(self._encode(path),name,value) 406 except AttributeError: 407 raise UnsupportedError("setxattr") 408 409 @rewrite_errors 410 def delxattr(self, path, name): 411 try: 412 return self.wrapped_fs.delxattr(self._encode(path),name) 413 except AttributeError: 414 raise UnsupportedError("delxattr") 415 416 @rewrite_errors 417 def listxattrs(self, path): 418 try: 419 return self.wrapped_fs.listxattrs(self._encode(path)) 420 except AttributeError: 421 raise UnsupportedError("listxattrs") 422 423 def __getattr__(self, attr): 424 # These attributes can be used by the destructor, but may not be 425 # defined if there are errors in the constructor. 426 if attr == "closed": 427 return False 428 if attr == "wrapped_fs": 429 return None 430 if attr.startswith("_"): 431 raise AttributeError(attr) 432 return getattr(self.wrapped_fs,attr) 433 434 @rewrite_errors 435 def close(self): 436 if not self.closed: 437 self.wrapped_fs.close() 438 super(WrapFS,self).close() 439 self.wrapped_fs = None 440 441 442def wrap_fs_methods(decorator, cls=None, exclude=[]): 443 """Apply the given decorator to all FS methods on the given class. 444 445 This function can be used in two ways. When called with two arguments it 446 applies the given function 'decorator' to each FS method of the given 447 class. When called with just a single argument, it creates and returns 448 a class decorator which will do the same thing when applied. So you can 449 use it like this:: 450 451 wrap_fs_methods(mydecorator,MyFSClass) 452 453 Or on more recent Python versions, like this:: 454 455 @wrap_fs_methods(mydecorator) 456 class MyFSClass(FS): 457 ... 458 459 """ 460 def apply_decorator(cls): 461 for method_name in wrap_fs_methods.method_names: 462 if method_name in exclude: 463 continue 464 method = getattr(cls,method_name,None) 465 if method is not None: 466 setattr(cls,method_name,decorator(method)) 467 return cls 468 if cls is not None: 469 return apply_decorator(cls) 470 else: 471 return apply_decorator 472 473wrap_fs_methods.method_names = ["open","exists","isdir","isfile","listdir", 474 "makedir","remove","setcontents","removedir","rename","getinfo","copy", 475 "move","copydir","movedir","close","getxattr","setxattr","delxattr", 476 "listxattrs","validatepath","getsyspath","createfile", "hasmeta", "getmeta","listdirinfo", 477 "ilistdir","ilistdirinfo"] 478 479 480