Refactoring DbEngine
This commit is contained in:
@@ -25,8 +25,8 @@ class DbException(Exception):
|
||||
|
||||
|
||||
class RefHelper:
|
||||
def __init__(self, get_obj_path):
|
||||
self.get_obj_path = get_obj_path
|
||||
def __init__(self, get_ref_path):
|
||||
self.get_ref_path = get_ref_path
|
||||
|
||||
def save_ref(self, obj):
|
||||
"""
|
||||
@@ -40,12 +40,12 @@ class RefHelper:
|
||||
|
||||
digest = get_stream_digest(buffer)
|
||||
|
||||
target_path = self.get_obj_path(digest)
|
||||
target_path = self.get_ref_path(digest)
|
||||
if not os.path.exists(os.path.dirname(target_path)):
|
||||
os.makedirs(os.path.dirname(target_path))
|
||||
|
||||
buffer.seek(0)
|
||||
with open(self.get_obj_path(digest), "wb") as file:
|
||||
with open(self.get_ref_path(digest), "wb") as file:
|
||||
while chunk := buffer.read(BUFFER_SIZE):
|
||||
file.write(chunk)
|
||||
|
||||
@@ -58,7 +58,7 @@ class RefHelper:
|
||||
:param digest:
|
||||
:return:
|
||||
"""
|
||||
with open(self.get_obj_path(digest), 'rb') as file:
|
||||
with open(self.get_ref_path(digest), 'rb') as file:
|
||||
return pickle.load(file)
|
||||
|
||||
|
||||
@@ -73,40 +73,50 @@ class DbEngine:
|
||||
|
||||
def __init__(self, root: str = None):
|
||||
self.root = root or ".mytools_db"
|
||||
self.serializer = Serializer(RefHelper(self._get_obj_path))
|
||||
self.debug_serializer = DebugSerializer(RefHelper(self.debug_load))
|
||||
self.serializer = Serializer(RefHelper(self._get_ref_path))
|
||||
self.debug_serializer = DebugSerializer(RefHelper(self._get_ref_path))
|
||||
self.lock = RLock()
|
||||
|
||||
def is_initialized(self):
|
||||
def is_initialized(self, user_id: str):
|
||||
"""
|
||||
|
||||
:return:
|
||||
"""
|
||||
return os.path.exists(self.root)
|
||||
return os.path.exists(self._get_user_root(user_id))
|
||||
|
||||
def init(self):
|
||||
def init(self, user_id: str):
|
||||
"""
|
||||
Make sure that the DbEngine is properly initialized
|
||||
:return:
|
||||
"""
|
||||
if not os.path.exists(self.root):
|
||||
logger.debug(f"Creating root folder in {os.path.abspath(self.root)}.")
|
||||
os.mkdir(self.root)
|
||||
if not os.path.exists(self._get_user_root(user_id)):
|
||||
logger.debug(f"Creating root folder in {os.path.abspath(self._get_user_root(user_id))}.")
|
||||
os.makedirs(self._get_user_root(user_id))
|
||||
|
||||
def save(self, user_id: str, entry: str, obj: object) -> str:
|
||||
def save(self, user_id: str, user_email: str, entry: str, obj: object) -> str:
|
||||
"""
|
||||
Save a snapshot of an entry
|
||||
:param user_id:
|
||||
:param user_email:
|
||||
:param entry:
|
||||
:param obj: snapshot to save
|
||||
:return:
|
||||
"""
|
||||
with self.lock:
|
||||
logger.info(f"Saving {user_id=}, {entry=}, {obj=}")
|
||||
|
||||
if not user_id:
|
||||
raise DbException("user_id is None")
|
||||
|
||||
if not user_email:
|
||||
raise DbException("user_email is None")
|
||||
|
||||
if not entry:
|
||||
raise DbException("entry is None")
|
||||
# prepare the data
|
||||
as_dict = self._serialize(obj)
|
||||
as_dict[TAG_PARENT] = [self._get_entry_digest(entry)]
|
||||
as_dict[TAG_USER] = user_id
|
||||
as_dict[TAG_PARENT] = [self._get_entry_digest(user_id, entry)]
|
||||
as_dict[TAG_USER] = user_email
|
||||
as_dict[TAG_DATE] = datetime.datetime.now().strftime('%Y%m%d %H:%M:%S %z')
|
||||
|
||||
# transform into a stream
|
||||
@@ -117,7 +127,7 @@ class DbEngine:
|
||||
# compute the digest to know where to store it
|
||||
digest = hashlib.sha256(byte_stream).hexdigest()
|
||||
|
||||
target_path = self._get_obj_path(digest)
|
||||
target_path = self._get_obj_path(user_id, digest)
|
||||
if os.path.exists(target_path):
|
||||
# the same object is already saved. Noting to do
|
||||
return digest
|
||||
@@ -128,8 +138,8 @@ class DbEngine:
|
||||
with open(target_path, "wb") as file:
|
||||
file.write(byte_stream)
|
||||
|
||||
# update the head to remember where is the latest entry
|
||||
self._update_head(entry, digest)
|
||||
# update the head to remember where the latest entry is
|
||||
self._update_head(user_id, entry, digest)
|
||||
logger.debug(f"New head for entry '{entry}' is {digest}")
|
||||
return digest
|
||||
|
||||
@@ -144,24 +154,25 @@ class DbEngine:
|
||||
with self.lock:
|
||||
logger.info(f"Loading {user_id=}, {entry=}, {digest=}")
|
||||
|
||||
digest_to_use = digest or self._get_entry_digest(entry)
|
||||
digest_to_use = digest or self._get_entry_digest(user_id, entry)
|
||||
logger.debug(f"Using digest {digest_to_use}.")
|
||||
|
||||
if digest_to_use is None:
|
||||
raise DbException(entry)
|
||||
|
||||
target_file = self._get_obj_path(digest_to_use)
|
||||
target_file = self._get_obj_path(user_id, digest_to_use)
|
||||
with open(target_file, 'r', encoding='utf-8') as file:
|
||||
as_dict = json.load(file)
|
||||
|
||||
return self._deserialize(as_dict)
|
||||
|
||||
def put(self, user_id: str, entry, key: str, value: object):
|
||||
def put(self, user_id: str, user_email, entry, key: str, value: object):
|
||||
"""
|
||||
Save a specific record.
|
||||
This will create a new snapshot is the record is new or different
|
||||
|
||||
You should not mix the usage of put_many() and save() as it's two different way to manage the db
|
||||
:param user_email:
|
||||
:param user_id:
|
||||
:param entry:
|
||||
:param key:
|
||||
@@ -182,16 +193,17 @@ class DbEngine:
|
||||
return False
|
||||
|
||||
entry_content[key] = value
|
||||
self.save(user_id, entry, entry_content)
|
||||
self.save(user_id, user_email, entry, entry_content)
|
||||
return True
|
||||
|
||||
def put_many(self, user_id: str, entry, items: list):
|
||||
def put_many(self, user_id: str, user_email, entry, items: list):
|
||||
"""
|
||||
Save a list of item as one single snapshot
|
||||
A new snapshot will not be created if all the items already exist
|
||||
|
||||
You should not mix the usage of put_many() and save() as it's two different way to manage the db
|
||||
:param user_id:
|
||||
:param user_email:
|
||||
:param entry:
|
||||
:param items:
|
||||
:return:
|
||||
@@ -213,12 +225,12 @@ class DbEngine:
|
||||
is_dirty = True
|
||||
|
||||
if is_dirty:
|
||||
self.save(user_id, entry, entry_content)
|
||||
self.save(user_id, user_email, entry, entry_content)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def exists(self, entry: str):
|
||||
def exists(self, user_id, entry: str):
|
||||
"""
|
||||
Tells if an entry exist
|
||||
:param user_id:
|
||||
@@ -226,7 +238,7 @@ class DbEngine:
|
||||
:return:
|
||||
"""
|
||||
with self.lock:
|
||||
return self._get_entry_digest(entry) is not None
|
||||
return self._get_entry_digest(user_id, entry) is not None
|
||||
|
||||
def get(self, user_id: str, entry: str, key: str | None = None, digest=None):
|
||||
"""
|
||||
@@ -247,9 +259,19 @@ class DbEngine:
|
||||
|
||||
return entry_content[key]
|
||||
|
||||
def debug_head(self):
|
||||
def debug_root(self):
|
||||
"""
|
||||
Lists all folders in the root directory
|
||||
:return: List of folder names
|
||||
"""
|
||||
with self.lock:
|
||||
head_path = os.path.join(self.root, self.HeadFile)
|
||||
if not os.path.exists(self.root):
|
||||
return []
|
||||
return [f for f in os.listdir(self.root) if os.path.isdir(os.path.join(self.root, f))]
|
||||
|
||||
def debug_head(self, user_id):
|
||||
with self.lock:
|
||||
head_path = os.path.join(self.root, user_id, self.HeadFile)
|
||||
# load
|
||||
try:
|
||||
with open(head_path, 'r') as file:
|
||||
@@ -259,14 +281,17 @@ class DbEngine:
|
||||
|
||||
return head
|
||||
|
||||
def debug_load(self, digest):
|
||||
def debug_load(self, user_id, digest):
|
||||
with self.lock:
|
||||
target_file = self._get_obj_path(digest)
|
||||
target_file = self._get_obj_path(user_id, digest)
|
||||
with open(target_file, 'r', encoding='utf-8') as file:
|
||||
as_dict = json.load(file)
|
||||
|
||||
return self.debug_serializer.deserialize(as_dict)
|
||||
|
||||
def debug_get_digest(self, user_id, entry):
|
||||
return self._get_entry_digest(user_id, entry)
|
||||
|
||||
def _serialize(self, obj):
|
||||
"""
|
||||
Just call the serializer
|
||||
@@ -280,14 +305,14 @@ class DbEngine:
|
||||
def _deserialize(self, as_dict):
|
||||
return self.serializer.deserialize(as_dict)
|
||||
|
||||
def _update_head(self, entry, digest):
|
||||
def _update_head(self, user_id, entry, digest):
|
||||
"""
|
||||
Actually dumps the snapshot in file system
|
||||
:param entry:
|
||||
:param digest:
|
||||
:return:
|
||||
"""
|
||||
head_path = os.path.join(self.root, self.HeadFile)
|
||||
head_path = os.path.join(self.root, user_id, self.HeadFile)
|
||||
# load
|
||||
try:
|
||||
with open(head_path, 'r') as file:
|
||||
@@ -302,13 +327,16 @@ class DbEngine:
|
||||
with open(head_path, 'w') as file:
|
||||
json.dump(head, file)
|
||||
|
||||
def _get_entry_digest(self, entry):
|
||||
def _get_user_root(self, user_id):
|
||||
return os.path.join(self.root, user_id)
|
||||
|
||||
def _get_entry_digest(self, user_id, entry):
|
||||
"""
|
||||
Search for the latest digest, for a given entry
|
||||
:param entry:
|
||||
:return:
|
||||
"""
|
||||
head_path = os.path.join(self.root, self.HeadFile)
|
||||
head_path = os.path.join(self._get_user_root(user_id), self.HeadFile)
|
||||
try:
|
||||
with open(head_path, 'r') as file:
|
||||
head = json.load(file)
|
||||
@@ -319,17 +347,25 @@ class DbEngine:
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def _get_head_path(self):
|
||||
def _get_head_path(self, user_id: str):
|
||||
"""
|
||||
Location of the Head file
|
||||
:return:
|
||||
"""
|
||||
return os.path.join(self.root, self.HeadFile)
|
||||
return os.path.join(self._get_user_root(user_id), self.HeadFile)
|
||||
|
||||
def _get_obj_path(self, digest):
|
||||
def _get_obj_path(self, user_id, digest):
|
||||
"""
|
||||
Location of objects
|
||||
:param digest:
|
||||
:return:
|
||||
"""
|
||||
return os.path.join(self.root, "objects", digest[:24], digest)
|
||||
return os.path.join(self._get_user_root(user_id), "objects", digest[:24], digest)
|
||||
|
||||
def _get_ref_path(self, digest):
|
||||
"""
|
||||
Location of reference. They are not linked to the user folder
|
||||
:param digest:
|
||||
:return:
|
||||
"""
|
||||
return os.path.join(self.root, "refs", digest[:24], digest)
|
||||
@@ -169,6 +169,24 @@ class SettingsManager:
|
||||
else:
|
||||
return default
|
||||
|
||||
def remove(self, session: dict, key: str):
|
||||
user_id = session["user_id"] if session else NO_SESSION
|
||||
user_email = session["user_email"] if session else NOT_LOGGED
|
||||
return self._db_engine.remove(user_email, user_id, key)
|
||||
|
||||
def update(self, session: dict, old_key: str, key: str, value: object):
|
||||
user_id = session["user_id"] if session else NO_SESSION
|
||||
user_email = session["user_email"] if session else NOT_LOGGED
|
||||
|
||||
def _update_helper(_old_key, _key, _value):
|
||||
pass
|
||||
|
||||
if hasattr(self._db_engine, "lock"):
|
||||
with self._db_engine.lock:
|
||||
_update_helper(old_key, key, value)
|
||||
else:
|
||||
_update_helper(old_key, key, value)
|
||||
|
||||
def init_user(self, user_id: str, user_email: str):
|
||||
"""
|
||||
Init the settings block space for a user
|
||||
|
||||
Reference in New Issue
Block a user