1
0
mirror of https://github.com/enpaul/kodak.git synced 2024-11-23 15:07:13 +00:00

Update database schema for new image/alias structure

This commit is contained in:
Ethan Paul 2021-10-30 10:59:41 -04:00
parent 1b7856c590
commit 158db1209b
No known key found for this signature in database
GPG Key ID: D0E2CBF1245E92BF
6 changed files with 115 additions and 38 deletions

View File

@ -4,14 +4,16 @@ from typing import Tuple
import peewee import peewee
from kodak import constants from kodak import constants
from kodak import exceptions
from kodak.configuration import KodakConfig from kodak.configuration import KodakConfig
from kodak.database._shared import INTERFACE as interface from kodak.database._shared import INTERFACE as interface
from kodak.database._shared import KodakModel from kodak.database._shared import KodakModel
from kodak.database.access import AccessRecord
from kodak.database.alias import AliasRecord
from kodak.database.image import ImageRecord from kodak.database.image import ImageRecord
from kodak.database.thumbnail import ThumbnailRecord
MODELS: Tuple[KodakModel, ...] = (ImageRecord, ThumbnailRecord) MODELS: Tuple[KodakModel, ...] = (ImageRecord, AliasRecord, AccessRecord)
def initialize(config: KodakConfig): def initialize(config: KodakConfig):
@ -26,13 +28,13 @@ def initialize(config: KodakConfig):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
if config.database.backend == constants.SupportedDatabaseBackend.SQLITE: if config.database.backend == constants.DatabaseBackend.SQLITE:
logger.debug("Using SQLite database backend") logger.debug("Using SQLite database backend")
logger.debug(f"Applying SQLite pragmas: {config.database.sqlite.pragmas}") logger.debug(f"Applying SQLite pragmas: {config.database.sqlite.pragmas}")
database = peewee.SqliteDatabase( database = peewee.SqliteDatabase(
config.database.sqlite.path, pragmas=config.database.sqlite.pragmas config.database.sqlite.path, pragmas=config.database.sqlite.pragmas
) )
elif config.database.backend == constants.SupportedDatabaseBackend.MARIADB: elif config.database.backend == constants.DatabaseBackend.MARIADB:
logger.debug("Using MariaDB database backend") logger.debug("Using MariaDB database backend")
logger.debug( logger.debug(
"Configuring MariaDB:" "Configuring MariaDB:"
@ -48,7 +50,7 @@ def initialize(config: KodakConfig):
charset="utf8mb4", charset="utf8mb4",
) )
else: else:
raise ValueError( raise exceptions.ConfigurationError(
f"Invalid storage backend in configuration: {config.database.backend}" f"Invalid storage backend in configuration: {config.database.backend}"
) )

View File

