added the KeyValueStore component
This commit is contained in:
parent
52220bffbd
commit
3e2b7d9014
|
@ -0,0 +1,3 @@
|
||||||
|
from .kvs import KeyValueStore
|
||||||
|
|
||||||
|
components = {KeyValueStore.component_type: KeyValueStore}
|
38
bunker/backends/component.py
Normal file
38
bunker/backends/component.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from abc import abstractmethod, abstractclassmethod, ABCMeta
|
||||||
|
class AbstractComponent(metaclass=ABCMeta):
|
||||||
|
component_type = "DO NOT INSTANTIATE abstract component"
|
||||||
|
def __init__(self, bunker, name, password):
|
||||||
|
self._bunker = bunker
|
||||||
|
self._name = name
|
||||||
|
self._password = password
|
||||||
|
|
||||||
|
@abstractclassmethod
|
||||||
|
def new(cls, bunker, name, password):
|
||||||
|
"""
|
||||||
|
Create a new component in the given bunker
|
||||||
|
using the given name and password.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def close(self, notify_bunker=True):
|
||||||
|
"""
|
||||||
|
This method should "close" the given component,
|
||||||
|
meaning it should write back the changes and bring
|
||||||
|
it in a state where it cannot be used anymore.
|
||||||
|
|
||||||
|
Such a state needs two important protections:
|
||||||
|
|
||||||
|
- It is protected against accidental usage, such that
|
||||||
|
a regular user that forgets that the component is already
|
||||||
|
closed cannot damage the data
|
||||||
|
- All sensitive data (i.e. the password) is destroyed.
|
||||||
|
|
||||||
|
If notify_bunker is True the parenting bunker will be
|
||||||
|
notified that the current component is already closed.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
@abstractmethod
|
||||||
|
def write_back(self):
|
||||||
|
pass
|
54
bunker/backends/kvs.py
Normal file
54
bunker/backends/kvs.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
from ljson.slapdash.mem import Table
|
||||||
|
from ljson.slapdash import SlapdashHeader
|
||||||
|
|
||||||
|
from .component import AbstractComponent
|
||||||
|
|
||||||
|
class KeyValueStore(AbstractComponent):
|
||||||
|
component_type = "kvs"
|
||||||
|
def __init__(self, bunker, name, password):
|
||||||
|
AbstractComponent.__init__(self, bunker, name, password)
|
||||||
|
|
||||||
|
file_, type_ = bunker._load_component(name, password)
|
||||||
|
self._content = Table.from_file(file_)
|
||||||
|
self._open = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def new(cls, bunker, name, password):
|
||||||
|
bunker._add_component(name, password, KeyValueStore.component_type)
|
||||||
|
|
||||||
|
file_, type_ = bunker._load_component(name, password)
|
||||||
|
table = Table(SlapdashHeader({}), [])
|
||||||
|
table.save(file_)
|
||||||
|
bunker._save_component(name, password, file_)
|
||||||
|
|
||||||
|
return cls(bunker, name, password)
|
||||||
|
|
||||||
|
def additem(self, key, value):
|
||||||
|
if(not self._open):
|
||||||
|
raise IOError("KeyValueStore is closed")
|
||||||
|
self._content.additem({"key": key, "value": value})
|
||||||
|
def getitem(self, key):
|
||||||
|
if(not self._open):
|
||||||
|
raise IOError("KeyValueStore is closed")
|
||||||
|
return self._content[{"key": key}]["value"][0]
|
||||||
|
|
||||||
|
def write_back(self):
|
||||||
|
if(not self._open):
|
||||||
|
raise IOError("KeyValueStore is closed")
|
||||||
|
file_, type_ = self._bunker._load_component(self._name, self._password)
|
||||||
|
self._content.save(file_)
|
||||||
|
self._bunker._save_component(self._name, self._password, file_)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closed(self):
|
||||||
|
return not self._open
|
||||||
|
|
||||||
|
def close(self, notify_bunker=True):
|
||||||
|
self.write_back()
|
||||||
|
self._open = False
|
||||||
|
self._name = ""
|
||||||
|
self._password = b""
|
||||||
|
del(self._content)
|
||||||
|
if(notify_bunker):
|
||||||
|
self._bunker.notify_component_is_closed(self)
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import uuid
|
import uuid
|
||||||
|
from collections import deque
|
||||||
import ljson
|
import ljson
|
||||||
from Crypto.Cipher import AES
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
from .files.tarfile import RewriteableTarFile
|
from .files.tarfile import RewriteableTarFile
|
||||||
from .files.bunkeredfile import BunkeredFile
|
from .files.bunkeredfile import BunkeredFile
|
||||||
|
from .backends import components
|
||||||
|
|
||||||
class Bunker(object):
|
class Bunker(object):
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self._tarfile = RewriteableTarFile.open(path)
|
self._tarfile = RewriteableTarFile.open(path)
|
||||||
self._components = ljson.Table.from_file(self._tarfile.get_file("__bunker_main__"))
|
self._components = ljson.Table.from_file(self._tarfile.get_file("__bunker_main__"))
|
||||||
|
self._stack_of_open_components = deque()
|
||||||
|
self._stack_of_open_components.appendleft(deque())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def open(cls, path):
|
def open(cls, path):
|
||||||
|
@ -52,6 +55,8 @@ class Bunker(object):
|
||||||
while(True):
|
while(True):
|
||||||
chunk = file_.read(chunk_size)
|
chunk = file_.read(chunk_size)
|
||||||
length_read += len(chunk)
|
length_read += len(chunk)
|
||||||
|
if(not chunk):
|
||||||
|
break
|
||||||
if(length_read == length):
|
if(length_read == length):
|
||||||
decrypted.write(key.decrypt(chunk))
|
decrypted.write(key.decrypt(chunk))
|
||||||
break
|
break
|
||||||
|
@ -87,7 +92,7 @@ class Bunker(object):
|
||||||
self._components.save(self._tarfile.get_file("__bunker_main__"))
|
self._components.save(self._tarfile.get_file("__bunker_main__"))
|
||||||
self._tarfile.writeback_file("__bunker_main__")
|
self._tarfile.writeback_file("__bunker_main__")
|
||||||
|
|
||||||
def _save_compontent(self, name, password, file_):
|
def _save_component(self, name, password, file_):
|
||||||
chunk_size = 16
|
chunk_size = 16
|
||||||
if(not {"component": name} in self._components):
|
if(not {"component": name} in self._components):
|
||||||
raise ValueError("unknown component: {}".format(name))
|
raise ValueError("unknown component: {}".format(name))
|
||||||
|
@ -102,6 +107,7 @@ class Bunker(object):
|
||||||
|
|
||||||
file_out = self._tarfile.get_file(file_name)
|
file_out = self._tarfile.get_file(file_name)
|
||||||
file_out.truncate(0)
|
file_out.truncate(0)
|
||||||
|
file_out.seek(0, 0)
|
||||||
|
|
||||||
file_.seek(0, 0)
|
file_.seek(0, 0)
|
||||||
chunk = file_.read(chunk_size)
|
chunk = file_.read(chunk_size)
|
||||||
|
@ -117,4 +123,33 @@ class Bunker(object):
|
||||||
file_out.close()
|
file_out.close()
|
||||||
|
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._stack_of_open_components.appendleft(deque())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self):
|
||||||
|
open_components = self._stack_of_open_components.popleft()
|
||||||
|
for component in open_components:
|
||||||
|
component.close(notify_bunker=False)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_component(self, type_, name, password):
|
||||||
|
if(not type_ in components):
|
||||||
|
raise ValueError("unknown component type: '{}'".format(type_))
|
||||||
|
component = components[type_].new(self, name, password)
|
||||||
|
self._stack_of_open_components[0].append(component)
|
||||||
|
return component
|
||||||
|
|
||||||
|
def get_component(self, name, password):
|
||||||
|
type_ = self._components[{"component": name}]["type"][0]
|
||||||
|
component = components[type_](self, name, password)
|
||||||
|
self._stack_of_open_components[0].append(component)
|
||||||
|
|
||||||
|
return component
|
||||||
|
|
||||||
|
def notify_component_is_closed(self, component):
|
||||||
|
for context in self._stack_of_open_components:
|
||||||
|
for open_component in context:
|
||||||
|
if(open_component == component):
|
||||||
|
context.remove(open_component)
|
||||||
|
break
|
||||||
|
|
Loading…
Reference in New Issue
Block a user