Pārlūkot izejas kodu

Merge branch 'master' into merge

Thomas Waldmann 10 gadi atpakaļ
vecāks
revīzija
354b3d34e3
8 mainītis faili ar 50 papildinājumiem un 28 dzēšanām
  1. 8 0
      CHANGES
  2. 5 9
      attic/archive.py
  3. 8 5
      attic/cache.py
  4. 8 0
      attic/helpers.py
  5. 5 1
      attic/key.py
  6. 4 6
      attic/platform.py
  7. 9 2
      attic/testsuite/helpers.py
  8. 3 5
      setup.py

+ 8 - 0
CHANGES

@@ -3,6 +3,14 @@ Attic Changelog
 
 Here you can see the full list of changes between each Attic release.
 
+Version 0.16
+------------
+
+(bugfix release, released on X)
+- Fix "All archives" output for attic info. (#183)
+- More user friendly error message when repository key file is not found (#236)
+- Fix parsing of iso 8601 timestamps with zero microseconds (#282)
+
 Version 0.15
 ------------
 

+ 5 - 9
attic/archive.py

@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta, timezone
+from datetime import datetime
 from getpass import getuser
 from itertools import groupby
 import errno
@@ -17,7 +17,7 @@ from attic import xattr
 from attic.platform import acl_get, acl_set
 from attic.chunker import Chunker
 from attic.hashindex import ChunkIndex
-from attic.helpers import Error, uid2user, user2uid, gid2group, group2gid, \
+from attic.helpers import parse_timestamp, Error, uid2user, user2uid, gid2group, group2gid, \
     Manifest, Statistics, decode_dict, st_mtime_ns, make_path_safe, StableDict, int_to_bigint, bigint_to_int
 
 ITEMS_BUFFER = 1024 * 1024
@@ -172,11 +172,7 @@ class Archive:
     @property
     def ts(self):
         """Timestamp of archive creation in UTC"""
-        t = self.metadata[b'time'].split('.', 1)
-        dt = datetime.strptime(t[0], '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc)
-        if len(t) > 1:
-            dt += timedelta(seconds=float('.' + t[1]))
-        return dt
+        return parse_timestamp(self.metadata[b'time'])
 
     def __repr__(self):
         return 'Archive(%r)' % self.name
@@ -225,9 +221,9 @@ class Archive:
 
     def calc_stats(self, cache):
         def add(id):
-            count, size, csize = self.cache.chunks[id]
+            count, size, csize = cache.chunks[id]
             stats.update(size, csize, count == 1)
-            self.cache.chunks[id] = count - 1, size, csize
+            cache.chunks[id] = count - 1, size, csize
         def add_file_chunks(chunks):
             for id, _, _ in chunks:
                 add(id)

+ 8 - 5
attic/cache.py

@@ -103,11 +103,7 @@ class Cache:
         os.remove(os.path.join(self.path, 'config'))  # kill config first
         shutil.rmtree(self.path)
 
-    def open(self):
-        if not os.path.isdir(self.path):
-            raise Exception('%s Does not look like an Attic cache' % self.path)
-        self.lock = UpgradableLock(os.path.join(self.path, 'config'), exclusive=True)
-        self.rollback()
+    def _do_open(self):
         self.config = RawConfigParser()
         self.config.read(os.path.join(self.path, 'config'))
         if self.config.getint('cache', 'version') != 1:
@@ -120,6 +116,12 @@ class Cache:
         self.chunks = ChunkIndex.read(os.path.join(self.path, 'chunks').encode('utf-8'))
         self.files = None
 
+    def open(self):
+        if not os.path.isdir(self.path):
+            raise Exception('%s Does not look like an Attic cache' % self.path)
+        self.lock = UpgradableLock(os.path.join(self.path, 'config'), exclusive=True)
+        self.rollback()
+
     def close(self):
         if self.lock:
             self.lock.release()
@@ -192,6 +194,7 @@ class Cache:
             if os.path.exists(os.path.join(self.path, 'txn.tmp')):
                 shutil.rmtree(os.path.join(self.path, 'txn.tmp'))
         self.txn_active = False
+        self._do_open()
 
     def sync(self):
         """Initializes cache by fetching and reading all archive indicies

+ 8 - 0
attic/helpers.py

@@ -197,6 +197,14 @@ def to_localtime(ts):
     return datetime(*time.localtime((ts - datetime(1970, 1, 1, tzinfo=timezone.utc)).total_seconds())[:6])
 
 
+def parse_timestamp(timestamp):
+    """Parse a ISO 8601 timestamp string"""
+    if '.' in timestamp:  # microseconds might not be pressent
+        return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f').replace(tzinfo=timezone.utc)
+    else:
+        return datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc)
+
+
 def update_excludes(args):
     """Merge exclude patterns from files with those on command line.
     Empty lines and lines starting with '#' are ignored, but whitespace

+ 5 - 1
attic/key.py

@@ -17,6 +17,10 @@ class UnsupportedPayloadError(Error):
     """Unsupported payload type {}. A newer version is required to access this repository.
     """
 
+class KeyfileNotFoundError(Error):
+    """No key file for repository {} found in {}.
+    """
+
 
 class HMAC(hmac.HMAC):
     """Workaround a bug in Python < 3.4 Where HMAC does not accept memoryviews
@@ -228,7 +232,7 @@ class KeyfileKey(AESKeyBase):
                 line = fd.readline().strip()
                 if line and line.startswith(cls.FILE_ID) and line[10:] == id:
                     return filename
-        raise Exception('Key file for repository with ID %s not found' % id)
+        raise KeyfileNotFoundError(repository._location.canonical_path(), get_keys_dir())
 
     def load(self, filename, passphrase):
         with open(filename, 'r') as fd:

+ 4 - 6
attic/platform.py

@@ -1,12 +1,10 @@
-import os
+import sys
 
-platform = os.uname()[0]
-
-if platform == 'Linux':
+if sys.platform.startswith('linux'):
     from attic.platform_linux import acl_get, acl_set, API_VERSION
-elif platform == 'FreeBSD':
+elif sys.platform.startswith('freebsd'):
     from attic.platform_freebsd import acl_get, acl_set, API_VERSION
-elif platform == 'Darwin':
+elif sys.platform == 'darwin':
     from attic.platform_darwin import acl_get, acl_set, API_VERSION
 else:
     API_VERSION = 2

+ 9 - 2
attic/testsuite/helpers.py

@@ -4,8 +4,8 @@ from datetime import datetime, timezone, timedelta
 import os
 import tempfile
 import unittest
-from attic.helpers import adjust_patterns, exclude_path, Location, format_timedelta, ExcludePattern, make_path_safe, UpgradableLock, prune_within, prune_split, \
-    StableDict, int_to_bigint, bigint_to_int
+from attic.helpers import adjust_patterns, exclude_path, Location, format_timedelta, IncludePattern, ExcludePattern, make_path_safe, UpgradableLock, prune_within, prune_split, to_localtime, \
+    StableDict, int_to_bigint, bigint_to_int, parse_timestamp
 from attic.testsuite import AtticTestCase
 import msgpack
 
@@ -209,3 +209,10 @@ class StableDictTestCase(AtticTestCase):
         d = StableDict(foo=1, bar=2, boo=3, baz=4)
         self.assert_equal(list(d.items()), [('bar', 2), ('baz', 4), ('boo', 3), ('foo', 1)])
         self.assert_equal(hashlib.md5(msgpack.packb(d)).hexdigest(), 'fc78df42cd60691b3ac3dd2a2b39903f')
+
+
+class TestParseTimestamp(AtticTestCase):
+
+    def test(self):
+        self.assert_equal(parse_timestamp('2015-04-19T20:25:00.226410'), datetime(2015, 4, 19, 20, 25, 0, 226410, timezone.utc))
+        self.assert_equal(parse_timestamp('2015-04-19T20:25:00'), datetime(2015, 4, 19, 20, 25, 0, 0, timezone.utc))

+ 3 - 5
setup.py

@@ -9,8 +9,6 @@ versioneer.versionfile_build = 'attic/_version.py'
 versioneer.tag_prefix = ''
 versioneer.parentdir_prefix = 'Attic-'  # dirname like 'myproject-1.2.0'
 
-platform = os.uname()[0]
-
 min_python = (3, 2)
 if sys.version_info < min_python:
     print("Attic requires Python %d.%d or later" % min_python)
@@ -89,11 +87,11 @@ ext_modules = [
     Extension('attic.chunker', [chunker_source]),
     Extension('attic.hashindex', [hashindex_source])
 ]
-if platform == 'Linux':
+if sys.platform.startswith('linux'):
     ext_modules.append(Extension('attic.platform_linux', [platform_linux_source], libraries=['acl']))
-elif platform == 'FreeBSD':
+elif sys.platform.startswith('freebsd'):
     ext_modules.append(Extension('attic.platform_freebsd', [platform_freebsd_source]))
-elif platform == 'Darwin':
+elif sys.platform == 'darwin':
     ext_modules.append(Extension('attic.platform_darwin', [platform_darwin_source]))
 
 setup(