From b1126d5313be6b82bd259af2986f7c9824676421 Mon Sep 17 00:00:00 2001 From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:14:54 -0800 Subject: [PATCH] fix: open libraries from v9.5.0-pr1 in newer versions (#815) * ui: show more informative library error messages * tests: add sqlite db migration tests * tests: fix and refactor migration tests * fix: apply db8 schema changes before repairing db6 * docs: add save file format change log * chore: remove db version explanations from docstrings --- docs/updates/schema_changes.md | 46 +++++++++++++++++ tagstudio/src/core/library/alchemy/library.py | 23 ++++----- tagstudio/src/qt/ts_qt.py | 22 +++++--- tagstudio/src/qt/widgets/landing.py | 2 +- .../DB_VERSION_6/.TagStudio/ts_library.sqlite | Bin 0 -> 98304 bytes .../DB_VERSION_7/.TagStudio/ts_library.sqlite | Bin 0 -> 98304 bytes .../DB_VERSION_8/.TagStudio/ts_library.sqlite | Bin 0 -> 98304 bytes tagstudio/tests/test_db_migrations.py | 47 ++++++++++++++++++ 8 files changed, 119 insertions(+), 21 deletions(-) create mode 100644 docs/updates/schema_changes.md create mode 100644 tagstudio/tests/fixtures/empty_libraries/DB_VERSION_6/.TagStudio/ts_library.sqlite create mode 100644 tagstudio/tests/fixtures/empty_libraries/DB_VERSION_7/.TagStudio/ts_library.sqlite create mode 100644 tagstudio/tests/fixtures/empty_libraries/DB_VERSION_8/.TagStudio/ts_library.sqlite create mode 100644 tagstudio/tests/test_db_migrations.py diff --git a/docs/updates/schema_changes.md b/docs/updates/schema_changes.md new file mode 100644 index 000000000..aeed09837 --- /dev/null +++ b/docs/updates/schema_changes.md @@ -0,0 +1,46 @@ +# Save Format Changes + +This page outlines the various changes made the TagStudio save file format over time, sometimes referred to as the "database" or "database file". + +## JSON + +| First Used | Last Used | Format | Location | +| ---------- | ----------------------------------------------------------------------- | ------ | --------------------------------------------- | +| v1.0.0 | [v9.4.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.4.2) | JSON | ``/.TagStudio/ts_library.json | + +The legacy database format for public TagStudio releases [v9.1](https://github.com/TagStudioDev/TagStudio/tree/Alpha-v9.1) through [v9.4.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.4.2). Variations of this format had been used privately since v1.0.0. + +Replaced by the new SQLite format introduced in TagStudio [v9.5.0 Pre-Release 1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1). + +## DB_VERSION 6 + +| First Used | Last Used | Format | Location | +| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------ | ----------------------------------------------- | +| [v9.5.0-PR1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) | [v9.5.0-PR1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) | SQLite | ``/.TagStudio/ts_library.sqlite | + +The first public version of the SQLite save file format. + +Migration from the legacy JSON format is provided via a walkthrough when opening a legacy library in TagStudio [v9.5.0 Pre-Release 1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) or later. + +## DB_VERSION 7 + +| First Used | Last Used | Format | Location | +| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------ | ----------------------------------------------- | +| [v9.5.0-PR2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr2) | [v9.5.0-PR3](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr3) | SQLite | ``/.TagStudio/ts_library.sqlite | + +### Changes + +- Repairs "Description" fields to use a TEXT_LINE key instead of a TEXT_BOX key. +- Repairs tags that may have a disambiguation_id pointing towards a deleted tag. + +## DB_VERSION 8 + +| First Used | Last Used | Format | Location | +| ------------------------------------------------------------------------------- | --------- | ------ | ----------------------------------------------- | +| [v9.5.0-PR4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr4) | _Current_ | SQLite | ``/.TagStudio/ts_library.sqlite | + +### Changes + +- Adds the `color_border` column to `tag_colors` table. Used for instructing the [secondary color](../library/tag_color.md#secondary-color) to apply to a tag's border as a new optional behavior. +- Adds three new default colors: "Burgundy (TagStudio Shades)", "Dark Teal (TagStudio Shades)", and "Dark Lavender (TagStudio Shades)". +- Updates Neon colors to use the the new `color_border` property. diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 703a9fb45..d1fa38c0c 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -184,6 +184,7 @@ class LibraryStatus: success: bool library_path: Path | None = None message: str | None = None + msg_description: str | None = None json_migration_req: bool = False @@ -460,10 +461,12 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: # Apply any post-SQL migration patches. if not is_new: + if db_version < 8: + self.apply_db8_schema_changes(session) if db_version == 6: - self.apply_db6_patches(session) + self.apply_repairs_for_db6(session) if db_version >= 6 and db_version < 8: - self.apply_db7_patches(session) + self.apply_db8_default_data(session) # Update DB_VERSION if LibraryPrefs.DB_VERSION.default > db_version: @@ -473,11 +476,8 @@ def open_sqlite_library(self, library_dir: Path, is_new: bool) -> LibraryStatus: self.library_dir = library_dir return LibraryStatus(success=True, library_path=library_dir) - def apply_db6_patches(self, session: Session): - """Apply migration patches to a library with DB_VERSION 6. - - DB_VERSION 6 was only used in v9.5.0-pr1. - """ + def apply_repairs_for_db6(self, session: Session): + """Apply database repairs introduced in DB_VERSION 7.""" logger.info("[Library][Migration] Applying patches to DB_VERSION: 6 library...") with session: # Repair "Description" fields with a TEXT_LINE key instead of a TEXT_BOX key. @@ -501,11 +501,8 @@ def apply_db6_patches(self, session: Session): session.commit() - def apply_db7_patches(self, session: Session): - """Apply migration patches to a library with DB_VERSION 7 or earlier. - - DB_VERSION 7 was used from v9.5.0-pr2 to v9.5.0-pr3. - """ + def apply_db8_schema_changes(self, session: Session): + """Apply database schema changes introduced in DB_VERSION 8.""" # TODO: Use Alembic for this part instead # Add the missing color_border column to the TagColorGroups table. color_border_stmt = text( @@ -522,6 +519,8 @@ def apply_db7_patches(self, session: Session): ) session.rollback() + def apply_db8_default_data(self, session: Session): + """Apply default data changes introduced in DB_VERSION 8.""" tag_colors: list[TagColorGroup] = default_color_groups.standard() tag_colors += default_color_groups.pastels() tag_colors += default_color_groups.shades() diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index f02cf8ed3..6fb391217 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -710,14 +710,16 @@ def create_about_modal(): app.exec() self.shutdown() - def show_error_message(self, message: str): - self.main_window.statusbar.showMessage(message, Qt.AlignmentFlag.AlignLeft) - self.main_window.landing_widget.set_status_label(message) - self.main_window.setWindowTitle(message) + def show_error_message(self, error_name: str, error_desc: str | None = None): + self.main_window.statusbar.showMessage(error_name, Qt.AlignmentFlag.AlignLeft) + self.main_window.landing_widget.set_status_label(error_name) + self.main_window.setWindowTitle(f"{self.base_title} - {error_name}") msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Icon.Critical) - msg_box.setText(message) + msg_box.setText(error_name) + if error_desc: + msg_box.setInformativeText(error_desc) msg_box.setWindowTitle(Translations["window.title.error"]) msg_box.addButton(Translations["generic.close"], QMessageBox.ButtonRole.AcceptRole) @@ -1871,12 +1873,14 @@ def open_library(self, path: Path) -> None: if self.lib.library_dir: self.close_library() - open_status: LibraryStatus = None + open_status: LibraryStatus | None = None try: open_status = self.lib.open_library(path) except Exception as e: logger.exception(e) - open_status = LibraryStatus(success=False, library_path=path, message=type(e).__name__) + open_status = LibraryStatus( + success=False, library_path=path, message=type(e).__name__, msg_description=str(e) + ) # Migration is required if open_status.json_migration_req: @@ -1892,7 +1896,9 @@ def open_library(self, path: Path) -> None: def init_library(self, path: Path, open_status: LibraryStatus): if not open_status.success: self.show_error_message( - open_status.message or Translations["window.message.error_opening_library"] + error_name=open_status.message + or Translations["window.message.error_opening_library"], + error_desc=open_status.msg_description, ) return open_status diff --git a/tagstudio/src/qt/widgets/landing.py b/tagstudio/src/qt/widgets/landing.py index 3f6b15942..1a17f59f8 100644 --- a/tagstudio/src/qt/widgets/landing.py +++ b/tagstudio/src/qt/widgets/landing.py @@ -161,7 +161,7 @@ def animate_logo_pop(self): # self.status_pos_anim.setEndValue(self.status_label.pos()) # self.status_pos_anim.start() - def set_status_label(self, text=str): + def set_status_label(self, text: str): """Set the text of the status label. Args: diff --git a/tagstudio/tests/fixtures/empty_libraries/DB_VERSION_6/.TagStudio/ts_library.sqlite b/tagstudio/tests/fixtures/empty_libraries/DB_VERSION_6/.TagStudio/ts_library.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..4f6acfe80c91a42b8a60d93e4622aed0794541b5 GIT binary patch literal 98304 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 HSAhQmWg;PX literal 0 HcmV?d00001 diff --git a/tagstudio/tests/fixtures/empty_libraries/DB_VERSION_7/.TagStudio/ts_library.sqlite b/tagstudio/tests/fixtures/empty_libraries/DB_VERSION_7/.TagStudio/ts_library.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..cee2bbb234a92d58ec1e2d8b9bb339e9dae04b06 GIT binary patch literal 98304 zcmeI5e{3V!UBEq_$;5VSzwBlmuj6Dh&Rj0>Cf;~#$9C?JTK^bl(@WxV_h5-PNZO2v_w~gB<%Jy!K zv)ODb`1dmY)n6Wb!00FVFJ(F&cDQ1@cIQJL^gWy3_B9(ypg;5SXvF*X9_0C^d&m8X z>wn!r=Z9T?=g5ybM?S~gVlLTVrT>xol>K+G`dj(Bd)+_k=XvVhlU?a%L+(oZRjE;z zI&#N!csW-RGZm4mWU`wgXA*FWE@!>QZERP>b+N>47c1QM?&jt)=W;fsLz#ObQ_5Y> zSOvsrsQ&k<`YjO2=!5mrl{r z1ca7)IE=~OLA_BMCbV?4kYmh<_gn1~E?X>aikWST3#jk4nr8i4y(1motnVL7-FmBO8XKp&&sq~aE|dgPet-X0 z6%p=Nqu$G$qUQ?V%a4ze?t0-}3fmg4YPWtUS8vtjMy=C#dci`xPe?}l9jS3_VKT&r zbx)4u>TUT%Z@7hsKAi`8%%fJPPQVNln)`X6Yt)Lh@KNeqB{kv}=Ww0oPR%R5LKZX4 zUubUiUK%Nm;)RqtMCE6@8p3YxaNL5=BzR||dyjY29}ZJ56cuzgTdjsHH8pzHGh_j1 zmGu}7oMnTrzvpipKI$Iz2ZPiLTMA5VkZONC7JSA49_TZPx4G47)a16w1zD!)k<>jH zIDb3Gw{F#+9Vj9$qq^6Y@t+!($E&5v#+FEmQUr&sS{?hH0TQWJ>q~y4sczKWGHKOR z^#VtV-QC`Jd{^WaNn7wLA>v{Enc%Qhf(EQAXb0YVQ?5~eC`3K~Q|hG0s|eN6k|l(C zG-g5GODCh%e5J6EHN5mdhD170Fn83hA4=_$xq;G-j4w1bEY_u@{T7O(KFgd@_$fEo zlb9C_MymPpf?3ALSpRxx7|R|Bb@8Cma^D?sC~oxb;|kHEwtP#*%PqWcFdaHAOicm; zRq^0@6D?HLYtwtmMeFs~fJPGbWgo9 zYBNy#!&qn-vva6N8tQY7kMT-l(2bO(d$qeI%d9uM0g?-G10!d-0j-wRn0CNMm^Y&? zXzC+(-IOv&?(Qjrq&I(hM|KOAUOE{h#@WE;66rvJg@cg?nWy%%I@QqZRE6F#jc=|U z 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 Tm9qGxTdRwpEK_#nRDk~j!gGja literal 0 HcmV?d00001 diff --git a/tagstudio/tests/test_db_migrations.py b/tagstudio/tests/test_db_migrations.py new file mode 100644 index 000000000..dd73440b9 --- /dev/null +++ b/tagstudio/tests/test_db_migrations.py @@ -0,0 +1,47 @@ +# Copyright (C) 2025 Travis Abendshien (CyanVoxel). +# Licensed under the GPL-3.0 License. +# Created for TagStudio: https://github.com/CyanVoxel/TagStudio + +import shutil +from pathlib import Path + +import pytest +from src.core.constants import TS_FOLDER_NAME +from src.core.library.alchemy.library import Library + +CWD = Path(__file__) +FIXTURES = "fixtures" +EMPTY_LIBRARIES = "empty_libraries" + + +@pytest.mark.parametrize( + "path", + [ + str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_6")), + str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_7")), + str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_8")), + ], +) +def test_library_migrations(path: str): + library = Library() + + # Copy libraries to temp dir so modifications don't show up in version control + original_path = Path(path) + temp_path = Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_TEMP") + temp_path.mkdir(exist_ok=True) + temp_path_ts = temp_path / TS_FOLDER_NAME + temp_path_ts.mkdir(exist_ok=True) + shutil.copy( + original_path / TS_FOLDER_NAME / Library.SQL_FILENAME, + temp_path / TS_FOLDER_NAME / Library.SQL_FILENAME, + ) + + try: + status = library.open_library(library_dir=temp_path) + library.close() + shutil.rmtree(temp_path) + assert status.success + except Exception as e: + library.close() + shutil.rmtree(temp_path) + raise (e)