helpers.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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 zero_pad(data, length):
  61. """Make sure data is `length` bytes long by prepending zero bytes
  62. >>> zero_pad('foo', 5)
  63. '\\x00\\x00foo'
  64. >>> zero_pad('foo', 3)
  65. 'foo'
  66. """
  67. return '\0' * (length - len(data)) + data
  68. def exclude_path(path, patterns):
  69. """Used by create and extract sub-commands to determine
  70. if an item should be processed or not
  71. """
  72. for pattern in (patterns or []):
  73. if pattern.match(path):
  74. return isinstance(pattern, ExcludePattern)
  75. return False
  76. class IncludePattern(object):
  77. """--include PATTERN
  78. >>> py = IncludePattern('*.py')
  79. >>> foo = IncludePattern('/foo')
  80. >>> py.match('/foo/foo.py')
  81. True
  82. >>> py.match('/bar/foo.java')
  83. False
  84. >>> foo.match('/foo/foo.py')
  85. True
  86. >>> foo.match('/bar/foo.java')
  87. False
  88. >>> foo.match('/foobar/foo.py')
  89. False
  90. >>> foo.match('/foo')
  91. True
  92. """
  93. def __init__(self, pattern):
  94. self.pattern = self.dirpattern = pattern
  95. if not pattern.endswith(os.path.sep):
  96. self.dirpattern += os.path.sep
  97. def match(self, path):
  98. dir, name = os.path.split(path)
  99. return (path == self.pattern
  100. or (dir + os.path.sep).startswith(self.dirpattern)
  101. or fnmatchcase(name, self.pattern))
  102. def __repr__(self):
  103. return '%s(%s)' % (type(self), self.pattern)
  104. class ExcludePattern(IncludePattern):
  105. """
  106. """
  107. def walk_path(path, skip_inodes=None):
  108. st = os.lstat(path)
  109. if skip_inodes and (st.st_ino, st.st_dev) in skip_inodes:
  110. return
  111. yield path, st
  112. if stat.S_ISDIR(st.st_mode):
  113. for f in os.listdir(path):
  114. for x in walk_path(os.path.join(path, f), skip_inodes):
  115. yield x
  116. def format_time(t):
  117. """Format datetime suitable for fixed length list output
  118. """
  119. if (datetime.now() - t).days < 365:
  120. return t.strftime('%b %d %H:%M')
  121. else:
  122. return t.strftime('%b %d %Y')
  123. def format_file_mode(mod):
  124. """Format file mode bits for list output
  125. """
  126. def x(v):
  127. return ''.join(v & m and s or '-'
  128. for m, s in ((4, 'r'), (2, 'w'), (1, 'x')))
  129. return '%s%s%s' % (x(mod / 64), x(mod / 8), x(mod))
  130. def format_file_size(v):
  131. """Format file size into a human friendly format
  132. """
  133. if v > 1024 * 1024 * 1024:
  134. return '%.2f GB' % (v / 1024. / 1024. / 1024.)
  135. elif v > 1024 * 1024:
  136. return '%.2f MB' % (v / 1024. / 1024.)
  137. elif v > 1024:
  138. return '%.2f kB' % (v / 1024.)
  139. else:
  140. return str(v)
  141. class IntegrityError(Exception):
  142. """
  143. """
  144. def memoize(function):
  145. cache = {}
  146. def decorated_function(*args):
  147. try:
  148. return cache[args]
  149. except KeyError:
  150. val = function(*args)
  151. cache[args] = val
  152. return val
  153. return decorated_function
  154. @memoize
  155. def uid2user(uid):
  156. try:
  157. return pwd.getpwuid(uid).pw_name
  158. except KeyError:
  159. return None
  160. @memoize
  161. def user2uid(user):
  162. try:
  163. return pwd.getpwnam(user).pw_uid
  164. except KeyError:
  165. return None
  166. @memoize
  167. def gid2group(gid):
  168. try:
  169. return grp.getgrgid(gid).gr_name
  170. except KeyError:
  171. return None
  172. @memoize
  173. def group2gid(group):
  174. try:
  175. return grp.getgrnam(group).gr_gid
  176. except KeyError:
  177. return None
  178. class Location(object):
  179. """Object representing a store / archive location
  180. >>> Location('ssh://user@host:1234/some/path::archive')
  181. Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')
  182. >>> Location('file:///some/path::archive')
  183. Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
  184. >>> Location('user@host:/some/path::archive')
  185. Location(proto='ssh', user='user', host='host', port=22, path='/some/path', archive='archive')
  186. >>> Location('/some/path::archive')
  187. Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')
  188. """
  189. proto = user = host = port = path = archive = None
  190. ssh_re = re.compile(r'(?P<proto>ssh)://(?:(?P<user>[^@]+)@)?'
  191. r'(?P<host>[^:/#]+)(?::(?P<port>\d+))?'
  192. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  193. file_re = re.compile(r'(?P<proto>file)://'
  194. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  195. scp_re = re.compile(r'((?:(?P<user>[^@]+)@)?(?P<host>[^:/]+):)?'
  196. r'(?P<path>[^:]*)(?:::(?P<archive>.+))?')
  197. def __init__(self, text):
  198. if not self.parse(text):
  199. raise ValueError
  200. def parse(self, text):
  201. m = self.ssh_re.match(text)
  202. if m:
  203. self.proto = m.group('proto')
  204. self.user = m.group('user')
  205. self.host = m.group('host')
  206. self.port = m.group('port') and int(m.group('port')) or 22
  207. self.path = m.group('path')
  208. self.archive = m.group('archive')
  209. return True
  210. m = self.file_re.match(text)
  211. if m:
  212. self.proto = m.group('proto')
  213. self.path = m.group('path')
  214. self.archive = m.group('archive')
  215. return True
  216. m = self.scp_re.match(text)
  217. if m:
  218. self.user = m.group('user')
  219. self.host = m.group('host')
  220. self.path = m.group('path')
  221. self.archive = m.group('archive')
  222. self.proto = self.host and 'ssh' or 'file'
  223. if self.proto == 'ssh':
  224. self.port = 22
  225. return True
  226. return False
  227. def __str__(self):
  228. items = []
  229. items.append('proto=%r' % self.proto)
  230. items.append('user=%r' % self.user)
  231. items.append('host=%r' % self.host)
  232. items.append('port=%r' % self.port)
  233. items.append('path=%r'% self.path)
  234. items.append('archive=%r' % self.archive)
  235. return ', '.join(items)
  236. def __repr__(self):
  237. return "Location(%s)" % self
  238. def location_validator(archive=None):
  239. def validator(text):
  240. try:
  241. loc = Location(text)
  242. except ValueError:
  243. raise argparse.ArgumentTypeError('Invalid location format: "%s"' % text)
  244. if archive is True and not loc.archive:
  245. raise argparse.ArgumentTypeError('"%s": No archive specified' % text)
  246. elif archive is False and loc.archive:
  247. raise argparse.ArgumentTypeError('"%s" No archive can be specified' % text)
  248. return loc
  249. return validator