helpers.py 8.7 KB

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