helpers.py 7.5 KB

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