Package conary :: Package lib :: Module util
[hide private]
[frames] | no frames]

Source Code for Module conary.lib.util

   1  # 
   2  # Copyright (c) 2011 rPath, Inc. 
   3  # 
   4  # This program is distributed under the terms of the Common Public License, 
   5  # version 1.0. A copy of this license should have been distributed with this 
   6  # source file in a file called LICENSE. If it is not present, the license 
   7  # is always available at http://www.rpath.com/permanent/licenses/CPL-1.0. 
   8  # 
   9  # This program is distributed in the hope that it will be useful, but 
  10  # without any warranty; without even the implied warranty of merchantability 
  11  # or fitness for a particular purpose. See the Common Public License for 
  12  # full details. 
  13  # 
  14   
  15  import bdb 
  16  import bz2 
  17  import debugger 
  18  import errno 
  19  import fcntl 
  20  import fnmatch 
  21  import gzip 
  22  import misc 
  23  import os 
  24  import re 
  25  import select 
  26  import shutil 
  27  import stat 
  28  import string 
  29  import StringIO 
  30  import struct 
  31  import subprocess 
  32  import sys 
  33  import tempfile 
  34  import time 
  35  import types 
  36  import urllib 
  37  import weakref 
  38  import xmlrpclib 
  39  import zlib 
  40   
  41  from conary.lib import fixedglob, log, api, urlparse 
  42  from conary.lib import networking 
  43   
  44  # Imported for the benefit of older code, 
  45  from conary.lib.formattrace import formatTrace 
  46   
  47   
  48  # Simple ease-of-use extensions to python libraries 
  49   
