浏览代码

NSIndex1: use borghash.HashTableNT

Also:
- have a small wrapper class NSIndex1 around HashTableNT to
  adapt API difference and add some special methods.
- keep old code as NSIndex1a
Thomas Waldmann 7 月之前
父节点
当前提交
7bb6b3ebfe
共有 1 个文件被更改,包括 90 次插入2 次删除
  1. 90 2
      src/borg/hashindex.pyx

+ 90 - 2
src/borg/hashindex.pyx

@@ -1,4 +1,6 @@
 from collections import namedtuple
+import os
+import struct
 
 cimport cython
 from libc.stdint cimport uint32_t, UINT32_MAX, uint64_t
@@ -237,7 +239,7 @@ cdef class NSKeyIterator:
                 NSIndexEntry(segment, _le32toh(value[1]), _le32toh(value[2])))
 
 
-cdef class NSIndex1(IndexBase):  # legacy borg 1.x
+cdef class NSIndex1a(IndexBase):  # legacy borg 1.x
 
     legacy = 1
     value_size = 8
@@ -284,7 +286,7 @@ cdef class NSIndex1(IndexBase):  # legacy borg 1.x
 
 
 cdef class NSKeyIterator1:  # legacy borg 1.x
-    cdef NSIndex1 idx
+    cdef NSIndex1a idx
     cdef HashIndex *index
     cdef const unsigned char *key
     cdef int key_size
@@ -405,3 +407,89 @@ class FuseVersionsIndex:
             return self[key]
         except KeyError:
             return default
+
+
+NSIndex1Entry = namedtuple('NSIndex1bEntry', 'segment offset')
+
+
+class NSIndex1:  # legacy borg 1.x
+
+    def __init__(self, capacity=1000, path=None, permit_compact=False, usable=None):
+        if usable is not None:
+            capacity = usable * 2  # load factor 0.5
+        self.ht = _borghash.HashTableNT(key_size=32, value_format="<II", namedtuple_type=NSIndex1Entry, capacity=capacity)
+        if path:
+            self._read(path)
+
+    def __setitem__(self, key, value):
+        self.ht[key] = value
+
+    def __getitem__(self, key):
+        return self.ht[key]
+
+    def __delitem__(self, key):
+        del self.ht[key]
+
+    def __contains__(self, key):
+        return key in self.ht
+
+    def __len__(self):
+        return len(self.ht)
+
+    def get(self, key, default=None):
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def iteritems(self):
+        yield from self.ht.iteritems()
+
+    def compact(self):
+        pass
+
+    def clear(self):
+        pass
+
+    @classmethod
+    def read(cls, path, permit_compact=False):
+        return cls(path=path)
+
+    def write(self, path):
+        raise NotImplementedError
+
+    def size(self):
+        raise NotImplementedError
+
+    def _read(self, path):
+        if isinstance(path, str):
+            with open(path, 'rb') as fd:
+                self._read_fd(fd)
+        else:
+            self._read_fd(path)
+
+    def _read_fd(self, fd):
+        MAGIC = b"BORG_IDX"  # borg 1.x
+        HEADER_FMT = "<8sIIBB"  # magic, entries, buckets, ksize, vsize
+        header_size = struct.calcsize(HEADER_FMT)
+        header_bytes = fd.read(header_size)
+        if len(header_bytes) < header_size:
+            raise ValueError(f"Invalid file, file is too short (header).")
+        magic, entries, buckets, ksize, vsize = struct.unpack(HEADER_FMT, header_bytes)
+        if magic != MAGIC:
+            raise ValueError(f"Invalid file, magic {MAGIC.decode()} not found.")
+        VALUE_FMT = "<II"  # borg 1.x on-disk: little-endian segment, offset
+        assert ksize == 32, "invalid key size"
+        assert vsize == struct.calcsize(VALUE_FMT), "invalid value size"
+        buckets_size = buckets * (ksize + vsize)
+        current_pos = fd.tell()
+        end_of_file = fd.seek(0, os.SEEK_END)
+        if current_pos + buckets_size != end_of_file:
+            raise ValueError(f"Invalid file, file size does not match (buckets).")
+        fd.seek(current_pos)
+        for i in range(buckets):
+            key = fd.read(ksize)
+            value = fd.read(vsize)
+            self.ht._set_raw(key, value)
+        pos = fd.tell()
+        assert pos == end_of_file