mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-08-28 23:04:53 +02:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27bd1622b7 | |||
| e2b54ef3ff | |||
| 70f1a3d975 |
Generated
+6
-6
@@ -7,11 +7,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1787559586,
|
||||
"narHash": "sha256-onL0VLf9vPllmT0H/OlURIU5r5t5WIEl7t4tVNKT0Nw=",
|
||||
"lastModified": 1782949081,
|
||||
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "9d0d87172c374f89da73c1cfe6d81ae62feac1f1",
|
||||
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -22,11 +22,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1787736819,
|
||||
"narHash": "sha256-cV5xEJJK3BvhU8rEd4mC9UsmDi5qscv/kzGPhBRC5WA=",
|
||||
"lastModified": 1785571196,
|
||||
"narHash": "sha256-KoTsyMQqnXQZq8deCEnu4QkyldkwH/bpMMhUcfMdGIw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9fbb54b33e91ee4ca368e35a78e0613c720600b3",
|
||||
"rev": "148bab9c1c3c53136ecb44a6ea356a0ed5b39b06",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
{
|
||||
description = "TagStudio";
|
||||
@@ -49,10 +49,9 @@
|
||||
inherit pillow-jxl-plugin;
|
||||
};
|
||||
|
||||
openexr = python3Packages.callPackage ./nix/package/openexr.nix { inherit (pkgs) openexr; };
|
||||
pillow-jxl-plugin = python3Packages.callPackage ./nix/package/pillow-jxl-plugin.nix {
|
||||
inherit (pkgs) cmake;
|
||||
inherit openexr pyexiv2;
|
||||
inherit pyexiv2;
|
||||
};
|
||||
pyexiv2 = python3Packages.callPackage ./nix/package/pyexiv2.nix { inherit (pkgs) exiv2; };
|
||||
in
|
||||
@@ -60,7 +59,7 @@
|
||||
inherit tagstudio;
|
||||
tagstudio-jxl = tagstudio.override { withJXLSupport = true; };
|
||||
|
||||
inherit openexr pillow-jxl-plugin pyexiv2;
|
||||
inherit pillow-jxl-plugin pyexiv2;
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
+38
-42
@@ -1,5 +1,5 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
{
|
||||
ffmpeg-headless,
|
||||
@@ -40,36 +40,12 @@ python3Packages.buildPythonApplication {
|
||||
qt6.qtmultimedia
|
||||
];
|
||||
|
||||
build-system = with python3Packages; [ hatchling ];
|
||||
dependencies =
|
||||
with python3Packages;
|
||||
[
|
||||
chardet_5
|
||||
ffmpeg-python
|
||||
humanfriendly
|
||||
mutagen
|
||||
numpy
|
||||
opencv-python
|
||||
pillow
|
||||
pillow-heif
|
||||
py7zr
|
||||
pydantic
|
||||
pydub
|
||||
pyside6
|
||||
rarfile
|
||||
rawpy
|
||||
requests
|
||||
semver
|
||||
send2trash
|
||||
sqlalchemy
|
||||
srctools
|
||||
structlog
|
||||
toml
|
||||
ujson
|
||||
wcmatch
|
||||
]
|
||||
++ lib.optional (pythonAtLeast "3.13") audioop-lts
|
||||
++ lib.optional withJXLSupport pillow-jxl-plugin;
|
||||
nativeCheckInputs = with python3Packages; [
|
||||
pytest-qt
|
||||
pytest-xdist
|
||||
pytestCheckHook
|
||||
syrupy
|
||||
];
|
||||
|
||||
# TODO: Install more icon resolutions when available.
|
||||
preInstall = ''
|
||||
@@ -114,19 +90,39 @@ python3Packages.buildPythonApplication {
|
||||
"structlog"
|
||||
"typing-extensions"
|
||||
];
|
||||
|
||||
# HACK: All tests fail with Python: Aborted for unknown reasons.
|
||||
doCheck = false;
|
||||
|
||||
nativeCheckInputs = with python3Packages; [
|
||||
pytest-qt
|
||||
pytest-xdist
|
||||
pytestCheckHook
|
||||
syrupy
|
||||
];
|
||||
|
||||
pythonImportsCheck = [ "tagstudio" ];
|
||||
|
||||
build-system = with python3Packages; [ hatchling ];
|
||||
dependencies =
|
||||
with python3Packages;
|
||||
[
|
||||
chardet_5
|
||||
ffmpeg-python
|
||||
humanfriendly
|
||||
mutagen
|
||||
numpy
|
||||
opencv-python
|
||||
pillow
|
||||
pillow-heif
|
||||
py7zr
|
||||
pydantic
|
||||
pydub
|
||||
pyside6
|
||||
rarfile
|
||||
rawpy
|
||||
requests
|
||||
semver
|
||||
send2trash
|
||||
sqlalchemy
|
||||
srctools
|
||||
structlog
|
||||
toml
|
||||
ujson
|
||||
wcmatch
|
||||
]
|
||||
++ lib.optional (pythonAtLeast "3.13") audioop-lts
|
||||
++ lib.optional withJXLSupport pillow-jxl-plugin;
|
||||
|
||||
# These tests require modifications to a library, which does not work
|
||||
# in a read-only environment.
|
||||
disabledTests = [
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
{
|
||||
lib,
|
||||
numpy,
|
||||
openexr,
|
||||
pybind11,
|
||||
pytest,
|
||||
python,
|
||||
pythonImportsCheckHook,
|
||||
toPythonModule,
|
||||
}:
|
||||
|
||||
toPythonModule (
|
||||
openexr.overrideAttrs (o: {
|
||||
buildInputs = o.buildInputs or [ ] ++ [ pybind11 ];
|
||||
|
||||
cmakeFlags = o.cmakeFlags or [ ] ++ [ (lib.cmakeBool "OPENEXR_BUILD_PYTHON" true) ];
|
||||
|
||||
# `python.sitePackages` replacement can be removed once Python install path is inherited from sysconfig.
|
||||
# Currently on main, but not part of a release.
|
||||
# See: https://github.com/AcademySoftwareFoundation/openexr/commit/30345db72944b38926f13b5114b9a01b4b553890
|
||||
postPatch = o.postPatch or "" + /* bash */ ''
|
||||
substituteInPlace src/wrappers/python/CMakeLists.txt \
|
||||
--replace-warn python/OpenEXR ${python.sitePackages} \
|
||||
--replace-fail 'PYTHONPATH=''${CMAKE_CURRENT_BINARY_DIR}' 'PYTHONPATH=''${CMAKE_CURRENT_BINARY_DIR}:'"$PYTHONPATH"
|
||||
'';
|
||||
|
||||
nativeCheckInputs = o.nativeCheckInputs or [ ] ++ [
|
||||
numpy
|
||||
pythonImportsCheckHook
|
||||
];
|
||||
checkInputs = o.checkInputs or [ ] ++ [ pytest ];
|
||||
|
||||
pythonImportsCheck = o.pythonImportsCheck or [ ] ++ [ "OpenEXR" ];
|
||||
})
|
||||
)
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
{
|
||||
buildPythonPackage,
|
||||
@@ -7,7 +7,6 @@
|
||||
fetchPypi,
|
||||
lib,
|
||||
numpy,
|
||||
openexr,
|
||||
packaging,
|
||||
pillow,
|
||||
pyexiv2,
|
||||
@@ -17,18 +16,18 @@
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "pillow-jxl-plugin";
|
||||
version = "1.3.8";
|
||||
version = "1.3.4";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchPypi {
|
||||
pname = "pillow_jxl_plugin";
|
||||
inherit version;
|
||||
hash = "sha256-RDD9d1eJl0IHnFSKfSU31tY88PHTIxgAlbwPbwPZ1Po=";
|
||||
hash = "sha256-jqWJ/FWep8XfzLQq9NgUj121CPX01FGDKLq1ox/LJo4=";
|
||||
};
|
||||
|
||||
cargoDeps = rustPlatform.fetchCargoVendor {
|
||||
inherit src;
|
||||
hash = "sha256-IiVTlKtKkfZnRXme7QFA5MS8PPiL8+riOYOEoNaHHXc=";
|
||||
hash = "sha256-7j+sCn+P6q6tsm2MJ/cM7hF2KEjILJNA6SDb35tecPg=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
@@ -37,22 +36,12 @@ buildPythonPackage rec {
|
||||
rustPlatform.maturinBuildHook
|
||||
];
|
||||
|
||||
dependencies = [
|
||||
packaging
|
||||
pillow
|
||||
];
|
||||
|
||||
dontUseCmakeConfigure = true;
|
||||
|
||||
nativeCheckInputs = [
|
||||
numpy
|
||||
openexr
|
||||
pyexiv2
|
||||
pytestCheckHook
|
||||
];
|
||||
|
||||
pythonImportsCheck = [ "pillow_jxl" ];
|
||||
|
||||
# Working directory takes precedence in the Python path. Remove
|
||||
# `pillow_jxl` to prevent it from being loaded during pytest, rather than the
|
||||
# built module, as it includes a `pillow_jxl.pillow_jxl.so` that is imported.
|
||||
@@ -62,6 +51,15 @@ buildPythonPackage rec {
|
||||
rm -r pillow_jxl
|
||||
'';
|
||||
|
||||
dontUseCmakeConfigure = true;
|
||||
|
||||
pythonImportsCheck = [ "pillow_jxl" ];
|
||||
|
||||
dependencies = [
|
||||
packaging
|
||||
pillow
|
||||
];
|
||||
|
||||
meta = {
|
||||
description = "Pillow plugin for JPEG-XL, using Rust for bindings";
|
||||
homepage = "https://github.com/Isotr0py/pillow-jpegxl-plugin";
|
||||
|
||||
+8
-16
@@ -1,42 +1,34 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
{
|
||||
autoPatchelfHook,
|
||||
buildPythonPackage,
|
||||
exiv2,
|
||||
fetchFromGitHub,
|
||||
lib,
|
||||
pybind11,
|
||||
python,
|
||||
setuptools,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "pyexiv2";
|
||||
version = "2.16.0";
|
||||
version = "2.15.3";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "LeoHsiao1";
|
||||
repo = "pyexiv2";
|
||||
tag = "v${version}";
|
||||
hash = "sha256-FH5nbbh0vaErJzBl6L2HPh0SQXkQ558abTBml7nSLU8=";
|
||||
hash = "sha256-83bFMaoXncvhRJNcCgkkC7B29wR5pjuLO/EdkQdqxxo=";
|
||||
};
|
||||
|
||||
buildInputs = [ exiv2.dev ];
|
||||
|
||||
build-system = [ setuptools ];
|
||||
dependencies = [ pybind11 ];
|
||||
|
||||
postBuild = ''
|
||||
lib_dir=$out/${python.sitePackages}/pyexiv2/lib
|
||||
|
||||
mkdir -p "$lib_dir"
|
||||
cp -rT ${exiv2.lib}/lib "$lib_dir"
|
||||
'';
|
||||
nativeBuildInputs = [ autoPatchelfHook ];
|
||||
buildInputs = [ exiv2.lib ];
|
||||
|
||||
pythonImportsCheck = [ "pyexiv2" ];
|
||||
|
||||
build-system = [ setuptools ];
|
||||
|
||||
meta = {
|
||||
description = "Read and write image metadata, including EXIF, IPTC, XMP, ICC Profile";
|
||||
homepage = "https://github.com/LeoHsiao1/pyexiv2";
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
{
|
||||
lib,
|
||||
@@ -21,7 +21,7 @@ let
|
||||
stdenv.cc.cc
|
||||
zstd
|
||||
]
|
||||
++ lib.optionals (!stdenv.hostPlatform.isDarwin) [
|
||||
++ lib.optionals (!stdenv.isDarwin) [
|
||||
dbus
|
||||
libGL
|
||||
libdrm
|
||||
@@ -35,7 +35,7 @@ let
|
||||
]
|
||||
);
|
||||
|
||||
libraryPath = "${lib.optionalString pkgs.stdenv.hostPlatform.isDarwin "DY"}LD_LIBRARY_PATH";
|
||||
libraryPath = "${lib.optionalString pkgs.stdenv.isDarwin "DY"}LD_LIBRARY_PATH";
|
||||
|
||||
python3Wrapped = pkgs.symlinkJoin {
|
||||
inherit (python3)
|
||||
|
||||
@@ -504,10 +504,9 @@ class Library:
|
||||
) -> LibraryStatus:
|
||||
logger.info("[Library] Opening SQLite Library", library_dir=library_dir)
|
||||
|
||||
self.engine = self.__get_engine(library_dir, in_memory, sql_filename)
|
||||
|
||||
# migrate if necessary
|
||||
try:
|
||||
migrations = DBMigrations(library_dir, self.engine)
|
||||
migrations = DBMigrations(library_dir, sql_filename)
|
||||
|
||||
# save backup if patches will be applied
|
||||
if migrations.required:
|
||||
@@ -517,7 +516,8 @@ class Library:
|
||||
except MigrationError as e:
|
||||
return LibraryStatus(success=False, message=e.args[0])
|
||||
|
||||
# everything is fine, set the library path
|
||||
# open up-to-date library
|
||||
self.engine = self.__get_engine(library_dir, in_memory, sql_filename)
|
||||
self.library_dir = library_dir
|
||||
return LibraryStatus(success=True, library_path=library_dir)
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
import sqlite3
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import override
|
||||
|
||||
import sqlalchemy
|
||||
import structlog
|
||||
import ujson
|
||||
from sqlalchemy import Engine, and_, delete, select, text, update
|
||||
from sqlalchemy import and_, delete, select, text, update
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from tagstudio.core.constants import IGNORE_NAME, TAG_ARCHIVED, TS_FOLDER_NAME
|
||||
@@ -23,6 +23,7 @@ from tagstudio.core.library.alchemy.constants import (
|
||||
from tagstudio.core.library.alchemy.fields import LEGACY_FIELD_MAP, DatetimeField, TextField
|
||||
from tagstudio.core.library.alchemy.joins import TagParent
|
||||
from tagstudio.core.library.alchemy.models import Entry, Tag, TagColorGroup, Version
|
||||
from tagstudio.core.library.alchemy.utils import list_tables
|
||||
from tagstudio.core.library.ignore import migrate_ext_list
|
||||
from tagstudio.core.utils.types import unwrap
|
||||
from tagstudio.i18n.translations import Translations
|
||||
@@ -46,9 +47,11 @@ class DBMigration:
|
||||
|
||||
|
||||
class DBMigrations:
|
||||
def __init__(self, library_dir: Path, engine: Engine) -> None:
|
||||
def __init__(self, library_dir: Path, sql_filename: str) -> None:
|
||||
self.library_dir = library_dir
|
||||
self.engine = engine
|
||||
self._connection = sqlite3.connect(
|
||||
str(library_dir / TS_FOLDER_NAME / sql_filename), autocommit=False
|
||||
)
|
||||
|
||||
# Don't check DB version when creating new library
|
||||
self.loaded_db_version = self._get_version(DB_VERSION_CURRENT_KEY)
|
||||
@@ -102,70 +105,63 @@ class DBMigrations:
|
||||
MigrationTo300, # changes: deletes folders
|
||||
MigrationTo400, # changes: add category_exclusions
|
||||
]
|
||||
with Session(self.engine) as session:
|
||||
for migration in migrations:
|
||||
if self.loaded_db_version < migration.version and (
|
||||
migration.initial_version is None
|
||||
or self.initial_db_version < migration.initial_version
|
||||
):
|
||||
logger.info(f"[Library][Migration][{migration.version}] Starting DB Migration")
|
||||
# any error causes transaction to rollback
|
||||
migration.run(
|
||||
session,
|
||||
self.library_dir,
|
||||
lambda msg, v=migration.version: f"[Library][Migration][{v}] {msg}",
|
||||
for migration in migrations:
|
||||
if self.loaded_db_version < migration.version and (
|
||||
migration.initial_version is None
|
||||
or self.initial_db_version < migration.initial_version
|
||||
):
|
||||
logger.info(f"[Library][Migration][{migration.version}] Starting DB Migration")
|
||||
# any error causes transaction to rollback
|
||||
migration.run(
|
||||
None, # TODO: remove session param once all Migrations have been updated
|
||||
self.library_dir,
|
||||
lambda msg, v=migration.version: f"[Library][Migration][{v}] {msg}",
|
||||
)
|
||||
self.loaded_db_version = migration.version
|
||||
try:
|
||||
self._set_version(DB_VERSION_CURRENT_KEY, migration.version)
|
||||
logger.info(f"[Library][Migration][{migration.version}] Completed DB Migration")
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
f"[Library][Migration][{migration.version}] "
|
||||
"Couldn't update version, continuing without commit",
|
||||
error=e,
|
||||
)
|
||||
self.loaded_db_version = migration.version
|
||||
try:
|
||||
self._set_version(session, DB_VERSION_CURRENT_KEY, migration.version)
|
||||
logger.info(
|
||||
f"[Library][Migration][{migration.version}] Completed DB Migration"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.info(
|
||||
f"[Library][Migration][{migration.version}] "
|
||||
"Couldn't update version, continuing without commit",
|
||||
error=e,
|
||||
)
|
||||
session.flush()
|
||||
else:
|
||||
session.commit()
|
||||
else:
|
||||
self._connection.commit()
|
||||
|
||||
assert self.loaded_db_version >= DB_VERSION, (
|
||||
"Ran all migrations, but the DB is still not on the newest version"
|
||||
)
|
||||
|
||||
def _get_version(self, key: str) -> int:
|
||||
with Session(self.engine) as session:
|
||||
inspector = sqlalchemy.inspect(self.engine)
|
||||
try:
|
||||
# "Version" table added in DB_VERSION 101
|
||||
if inspector and inspector.has_table("versions"):
|
||||
version = session.scalar(select(Version).where(Version.key == key))
|
||||
assert version
|
||||
return version.value
|
||||
# "Preferences" table deprecated in TagStudio 9.5.4
|
||||
else:
|
||||
return int(
|
||||
unwrap(
|
||||
session.scalar(
|
||||
text("SELECT value FROM preferences WHERE key == 'DB_VERSION'")
|
||||
)
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
return 0
|
||||
"""Get a version value from the DB.
|
||||
|
||||
def _set_version(self, session: Session, key: str, value: int) -> None:
|
||||
Args:
|
||||
key(str): The name of the version type to retrieve.
|
||||
"""
|
||||
# "Version" table added in DB_VERSION 101
|
||||
if "versions" in list_tables(self._connection):
|
||||
query = ("SELECT value FROM versions WHERE key == ?", [key])
|
||||
# "Preferences" table deprecated in TagStudio 9.5.4
|
||||
else:
|
||||
query = ("SELECT value FROM preferences WHERE key == 'DB_VERSION'", [])
|
||||
|
||||
return int(unwrap(self._connection.execute(*query).fetchone())[0])
|
||||
|
||||
def _set_version(self, key: str, value: int) -> None:
|
||||
"""Set a version value to the DB.
|
||||
|
||||
Args:
|
||||
session(Session): The SQLAlchemy DB Session to use.
|
||||
key(str): The key for the name of the version type to set.
|
||||
key(str): The the name of the version type to set.
|
||||
value(int): The version value to set.
|
||||
"""
|
||||
# Insert if key has no value yet, otherwise update the value
|
||||
session.merge(Version(key=key, value=value))
|
||||
self._connection.execute(
|
||||
"INSERT INTO versions (key, value) VALUES (?, ?)"
|
||||
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
||||
[key, value],
|
||||
)
|
||||
|
||||
|
||||
class MigrationTo7(DBMigration):
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
||||
from sqlite3 import Connection
|
||||
|
||||
|
||||
def list_tables(con: Connection) -> list[str]:
|
||||
return [
|
||||
row[0]
|
||||
for row in con.execute("SELECT name FROM sqlite_master WHERE type == 'table'").fetchall()
|
||||
]
|
||||
Reference in New Issue
Block a user