@ -1,5 +1,9 @@
import datetime import datetime
import enum
import hashlib
import uuid import uuid
from typing import NamedTuple
from typing import Type
import peewee import peewee
@ -7,7 +11,82 @@ import peewee
INTERFACE = peewee.DatabaseProxy() INTERFACE = peewee.DatabaseProxy()
class Checksum(NamedTuple):
"""Checksum data container
:param algorithm: Hashing algorithm, must be supported by Python's hashlib
:param digest: Hex digest of the hash
"""
algorithm: str
digest: str
@classmethod
def from_hash(cls, data: hashlib._hashlib.HASH): # pylint: disable=protected-access
"""Construct from a hashlib object"""
return cls(algorithm=data.name, digest=data.hexdigest())
class EnumField(peewee.CharField):
"""Custom field for storing enums"""
def __init__(self, enumeration: Type[enum.Enum], *args, **kwargs):
super().__init__(*args, **kwargs)
self.enumeration = enumeration
def db_value(self, value: enum.Enum) -> str:
"""Convert the enum value to the database string
:param value: Enum item to store the name of
:raises peewee.IntegrityError: When the item passed to ``value`` is not in the field's enum
:returns: The name of the enum item passed to ``value``
"""
if not isinstance(value, self.enumeration):
raise peewee.IntegrityError(
f"Enum {self.enumeration.__name__} has no value '{value}'"
)
return value.name
def python_value(self, value: str) -> enum.Enum:
"""Convert the stored string to the corresponding enum
:param value: Name of the item from the field's enum to return
:raises peewee.IntegrityError: When the name passed to ``value`` does not correspond to an
item in the field's enum
:returns: The enum item with the name passed to ``value``
"""
try:
return self.enumeration[value]
except KeyError:
raise peewee.InterfaceError(
f"Enum {self.enumeration.__name__} has no value with name '{value}'"
) from None
class ChecksumField(peewee.CharField):
"""Field for storing checksum hashes in the database
.. note:: The reason for implementing this is to protect against future changes to the hashing
algorithm. Just storing the digest means that if the hashing algorithm is ever
changed (for performance, etc) then any existing records will be invalidated. By
storing the hashing algorithm with the digest we can protect against that possibility.
A custom container needs to be implemented because the builtin hashlib has no way to
recreate a hash object from the algorithm+digest without the original data.
"""
def db_value(self, value: Checksum) -> str:
"""Serialize the checkstum to a database string"""
return f"{value.algorithm}:{value.digest}"
def python_value(self, value: str) -> Checksum:
"""Deserailize a string to a checksum container"""
alg, _, digest = value.partition(":")
return Checksum(algorithm=alg, digest=digest)
class KodakModel(peewee.Model): class KodakModel(peewee.Model):
"""Base model for defining common fields and attaching database"""
class Meta: # pylint: disable=too-few-public-methods,missing-class-docstring class Meta: # pylint: disable=too-few-public-methods,missing-class-docstring
database = INTERFACE database = INTERFACE

9
kodak/database/access.py Normal file
View File

@ -0,0 +1,9 @@
import peewee
from kodak.database._shared import KodakModel
class AccessRecord(KodakModel):
"""Model for access keys when operating in private mode"""
password = peewee.CharField(null=False)

13
kodak/database/alias.py Normal file
View File

@ -0,0 +1,13 @@
import peewee
from kodak.database._shared import ChecksumField
from kodak.database._shared import KodakModel
from kodak.database.image import ImageRecord
class AliasRecord(KodakModel):
"""Model for manipulated image records"""
parent = peewee.ForeignKeyField(ImageRecord, null=False)
name = peewee.CharField(null=False)
checksum = ChecksumField(null=False)

View File

@ -1,30 +1,15 @@
import json
import uuid
from typing import List
import peewee import peewee
from kodak import constants
from kodak.database._shared import ChecksumField
from kodak.database._shared import EnumField
from kodak.database._shared import KodakModel from kodak.database._shared import KodakModel
class ImageRecord(KodakModel): class ImageRecord(KodakModel):
"""Database record for""" """Model for source images"""
width = peewee.IntegerField(null=False) name = peewee.Charfield(null=False)
height = peewee.IntegerField(null=False) format = EnumField(constants.ImageFormat, null=False)
format = peewee.CharField(null=False)
deleted = peewee.BooleanField(null=False, default=False) deleted = peewee.BooleanField(null=False, default=False)
public = peewee.BooleanField(null=False, default=False) checksum = ChecksumField(null=False)
owner = peewee.UUIDField(null=False)
sha256 = peewee.CharField(null=False)
_readable = peewee.CharField(null=False, default="[]")
@property
def readable(self) -> List[uuid.UUID]:
"""List of UUIDs corresponding to accounts that can read the file"""
return [uuid.UUID(item) for item in json.loads(self._readable)]
@readable.setter
def readable(self, value: List[uuid.UUID]):
"""Update the list of UUIDs for accounts that can read the file"""
self._readable = json.dumps([str(item) for item in value])

View File

@ -1,11 +0,0 @@
import peewee
from kodak.database._shared import KodakModel
from kodak.database.image import ImageRecord
class ThumbnailRecord(KodakModel):
parent = peewee.ForeignKeyField(ImageRecord)
width = peewee.IntegerField(null=False)
height = peewee.IntegerField(null=False)