helpers.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. from __future__ import with_statement
  2. import argparse
  3. from datetime import datetime, timedelta
  4. from fnmatch import fnmatchcase
  5. import grp
  6. import os
  7. import pwd
  8. import re
  9. import stat
  10. import struct
  11. import time
  12. def deferrable(f):
  13. def wrapper(*args, **kw):
  14. callback = kw.pop('callback', None)
  15. if callback:
  16. data = kw.pop('callback_data', None)
  17. try:
  18. callback(f(*args, **kw), None, data)
  19. except Exception, e:
  20. callback(None, e, data)
  21. else:
  22. return f(*args, **kw)
  23. return wrapper
  24. def error_callback(res, error, data):
  25. if res:
  26. raise res
  27. def to_localtime(ts):
  28. """Convert datetime object from UTC to local time zone"""
  29. return ts - timedelta(seconds=time.altzone)
  30. def read_set(path):
  31. """Read set from disk (as int32s)
  32. """
  33. with open(path, 'rb') as fd:
  34. data = fd.read()
  35. return set(struct.unpack('<%di' % (len(data) / 4), data))
  36. def write_set(s, path):
  37. """Write set to disk (as int32s)
  38. """
  39. with open(path, 'wb') as fd:
  40. fd.write(struct.pack('<%di' % len(s), *s))
  41. def encode_long(v):
  42. bytes = []
  43. while True:
  44. if v > 0x7f:
  45. bytes.append(0x80 | (v % 0x80))
  46. v >>= 7
  47. else:
  48. bytes.append(v)
  49. return ''.join(chr(x) for x in bytes)
  50. def decode_long(bytes):
  51. v = 0
  52. base = 0
  53. for x in bytes:
  54. b = ord(x)
  55. if b & 0x80:
  56. v += (b & 0x7f) << base
  57. base += 7
  58. else:
  59. return v + (b << base)
  60. def exclude_path(path, patterns):
  61. """Used by create and extract sub-commands to determine
  62. if an item should be processed or not
  63. """
  64. for pattern in (patterns or []):
  65. if pattern.match(path):
  66. return isinstance(pattern, ExcludePattern)
  67. return False
  68. class IncludePattern(object):
  69. """--include PATTERN
  70. >>> py = IncludePattern('*.py')
  71. >>> foo = IncludePattern('/foo')
  72. >>> py.match('/foo/foo.py')
  73. True
  74. >>> py.match('/bar/foo.java')
  75. False
  76. >>> foo.match('/foo/foo.py')
  77. True
  78. >>> foo.match('/bar/foo.java')
  79. False
  80. >>> foo.match('/foobar/foo.py')
  81. False
  82. >>> foo.match('/foo')
  83. True
  84. """
  85. def __init__(self, pattern):
  86. self.pattern = self.dirpattern = pattern
  87. if not pattern.endswith(os.path.sep):
  88. self.dirpattern += os.path.sep
  89. def match(self, path):
  90. dir, name = os.path.split(path)
  91. return (path == self.pattern
  92. or (dir + os.path.sep).startswith(self.dirpattern)
  93. or fnmatchcase(name, self.pattern))
  94. def __repr__(self):
  95. return '%s(%s)' % (type(self), self.pattern)
  96. class ExcludePattern(IncludePattern):
  97. """
  98. """
  99. def walk_path(path, skip_inodes=None):
  100. st = os.lstat(path)
  101. if skip_inodes and (st.st_ino, st.st_dev) in skip_inodes:
  102. return
  103. yield path, st
  104. if stat.S_ISDIR(st.st_mode):
  105. for f in os.listdir(path):
  106. for x in walk_path(os.path.join(path, f), skip_inodes):
  107. yield x
  108. def format_time(t):
  109. """Format datetime suitable for fixed length list output
  110. """
  111. if (datetime.now() - t).days < 365:
  112. return t.strftime('%b %d %H:%M')
  113. else:
  114. return t.strftime('%b %d %Y')
  115. def format_file_mode(mod):
  116. """Format file mode bits for list output
  117. """
  118. def x(v):
  119. return ''.join(v & m and s or '-'
  120. for m, s in ((4, 'r'), (2, 'w'), (1, 'x')))
  121. return '%s%s%s' % (x(mod / 64), x(mod / 8), x(mod))
  122. def format_file_size(v):
  123. """Format file size into a human friendly format
  124. """
  125. if v > 1024 * 1024 * 1024:
  126. return '%.2f GB' % (v / 1024. / 1024. / 1024.)
  127. elif v > 1024 * 1024:
  128. return '%.2f MB' % (v / 1024. / 1024.)
  129. elif v > 1024:
  130. return '%.2f kB' % (v / 1024.)
  131. else:
  132. return str(v)
  133. class IntegrityError(Exception):
  134. """
  135. """
  136. def memoize(function):
  137. cache = {}
  138. def decorated_function(*args):
  139. try:
  140. return cache[args]
  141. except KeyError:
  142. val = function(*args)
  143. cache[args] = val
  144. return val
  145. return decorated_function
  146. @memoize
  147. def uid2user(uid):
  148. try:
  149. return pwd.getpwuid(uid).pw_name
  150. except KeyError:
  151. return None
  152. @memoize
  153. def user2uid(user):
  154. try:
  155. return pwd.getpwnam(user).pw_uid
  156. except KeyError:
  157. return None
  158. @memoize
  159. def gid2group(gid):
  160. try:
  161. return grp.getgrgid(gid).gr_name
  162. except KeyError:
  163. return None
  164. @memoize
  165. def group2gid(group):
  166. try:
  167. return grp.getgrnam(group).gr_gid
  168. except KeyError:
  169. return None
  170. class Location(object):
  171. """Object representing a store / archive location
  172. >>> Location('ssh://user@host:1234/some/path::archive')
  173. Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')
  174. >>> Location('file:///some/path::archive')
  175. Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
  176. >>> Location('user@host:/some/path::archive')
  177. Location(proto='ssh', user='user', host='host', port=22, path='/some/path', archive='archive')
  178. >>> Location('/some/path::archive')
  179. Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
  180. """
  181. proto = user = host = port = path = archive = None
  182. ssh_re = re.compile(r'(?P<proto>ssh)://(?:(?P<user>[^@]+)@)?'
  183. r'(?P<host>[^:/#]+)(?::(?P<port>\d+))?'
  184. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  185. file_re = re.compile(r'(?P<proto>file)://'
  186. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  187. scp_re = re.compile(r'((?:(?P<user>[^@]+)@)?(?P<host>[^:/]+):)?'
  188. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  189. def __init__(self, text):
  190. if not self.parse(text):
  191. raise ValueError
  192. def parse(self, text):
  193. m = self.ssh_re.match(text)
  194. if m:
  195. self.proto = m.group('proto')
  196. self.user = m.group('user')
  197. self.host = m.group('host')
  198. self.port = m.group('port') and int(m.group('port')) or 22
  199. self.path = m.group('path')
  200. self.archive = m.group('archive')
  201. return True
  202. m = self.file_re.match(text)
  203. if m:
  204. self.proto = m.group('proto')
  205. self.path = m.group('path')
  206. self.archive = m.group('archive')
  207. return True
  208. m = self.scp_re.match(text)
  209. if m:
  210. self.user = m.group('user')
  211. self.host = m.group('host')
  212. self.path = m.group('path')
  213. self.archive = m.group('archive')
  214. self.proto = self.host and 'ssh' or 'file'
  215. if self.proto == 'ssh':
  216. self.port = 22
  217. return True
  218. return False
  219. def __str__(self):
  220. items = []
  221. items.append('proto=%r' % self.proto)
  222. items.append('user=%r' % self.user)
  223. items.append('host=%r' % self.host)
  224. items.append('port=%r' % self.port)
  225. items.append('path=%r'% self.path)
  226. items.append('archive=%r' % self.archive)
  227. return ', '.join(items)
  228. def __repr__(self):
  229. return "Location(%s)" % self
  230. def location_validator(archive=None):
  231. def validator(text):
  232. try:
  233. loc = Location(text)
  234. except ValueError:
  235. raise argparse.ArgumentTypeError('Invalid location format: "%s"' % text)
  236. if archive is True and not loc.archive:
  237. raise argparse.ArgumentTypeError('"%s": No archive specified' % text)
  238. elif archive is False and loc.archive:
  239. raise argparse.ArgumentTypeError('"%s" No archive can be specified' % text)
  240. return loc
  241. return validator