50 -def normpath(path):
51 s = os.path.normpath(path) 52 if s.startswith(os.sep + os.sep): 53 return s[1:] 54 return s
55
56 -def realpath(path):
57 # returns the real path of a file, if and only if it is not a symbolic 58 # link 59 if not os.path.exists(path): 60 return path 61 if stat.S_ISLNK(os.lstat(path)[stat.ST_MODE]): 62 return path 63 return os.path.realpath(path)
64
65 -def isregular(path):
66 return stat.S_ISREG(os.lstat(path)[stat.ST_MODE])
67 68
69 -def _mkdirs(path, mode=0777):
70 """ 71 Recursive helper to L{mkdirChain}. Internal use only. 72 """ 73 head, tail = os.path.split(path) 74 if head and tail and not os.path.exists(head): 75 _mkdirs(head, mode) 76 77 # Make the directory while ignoring errors about it existing. 78 misc.mkdirIfMissing(path)
79 80 81 @api.developerApi
82 -def mkdirChain(*paths):
83 """ 84 Make one or more directories if they do not already exist, including any 85 needed parent directories. Similar to L{os.makedirs} except that it does 86 not error if the requested directory already exists, and it is more 87 resilient to race conditions. 88 """ 89 for path in paths: 90 path = normpath(os.path.abspath(path)) 91 if not os.path.exists(path): 92 _mkdirs(path)
93
94 -def searchPath(filename, basepath):
95 path = os.path.join(basepath,filename) 96 for root, dirs, files in os.walk(basepath): 97 if filename in files: 98 return os.path.join(root,filename)
99
100 -def searchFile(file, searchdirs, error=None):
101 for dir in searchdirs: 102 s = "%s%s%s" %(dir, os.sep, file) 103 if os.path.exists(s): 104 return s 105 if error: 106 raise OSError, (errno.ENOENT, os.strerror(errno.ENOENT)) 107 return None
108
109 -def findFile(file, searchdirs):
110 return searchFile(file, searchdirs, error=1)
111
112 -def which (filename):
113 if not os.environ.has_key('PATH') or os.environ['PATH'] == '': 114 p = os.defpath 115 else: 116 p = os.environ['PATH'] 117 118 pathlist = p.split (os.pathsep) 119 120 for path in pathlist: 121 f = os.path.join(path, filename) 122 if os.access(f, os.X_OK): 123 return f 124 return None
125
126 -def recurseDirectoryList(topdir, withDirs=False):
127 """Recursively list all files in the directory""" 128 items = [topdir] 129 while items: 130 item = items.pop() 131 if os.path.islink(item) or os.path.isfile(item): 132 yield item 133 continue 134 # Directory 135 listdir = os.listdir(item) 136 # Add the contents of the directory in reverse order (we use pop(), so 137 # last element in the list is the one popped out) 138 listdir.sort() 139 listdir.reverse() 140 listdir = [ os.path.join(item, x) for x in listdir ] 141 items.extend(listdir) 142 143 if withDirs: 144 # This is useful if one wants to catch empty directories 145 yield item
146
147 -def normurl(url):
148 surl = list(urlparse.urlsplit(url)) 149 if surl[2] == '': 150 surl[2] = '/' 151 elif surl[2] != '/': 152 tail = '' 153 if surl[2].endswith('/'): 154 tail = '/' 155 surl[2] = normpath(surl[2]) + tail 156 return urlparse.urlunsplit(surl)
157 158 errorMessage = ''' 159 ERROR: An unexpected condition has occurred in Conary. This is 160 most likely due to insufficient handling of erroneous input, but 161 may be some other bug. In either case, please report the error at 162 http://issues.rpath.com/ and attach to the issue the file 163 %(stackfile)s 164 165 Then, for more complete information, please run the following script: 166 conary-debug "%(command)s" 167 You can attach the resulting archive to your issue report at 168 http://issues.rpath.com/ For more information, or if you have 169 trouble with the conary-debug command, go to: 170 http://wiki.rpath.com/wiki/Conary:How_To_File_An_Effective_Bug_Report 171 172 To get a debug prompt, rerun the command with --debug-all 173 174 Error details follow: 175 176 %(filename)s:%(lineno)s 177 %(errtype)s: %(errmsg)s 178 179 The complete related traceback has been saved as %(stackfile)s 180 ''' 181 _debugAll = False 182 183 @api.developerApi
184 -def genExcepthook(debug=True, 185 debugCtrlC=False, prefix='conary-error-', 186 catchSIGUSR1=True, error=errorMessage):
187 def SIGUSR1Handler(signum, frame): 188 global _debugAll 189 _debugAll = True 190 print >>sys.stderr, '<Turning on KeyboardInterrupt catching>'
191 192 def excepthook(typ, value, tb): 193 if typ is bdb.BdbQuit: 194 sys.exit(1) 195 #pylint: disable-msg=E1101 196 sys.excepthook = sys.__excepthook__ 197 if not _debugAll and (typ == KeyboardInterrupt and not debugCtrlC): 198 sys.exit(1) 199 200 out = BoundedStringIO() 201 formatTrace(typ, value, tb, stream = out, withLocals = False) 202 out.write("\nFull stack:\n") 203 formatTrace(typ, value, tb, stream = out, withLocals = True) 204 out.seek(0) 205 tbString = out.read() 206 del out 207 if log.syslog is not None: 208 log.syslog("command failed\n%s", tbString) 209 210 if debug or _debugAll: 211 formatTrace(typ, value, tb, stream = sys.stderr, 212 withLocals = False) 213 if sys.stdout.isatty() and sys.stdin.isatty(): 214 debugger.post_mortem(tb, typ, value) 215 else: 216 sys.exit(1) 217 elif log.getVerbosity() is log.DEBUG: 218 log.debug(tbString) 219 else: 220 cmd = sys.argv[0] 221 if cmd.endswith('/commands/conary'): 222 cmd = cmd[:len('/commands/conary')] + '/bin/conary' 223 elif cmd.endswith('/commands/cvc'): 224 cmd = cmd[:len('/commands/cvc')] + '/bin/cvc' 225 226 origTb = tb 227 cmd = normpath(cmd) 228 sys.argv[0] = cmd 229 while tb.tb_next: tb = tb.tb_next 230 lineno = tb.tb_frame.f_lineno 231 filename = tb.tb_frame.f_code.co_filename 232 tmpfd, stackfile = tempfile.mkstemp('.txt', prefix) 233 os.write(tmpfd, tbString) 234 os.close(tmpfd) 235 236 sys.stderr.write(error % dict(command=' '.join(sys.argv), 237 filename=filename, 238 lineno=lineno, 239 errtype=typ.__name__, 240 errmsg=value, 241 stackfile=stackfile)) 242 243 #if catchSIGUSR1: 244 # signal.signal(signal.SIGUSR1, SIGUSR1Handler) 245 return excepthook 246 247 248
249 -def _handle_rc(rc, cmd):
250 if rc: 251 if not os.WIFEXITED(rc): 252 info = 'Shell command "%s" killed with signal %d' \ 253 %(cmd, os.WTERMSIG(rc)) 254 if os.WEXITSTATUS(rc): 255 info = 'Shell command "%s" exited with exit code %d' \ 256 %(cmd, os.WEXITSTATUS(rc)) 257 log.error(info) 258 raise RuntimeError, info
259
260 -def execute(cmd, destDir=None, verbose=True):
261 """ 262 similar to os.system, but raises errors if exit code != 0 and closes stdin 263 so processes can never block on user input 264 """ 265 if verbose: 266 log.info(cmd) 267 rc = subprocess.call(cmd, shell=True, cwd=destDir, stdin=open(os.devnull)) 268 # form the rc into a standard exit status 269 if rc < 0: 270 # turn rc positive 271 rc = rc * -1 272 else: 273 # shift the return code into the high bits 274 rc = rc << 8 275 _handle_rc(rc, cmd)
276
277 -class popen:
278 """ 279 Version of popen() that throws errors on close(), unlike os.popen() 280 """ 281 # unfortunately, can't derive from os.popen. Add methods as necessary.
282 - def __init__(self, *args):
283 self.p = os.popen(*args) 284 self.write = self.p.write 285 self.read = self.p.read 286 self.readline = self.p.readline 287 self.readlines = self.p.readlines 288 self.writelines = self.p.writelines
289
290 - def close(self, *args):
291 rc = self.p.close(*args) 292 _handle_rc(rc, self.p.name) 293 return rc
294 295 # string extensions 296
297 -def find(s, subs, start=0):
298 ret = -1 299 found = None 300 for sub in subs: 301 this = string.find(s, sub, start) 302 if this > -1 and ( ret < 0 or this < ret): 303 ret = this 304 found = s[this:this+1] 305 return (ret, found)
306
307 -def literalRegex(s):
308 return re.escape(s)
309 310 311 # shutil module extensions, with {}-expansion and globbing
312 -class BraceExpander(object):
313 """Class encapsulating the logic required by the brace expander parser"""
314 - class Alternative(list):
315 - def __repr__(self):
316 return "Alternative%s" % list.__repr__(self)
317 - class Product(list):
318 - def __repr__(self):
319 return "Product%s" % list.__repr__(self)
320 - class Comma(object):
321 "Comma operator"
322 - class Concat(object):
323 "Concatenation operator"
324 325 @classmethod
326 - def _collapseNode(cls, node):
327 if isinstance(node, basestring): 328 # Char data 329 return [ node ] 330 if not node: 331 return [] 332 components = [ cls._collapseNode(x) for x in node ] 333 if isinstance(node, cls.Product): 334 ret = cls._cartesianProduct(components) 335 return ret 336 ret = [] 337 for comp in components: 338 ret.extend(comp) 339 if not isinstance(node, cls.Alternative) or len(components) != 1: 340 return ret 341 # CNY-3158 - single-length items should not be expanded 342 return [ '{%s}' % x for x in ret ]
343 344 @classmethod
345 - def _cartesianProduct(cls, components):
346 ret = list(components.pop()) 347 while components: 348 comp = components.pop() 349 nret = [] 350 for j in comp: 351 nret.extend("%s%s" % (j, x) for x in ret) 352 ret = nret 353 return ret
354 355 @classmethod
356 - def _reversePolishNotation(cls, listObj):
357 haveComma = False 358 haveText = False 359 # Sentinel 360 listObj.append(None) 361 outputQ = [] 362 operators = [] 363 lastWasLiteral = False 364 for item in listObj: 365 if isinstance(item, basestring): 366 if not haveText: 367 text = [] 368 outputQ.append(text) 369 haveText = True 370 else: 371 text = outputQ[-1] 372 text.append(item) 373 continue 374 if haveText: 375 topNode = outputQ.pop() 376 topNode = ''.join(topNode) 377 haveText = False 378 outputQ.append(topNode) 379 lastWasLiteral = True 380 381 if item is None: 382 # We've reached the sentinel 383 break 384 if item is cls.Comma: 385 haveComma = True 386 lastWasLiteral = False 387 while operators: 388 op = operators.pop() 389 outputQ.append(op) 390 operators.append(item) 391 continue 392 outputQ.append(item) 393 if not lastWasLiteral: 394 lastWasLiteral = True 395 continue 396 # Concatenation 397 while operators and operators[-1] is not cls.Comma: 398 op = operators.pop() 399 outputQ.append(op) 400 operators.append(cls.Concat) 401 while operators: 402 op = operators.pop() 403 outputQ.append(op) 404 # Now collapse into meaningful nodes 405 stack = [] 406 opMap = { 407 cls.Comma: cls.Alternative, 408 cls.Concat: cls.Product, 409 } 410 for item in outputQ: 411 if not (item is cls.Comma or item is cls.Concat): 412 stack.append(item) 413 continue 414 op2 = stack.pop() 415 op1 = stack.pop() 416 ncls = opMap[item] 417 if isinstance(op1, ncls): 418 op1.append(op2) 419 stack.append(op1) 420 elif isinstance(op2, ncls): 421 op2[0:0] = [op1] 422 stack.append(op2) 423 else: 424 nobj = ncls() 425 nobj.extend([op1, op2]) 426 stack.append(nobj) 427 ret = stack[0] 428 if not haveComma: 429 ret = cls.Alternative([ret]) 430 return ret
431 432 @classmethod
433 - def removeComma(cls, l):
434 for item in l: 435 if item is cls.Comma: 436 yield ',' 437 else: 438 yield item
439 440 @classmethod
441 - def braceExpand(cls, path):
442 stack = [ cls.Product() ] 443 isEscaping = False 444 for c in path: 445 if isEscaping: 446 isEscaping = False 447 stack[-1].append(c) 448 continue 449 if c == '\\': 450 isEscaping = True 451 continue 452 if c == '{': 453 stack.append([]) 454 continue 455 if not stack: 456 raise ValueError, 'path %s has unbalanced {}' %path 457 if c == '}': 458 if len(stack) == 1: 459 # Unbalanced }; add it as literal 460 stack[-1].append(c) 461 continue 462 n = stack.pop() 463 # ,} case 464 if n and n[-1] is cls.Comma: 465 n.append("") 466 stack[-1].append(cls._reversePolishNotation(n)) 467 continue 468 if c == ',': 469 # Mark the comma separator, but only if a previous { was 470 # found, otherwise treat it as a regular character 471 if len(stack) > 1: 472 # {,a} case - leading comma will produce an empty string 473 if not stack[-1]: 474 stack[-1].append("") 475 c = cls.Comma 476 stack[-1].append(c) 477 if len(stack) > 1: 478 # Unbalanced {; add it as literal 479 node = stack[0] 480 for onode in stack[1:]: 481 node.append('{') 482 node.extend(cls.removeComma(onode)) 483 node = stack[0] 484 del stack 485 # We need to filter empty strings from the output: 486 # a{,b} should produce a ab while {,a} should produce a 487 return [ x for x in cls._collapseNode(node) if x]
488
489 -def braceExpand(path):
490 return BraceExpander.braceExpand(path)
491 492 @api.publicApi
493 -def braceGlob(paths):
494 """ 495 @raises ValueError: raised if paths has unbalanced braces 496 @raises OSError: raised in some cases where lstat on a path fails 497 """ 498 pathlist = [] 499 for path in braceExpand(paths): 500 pathlist.extend(fixedglob.glob(path)) 501 return pathlist
502 503 @api.developerApi
504 -def rmtree(paths, ignore_errors=False, onerror=None):
505 for path in braceGlob(paths): 506 log.debug('deleting [tree] %s', path) 507 # act more like rm -rf -- allow files, too 508 if (os.path.islink(path) or 509 (os.path.exists(path) and not os.path.isdir(path))): 510 os.remove(path) 511 else: 512 os.path.walk(path, _permsVisit, None) 513 shutil.rmtree(path, ignore_errors, onerror)
514
515 -def _permsVisit(arg, dirname, names):
516 for name in names: 517 path = dirname + os.sep + name 518 mode = os.lstat(path)[stat.ST_MODE] 519 # has to be executable to cd, readable to list, writeable to delete 520 if stat.S_ISDIR(mode) and (mode & 0700) != 0700: 521 log.warning("working around illegal mode 0%o at %s", mode, path) 522 mode |= 0700 523 os.chmod(path, mode)
524
525 -def remove(paths, quiet=False):
526 for path in braceGlob(paths): 527 if os.path.isdir(path) and not os.path.islink(path): 528 log.warning('Not removing directory %s', path) 529 elif os.path.exists(path) or os.path.islink(path): 530 if not quiet: 531 log.debug('deleting [file] %s', path) 532 os.remove(path) 533 else: 534 log.warning('file %s does not exist when attempting to delete [file]', path)
535
536 -def copyfile(sources, dest, verbose=True):
537 for source in braceGlob(sources): 538 if verbose: 539 log.info('copying %s to %s', source, dest) 540 shutil.copy2(source, dest)
541
542 -def copyfileobj(source, dest, callback = None, digest = None, 543 abortCheck = None, bufSize = 128*1024, rateLimit = None, 544 sizeLimit = None, total=0):
545 if hasattr(dest, 'send'): 546 write = dest.send 547 else: 548 write = dest.write 549 550 if rateLimit is None: 551 rateLimit = 0 552 553 if not rateLimit == 0: 554 if rateLimit < 8 * 1024: 555 bufSize = 4 * 1024 556 else: 557 bufSize = 8 * 1024 558 559 rateLimit = float(rateLimit) 560 561 starttime = time.time() 562 563 copied = 0 564 565 if abortCheck and hasattr(source, 'fileno'): 566 pollObj = select.poll() 567 pollObj.register(source.fileno(), select.POLLIN) 568 else: 569 pollObj = None 570 571 while True: 572 if sizeLimit and (sizeLimit - copied < bufSize): 573 bufSize = sizeLimit - copied 574 575 if abortCheck: 576 # if we need to abortCheck, make sure we check it every time 577 # read returns, and every five seconds 578 l = [] 579 while not l: 580 if abortCheck(): 581 return None 582 if pollObj: 583 l = pollObj.poll(5000) 584 else: 585 break 586 587 buf = source.read(bufSize) 588 if not buf: 589 break 590 591 total += len(buf) 592 copied += len(buf) 593 write(buf) 594 595 if digest: 596 digest.update(buf) 597 598 now = time.time() 599 if now == starttime: 600 rate = 0 # don't bother limiting download until now > starttime. 601 else: 602 rate = copied / ((now - starttime)) 603 604 if callback: 605 callback(total, rate) 606 607 if copied == sizeLimit: 608 break 609 610 if rateLimit > 0 and rate > rateLimit: 611 time.sleep((copied / rateLimit) - (copied / rate)) 612 613 return copied
614
615 -def rename(sources, dest):
616 for source in braceGlob(sources): 617 log.debug('renaming %s to %s', source, dest) 618 os.rename(source, dest)
619
620 -def _copyVisit(arg, dirname, names):
621 sourcelist = arg[0] 622 sourcelen = arg[1] 623 dest = arg[2] 624 filemode = arg[3] 625 dirmode = arg[4] 626 if dirmode: 627 os.chmod(dirname, dirmode) 628 for name in names: 629 if filemode: 630 os.chmod(dirname+os.sep+name, filemode) 631 sourcelist.append(os.path.normpath( 632 dest + os.sep + dirname[sourcelen:] + os.sep + name))
633
634 -def copytree(sources, dest, symlinks=False, filemode=None, dirmode=None):
635 """ 636 Copies tree(s) from sources to dest, returning a list of 637 the filenames that it has written. 638 """ 639 sourcelist = [] 640 for source in braceGlob(sources): 641 if os.path.isdir(source): 642 if source[-1] == '/': 643 source = source[:-1] 644 thisdest = '%s%s%s' %(dest, os.sep, os.path.basename(source)) 645 log.debug('copying [tree] %s to %s', source, thisdest) 646 shutil.copytree(source, thisdest, symlinks) 647 if dirmode: 648 os.chmod(thisdest, dirmode) 649 os.path.walk(source, _copyVisit, 650 (sourcelist, len(source), thisdest, filemode, dirmode)) 651 else: 652 log.debug('copying [file] %s to %s', source, dest) 653 shutil.copy2(source, dest) 654 if dest.endswith(os.sep): 655 thisdest = dest + os.sep + os.path.basename(source) 656 else: 657 thisdest = dest 658 if filemode: 659 os.chmod(thisdest, filemode) 660 sourcelist.append(thisdest) 661 return sourcelist
662
663 -def checkPath(binary, root=None):
664 """ 665 Examine $PATH to determine if a binary exists, returns full pathname 666 if it exists; otherwise None. 667 """ 668 path = os.environ.get('PATH', '') 669 if binary[0] == '/': 670 # handle case where binary starts with / seperately 671 # because os.path.join will not do the right 672 # thing with root set. 673 if root: 674 if os.path.exists(root + binary): 675 return root + binary 676 elif os.path.exists(binary): 677 return binary 678 return None 679 680 for path in path.split(os.pathsep): 681 if root: 682 path = joinPaths(root, path) 683 candidate = os.path.join(path, binary) 684 if os.access(candidate, os.X_OK): 685 if root: 686 return candidate[len(root):] 687 return candidate 688 return None
689
690 -def joinPaths(*args):
691 return normpath(os.sep.join(args))
692
693 -def splitPathReverse(path):
694 """Split the path at the operating system's separators. 695 Returns a list with the path components in reverse order. 696 Empty path components are stripped out. 697 Example: 'a//b//c/d' -> ['d', 'c', 'b', 'a'] 698 """ 699 while 1: 700 path, tail = os.path.split(path) 701 if not tail: 702 break 703 yield tail
704
705 -def splitPath(path):
706 """Split the path at the operating system's separators 707 Empty path components are stripped out 708 Example: 'a//b//c/d' -> ['a', 'b', 'c', 'd'] 709 """ 710 ret = list(splitPathReverse(path)) 711 ret.reverse() 712 return ret
713
714 -def assertIteratorAtEnd(iter):
715 try: 716 iter.next() 717 raise AssertionError 718 except StopIteration: 719 return True
720 721 ref = weakref.ref
722 -class ObjectCache(dict):
723 """ 724 Implements a cache of arbitrary (hashable) objects where an object 725 can be looked up and have its cached value retrieved. This allows 726 a single copy of immutable objects to be kept in memory. 727 """
728 - def __init__(self, *args):
729 dict.__init__(self, *args) 730 731 def remove(k, selfref=ref(self)): 732 self = selfref() 733 if self is not None: 734 return dict.__delitem__(self, k)
735 self._remove = remove
736
737 - def __setitem__(self, key, value):
738 return dict.__setitem__(self, ref(key, self._remove), ref(value))
739
740 - def __contains__(self, key):
741 return dict.__contains__(self, ref(key))
742
743 - def has_key(self, key):
744 return key in self
745
746 - def __delitem__(self, key):
747 return dict.__delitem__(self, ref(key))
748
749 - def __getitem__(self, key):
750 return dict.__getitem__(self, ref(key))()
751
752 - def setdefault(self, key, value):
753 return dict.setdefault(self, ref(key, self._remove), ref(value))()
754
755 -def memsize(pid = None):
756 return memusage(pid = pid)[0]
757
758 -def memusage(pid = None):
759 """Get the memory usage. 760 @param pid: Process to analyze (None for current process) 761 """ 762 if pid is None: 763 pfn = "/proc/self/statm" 764 else: 765 pfn = "/proc/%d/statm" % pid 766 line = open(pfn).readline() 767 # Assume page size is 4k (true for i386). This can be adjusted by reading 768 # resource.getpagesize() 769 arr = [ 4 * int(x) for x in line.split()[:6] ] 770 vmsize, vmrss, vmshared, text, lib, data = arr 771 772 # The RHS in the following description is the fields in /proc/self/status 773 # text is VmExe 774 # data is VmData + VmStk 775 return vmsize, vmrss, vmshared, text, lib, data
776 786
787 -def tupleListBsearchInsert(haystack, newItem, cmpFn):
788 """ 789 Inserts newItem into haystack, maintaining the sorted order. The 790 cmpIdx is the item number in the list of tuples to base comparisons on. 791 Duplicates items aren't added. Returns True if the item was added, 792 False if it was already present. 793 794 @param haystack: list of tuples. 795 @type haystack: list 796 @param newItem: The item to be inserted 797 @type newItem: tuple 798 @param cmpFn: Comparison function 799 @type cmpFn: function 800 @rtype: bool 801 """ 802 start = 0 803 finish = len(haystack) - 1 804 while start < finish: 805 i = (start + finish) / 2 806 807 rc = cmpFn(haystack[i], newItem) 808 if rc == 0: 809 start = i 810 finish = i 811 break 812 elif rc < 0: 813 start = i + 1 814 else: 815 finish = i - 1 816 817 if start >= len(haystack): 818 haystack.append(newItem) 819 else: 820 rc = cmpFn(haystack[start], newItem) 821 if rc < 0: 822 haystack.insert(start + 1, newItem) 823 elif rc > 0: 824 haystack.insert(start, newItem) 825 else: 826 return False 827 828 return True
829 830 _tempdir = tempfile.gettempdir()
831 -def settempdir(tempdir):
832 # XXX add locking if we ever go multi-threadded 833 global _tempdir 834 _tempdir = tempdir
835
836 -def mkstemp(suffix="", prefix=tempfile.template, dir=None, text=False):
837 """ 838 a wrapper for tempfile.mkstemp that uses a common prefix which 839 is set through settempdir() 840 """ 841 if dir is None: 842 global _tempdir 843 dir = _tempdir 844 return tempfile.mkstemp(suffix=suffix, prefix=prefix, dir=dir, text=text)
845
846 -class SendableFileSet:
847 848 tags = {} 849 ptrSize = len(struct.pack("@P", 0)) 850 851 @staticmethod
852 - def _register(klass):
853 SendableFileSet.tags[klass._tag] = klass
854
855 - def __init__(self):
856 self.l = []
857 858 @staticmethod
859 - def sendObjIds(sock, l):
860 sendmsg(sock, [ struct.pack("@" + ("P" * len(l)), 861 *[ id(x) for x in l] ) ])
862 863 @staticmethod
864 - def recvObjIds(sock, count):
865 s = recvmsg(sock, SendableFileSet.ptrSize * count) 866 idList = struct.unpack("@" + ("P" * count), s) 867 return idList
868
869 - def add(self, f):
870 self.l.append(f)
871
872 - def send(self, sock):
873 stack = self.l[:] 874 allFds = [] 875 toSend = [] 876 handled = set() 877 878 while stack: 879 f = stack.pop() 880 881 if f in handled: 882 continue 883 884 fd = None 885 objDepList = [] 886 887 dependsOn, s = f._sendInfo() 888 889 if type(dependsOn) == int: 890 fd = dependsOn 891 elif dependsOn is not None: 892 assert(type(dependsOn) == list) 893 894 notHandled = list(set(dependsOn) - set(handled)) 895 if notHandled: 896 stack.append(f) 897 stack.extend(notHandled) 898 continue 899 900 # we depend on something we know about 901 objDepList = dependsOn 902 903 toSend.append((f, fd, objDepList, s)) 904 handled.add(f) 905 906 fds = list(set([ x[1] for x in toSend if x[1] is not None])) 907 objsById = dict( (id(x[2]), x[2]) for x in toSend ) 908 909 sendmsg(sock, [ struct.pack("@I", len(fds)) ] ) 910 sendmsg(sock, [ struct.pack("@II", len(self.l), len(toSend)) ], fds) 911 912 for f, fd, objDepList, s in toSend: 913 if fd is None: 914 fdIndex = 0xffffffff 915 else: 916 fdIndex = fds.index(fd) 917 918 depList = objDepList 919 920 sendmsg(sock, [ struct.pack("@BIIIP", len(f._tag), fdIndex, 921 len(s), len(depList), id(f)), 922 f._tag, s ]) 923 self.sendObjIds(sock, depList) 924 925 self.sendObjIds(sock, self.l)
926 927 @staticmethod
928 - def recv(sock):
929 hdrSize = len(struct.pack("@BIIPP", 0, 0, 0, 0, 0)) 930 931 q = IterableQueue() 932 s = recvmsg(sock, 4) 933 fdCount = struct.unpack("@I", s)[0] 934 if fdCount: 935 s, fds = recvmsg(sock, 8, fdCount) 936 else: 937 s = recvmsg(sock, 8, 0) 938 939 objCount, fileCount = struct.unpack("@II", s) 940 941 fileList = [] 942 objById = {} 943 944 for i in range(fileCount): 945 s = recvmsg(sock, hdrSize) 946 tagLen, fdIndex, dataLen, depLen, thisId = struct.unpack("@BIIIP", s) 947 tag = recvmsg(sock, tagLen) 948 if dataLen: 949 s = recvmsg(sock, dataLen) 950 else: 951 s = '' 952 953 if not depLen: 954 depList = [] 955 else: 956 depList = SendableFileSet.recvObjIds(sock, depLen) 957 958 if fdIndex != 0xffffffff: 959 assert(not depList) 960 dep = fds[fdIndex] 961 elif depList: 962 assert(fdIndex == 0xffffffff) 963 dep = [ objById[x] for x in depList ] 964 else: 965 dep = None 966 967 f = SendableFileSet.tags[tag]._fromInfo(dep, s) 968 objById[thisId] = f 969 970 fileList.append(f) 971 972 fileIds = SendableFileSet.recvObjIds(sock, objCount) 973 files = [ objById[x] for x in fileIds] 974 975 return files
976
977 -class ExtendedFdopen(object):
978 979 _tag = 'efd' 980 __slots__ = [ 'fd' ] 981
982 - def __init__(self, fd):
983 self.fd = fd 984 # set close-on-exec flag 985 fcntl.fcntl(self.fd, fcntl.F_SETFD, 1)
986 987 @staticmethod
988 - def _fromInfo(fd, s):
989 assert(s == '-') 990 return ExtendedFdopen(fd)
991
992 - def _sendInfo(self):
993 return (self.fd, '-')
994
995 - def fileno(self):
996 return self.fd
997
998 - def close(self):
999 os.close(self.fd) 1000 self.fd = None
1001
1002 - def __del__(self):
1003 if self.fd is not None: 1004 try: 1005 self.close() 1006 except OSError: 1007 self.fd = None
1008
1009 - def read(self, bytes = -1):
1010 # -1 is not a valid argument for os.read(); we have to 1011 # implement "read all data available" ourselves 1012 if bytes == -1: 1013 bufSize = 8 * 1024 1014 l = [] 1015 while 1: 1016 s = os.read(self.fd, bufSize) 1017 if not s: 1018 return ''.join(l) 1019 l.append(s) 1020 return os.read(self.fd, bytes)
1021
1022 - def truncate(self, offset=0):
1023 return os.ftruncate(self.fd, offset)
1024
1025 - def write(self, s):
1026 return os.write(self.fd, s)
1027
1028 - def pread(self, bytes, offset):
1029 return misc.pread(self.fd, bytes, offset)
1030
1031 - def seek(self, offset, whence = 0):
1032 return os.lseek(self.fd, offset, whence)
1033
1034 - def tell(self):
1035 # 1 is SEEK_CUR 1036 return os.lseek(self.fd, 0, 1)
1037 1038 SendableFileSet._register(ExtendedFdopen) 1039
1040 -class ExtendedFile(ExtendedFdopen):
1041 1042 __slots__ = [ 'fObj', 'name' ] 1043
1044 - def close(self):
1045 if not self.fObj: 1046 return 1047 self.fObj.close() 1048 self.fd = None 1049 self.fObj = None
1050
1051 - def __repr__(self):
1052 return '<ExtendedFile %r>' % (self.name,)
1053
1054 - def __init__(self, path, mode = "r", buffering = True):
1055 self.fd = None 1056 1057 assert(not buffering) 1058 # we use a file object here to avoid parsing the mode ourself, as well 1059 # as to get the right exceptions on open. we have to keep the file 1060 # object around to keep it from getting garbage collected though 1061 self.fObj = file(path, mode) 1062 self.name = path 1063 fd = self.fObj.fileno() 1064 ExtendedFdopen.__init__(self, fd)
1065
1066 -class ExtendedStringIO(StringIO.StringIO):
1067 1068 _tag = 'efs' 1069 1070 @staticmethod
1071 - def _fromInfo(ef, s):
1072 assert(ef is None) 1073 return ExtendedStringIO(s)
1074
1075 - def _sendInfo(self):
1076 return (None, self.getvalue())
1077
1078 - def pread(self, bytes, offset):
1079 pos = self.tell() 1080 self.seek(offset, 0) 1081 data = self.read(bytes) 1082 self.seek(pos, 0) 1083 return data
1084 1085 SendableFileSet._register(ExtendedStringIO) 1086
1087 -class SeekableNestedFile:
1088 1089 _tag = "snf" 1090
1091 - def __init__(self, file, size, start = -1):
1092 self.file = file 1093 self.size = size 1094 self.end = self.size 1095 self.pos = 0 1096 1097 if start == -1: 1098 self.start = file.tell() 1099 else: 1100 self.start = start
1101 1102 @staticmethod
1103 - def _fromInfo(efList, s):
1104 assert(len(efList) == 1) 1105 size, start = struct.unpack("!II", s) 1106 return SeekableNestedFile(efList[0], size, start = start)
1107
1108 - def _sendInfo(self):
1109 return ([ self.file ], struct.pack("!II", self.size, self.start))
1110
1111 - def _fdInfo(self):
1112 if hasattr(self.file, '_fdInfo'): 1113 fd, start, size = self.file._fdInfo() 1114 start += self.start 1115 size = self.size 1116 elif hasattr(self.file, 'fileno'): 1117 fd, start, size = self.file.fileno(), self.start, self.size 1118 else: 1119 return (None, None, None) 1120 1121 return (fd, start, size)
1122
1123 - def close(self):
1124 pass
1125
1126 - def read(self, bytes = -1, offset = None):
1127 if offset is None: 1128 readPos = self.pos 1129 else: 1130 readPos = offset 1131 1132 if bytes < 0 or (self.end - readPos) <= bytes: 1133 # return the rest of the file 1134 count = self.end - readPos 1135 newPos = self.end 1136 else: 1137 count = bytes 1138 newPos = readPos + bytes 1139 1140 buf = self.file.pread(count, readPos + self.start) 1141 1142 if offset is None: 1143 self.pos = newPos 1144 1145 return buf
1146 1147 pread = read 1148
1149 - def seek(self, offset, whence = 0):
1150 if whence == 0: 1151 newPos = offset 1152 elif whence == 1: 1153 newPos = self.pos + offset 1154 else: 1155 newPos = self.size + offset 1156 1157 if newPos > self.size or newPos < 0: 1158 raise IOError("Position %d is outside file (len %d)" 1159 % (newPos, self.size)) 1160 1161 self.pos = newPos 1162 return self.pos
1163
1164 - def tell(self):
1165 return self.pos
1166 SendableFileSet._register(SeekableNestedFile) 1167
1168 -class BZ2File:
1169 - def __init__(self, fobj):
1170 self.decomp = bz2.BZ2Decompressor() 1171 self.fobj = fobj 1172 self.leftover = ''
1173
1174 - def read(self, bytes):
1175 while 1: 1176 buf = self.fobj.read(2048) 1177 if not buf: 1178 # ran out of compressed input 1179 if self.leftover: 1180 # we have some uncompressed stuff left, return 1181 # it 1182 if len(self.leftover) > bytes: 1183 rc = self.leftover[:bytes] 1184 self.leftover = self.leftover[bytes:] 1185 else: 1186 rc = self.leftover[:] 1187 self.leftover = None 1188 return rc 1189 # done returning all data, return None as the EOF 1190 return None 1191 # decompressed the newly read compressed data 1192 self.leftover += self.decomp.decompress(buf) 1193 # if we have at least what the caller asked for, return it 1194 if len(self.leftover) > bytes: 1195 rc = self.leftover[:bytes] 1196 self.leftover = self.leftover[bytes:] 1197 return rc
1198 # read some more data and try to get enough uncompressed 1199 # data to return 1200
1201 -class PushIterator:
1202
1203 - def push(self, val):
1204 self.head.insert(0, val)
1205
1206 - def next(self):
1207 if self.head: 1208 val = self.head.pop(0) 1209 return val 1210 1211 return self.iter.next()
1212
1213 - def __init__(self, iter):
1214 self.head = [] 1215 self.iter = iter
1216
1217 -class PeekIterator:
1218
1219 - def _next(self):
1220 try: 1221 self.val = self.iter.next() 1222 except StopIteration: 1223 self.done = True
1224
1225 - def peek(self):
1226 if self.done: 1227 raise StopIteration 1228 1229 return self.val
1230
1231 - def next(self):
1232 if self.done: 1233 raise StopIteration 1234 1235 val = self.val 1236 self._next() 1237 return val
1238
1239 - def __iter__(self):
1240 while True: 1241 yield self.next()
1242
1243 - def __init__(self, iter):
1244 self.done = False 1245 self.iter = iter 1246 self._next()
1247
1248 -class IterableQueue:
1249
1250 - def add(self, item):
1251 self.l.append(item)
1252
1253 - def peekRemainder(self):
1254 return self.l
1255
1256 - def __iter__(self):
1257 while self.l: 1258 yield self.l.pop(0) 1259 1260 raise StopIteration
1261
1262 - def __init__(self):
1263 self.l = []
1264
1265 -def lstat(path):
1266 """ 1267 Return None if the path doesn't exist. 1268 """ 1269 if not misc.exists(path): 1270 return None 1271 1272 try: 1273 sb = os.lstat(path) 1274 except OSError, e: 1275 if e.errno != errno.ENOENT: 1276 raise 1277 return None 1278 1279 return sb
1280
1281 -class LineReader:
1282
1283 - def readlines(self):
1284 s = os.read(self.fd, 4096) 1285 if not s: 1286 if self.buf: 1287 s = self.buf 1288 self.buf = '' 1289 return [ s ] 1290 1291 return None 1292 1293 self.buf += s 1294 1295 lines = self.buf.split('\n') 1296 self.buf = lines[-1] 1297 del lines[-1] 1298 1299 return [ x + "\n" for x in lines ]
1300
1301 - def __init__(self, fd):
1302 self.fd = fd 1303 self.buf = ''
1304 1305 exists = misc.exists 1306 removeIfExists = misc.removeIfExists 1307 pread = misc.pread 1308 res_init = misc.res_init 1309 sha1Uncompress = misc.sha1Uncompress 1310 fchmod = misc.fchmod 1311 fopenIfExists = misc.fopenIfExists 1312
1313 -def _LazyFile_reopen(method):
1314 """Decorator to perform the housekeeping of opening/closing of fds""" 1315 def wrapper(self, *args, **kwargs): 1316 if self._realFd is not None: 1317 # Object is already open 1318 # Mark it as being used 1319 self._timestamp = time.time() 1320 # Return the real method 1321 return getattr(self._realFd, method.func_name)(*args, **kwargs) 1322 if self._cache is None: 1323 raise Exception("Cache object is closed") 1324 try: 1325 self._cache()._getSlot() 1326 except ReferenceError: 1327 # re-raise for now, until we decide what to do 1328 raise 1329 self._reopen() 1330 return getattr(self._realFd, method.func_name)(*args, **kwargs)
1331 return wrapper 1332 1333
1334 -class _LazyFile(object):
1335 __slots__ = ['path', 'marker', 'mode', '_cache', '_hash', '_realFd', 1336 '_timestamp']
1337 - def __init__(self, cache, path, mode):
1338 self.path = path 1339 self.mode = mode 1340 self.marker = (0, 0) 1341 self._hash = cache._getCounter() 1342 self._cache = weakref.ref(cache, self._closeCallback) 1343 self._realFd = None 1344 self._timestamp = time.time()
1345
1346 - def _reopen(self):
1347 # Initialize the file descriptor 1348 self._realFd = ExtendedFile(self.path, self.mode, buffering = False) 1349 self._realFd.seek(*self.marker) 1350 self._timestamp = time.time()
1351
1352 - def _release(self):
1353 self._close()
1354
1355 - def _closeCallback(self, cache):
1356 """Called when the cache object gets destroyed""" 1357 self._close() 1358 self._cache = None
1359 1360 @_LazyFile_reopen
1361 - def read(self, bytes):
1362 pass
1363 1364 @_LazyFile_reopen
1365 - def pread(self, bytes, offset):
1366 pass
1367 1368 @_LazyFile_reopen
1369 - def seek(self, loc, type):
1370 pass
1371 1372 @_LazyFile_reopen
1373 - def tell(self):
1374 pass
1375 1376 @_LazyFile_reopen
1377 - def trucate(self):
1378 pass
1379 1380 @_LazyFile_reopen
1381 - def fileno(self):
1382 pass
1383
1384 - def _close(self):
1385 # Close only the file descriptor 1386 if self._realFd is not None: 1387 self.marker = (self._realFd.tell(), 0) 1388 self._realFd.close() 1389 self._realFd = None
1390
1391 - def close(self):
1392 self._close() 1393 if self._cache is None: 1394 return 1395 cache = self._cache() 1396 if cache is not None: 1397 try: 1398 cache._closeSlot(self) 1399 except ReferenceError: 1400 # cache object is already gone 1401 pass 1402 self._cache = None
1403
1404 - def __hash__(self):
1405 return self._hash
1406
1407 - def __del__(self):
1408 self.close()
1409
1410 -class LazyFileCache:
1411 """An object tracking open files. It will serve file-like objects that get 1412 closed behind the scene (and reopened on demand) if the number of open 1413 files in the current process exceeds a threshold. 1414 The objects will close automatically when they fall out of scope. 1415 """ 1416 # Assuming maxfd is 1024, this should be ok 1417 threshold = 900 1418 1419 @api.publicApi
1420 - def __init__(self, threshold=None):
1421 if threshold: 1422 self.threshold = threshold 1423 # Counter used for hashing 1424 self._fdCounter = 0 1425 self._fdMap = {}
1426 1427 @api.publicApi
1428 - def open(self, path, mode="r"):
1429 """ 1430 @raises IOError: raised if there's an I/O error opening the fd 1431 @raises OSError: raised on other errors opening the fd 1432 """ 1433 fd = _LazyFile(self, path, mode=mode) 1434 self._fdMap[fd._hash] = fd 1435 # Try to open the fd, to push the errors up early 1436 fd.tell() 1437 return fd
1438
1439 - def _getFdCount(self):
1440 try: 1441 return countOpenFileDescriptors() 1442 except OSError, e: 1443 # We may be hitting a kernel bug (CNY-2571) 1444 if e.errno != errno.EINVAL: 1445 raise 1446 # Count the open file descriptors this instance has 1447 return len([ x for x in self._fdMap.values() 1448 if x._realFd is not None])
1449
1450 - def _getCounter(self):
1451 ret = self._fdCounter; 1452 self._fdCounter += 1; 1453 return ret;
1454
1455 - def _getSlot(self):
1456 if self._getFdCount() < self.threshold: 1457 # We can open more file descriptors 1458 return 1459 # There are several ways we can obtain a slot if the object is full: 1460 # 1. free one slot 1461 # 2. free a batch of slots 1462 # 3. free all slots 1463 # Running tests which are not localized (i.e. walk over the list of 1464 # files and do some operation on them) shows that 1. is extremely 1465 # expensive. 2. and 3. are comparatively similar if we're freeing 10% 1466 # of the threshold, so that's the current implementation. 1467 1468 # Sorting would be expensive for selecting just the oldest fd, but 1469 # when selecting the oldest m fds, performance is m * n. For m large 1470 # enough, log n will be smaller. For n = 5k, 10% is 500, while log n 1471 # is about 12. Even factoring in other sorting constants, you're still 1472 # winning. 1473 l = sorted([ x for x in self._fdMap.values() if x._realFd is not None], 1474 lambda a, b: cmp(a._timestamp, b._timestamp)) 1475 for i in range(int(self.threshold / 10)): 1476 l[i]._release()
1477
1478 - def _closeSlot(self, fd):
1479 del self._fdMap[fd._hash]
1480 1481 @api.publicApi
1482 - def close(self):
1483 """ 1484 @raises IOError: could be raised if tell() fails prior to close() 1485 """ 1486 # No need to call fd's close(), we're destroying this object 1487 for fd in self._fdMap.values(): 1488 fd._close() 1489 fd._cache = None 1490 self._fdMap.clear()
1491
1492 - def release(self):
1493 """Release the file descriptors kept open by the LazyFile objects""" 1494 for fd in self._fdMap.values(): 1495 fd._close()
1496 1497 __del__ = close
1498
1499 -class Flags(object):
1500 1501 # set the slots to the names of the flags to support 1502 1503 __slots__ = [] 1504
1505 - def __init__(self, **kwargs):
1506 for flag in self.__slots__: 1507 setattr(self, flag, False) 1508 1509 for (flag, val) in kwargs.iteritems(): 1510 setattr(self, flag, val)
1511
1512 - def __setattr__(self, flag, val):
1513 if type(val) != bool: 1514 raise TypeError, 'bool expected' 1515 object.__setattr__(self, flag, val)
1516
1517 - def __repr__(self):
1518 return "%s(%s)" % (self.__class__.__name__, 1519 "".join( flag for flag in self.__slots__ 1520 if getattr(self, flag) ) )
1521
1522 -def stripUserPassFromUrl(url):
1523 arr = list(urlparse.urlparse(url)) 1524 hostUserPass = arr[1] 1525 userPass, host = urllib.splituser(hostUserPass) 1526 arr[1] = host 1527 return urlparse.urlunparse(arr)
1528 1529
1530 -def _FileIgnoreEpipe_ignoreEpipe(fn):
1531 def wrapper(*args, **kwargs): 1532 try: 1533 return fn(*args, **kwargs) 1534 except IOError, e: 1535 if e.errno != errno.EPIPE: 1536 raise 1537 return
1538 return wrapper 1539 1540
1541 -class FileIgnoreEpipe(object):
1542 1543 @_FileIgnoreEpipe_ignoreEpipe
1544 - def write(self, *args):
1545 return self.f.write(*args)
1546 1547 @_FileIgnoreEpipe_ignoreEpipe
1548 - def close(self, *args):
1549 return self.f.close(*args)
1550
1551 - def __getattr__(self, name):
1552 return getattr(self.f, name)
1553
1554 - def __init__(self, f):
1555 self.f = f
1556
1557 -class BoundedStringIO(object):
1558 """ 1559 An IO object that behaves like a StringIO. 1560 Data is stored in memory (just like in a StringIO) if shorter than 1561 maxMemorySize, or in a temporary file. 1562 """ 1563 defaultMaxMemorySize = 65536 1564 __slots__ = ['_backend', '_backendType', 'maxMemorySize']
1565 - def __init__(self, buf='', maxMemorySize=None):
1566 if maxMemorySize is None: 1567 maxMemorySize = object.__getattribute__(self, 'defaultMaxMemorySize') 1568 self.maxMemorySize = maxMemorySize 1569 # Store in memory by default 1570 self._backend = StringIO.StringIO(buf) 1571 self._backendType = "memory"
1572
1573 - def _writeImpl(self, s):
1574 backend = object.__getattribute__(self, '_backend') 1575 if isinstance(backend, file): 1576 # File backend 1577 return backend.write(s) 1578 # StringIO backend 1579 1580 maxMemorySize = object.__getattribute__(self, 'maxMemorySize') 1581 1582 # Save current position 1583 curPos = backend.tell() 1584 if curPos + len(s) < maxMemorySize: 1585 # No danger to overflow the limit 1586 return backend.write(s) 1587 1588 fd, name = tempfile.mkstemp(suffix=".tmp", prefix="tmpBSIO") 1589 # Get rid of the file from the filesystem, we'll keep an open fd to it 1590 os.unlink(name) 1591 fcntl.fcntl(fd, fcntl.F_SETFD, 1) 1592 backendFile = os.fdopen(fd, "w+") 1593 # Copy the data from the current StringIO (up to the current position) 1594 backend.seek(0) 1595 backendFile.write(backend.read(curPos)) 1596 ret = backendFile.write(s) 1597 self._backend = backendFile 1598 self._backendType = "file" 1599 return ret
1600
1601 - def _truncateImpl(self, size=None):
1602 if size is None: 1603 # Truncate to current position by default 1604 size = self.tell() 1605 backend = object.__getattribute__(self, '_backend') 1606 maxMemorySize = object.__getattribute__(self, 'maxMemorySize') 1607 1608 if not isinstance(backend, file): 1609 # Memory backend 1610 # Truncating always reduces size, so we will not switch to a file 1611 # for this case 1612 return backend.truncate(size) 1613 1614 # File backend 1615 if size > maxMemorySize: 1616 # truncating a file to a size larger than the memory limit - just 1617 # pass it through 1618 return backend.truncate(size) 1619 1620 # Need to go from file to memory 1621 # Read data from file first 1622 backend.seek(0) 1623 backendMem = StringIO.StringIO(backend.read(size)) 1624 self._backendType = "memory" 1625 self._backend = backendMem 1626 backend.close()
1627
1628 - def getBackendType(self):
1629 return object.__getattribute__(self, '_backendType')
1630
1631 - def __getattribute__(self, attr):
1632 # Passing calls to known local objects through 1633 locs = ['_backend', '_backendType', 'getBackendType', 'maxMemorySize'] 1634 if attr in locs: 1635 return object.__getattribute__(self, attr) 1636 1637 if attr == 'write': 1638 # Return the real implementation of the write method 1639 return object.__getattribute__(self, '_writeImpl') 1640 1641 if attr == 'truncate': 1642 # Return the real implementation of the truncate method 1643 return object.__getattribute__(self, '_truncateImpl') 1644 1645 backend = object.__getattribute__(self, '_backend') 1646 return getattr(backend, attr)
1647
1648 -class ProtectedString(str):
1649 """A string that is not printed in tracebacks"""
1650 - def __safe_str__(self):
1651 return "<Protected Value>"
1652 1653 __repr__ = __safe_str__
1654
1655 -class ProtectedTemplate(str):
1656 _substArgs = None 1657 _templ = None 1658 1659 """A string template that hides parts of its components. 1660 The first argument is a template (see string.Template for a complete 1661 documentation). The values that can be filled in are using the format 1662 ${VAR} or $VAR. The keyword arguments are expanding the template. 1663 If one of the keyword arguments has a __safe_str__ method, its value is 1664 going to be hidden when this object's __safe_str__ is called."""
1665 - def __new__(cls, templ, **kwargs):
1666 tmpl = string.Template(templ) 1667 s = str.__new__(cls, tmpl.safe_substitute(kwargs)) 1668 s._templ = tmpl 1669 s._substArgs = kwargs 1670 return s
1671
1672 - def __safe_str__(self):
1673 nargs = {} 1674 for k, v in self._substArgs.iteritems(): 1675 if hasattr(v, '__safe_str__'): 1676 v = "<%s>" % k.upper() 1677 nargs[k] = v 1678 return self._templ.safe_substitute(nargs)
1679 1680 __repr__ = __safe_str__
1681
1682 -def urlSplit(url, defaultPort = None):
1683 """A function to split a URL in the format 1684 <scheme>://<user>:<pass>@<host>:<port>/<path>;<params>#<fragment> 1685 into a tuple 1686 (<scheme>, <user>, <pass>, <host>, <port>, <path>, <params>, <fragment>) 1687 Any missing pieces (user/pass) will be set to None. 1688 If the port is missing, it will be set to defaultPort; otherwise, the port 1689 should be a numeric value. 1690 """ 1691 scheme, netloc, path, query, fragment = urlparse.urlsplit(url) 1692 userpass, hostport = urllib.splituser(netloc) 1693 host, port = networking.splitHostPort(hostport) 1694 1695 if userpass: 1696 user, passwd = urllib.splitpasswd(userpass) 1697 if passwd: 1698 passwd = ProtectedString(passwd) 1699 else: 1700 user, passwd = None, None 1701 return scheme, user, passwd, host, port, path, \ 1702 query or None, fragment or None
1703
1704 -def urlUnsplit(urlTuple):
1705 """Recompose a split URL as returned by urlSplit into a single string 1706 """ 1707 scheme, user, passwd, host, port, path, query, fragment = urlTuple 1708 userpass = None 1709 if user: 1710 if passwd: 1711 userpass = "%s:${passwd}" % (urllib.quote(user)) 1712 else: 1713 userpass = urllib.quote(user) 1714 if host and ':' in host: 1715 # Support IPv6 addresses as e.g. [dead::beef]:80 1716 host = '[%s]' % (host,) 1717 if port is not None: 1718 hostport = urllib.quote("%s:%s" % (host, port), safe = ':[]') 1719 else: 1720 hostport = host 1721 netloc = hostport 1722 if userpass: 1723 netloc = "%s@%s" % (userpass, hostport) 1724 urlTempl = urlparse.urlunsplit((scheme, netloc, path, query, fragment)) 1725 if passwd is None: 1726 return urlTempl 1727 return ProtectedTemplate(urlTempl, passwd = ProtectedString(urllib.quote(passwd)))
1728 1729
1730 -class XMLRPCMarshaller(xmlrpclib.Marshaller):
1731 """Marshaller for XMLRPC data""" 1732 dispatch = xmlrpclib.Marshaller.dispatch.copy()
1733 - def dump_string(self, value, write, escape=xmlrpclib.escape):
1734 try: 1735 value = value.encode("ascii") 1736 except UnicodeError: 1737 sio = StringIO.StringIO() 1738 xmlrpclib.Binary(value).encode(sio) 1739 write(sio.getvalue()) 1740 return 1741 return xmlrpclib.Marshaller.dump_string(self, value, write, escape)
1742
1743 - def dump(self, values, stream):
1744 write = stream.write 1745 if isinstance(values, xmlrpclib.Fault): 1746 # Fault instance 1747 write("<fault>\n") 1748 self._dump({'faultCode' : values.faultCode, 1749 'faultString' : values.faultString}, 1750 write) 1751 write("</fault>\n") 1752 else: 1753 write("<params>\n") 1754 for v in values: 1755 write("<param>\n") 1756 self._dump(v, write) 1757 write("</param>\n") 1758 write("</params>\n")
1759
1760 - def dumps(self, values):
1761 sio = StringIO.StringIO() 1762 self.dump(values, sio) 1763 return sio.getvalue()
1764
1765 - def _dump(self, value, write):
1766 # Incorporates Patch #1070046: Marshal new-style objects like 1767 # InstanceType 1768 try: 1769 f = self.dispatch[type(value)] 1770 except KeyError: 1771 # check if this object can be marshalled as a structure 1772 try: 1773 value.__dict__ 1774 except: 1775 raise TypeError, "cannot marshal %s objects" % type(value) 1776 # check if this class is a sub-class of a basic type, 1777 # because we don't know how to marshal these types 1778 # (e.g. a string sub-class) 1779 for type_ in type(value).__mro__: 1780 if type_ in self.dispatch.keys(): 1781 raise TypeError, "cannot marshal %s objects" % type(value) 1782 f = self.dispatch[types.InstanceType] 1783 f(self, value, write)
1784 1785 dispatch[str] = dump_string 1786 dispatch[ProtectedString] = dump_string 1787 dispatch[ProtectedTemplate] = dump_string
1788
1789 -class XMLRPCUnmarshaller(xmlrpclib.Unmarshaller):
1790 dispatch = xmlrpclib.Unmarshaller.dispatch.copy()
1791 - def end_base64(self, data):
1792 value = xmlrpclib.Binary() 1793 value.decode(data) 1794 self.append(value.data) 1795 self._value = 0
1796 1797 dispatch["base64"] = end_base64 1798
1799 - def _stringify(self, data):
1800 try: 1801 return data.encode("ascii") 1802 except UnicodeError: 1803 return xmlrpclib.Binary(data)
1804
1805 -def xmlrpcGetParser():
1806 parser, target = xmlrpclib.getparser() 1807 # Use our own marshaller 1808 target = XMLRPCUnmarshaller() 1809 # Reuse the parser class as computed by xmlrpclib 1810 parser = parser.__class__(target) 1811 return parser, target
1812
1813 -def xmlrpcDump(params, methodname=None, methodresponse=None, stream=None, 1814 encoding=None, allow_none=False):
1815 assert isinstance(params, tuple) or isinstance(params, xmlrpclib.Fault),\ 1816 "argument must be tuple or Fault instance" 1817 if isinstance(params, xmlrpclib.Fault): 1818 methodresponse = 1 1819 elif methodresponse and isinstance(params, tuple): 1820 assert len(params) == 1, "response tuple must be a singleton" 1821 1822 if not encoding: 1823 encoding = "utf-8" 1824 1825 m = XMLRPCMarshaller(encoding, allow_none) 1826 if encoding != "utf-8": 1827 xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding) 1828 else: 1829 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default 1830 1831 if stream is None: 1832 io = StringIO.StringIO(stream) 1833 else: 1834 io = stream 1835 1836 # standard XML-RPC wrappings 1837 if methodname: 1838 if not isinstance(methodname, str): 1839 methodname = methodname.encode(encoding) 1840 io.write(xmlheader) 1841 io.write("<methodCall>\n") 1842 io.write("<methodName>%s</methodName>\n" % methodname) 1843 m.dump(params, io) 1844 io.write("</methodCall>\n") 1845 elif methodresponse: 1846 io.write(xmlheader) 1847 io.write("<methodResponse>\n") 1848 m.dump(params, io) 1849 io.write("</methodResponse>\n") 1850 else: 1851 # Return as-is 1852 m.dump(params, io) 1853 1854 if stream is None: 1855 return io.getvalue() 1856 return ""
1857
1858 -def xmlrpcLoad(stream):
1859 p, u = xmlrpcGetParser() 1860 if hasattr(stream, "read"): 1861 # A real stream 1862 while 1: 1863 data = stream.read(16384) 1864 if not data: 1865 break 1866 p.feed(data) 1867 else: 1868 # Assume it's a string 1869 p.feed(stream) 1870 # This is not the most elegant solution, we could accommodate more parsers 1871 if hasattr(xmlrpclib, 'expat'): 1872 try: 1873 p.close() 1874 except xmlrpclib.expat.ExpatError: 1875 raise xmlrpclib.ResponseError 1876 else: 1877 p.close() 1878 return u.close(), u.getmethodname()
1879 1880
1881 -class ServerProxyMethod(object):
1882
1883 - def __init__(self, send, name):
1884 self._send = send 1885 self._name = name
1886
1887 - def __getattr__(self, name):
1888 if name.startswith('_'): 1889 raise AttributeError(name) 1890 return self.__class__(self._send, "%s.%s" % (self._name, name))
1891
1892 - def __call__(self, *args):
1893 return self._send(self._name, args)
1894 1895
1896 -class ServerProxy(object):
1897 # This used to inherit from xmlrpclib but it replaced everything anyway... 1898
1899 - def __init__(self, url, transport, encoding=None, allow_none=False):
1900 if isinstance(url, basestring): 1901 # Have to import here to avoid an import loop -- one of the many 1902 # dangers of having a monolithic util.py 1903 from conary.lib.http.request import URL 1904 url = URL.parse(url) 1905 self._url = url 1906 self._transport = transport 1907 self._encoding = encoding 1908 self._allow_none = allow_none
1909
1910 - def _request(self, methodname, params):
1911 # Call a method on the remote server 1912 request = xmlrpcDump(params, methodname, 1913 encoding = self._encoding, allow_none=self._allow_none) 1914 1915 response = self._transport.request(self._url, request) 1916 1917 if len(response) == 1: 1918 response = response[0] 1919 1920 return response
1921
1922 - def __getattr__(self, name):
1923 # magic method dispatcher 1924 if name.startswith('_'): 1925 raise AttributeError(name) 1926 return self._createMethod(name)
1927
1928 - def _createMethod(self, name):
1929 return ServerProxyMethod(self._request, name)
1930
1931 - def __repr__(self):
1932 return "<ServerProxy for %s>" % (self._url,)
1933 1934 __str__ = __repr__
1935 1936
1937 -def copyStream(src, dest, length = None, bufferSize = 16384):
1938 """Copy from one stream to another, up to a specified length""" 1939 amtread = 0 1940 while amtread != length: 1941 if length is None: 1942 bsize = bufferSize 1943 else: 1944 bsize = min(bufferSize, length - amtread) 1945 buf = src.read(bsize) 1946 if not buf: 1947 break 1948 dest.write(buf) 1949 amtread += len(buf) 1950 return amtread
1951
1952 -def decompressStream(src, bufferSize = 8092):
1953 sio = BoundedStringIO() 1954 z = zlib.decompressobj() 1955 while 1: 1956 buf = src.read(bufferSize) 1957 if not buf: 1958 break 1959 sio.write(z.decompress(buf)) 1960 sio.write(z.flush()) 1961 sio.seek(0) 1962 return sio
1963
1964 -def compressStream(src, level = 5, bufferSize = 16384):
1965 sio = BoundedStringIO() 1966 z = zlib.compressobj(level) 1967 while 1: 1968 buf = src.read(bufferSize) 1969 if not buf: 1970 break 1971 sio.write(z.compress(buf)) 1972 sio.write(z.flush()) 1973 return sio
1974
1975 -def decompressString(s):
1976 return zlib.decompress(s, 31)
1977
1978 -def massCloseFileDescriptors(start, unusedCount):
1979 """Close all file descriptors starting with start, until we hit 1980 unusedCount consecutive file descriptors that were already closed""" 1981 return misc.massCloseFileDescriptors(start, unusedCount, 0);
1982
1983 -def nullifyFileDescriptor(fdesc):
1984 """Connects the file descriptor to /dev/null or an open file (if /dev/null 1985 does not exist)""" 1986 try: 1987 fd = os.open('/dev/null', os.O_RDONLY) 1988 except OSError: 1989 # in case /dev/null does not exist 1990 fd, fn = tempfile.mkstemp() 1991 os.unlink(fn) 1992 if fd != fdesc: 1993 os.dup2(fd, fdesc) 1994 os.close(fd)
1995
1996 -def sendmsg(sock, dataList, fdList = []):
1997 """ 1998 Sends multiple strings and an optional list of file descriptors through 1999 a unix domain socket. 2000 2001 @param sock: Unix domain socket to send message through 2002 @type sock: socket 2003 @param dataList: List of strings to send 2004 @type dataList: list of str 2005 @param fdList: File descriptors to send 2006 @type fdList: list of int 2007 @rtype: None 2008 """ 2009 return misc.sendmsg(sock.fileno(), dataList, fdList)
2010
2011 -def recvmsg(sock, dataSize, fdCount = 0):
2012 """ 2013 Receives data and optional file descriptors from a unix domain socket. 2014 Returns a (data, fdList) tuple. 2015 2016 @param sock: Unix domain socket to send message through 2017 @type sock: socket 2018 @param dataSize: Number of bytes to try to read from the socket. 2019 @type dataSize: int 2020 @param fdCount: Exact number of file descriptors to read from the socket 2021 @type fdCount: int 2022 @rtype: tuple 2023 """ 2024 return misc.recvmsg(sock.fileno(), dataSize, fdCount)
2025
2026 -class Timer:
2027
2028 - def start(self):
2029 self.started = time.time()
2030
2031 - def stop(self):
2032 self.total += (time.time() - self.started) 2033 self.started = None
2034
2035 - def get(self):
2036 if self.started: 2037 running = time.time() - self.started 2038 else: 2039 running = 0 2040 2041 return self.total + running
2042
2043 - def __init__(self, start = False):
2044 self.started = None 2045 self.total = 0 2046 if start: 2047 self.start()
2048
2049 -def countOpenFileDescriptors():
2050 """Return the number of open file descriptors for this process.""" 2051 return misc.countOpenFileDescriptors()
2052
2053 -def convertPackageNameToClassName(pkgname):
2054 return ''.join([ x.capitalize() for x in pkgname.split('-') ])
2055
2056 -class LZMAFile:
2057
2058 - def read(self, limit = 4096):
2059 # Read exactly the specified amount of bytes. Since the underlying 2060 # file descriptor is a pipe, os.read may return with fewer than 2061 # expected bytes, so we need to iterate 2062 buffers = [] 2063 pos = 0 2064 while pos < limit: 2065 buf = os.read(self.infd, limit - pos) 2066 if not buf: 2067 break 2068 buffers.append(buf) 2069 pos += len(buf) 2070 return ''.join(buffers)
2071
2072 - def close(self):
2073 if self.childpid: 2074 os.close(self.infd) 2075 os.waitpid(self.childpid, 0) 2076 self.childpid = None
2077
2078 - def __del__(self):
2079 self.close()
2080
2081 - def __init__(self, fileobj = None):
2082 self.executable = None 2083 for executable, args in (('xz', ('-dc',)), ('unlzma', ('-dc',))): 2084 for pathElement in os.getenv('PATH', '').split(os.path.pathsep): 2085 fullpath = os.sep.join((pathElement, executable)) 2086 if os.path.exists(fullpath): 2087 self.executable = fullpath 2088 commandLine = (executable,) + args 2089 break 2090 if self.executable: 2091 break 2092 if self.executable is None: 2093 raise RuntimeError('xz or unlzma is required to decompress this file') 2094 2095 [ self.infd, outfd ] = os.pipe() 2096 self.childpid = os.fork() 2097 if self.childpid == 0: 2098 try: 2099 os.close(self.infd) 2100 if isinstance(fileobj, gzip.GzipFile): 2101 # We can't rely on the underlying file descriptor to feed 2102 # correct data. 2103 # This should really be made to use the read() method of 2104 # fileobj 2105 f = tempfile.TemporaryFile() 2106 copyfileobj(fileobj, f) 2107 f.seek(0) 2108 fileobj.close() 2109 fileobj = f 2110 os.close(0) 2111 os.close(1) 2112 2113 fd = fileobj.fileno() 2114 # this undoes any buffering 2115 os.lseek(fd, fileobj.tell(), 0) 2116 2117 os.dup2(fd, 0) 2118 fileobj.close() # This closes fd 2119 os.dup2(outfd, 1) 2120 os.close(outfd) 2121 os.execv(self.executable, commandLine) 2122 finally: 2123 os._exit(1) 2124 2125 os.close(outfd)
2126 2127
2128 -class SavedException(object):
2129
2130 - def __init__(self, exc_info=None):
2131 if not exc_info: 2132 exc_info = sys.exc_info() 2133 elif isinstance(exc_info, Exception): 2134 exc_info = exc_info.__class__, exc_info, None 2135 self.type, self.value, self.tb = exc_info
2136
2137 - def __repr__(self):
2138 return "<saved %s exception>" % self.getName()
2139
2140 - def getName(self):
2141 return '.'.join((self.type.__module__, self.type.__name__))
2142
2143 - def format(self):
2144 return self.getName() + ': ' + str(self.value)
2145
2146 - def throw(self):
2147 raise self.type, self.value, self.tb
2148
2149 - def clear(self):
2150 """Free the exception and traceback to avoid reference loops.""" 2151 self.value = self.tb = None
2152
2153 - def replace(self, value):
2154 """Replace the saved exception with a new one. The traceback is 2155 preserved. 2156 """ 2157 self.type = value.__class__ 2158 self.value = value
2159
2160 - def check(self, *types):
2161 for type_ in types: 2162 if issubclass(self.type, type_): 2163 return True 2164 return False
2165 2166
2167 -def rethrow(newClassOrInstance, prependClassName=True, oldTup=None):
2168 ''' 2169 Re-throw an exception, either from C{sys.exc_info()} (the default) 2170 or from C{oldTup} (when set). If C{newClassOrInstance} is a class, 2171 the original traceback will be stringified and used as the parameter 2172 to the new exception, otherwise it should be an instance which will 2173 be thrown as-is. In either case, the original traceback will be 2174 preserved. Additionally, if it is a class and C{prependClassName} is 2175 C{True} (the default), the resulting exception will after 2176 stringification be prepended with the name of the original class. 2177 2178 Note that C{prependClassName} should typically be set to C{False} 2179 when re-throwing a re-thrown exception so that the intermediate 2180 class is not prepended to a value that already has the original 2181 class name in it. 2182 2183 @param newClassOrInstance: Class of the new exception to be thrown, 2184 or the exact exception instance to be thrown. 2185 @type newClassOrInstance: subclass or instance of Exception 2186 @param prependClassName: If C{True}, prepend the original class 2187 name to the new exception 2188 @type prependClassName: bool 2189 @param oldTup: Exception triple to use instead of the current 2190 exception 2191 @type oldTup: (exc_class, exc_value, exc_traceback) 2192 ''' 2193 2194 if oldTup is None: 2195 oldTup = sys.exc_info() 2196 exc_class, exc_value, exc_traceback = oldTup 2197 2198 if isinstance(newClassOrInstance, Exception): 2199 newClass = newClassOrInstance.__class__ 2200 newValue = newClassOrInstance 2201 else: 2202 newClass = newClassOrInstance 2203 newStr = str(exc_value) 2204 if prependClassName: 2205 exc_name = getattr(exc_class, '__name__', 'Unknown Error') 2206 newStr = '%s: %s' % (exc_name, newStr) 2207 newValue = newClass(newStr) 2208 2209 raise newClass, newValue, exc_traceback
2210
2211 -class Tick:
2212 - def __init__(self):
2213 self.last = self.start = time.time()
2214 - def log(self, m = ''):
2215 now = time.time() 2216 print "tick: +%.2f %s total=%.3f" % (now-self.last, m, now-self.start) 2217 self.last = now
2218
2219 -class GzipFile(gzip.GzipFile):
2220 2221 # fix gzip implementation to not seek. i'll probably end up in a 2222 # hot, firey place for this
2223 - def __init__(self, *args, **kwargs):
2224 self._first = True 2225 gzip.GzipFile.__init__(self, *args, **kwargs)
2226
2227 - def _read_gzip_header(self):
2228 magic = self.fileobj.read(2) 2229 if magic == '': 2230 return False 2231 2232 elif magic != '\037\213': 2233 raise IOError, 'Not a gzipped file' 2234 method = ord( self.fileobj.read(1) ) 2235 if method != 8: 2236 raise IOError, 'Unknown compression method' 2237 flag = ord( self.fileobj.read(1) ) 2238 # modtime = self.fileobj.read(4) 2239 # extraflag = self.fileobj.read(1) 2240 # os = self.fileobj.read(1) 2241 self.fileobj.read(6) 2242 2243 if flag & gzip.FEXTRA: 2244 # Read & discard the extra field, if present 2245 xlen = ord(self.fileobj.read(1)) 2246 xlen = xlen + 256*ord(self.fileobj.read(1)) 2247 self.fileobj.read(xlen) 2248 if flag & gzip.FNAME: 2249 # Read and discard a null-terminated string containing the filename 2250 while True: 2251 s = self.fileobj.read(1) 2252 if not s or s=='\000': 2253 break 2254 if flag & gzip.FCOMMENT: 2255 # Read and discard a null-terminated string containing a comment 2256 while True: 2257 s = self.fileobj.read(1) 2258 if not s or s=='\000': 2259 break 2260 if flag & gzip.FHCRC: 2261 self.fileobj.read(2) # Read & discard the 16-bit header CRC 2262 2263 return True
2264
2265 - def _read(self, size=1024):
2266 if self.fileobj is None: 2267 raise EOFError, "Reached EOF" 2268 2269 if self._new_member: 2270 # If the _new_member flag is set, we have to 2271 # jump to the next member, if there is one. 2272 self._init_read() 2273 if not self._read_gzip_header(): 2274 raise EOFError, "Reached EOF" 2275 self.decompress = zlib.decompressobj(-zlib.MAX_WBITS) 2276 self._new_member = False 2277 2278 # Read a chunk of data from the file 2279 buf = self.fileobj.read(size) 2280 2281 # If the EOF has been reached, flush the decompression object 2282 # and mark this object as finished. 2283 2284 if buf == "": 2285 uncompress = self.decompress.flush() 2286 self._read_eof() 2287 self._add_read_data( uncompress ) 2288 raise EOFError, 'Reached EOF' 2289 2290 uncompress = self.decompress.decompress(buf) 2291 self._add_read_data( uncompress ) 2292 2293 if self.decompress.unused_data != "": 2294 eof = self.decompress.unused_data 2295 eof += self.fileobj.read(8 - len(eof)) 2296 2297 # Check the CRC and file size, and set the flag so we read 2298 # a new member on the next call 2299 self._read_eof(eof) 2300 self._new_member = True
2301
2302 - def _read_eof(self, eof):
2303 # We've read to the end of the file, so we have to rewind in order 2304 # to reread the 8 bytes containing the CRC and the file size. 2305 # We check the that the computed CRC and size of the 2306 # uncompressed data matches the stored values. Note that the size 2307 # stored is the true file size mod 2**32. 2308 #self.fileobj.seek(-8, 1) 2309 crc32, isize = struct.unpack("<LL", eof) 2310 2311 actualCrc = (self.crc & 0xffffffff) 2312 if crc32 != actualCrc: 2313 raise IOError("CRC check failed %s != %s" % (hex(crc32), 2314 hex(actualCrc))) 2315 elif isize != (self.size & 0xffffffffL): 2316 raise IOError, "Incorrect length of data produced"
2317 2318 # yields sorted paths and their stat bufs
2319 -def walkiter(dirNameList, skipPathSet = set(), root = '/'):
2320 dirNameList.sort() 2321 2322 for dirName in dirNameList: 2323 try: 2324 entries = os.listdir(root + dirName) 2325 except: 2326 return 2327 2328 entries.sort() 2329 for entry in entries: 2330 fullPath = os.path.join(dirName, entry) 2331 if fullPath in skipPathSet: 2332 continue 2333 2334 sb = os.lstat(root + fullPath) 2335 yield fullPath, sb 2336 2337 if stat.S_ISDIR(sb.st_mode): 2338 for x in walkiter([fullPath], root = root, 2339 skipPathSet = skipPathSet): 2340 yield x
2341
2342 -class noproxyFilter(object):
2343 '''Reads the no-proxy environment variable and can be used to decide 2344 if the proxy should be bypassed for a specific URL''' 2345 alwayBypass = False 2346 no_proxy_list = []
2347 - def __init__(self):
2348 # From python 2.6's urllib (lynx also seems to obey NO_PROXY) 2349 no_proxy = os.environ.get('no_proxy', '') or \ 2350 os.environ.get('NO_PROXY', '') 2351 # '*' is special case for always bypass 2352 self.alwaysBypass = no_proxy == '*' 2353 2354 for name in no_proxy.split(','): 2355 name = name.strip() 2356 if name: 2357 self.no_proxy_list.append(name)
2358
2359 - def bypassProxy(self,urlStr):
2360 if self.alwaysBypass: 2361 return True 2362 for x in self.no_proxy_list: 2363 if urlStr.endswith(x): 2364 return True 2365 return False
2366
2367 -def fnmatchTranslate(pattern):
2368 "Like fnmatch.translate, but do not add the end-of-string character(s)" 2369 patt = fnmatch.translate(pattern) 2370 # Python 2.6.5 appends \Z(?ms) instead of $ 2371 if patt.endswith('$'): 2372 return patt[:-1] 2373 if patt.endswith(r'\Z(?ms)'): 2374 return patt[:-7] 2375 raise RuntimeError("Unrecognized end-of-string in %s" % patt)
2376
2377 -class LockedFile(object):
2378 """ 2379 A file protected by a lock. 2380 To use it:: 2381 2382 l = LockedFile("filename") 2383 fileobj = l.open() 2384 if fileobj is None: 2385 # The target file does not exist. Create it. 2386 l.write("Some content") 2387 fileobj = l.commit() 2388 else: 2389 # The target file exists 2390 pass 2391 print fileobj.read() 2392 """ 2393 __slots__ = ('fileName', 'lockFileName', '_lockfobj', '_tmpfobj') 2394
2395 - def __init__(self, fileName):
2396 self.fileName = fileName 2397 self.lockFileName = self.fileName + '.lck' 2398 self._lockfobj = None 2399 self._tmpfobj = None
2400
2401 - def open(self, shouldLock = True):
2402 """ 2403 Attempt to open the file. 2404 2405 Returns a file object if the file exists. 2406 2407 Returns None if the file does not exist, and needs to be created. 2408 At this point the lock is acquired. Use write() and commit() to 2409 have the file created and the lock released. 2410 """ 2411 2412 if self._lockfobj is not None: 2413 self.close() 2414 2415 fobj = fopenIfExists(self.fileName, "r") 2416 if fobj is not None or not shouldLock: 2417 return fobj 2418 2419 self._lockfobj = open(self.lockFileName, "w") 2420 2421 # Attempt to lock file in write mode 2422 fcntl.lockf(self._lockfobj, fcntl.LOCK_EX) 2423 # If we got this far, we now have the lock. Check if the data file was 2424 # created 2425 fobj = fopenIfExists(self.fileName, "r") 2426 if fobj is not None: 2427 # The other process committed (and we probably hold a link to a 2428 # removed file). 2429 self.unlock() 2430 return fobj 2431 2432 if not os.path.exists(self.lockFileName): 2433 # The original caller returned without creating the data file, and 2434 # it also removed the lock file - so now we hold a lock on an 2435 # orphaned fd 2436 # This should normally not happen, since a close() will not remove 2437 # the lock file after releasing the lock 2438 return self.open() 2439 # We now hold the lock 2440 return None
2441
2442 - def write(self, data):
2443 if self._tmpfobj is None: 2444 # Create temporary file 2445 self._tmpfobj = AtomicFile(self.fileName) 2446 self._tmpfobj.write(data)
2447
2448 - def commit(self):
2449 # It is important that we move the file into place first, before 2450 # releasing the lock. This make sure that any process that was blocked 2451 # will see the file immediately, instead of retrying to lock 2452 if self._tmpfobj is None: 2453 fileobj = None 2454 else: 2455 fileobj = self._tmpfobj.commit(returnHandle = True) 2456 self._tmpfobj = None 2457 self.unlock() 2458 return fileobj
2459
2460 - def unlock(self):
2461 removeIfExists(self.lockFileName) 2462 self.close()
2463
2464 - def close(self):
2465 """Close without removing the lock file""" 2466 if self._tmpfobj is not None: 2467 self._tmpfobj.close() 2468 self._tmpfobj = None 2469 # This also releases the lock 2470 if self._lockfobj is not None: 2471 self._lockfobj.close() 2472 self._lockfobj = None
2473 2474 __del__ = close
2475
2476 -class AtomicFile(object):
2477 """ 2478 Open a temporary file adjacent to C{path} for writing. When 2479 C{f.commit()} is called, the temporary file will be flushed and 2480 renamed on top of C{path}, constituting an atomic file write. 2481 """ 2482 2483 fObj = None 2484
2485 - def __init__(self, path, mode='w+b', chmod=0644, tmpsuffix = "", 2486 tmpprefix = None):
2487 self.finalPath = os.path.realpath(path) 2488 self.finalMode = chmod 2489 2490 if tmpprefix is None: 2491 tmpprefix = os.path.basename(self.finalPath) + '.tmp.' 2492 fDesc, self.name = tempfile.mkstemp(dir=os.path.dirname(self.finalPath), 2493 suffix=tmpsuffix, prefix=tmpprefix) 2494 self.fObj = os.fdopen(fDesc, mode)
2495
2496 - def __getattr__(self, name):
2497 return getattr(self.fObj, name)
2498
2499 - def commit(self, returnHandle=False):
2500 """ 2501 C{flush()}, C{chmod()}, and C{rename()} to the target path. 2502 C{close()} afterwards. 2503 """ 2504 if self.fObj.closed: 2505 raise RuntimeError("Can't commit a closed file") 2506 2507 # Flush and change permissions before renaming so the contents 2508 # are immediately present and accessible. 2509 self.fObj.flush() 2510 os.chmod(self.name, self.finalMode) 2511 os.fsync(self.fObj) 2512 2513 # Rename to the new location. Since both are on the same 2514 # filesystem, this will atomically replace the old with the new. 2515 os.rename(self.name, self.finalPath) 2516 2517 # Now close the file. 2518 if returnHandle: 2519 fObj, self.fObj = self.fObj, None 2520 return fObj 2521 return fObj 2522 else: 2523 self.fObj.close()
2524
2525 - def close(self):
2526 if self.fObj and not self.fObj.closed: 2527 removeIfExists(self.name) 2528 self.fObj.close()
2529 __del__ = close
2530 2531
2532 -class TimestampedMap(object):
2533 """ 2534 A map that timestamps entries, to cycle them out after delta seconds. 2535 If delta is set to None, new entries will never go stale. 2536 """ 2537 __slots__ = [ 'delta', '_map' ] 2538 _MISSING = object()
2539 - def __init__(self, delta = None):
2540 self.delta = delta 2541 self._map = dict()
2542
2543 - def get(self, key, default = None, stale = False):
2544 v = self._map.get(key, None) 2545 if v is not None: 2546 v, ts = v 2547 if stale or ts is None or time.time() <= ts: 2548 return v 2549 return default
2550
2551 - def set(self, key, value):
2552 if self.delta is None: 2553 ts = None 2554 else: 2555 ts = time.time() + self.delta 2556 self._map[key] = (value, ts) 2557 return self
2558
2559 - def clear(self):
2560 self._map.clear()
2561
2562 - def iteritems(self, stale=False):
2563 now = time.time() 2564 ret = sorted(self._map.items(), key = lambda x: x[1][1]) 2565 ret = [ (k, v[0]) for (k, v) in ret 2566 if stale or now <= v[1] ] 2567 return ret
2568 2569
2570 -def statFile(pathOrFile, missingOk=False, inodeOnly=False):
2571 """Return a (dev, inode, size, mtime, ctime) tuple of the given file. 2572 2573 Accepts paths, file descriptors, and file-like objects with a C{fileno()} 2574 method. 2575 2576 @param pathOrFile: A file path or file-like object 2577 @type pathOrFile: C{basestring} or file-like object or C{int} 2578 @param missingOk: If C{True}, return C{None} if the file is missing. 2579 @type missingOk: C{bool} 2580 @param inodeOnly: If C{True}, return just (dev, inode). 2581 @type inodeOnly: C{bool} 2582 @rtype: C{tuple} 2583 """ 2584 try: 2585 if isinstance(pathOrFile, basestring): 2586 st = os.stat(pathOrFile) 2587 else: 2588 if hasattr(pathOrFile, 'fileno'): 2589 pathOrFile = pathOrFile.fileno() 2590 st = os.fstat(pathOrFile) 2591 except OSError, err: 2592 if err.errno == errno.ENOENT and missingOk: 2593 return None 2594 raise 2595 2596 if inodeOnly: 2597 return (st.st_dev, st.st_ino) 2598 else: 2599 return (st.st_dev, st.st_ino, st.st_size, st.st_mtime, st.st_ctime)
2600 2601
2602 -def iterFileChunks(fobj):
2603 """Yield chunks of data from the given file object.""" 2604 while True: 2605 data = fobj.read(16384) 2606 if not data: 2607 break 2608 yield data
2609