From af4ef217a5ffd0b257326cb1130d771e93dd50b8 Mon Sep 17 00:00:00 2001 From: Paul Friederichsen Date: Sat, 7 Sep 2024 01:10:39 -0500 Subject: [PATCH 01/25] ci: use MkDocs for documentation site (#460) * Use MkDocs for documentation Using Material for MkDocs * Enable edit buttons on pages * Updates to mkdocs.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 4 +- .github/ISSUE_TEMPLATE/feature_request.yml | 4 +- .github/workflows/publish_docs.yaml | 33 +++++ CONTRIBUTING.md | 4 +- README.md | 8 +- doc/index.md | 24 --- doc/library/library.md | 11 -- doc/library/tag_categories.md | 3 - doc/library/tag_overrides.md | 16 -- doc/updates/planned_features.md | 59 -------- doc/utilities/macro.md | 43 ------ {doc => docs}/assets/db_schema.png | Bin .../assets/github_header.png | Bin docs/assets/icon.ico | Bin 0 -> 370070 bytes docs/assets/icon.png | Bin 0 -> 693030 bytes screenshot.jpg => docs/assets/screenshot.jpg | Bin {doc => docs}/assets/tag_override_ex-1.png | Bin {doc => docs}/assets/tag_override_ex-2.png | Bin docs/index.md | 50 +++++++ docs/install.md | 18 +++ {doc => docs}/library/entry.md | 29 ++-- {doc => docs}/library/entry_groups.md | 7 +- {doc => docs}/library/field.md | 6 +- docs/library/index.md | 4 + {doc => docs}/library/tag.md | 16 +- docs/library/tag_categories.md | 8 + docs/library/tag_overrides.md | 20 +++ docs/updates/changelog.md | 4 + {doc => docs}/updates/db_migration.md | 10 +- docs/updates/planned_features.md | 59 ++++++++ docs/usage.md | 86 +++++++++++ docs/utilities/macro.md | 46 ++++++ mkdocs.yml | 137 ++++++++++++++++++ 33 files changed, 512 insertions(+), 197 deletions(-) create mode 100644 .github/workflows/publish_docs.yaml delete mode 100644 doc/index.md delete mode 100644 doc/library/library.md delete mode 100644 doc/library/tag_categories.md delete mode 100644 doc/library/tag_overrides.md delete mode 100644 doc/updates/planned_features.md delete mode 100644 doc/utilities/macro.md rename {doc => docs}/assets/db_schema.png (100%) rename github_header.png => docs/assets/github_header.png (100%) create mode 100644 docs/assets/icon.ico create mode 100644 docs/assets/icon.png rename screenshot.jpg => docs/assets/screenshot.jpg (100%) rename {doc => docs}/assets/tag_override_ex-1.png (100%) rename {doc => docs}/assets/tag_override_ex-2.png (100%) create mode 100644 docs/index.md create mode 100644 docs/install.md rename {doc => docs}/library/entry.md (50%) rename {doc => docs}/library/entry_groups.md (79%) rename {doc => docs}/library/field.md (65%) create mode 100644 docs/library/index.md rename {doc => docs}/library/tag.md (94%) create mode 100644 docs/library/tag_categories.md create mode 100644 docs/library/tag_overrides.md create mode 100644 docs/updates/changelog.md rename {doc => docs}/updates/db_migration.md (64%) create mode 100644 docs/updates/planned_features.md create mode 100644 docs/usage.md create mode 100644 docs/utilities/macro.md create mode 100644 mkdocs.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 97cc8dc12..4bfe6d49f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -8,7 +8,7 @@ body: value: | *Please add an appropriate title for this issue.* - Before reporting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues). + Before reporting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues). Validate that you are using an up-to-date version[^1], your issue might already be fixed! Questions, guidance, and usage goes in [discussions](https://github.com/TagStudioDev/TagStudio/discussions). Invalid issues will be closed. @@ -20,7 +20,7 @@ body: options: - label: I am using an up-to-date version. required: true - - label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md). + - label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md). required: true - label: I have searched existing [issues](https://github.com/TagStudioDev/TagStudio/issues). required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 3f98ccf07..18abecb46 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -8,7 +8,7 @@ body: value: | *Please add an appropriate title for this feature request.* - Before suggesting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues). + Before suggesting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues). Validate that you are using an up-to-date version[^1], your feature might already be implemented! Questions, guidance, and usage goes in [discussions](https://github.com/TagStudioDev/TagStudio/discussions). Invalid issues will be closed. @@ -20,7 +20,7 @@ body: options: - label: I am using an up-to-date version. required: true - - label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md). + - label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md). required: true - label: I have searched existing [issues](https://github.com/TagStudioDev/TagStudio/issues). required: true diff --git a/.github/workflows/publish_docs.yaml b/.github/workflows/publish_docs.yaml new file mode 100644 index 000000000..851ddd20a --- /dev/null +++ b/.github/workflows/publish_docs.yaml @@ -0,0 +1,33 @@ +name: Publish Docs + +on: + push: + branches: + - main + +permissions: + contents: write + +concurrency: + group: publish-docs + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 + with: + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material mkdocs-material[imaging] + - run: mkdocs gh-deploy --force + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24f3b0e3f..1316dfba7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,13 +6,13 @@ Thank you so much for showing interest in contributing to TagStudio! Here are a ## Getting Started -- Check the [Planned Features](https://github.com/TagStudioDev/TagStudio/blob/main/doc/updates/planned_features.md) page, [FAQ](/README.md/#faq), as well as the open [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls). +- Check the [Planned Features](https://github.com/TagStudioDev/TagStudio/blob/main/docs/updates/planned_features.md) page, [FAQ](/README.md/#faq), as well as the open [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls). - If you'd like to add a feature that isn't on the roadmap or doesn't have an open issue, **PLEASE create a feature request** issue for it discussing your intentions so any feedback or important information can be given by the team first. - We don't want you wasting time developing a feature or making a change that can't/won't be added for any reason ranging from pre-existing refactors to design philosophy differences. ### Contribution Checklist -- I've read the [Planned Features](https://github.com/TagStudioDev/TagStudio/blob/main/doc/updates/planned_features.md) page +- I've read the [Planned Features](https://github.com/TagStudioDev/TagStudio/blob/main/docs/updates/planned_features.md) page - I've read the [FAQ](/README.md/#faq), including the "[Features I Likely Won't Add/Pull](/README.md/#features-i-likely-wont-addpull)" section - I've checked the open [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls) - **I've created a new issue for my feature _before_ starting work on it**, or have at least notified others in the relevant existing issue(s) of my intention to work on it diff --git a/README.md b/README.md index 0d9ae28b3..91e76fa04 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # TagStudio (Alpha): A User-Focused Document Management System

- +

> [!CAUTION] @@ -11,7 +11,7 @@ TagStudio is a photo & file organization application with an underlying system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure.
- TagStudio Screenshot + TagStudio Screenshot
TagStudio Alpha v9.1.0 running on Windows 10.
@@ -53,7 +53,7 @@ TagStudio is a photo & file organization application with an underlying system t - Special search conditions for entries that are: `untagged`/`no tags` and `empty`/`no fields`. > [!NOTE] -> For more information on the project itself, please see the [FAQ](#faq) section as well as the [documentation](/doc/index.md). +> For more information on the project itself, please see the [FAQ](#faq) section as well as the [documentation](/docs/index.md). ## Contributing @@ -178,7 +178,7 @@ As of writing (Alpha v9.3.0) the project is in a useable state, however it lacks ### What Features Are You Planning on Adding? > [!IMPORTANT] -> See the [Planned Features](/doc/updates/planned_features.md) documentation for the latest feature lists. The lists here are currently being migrated over there with individual pages for larger features. +> See the [Planned Features](/docs/updates/planned_features.md) documentation for the latest feature lists. The lists here are currently being migrated over there with individual pages for larger features. Of the several features I have planned for the project, these are broken up into “priority” features and “future” features. Priority features were originally intended for the first public release, however are currently absent from the Alpha v9.x.x builds. diff --git a/doc/index.md b/doc/index.md deleted file mode 100644 index 50c9c5187..000000000 --- a/doc/index.md +++ /dev/null @@ -1,24 +0,0 @@ -# Welcome to the TagStudio Documentation! - -> [!WARNING] -> This documentation is still a work in progress, and is intended to aide with deconstructing and understanding of the core mechanics of TagStudio and how it operates. - -
- TagStudio Alpha - Under Construction -
- -## Table of Contents - -- [Library](/doc/library/library.md) -- [Entries](/doc/library/entry.md) -- [Fields](/doc/library/field.md) -- [Tags](/doc/library/tag.md) -- [Tools & Macros](/doc/utilities/macro.md) -- [Planned Features](/doc/updates/planned_features.md) - ---- - -### [Database Migration](/doc/updates/db_migration.md) - -The "Database Migration", "DB Migration", or "SQLite Migration" is an upcoming update to TagStudio which will replace the current JSON [library](/doc/library/library.md) with a SQL-based one, and will additionally include some fundamental changes to how some features such as [tags](/doc/library/tag.md) will work. diff --git a/doc/library/library.md b/doc/library/library.md deleted file mode 100644 index bc8a6d0d7..000000000 --- a/doc/library/library.md +++ /dev/null @@ -1,11 +0,0 @@ -# Library - -The library is how TagStudio represents your chosen directory, with every file inside of it being displayed as an [entry](/doc/library/entry.md). You can have as many or few libraries as you wish, since each libraries' data is stored within a "`.TagStudio`" folder at its root. -Note that this means [tags](/doc/library/tag.md) you create only exist _per-library_. - -### Library Contents - -- [Entries](/doc/library/entry.md) -- [Fields](/doc/library/field.md) -- [Tags](/doc/library/tag.md) -- [Macros](/doc/utilities/macro.md) diff --git a/doc/library/tag_categories.md b/doc/library/tag_categories.md deleted file mode 100644 index 6edec95bf..000000000 --- a/doc/library/tag_categories.md +++ /dev/null @@ -1,3 +0,0 @@ -# Tag Categories (Upcoming Feature) - -Replaces [Tag Fields](/doc/library/field.md#tag_box). Tags are able to be marked as a “category” which then displays as tag fields currently do, with any tags inheriting from that category being displayed underneath. diff --git a/doc/library/tag_overrides.md b/doc/library/tag_overrides.md deleted file mode 100644 index 09673f74b..000000000 --- a/doc/library/tag_overrides.md +++ /dev/null @@ -1,16 +0,0 @@ -# Tag Overrides (Upcoming Feature) - -Tag overrides are the ability to add or remove [parent tags](/doc/library/tag.md#subtags) from a [tag](/doc/library/tag.md) on a per- [entry](/doc/library/entry.md) basis. Relies on the [Database Migration](/doc/updates/db_migration.md) update being complete. - -## Examples - -
- Example 1 -
Ex. 1 - Comparing standard tag composition vs additive and subtractive inheritance overrides.
-
- -
- Example 2 - -
Ex. 2 - Parent tag swap using tag overrides.
-
diff --git a/doc/updates/planned_features.md b/doc/updates/planned_features.md deleted file mode 100644 index 12f892029..000000000 --- a/doc/updates/planned_features.md +++ /dev/null @@ -1,59 +0,0 @@ -# Planned Features - -The following lists outline the planned major and minor features for TagStudio, in no particular order. - -# Major Features - -- [SQL Database Migration](/doc/updates/db_migration.md) -- Multiple Directory Support -- [Tags Categories](/doc/library/tag_categories.md) -- [Entry Groups](/doc/library/entry_groups.md) -- [Tag Overrides](/doc/library/tag_overrides.md) -- Tagging Panel - - Top Tags - - Recent Tags - - Tag Search - - Pinned Tags -- Configurable Default Fields (May be part of [Macros](/doc/utilities/macro.md)) -- Deep File Extension Control -- Settings Menu -- Custom User Colors -- Search Engine Rework - - Boolean Search - - Tag Objects In Search - - Search For Fields - - Sortable Search Results -- Automatic Entry Relinking - - Detect Renames - - Detect Moves -- Thumbnail Caching -- User-Defined Fields -- Exportable Library/Tag Data - - Exportable Human-Readable Library - - Exportable/Importable Human-Readable “Tag Packs” - - Exportable/Importable Color Palettes -- Configurable Thumbnail Labels - - Toggle Extension Label - - Toggle File Size Label -- Configurable Thumbnail Tag Badges - - Customize tags that appear instead of just “Archive” and “Favorite” -- OCR Search - -## Minor Features - -- Deleting Tags -- Merging Tags -- Tag Icons -- Tag/Field Copy + Paste -- Collage UI -- Resizable Thumbnail Grid -- Draggable Files Outside The Program -- File Property Caching -- 3D Previews -- Audio Waveform Previews - - Toggle Between Waveform And Album Artwork -- PDF Previews -- SVG Previews -- Full Video Player -- Duration Properties For Video + Audio Files -- Optional Starter Tag Packs diff --git a/doc/utilities/macro.md b/doc/utilities/macro.md deleted file mode 100644 index de576377f..000000000 --- a/doc/utilities/macro.md +++ /dev/null @@ -1,43 +0,0 @@ -# Tools & Macros - -Tools and macros are features that serve to create a more fluid [library](/doc/library/library.md)-managing process, or provide some extra functionality. Please note that some are still in active development and will be fleshed out in future updates. - -## Tools - -### Fix Unlinked Entries - -This tool displays the number of unlinked [entries](/doc/library/entry.md), and some options for their resolution. - -1. Refresh - - Scans through the library and updates the unlinked entry count. -2. Search & Relink - - Attempts to automatically find and reassign missing files. -3. Delete Unlinked Entries - - Displays a confirmation prompt containing the list of all missing files to be deleted before committing to or cancelling the operation. - -### Fix Duplicate Files - -This tool allows for management of duplicate files in the library using a [DupeGuru](https://dupeguru.voltaicideas.net/) file. - -1. Load DupeGuru File - - load the "results" file created from a DupeGuru scan -2. Mirror Entries - - Duplicate entries will have their contents mirrored across all instances. This allows for duplicate files to then be deleted with DupeGuru as desired, without losing the [field](/doc/library/field.md) data that has been assigned to either. (Once deleted, the "Fix Unlinked Entries" tool can be used to clean up the duplicates) - -### Create Collage - -This tool is a preview of an upcoming feature. When selected, TagStudio will generate a collage of all the contents in a Library, which can be found in the Library folder ("/your-folder/.TagStudio/collages/"). Note that this feature is still in early development, and doesn't yet offer any customization options. - -## Macros - -### Auto-fill [WIP] - -Tool is in development and will be documented in future update. - -### Sort fields - -Tool is in development, will allow for user-defined sorting of [fields](/doc/library/field.md). - -### Folders to Tags - -Creates tags from the existing folder structure in the library, which are previewed in a hierarchy view for the user to confirm. A tag will be created for each folder and applied to all entries, with each subfolder being linked to the parent folder as a [parent tag](/doc/library/tag.md#subtags). Tags will initially be named after the folders, but can be fully edited and customized afterwards. diff --git a/doc/assets/db_schema.png b/docs/assets/db_schema.png similarity index 100% rename from doc/assets/db_schema.png rename to docs/assets/db_schema.png diff --git a/github_header.png b/docs/assets/github_header.png similarity index 100% rename from github_header.png rename to docs/assets/github_header.png diff --git a/docs/assets/icon.ico b/docs/assets/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..71335bd510d7cb3e4f3e94f0d75909281ac2adab GIT binary patch literal 370070 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 z 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 z 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
+&#RmPqZ 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%0DIEL`#J$s4cd6&g9tQoy1qYM$*_fC= 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 z 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

~>NQRg?=x(0v=oDW|F^ez866tvAD9rN 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

A9Zy0>(2gNLY1yJe_ht9_R4h2i*g%_Y@N16 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�jW4$(pSCZCyPU}@j-OtjLWnC?h7}C1JRe_8 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 literal 0 HcmV?d00001 diff --git a/screenshot.jpg b/docs/assets/screenshot.jpg similarity index 100% rename from screenshot.jpg rename to docs/assets/screenshot.jpg diff --git a/doc/assets/tag_override_ex-1.png b/docs/assets/tag_override_ex-1.png similarity index 100% rename from doc/assets/tag_override_ex-1.png rename to docs/assets/tag_override_ex-1.png diff --git a/doc/assets/tag_override_ex-2.png b/docs/assets/tag_override_ex-2.png similarity index 100% rename from doc/assets/tag_override_ex-2.png rename to docs/assets/tag_override_ex-2.png diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..9f8240b4c --- /dev/null +++ b/docs/index.md @@ -0,0 +1,50 @@ +--- +title: Home +--- + +# Welcome to the TagStudio Documentation! + +!!! warning + This documentation is still a work in progress, and is intended to aide with deconstructing and understanding of the core mechanics of TagStudio and how it operates. + +![TagStudio Alpha](assets/github_header.png) + + +TagStudio is a photo & file organization application with an underlying system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure. + +
+ ![TagStudio screenshot](assets/screenshot.jpg) +
TagStudio Alpha v9.1.0 running on Windows 10
+
+ +## Goals + +- To achieve a portable, privacy-oriented, open, extensible, and feature-rich system of organizing and rediscovering files. +- To provide powerful methods for organization, notably the concept of tag composition, or “taggable tags”. +- To create an implementation of such a system that is resilient against a user’s actions outside the program (modifying, moving, or renaming files) while also not burdening the user with mandatory sidecar files or otherwise requiring them to change their existing file structures and workflows. +- To support a wide range of users spanning across different platforms, multi-user setups, and those with large (several terabyte) libraries. +- To make the darn thing look like nice, too. It’s 2024, not 1994. + +## Priorities + +1. **The concept.** Even if TagStudio as a project or application fails, I’d hope that the idea lives on in a superior project. The [goals](#goals) outlined above don’t reference TagStudio once - _TagStudio_ is what references the _goals._ +2. **The system.** Frontends and implementations can vary, as they should. The core underlying metadata management system is what should be interoperable between different frontends, programs, and operating systems. A standard implementation for this should settle as development continues. This opens up the doors for improved and varied clients, integration with third-party applications, and more. +3. **The application.** If nothing else, TagStudio the application serves as the first (and so far only) implementation for this system of metadata management. This has the responsibility of doing the idea justice and showing just what’s possible when it comes to user file management. +4. (The name.) I think it’s fine for an app or client, but it doesn’t really make sense for a system or standard. I suppose this will evolve with time. + +## Current Features + +- Create libraries/vaults centered around a system directory. Libraries contain a series of entries: the representations of your files combined with metadata fields. Each entry represents a file in your library’s directory, and is linked to its location. +- Add metadata to your library entries, including: + - Name, Author, Artist (Single-Line Text Fields) + - Description, Notes (Multiline Text Fields) + - Tags, Meta Tags, Content Tags (Tag Boxes) +- Create rich tags composed of a name, a list of aliases, and a list of “subtags” - being tags in which these tags inherit values from. +- Search for entries based on tags, ~~metadata~~ (TBA), or filenames/filetypes (using `filename: `) +- Special search conditions for entries that are: `untagged`/`no tags` and `empty`/`no fields`. + +## Important Updates + +### [Database Migration](updates/db_migration.md) + +The "Database Migration", "DB Migration", or "SQLite Migration" is an upcoming update to TagStudio which will replace the current JSON [library](library/index.md) with a SQL-based one, and will additionally include some fundamental changes to how some features such as [tags](library/tag.md) will work. diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 000000000..19edba2d9 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,18 @@ +# Installation + +To download TagStudio, visit the [Releases](https://github.com/TagStudioDev/TagStudio/releases) section of the GitHub repository and download the latest release for your system under the "Assets" section. TagStudio is available for **Windows**, **macOS** _(Apple Silicon & Intel)_, and **Linux**. Windows and Linux builds are also available in portable versions if you want a more self-contained executable to move around. + +For video thumbnails and playback, you'll also need [FFmpeg](https://ffmpeg.org/download.html) installed on your system. + +!!! info "For macOS Users" + On macOS, you may be met with a message saying _""TagStudio" can't be opened because Apple cannot check it for malicious software."_ If you encounter this, then you'll need to go to the "Settings" app, navigate to "Privacy & Security", and scroll down to a section that says _""TagStudio" was blocked from use because it is not from an identified developer."_ Click the "Open Anyway" button to allow TagStudio to run. You should only have to do this once after downloading the application. + +## Optional Arguments + +Optional arguments to pass to the program: + +`--open ` / `-o ` +: Path to a TagStudio Library folder to open on start. + +`--config-file ` / `-c ` +: Path to the TagStudio config file to load. \ No newline at end of file diff --git a/doc/library/entry.md b/docs/library/entry.md similarity index 50% rename from doc/library/entry.md rename to docs/library/entry.md index 10c85c107..89d299b75 100644 --- a/doc/library/entry.md +++ b/docs/library/entry.md @@ -1,25 +1,26 @@ # Entry -Entries are the units that fill a [library](/doc/library/library.md). Each one corresponds to a file, holding a reference to it along with the metadata associated with it. +Entries are the units that fill a [library](index.md). Each one corresponds to a file, holding a reference to it along with the metadata associated with it. ### Entry Object Structure -1. `id`: - - Int, Unique, **Required** - - The ID for the Entry. - - Used for internal processing +1. `id` + - Int, Unique, **Required** + - The ID for the Entry. + - Used for internal processing 2. `filename`: - - String, **Required** - - The filename with extension of the referenced media file. + - String, **Required** + - The filename with extension of the referenced media file. 3. `path`: - - String, **Required**, OS Agnostic - - The folder path in which the media file is located in. -4. [`fields`](/doc/library/field.md): - - List of dicts, Optional - - A list of Field ID/Value dicts. + - String, **Required**, OS Agnostic + - The folder path in which the media file is located in. +4. [`fields`](field.md): + - List of dicts, Optional + - A list of Field ID/Value dicts. -NOTE: _Entries currently have several unused optional fields intended for later features._ +!!! note + Entries currently have several unused optional fields intended for later features. -## Retrieving Entries based on [Tag](/doc/library/tag.md) Cluster +## Retrieving Entries based on [Tag](tag.md) Cluster By default when querying Entries, each Entry's `tags` list (stored in the form of Tag `id`s) is compared against the Tag `id`s in a given Tag cluster (list of Tag `id`s) or appended clusters in the case of multi-term queries. The type of comparison depends on the type of query and whether or not it is an inclusive or exclusive query, or a combination of both. This default searching behavior is done in _O(n)_ time, but can be sped up in the future by building indexes on certain search terms. These indexes can be stored on disk and loaded back into memory in future sessions. These indexes will also need to be updated as new Tags and Entries are added or edited. diff --git a/doc/library/entry_groups.md b/docs/library/entry_groups.md similarity index 79% rename from doc/library/entry_groups.md rename to docs/library/entry_groups.md index 1372e7ddf..a9dcb4591 100644 --- a/doc/library/entry_groups.md +++ b/docs/library/entry_groups.md @@ -1,3 +1,8 @@ -# Entry Groups (Upcoming Feature) +--- +tags: + - Upcoming Feature +--- + +# Entry Groups Entries can be grouped via tags marked as “groups” which when applied to different entries will signal TagStudio to treat those entries as a single group inside of searches and browsing. diff --git a/doc/library/field.md b/docs/library/field.md similarity index 65% rename from doc/library/field.md rename to docs/library/field.md index e40b0c437..0f2a4538e 100644 --- a/doc/library/field.md +++ b/docs/library/field.md @@ -1,6 +1,6 @@ # Field -Fields are the building blocks of metadata stored in [entries](/doc/library/entry.md). Fields have several base types for representing different kinds of information, including: +Fields are the building blocks of metadata stored in [entries](entry.md). Fields have several base types for representing different kinds of information, including: #### `text_line` @@ -14,7 +14,7 @@ Fields are the building blocks of metadata stored in [entries](/doc/library/entr #### `tag_box` -- A box of [tags](/doc/library/tag.md) defined and added by the user. +- A box of [tags](tag.md) defined and added by the user. - Multiple tag boxes can be used to separate classifications of tags. - e.g: Content Tags, Meta Tags, etc. @@ -31,4 +31,4 @@ Fields are the building blocks of metadata stored in [entries](/doc/library/entr #### `collation` [obsolete] -- Previously used for associating files to be used in a [collation](/doc/utilities/macro.md#create-collage), will be removed in favor of a more flexible feature in future updates. +- Previously used for associating files to be used in a [collation](../utilities/macro.md#create-collage), will be removed in favor of a more flexible feature in future updates. diff --git a/docs/library/index.md b/docs/library/index.md new file mode 100644 index 000000000..5464cdf5c --- /dev/null +++ b/docs/library/index.md @@ -0,0 +1,4 @@ +# Library + +The library is how TagStudio represents your chosen directory, with every file inside of it being displayed as an [entry](entry.md). You can have as many or few libraries as you wish, since each libraries' data is stored within a `.TagStudio` folder at its root. +Note that this means [tags](tag.md) you create only exist _per-library_. diff --git a/doc/library/tag.md b/docs/library/tag.md similarity index 94% rename from doc/library/tag.md rename to docs/library/tag.md index 599d55119..1cc38f634 100644 --- a/doc/library/tag.md +++ b/docs/library/tag.md @@ -1,20 +1,20 @@ # Tag -Tags are user-defined attributes made up of one or more keywords, aliases, and relationships to other tags. A person, place, thing, concept, you name it! Tags allow for a more sophisticated way to organize and search [entries](/doc/library/entry.md) thanks to their aliases, parent tags, and more. +Tags are user-defined attributes made up of one or more keywords, aliases, and relationships to other tags. A person, place, thing, concept, you name it! Tags allow for a more sophisticated way to organize and search [entries](entry.md) thanks to their aliases, parent tags, and more. Tags can be as simple or complex as wanted, so that any user can tune TagStudio to fit their needs. -Among the things that make tags so useful, aliases give the ability to contain alternate names and spellings, making searches intuitive and expansive. Furthermore, parent-tags/subtags offer relational organization capabilities for the structuring and connection of the [library's](/doc/library/library.md) contents. +Among the things that make tags so useful, aliases give the ability to contain alternate names and spellings, making searches intuitive and expansive. Furthermore, parent-tags/subtags offer relational organization capabilities for the structuring and connection of the [library's](index.md) contents. ## Tag Object Structure -#### `id` +### `id` ID for the tag. - Int, Unique, Required - Used for internal processing -#### `name` +### `name` The normal name of the tag, with no shortening or specification. @@ -22,7 +22,7 @@ The normal name of the tag, with no shortening or specification. - Doesn't have to be unique - Used for display, searching, and storing -#### `shorthand` +### `shorthand` The shorthand name for the tag. Works like an alias but is used for specific display purposes. @@ -30,7 +30,7 @@ The shorthand name for the tag. Works like an alias but is used for specific dis - Doesn't have to be unique - Used for display and searching -#### `aliases` +### `aliases` Alternate names for the tag. @@ -38,14 +38,14 @@ Alternate names for the tag. - Recommended to be unique to this tag - Used for searching -#### `subtags` +### `subtags` Other Tags that make up properties of this tag. Also called "parent tags". - List of Strings, Optional - Used for display (first parent tag only) and searching. -#### `color` +### `color` A color name string for customizing the tag's display color diff --git a/docs/library/tag_categories.md b/docs/library/tag_categories.md new file mode 100644 index 000000000..4f2b1ec82 --- /dev/null +++ b/docs/library/tag_categories.md @@ -0,0 +1,8 @@ +--- +tags: + - Upcoming Feature +--- + +# Tag Categories + +Replaces [Tag Fields](field.md#tag_box). Tags are able to be marked as a “category” which then displays as tag fields currently do, with any tags inheriting from that category being displayed underneath. diff --git a/docs/library/tag_overrides.md b/docs/library/tag_overrides.md new file mode 100644 index 000000000..ee9f6cec7 --- /dev/null +++ b/docs/library/tag_overrides.md @@ -0,0 +1,20 @@ +--- +tags: + - Upcoming Feature +--- + +# Tag Overrides + +Tag overrides are the ability to add or remove [parent tags](tag.md#subtags) from a [tag](tag.md) on a per- [entry](entry.md) basis. Relies on the [Database Migration](../updates/db_migration.md) update being complete. + +## Examples + +
+ ![Example 1](../assets/tag_override_ex-1.png){ height="300" } +
Ex. 1 - Comparing standard tag composition vs additive and subtractive inheritance overrides.
+
+ +
+ ![Example 2](../assets/tag_override_ex-2.png){ height="300" } +
Ex. 2 - Parent tag swap using tag overrides.
+
diff --git a/docs/updates/changelog.md b/docs/updates/changelog.md new file mode 100644 index 000000000..01d18d843 --- /dev/null +++ b/docs/updates/changelog.md @@ -0,0 +1,4 @@ +--- +title: Changelog +--- +--8<-- "CHANGELOG.md" \ No newline at end of file diff --git a/doc/updates/db_migration.md b/docs/updates/db_migration.md similarity index 64% rename from doc/updates/db_migration.md rename to docs/updates/db_migration.md index bccda350f..4e2a7abb9 100644 --- a/doc/updates/db_migration.md +++ b/docs/updates/db_migration.md @@ -1,10 +1,10 @@ # Database Migration -The database migration is an upcoming refactor to TagStudio's library data storage system. The database will be migrated from a JSON-based one to a SQLite-based one. Part of this migration will include a reworked schema, which will allow for several new features and changes to how [tags](/doc/library/tag.md) and [fields](/doc/library/field.md) operate. +The database migration is an upcoming refactor to TagStudio's library data storage system. The database will be migrated from a JSON-based one to a SQLite-based one. Part of this migration will include a reworked schema, which will allow for several new features and changes to how [tags](../library/tag.md) and [fields](../library/field.md) operate. ## Schema -Database Schema +![Database Schema](../assets/db_schema.png){ width="600" } ### `alias` Table @@ -37,7 +37,7 @@ _Description TBA_ ## Resulting New Features and Changes - Multiple Directory Support -- [Tag Categories](/doc/library/tag_categories.md) (Replaces [Tag Fields](/doc/library/field.md#tag_box)) -- [Tag Overrides](/doc/library/tag_overrides.md) -- User-Defined [Fields](/doc/library/field.md) +- [Tag Categories](../library/tag_categories.md) (Replaces [Tag Fields](../library/field.md#tag_box)) +- [Tag Overrides](../library/tag_overrides.md) +- User-Defined [Fields](../library/field.md) - Tag Icons diff --git a/docs/updates/planned_features.md b/docs/updates/planned_features.md new file mode 100644 index 000000000..cf6da56dc --- /dev/null +++ b/docs/updates/planned_features.md @@ -0,0 +1,59 @@ +# Planned Features + +The following lists outline the planned major and minor features for TagStudio, in no particular order. + +# Major Features + +- [ ] [SQL Database Migration](../updates/db_migration.md) +- [ ] Multiple Directory Support +- [ ] [Tags Categories](../library/tag_categories.md) +- [ ] [Entry Groups](../library/entry_groups.md) +- [ ] [Tag Overrides](../library/tag_overrides.md) +- [ ] Tagging Panel + - [ ] Top Tags + - [ ] Recent Tags + - [ ] Tag Search + - [ ] Pinned Tags +- [ ] Configurable Default Fields (May be part of [Macros](../utilities/macro.md)) +- [ ] Deep File Extension Control +- [ ] Settings Menu +- [ ] Custom User Colors +- [ ] Search Engine Rework + - [ ] Boolean Search + - [ ] Tag Objects In Search + - [ ] Search For Fields + - [ ] Sortable Search Results +- [ ] Automatic Entry Relinking + - [ ] Detect Renames + - [ ] Detect Moves +- [ ] Thumbnail Caching +- [ ] User-Defined Fields +- [ ] Exportable Library/Tag Data + - [ ] Exportable Human-Readable Library + - [ ] Exportable/Importable Human-Readable “Tag Packs” + - [ ] Exportable/Importable Color Palettes +- [ ] Configurable Thumbnail Labels + - [ ] Toggle Extension Label + - [ ] Toggle File Size Label +- [ ] Configurable Thumbnail Tag Badges + - [ ] Customize tags that appear instead of just “Archive” and “Favorite” +- [ ] OCR Search + +## Minor Features + +- [ ] Deleting Tags +- [ ] Merging Tags +- [ ] Tag Icons +- [ ] Tag/Field Copy + Paste +- [ ] Collage UI +- [ ] Resizable Thumbnail Grid +- [ ] Draggable Files Outside The Program +- [ ] File Property Caching +- [ ] 3D Previews +- [ ] Audio Waveform Previews + - [ ] Toggle Between Waveform And Album Artwork +- [ ] PDF Previews +- [ ] SVG Previews +- [ ] Full Video Player +- [ ] Duration Properties For Video + Audio Files +- [ ] Optional Starter Tag Packs diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 000000000..450f131a0 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,86 @@ +# Usage + +## Creating/Opening a Library + +With TagStudio opened, start by creating a new library or opening an existing one using File -> Open/Create Library from the menu bar. TagStudio will automatically create a new library from the chosen directory if one does not already exist. Upon creating a new library, TagStudio will automatically scan your folders for files and add those to your library (no files are moved during this process!). + +## Refreshing the Library + +In order to scan for new files or file changes, you’ll need to manually go to File -> Refresh Directories. + +!!! note + In the future, library refreshing will also be automatically done in the background, or additionally on app startup. + +## Adding Metadata to Entries + +To add a metadata field to a file entry, start by clicking the “Add Field” button under the file preview in the right-hand preview panel. From the dropdown menu, select the type of metadata field you’d like to add to the entry. + +## Editing Metadata Fields + +### Text Line / Text Box + +Hover over the field and click the pencil icon. From there, add or edit text in the dialog box popup. + +### Tag Box + +Click the “+” button at the end of the Tags list, and search for tags to add inside the new dialog popup. Click the “+” button next to whichever tags you want to add. Alternatively, after you search for a tag, press the Enter/Return key to add the add the first item in the list. Press Enter/Return once more to close the dialog box + +!!! warning + Keyboard control and navigation is currently _very_ buggy, but will be improved in future versions. + +## Creating Tags + +To create a new tag, click on Edit -> New Tag from the menu bar. From there, enter a tag name, shorthand name, any tag aliases separated by newlines, any subtags, and an optional color. + +- The tag **shorthand** is a type of alias that displays in situations when screen space is more valuable (ex. as a subtag for other tags). +- **Aliases** are alternate names for a tag. These let you search for terms other than the exact tag name in order to find the tag again. +- **Subtags** are tags in which this tag is a child tag of. In other words, tags under this section are parents of this tag. For example, if you had a tag for a character from a show, you would make the show a subtag of this character. This would display as “Character (Show)” in most areas of the app. The first tag in this list is used as the tag shown in parentheses for specification. +- The **color** dropdown lets you select an optional color for this tag to display as. + +## Editing Tags + +To edit a tag, right-click the tag in the tag field of the preview pane and select “Edit Tag” + +## Relinking Renamed/Moved Files + +Inevitably, some of the files inside your library will be renamed, moved, or deleted. If a file has been renamed or moved, TagStudio will display the thumbnail as a red tag with a cross through it _(this icon is also used for items with broken thumbnails)._ To relink moved files or delete these entries, go to Tools -> Manage Unlinked Entries. Click the “Refresh” button to scan your library for unlinked entries. Once complete, you can attempt to “Search & Relink” any unlinked entries to their respective files, or “Delete Unlinked Entries” in the event the original files have been deleted and you no longer wish to keep their metadata entries inside your library. + +!!! warning + There is currently no method to relink entries to files that have been renamed - only moved or deleted. This is a top priority for future releases. + +!!! warning + If multiple matches for a moved file are found (matches are currently defined as files with a matching filename as the original), TagStudio will currently ignore the match groups. Adding a GUI for manual selection, as well as smarter automated relinking, are top priorities for future versions. + +## Saving the Library + +Libraries are saved upon exiting the program. To manually save, select File -> Save Library from the menu bar. To save a backup of your library, select File -> Save Library Backup from the menu bar. + +## Half-Implemented Features + +### Fix Duplicate Files + +Load in a .dupeguru file generated by [dupeGuru](https://github.com/arsenetar/dupeguru/) and mirror metadata across entries marked as duplicates. After mirroring, return to dupeGuru to manage deletion of the duplicate files. After deletion, use the “Fix Unlinked Entries” feature in TagStudio to delete the duplicate set of entries for the now-deleted files + +!!! danger "Caution" + While this feature is functional, it’s a pretty roundabout process and can be streamlined in the future. + +### Image Collage + +Create an image collage of your photos and videos. + +!!! danger "Caution" + Collage sizes and options are hardcoded, and there's no GUI indicating the process of the collage creation. + +### Macros + +Apply tags and other metadata automatically depending on certain criteria. Set specific macros to run when the files are added to the library. Part of this includes applying tags automatically based on parent folders. + +!!! danger "Caution" + Macro options are hardcoded, and there’s currently no way for the user to interface with this (still incomplete) system at all. + +### Gallery-dl Sidecar Importing + +Import JSON sidecar data generated by [gallery-dl](https://github.com/mikf/gallery-dl). + +!!! danger "Caution" + This feature is not supported or documented in any official capacity whatsoever. It will likely be rolled-in to a larger and more generalized sidecar importing feature in the future. \ No newline at end of file diff --git a/docs/utilities/macro.md b/docs/utilities/macro.md new file mode 100644 index 000000000..ac83ddf8c --- /dev/null +++ b/docs/utilities/macro.md @@ -0,0 +1,46 @@ +# Tools & Macros + +Tools and macros are features that serve to create a more fluid [library](../library/index.md)-managing process, or provide some extra functionality. Please note that some are still in active development and will be fleshed out in future updates. + +## Tools + +### Fix Unlinked Entries + +This tool displays the number of unlinked [entries](../library/entry.md), and some options for their resolution. + +Refresh +: Scans through the library and updates the unlinked entry count. + +Search & Relink +: Attempts to automatically find and reassign missing files. + +Delete Unlinked Entries +: Displays a confirmation prompt containing the list of all missing files to be deleted before committing to or cancelling the operation. + +### Fix Duplicate Files + +This tool allows for management of duplicate files in the library using a [DupeGuru](https://dupeguru.voltaicideas.net/) file. + +Load DupeGuru File +: load the "results" file created from a DupeGuru scan + +Mirror Entries +: Duplicate entries will have their contents mirrored across all instances. This allows for duplicate files to then be deleted with DupeGuru as desired, without losing the [field](../library/field.md) data that has been assigned to either. (Once deleted, the "Fix Unlinked Entries" tool can be used to clean up the duplicates) + +### Create Collage + +This tool is a preview of an upcoming feature. When selected, TagStudio will generate a collage of all the contents in a Library, which can be found in the Library folder ("/your-folder/.TagStudio/collages/"). Note that this feature is still in early development, and doesn't yet offer any customization options. + +## Macros + +### Auto-fill [WIP] + +Tool is in development and will be documented in future update. + +### Sort fields + +Tool is in development, will allow for user-defined sorting of [fields](../library/field.md). + +### Folders to Tags + +Creates tags from the existing folder structure in the library, which are previewed in a hierarchy view for the user to confirm. A tag will be created for each folder and applied to all entries, with each subfolder being linked to the parent folder as a [parent tag](../library/tag.md#subtags). Tags will initially be named after the folders, but can be fully edited and customized afterwards. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..9f382e3b4 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,137 @@ +# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json + +# MkDocs: https://www.mkdocs.org/ +# Material for MkDocs: https://squidfunk.github.io/mkdocs-material/ + +# To install: +# pip install mkdocs-material + +# To run the preview server: +# mkdocs serve + +site_name: TagStudio +site_description: "A User-Focused Photo & File Management System" +site_url: https://tagstudiodev.github.io/tagstudio +repo_url: https://github.com/TagStudioDev/TagStudio +repo_name: TagStudioDev/TagStudio + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/TagStudioDev + - icon: fontawesome/brands/discord + link: https://discord.gg/hRNnVKhF2G + tags: + Upcoming Feature: upcoming + +# by default the navigation is an alphanumerically sorted, +# nested list of all the Markdown files found within the /docs directory +# where index files are always first +# uncomment the following to configure the navigation manually: + +# nav: +# - Home: +# - index.md +# - install.md +# - usage.md +# - Library: +# - library/index.md +# - library/entry.md +# - library/entry_groups.md +# - library/field.md +# - library/tag.md +# - library/tag_categories.md +# - library/tag_overrides.md +# - Utilities: +# - utilities/macro.md +# - Updates: +# - updates/changelog.md +# - updates/planned_features.md +# - updates/db_migration.md + +theme: + name: material + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: deep purple + accent: deep purple + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: deep purple + accent: deep purple + toggle: + icon: material/brightness-4 + name: SSwitch to system preference + logo: assets/icon.png + favicon: assets/icon.ico + font: false # use system fonts + language: en + features: + - navigation.instant + - navigation.indexes + - navigation.tracking + - navigation.expand + - navigation.sections + #- navigation.tabs + #- content.tabs.link + #- navigation.top + - search.suggest + - content.code.annotate + - content.action.edit + icon: + repo: fontawesome/brands/github + tag: + upcoming: material/flask-outline + +markdown_extensions: + + # Python Markdown + - abbr + - admonition + - attr_list + - def_list + - footnotes + - md_in_html + - toc: + permalink: true + + # Python Markdown Extensions + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.highlight + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + - pymdownx.snippets + +plugins: + - search + - tags + - social # social embed cards + enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions) \ No newline at end of file From 0b56e7344f5c683404163c47e222da382f6c8f44 Mon Sep 17 00:00:00 2001 From: xarvex Date: Sat, 7 Sep 2024 01:30:19 -0500 Subject: [PATCH 02/25] fix(ci): single character typo for mkdocs --- mkdocs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 9f382e3b4..3ff7fac9c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -133,5 +133,5 @@ markdown_extensions: plugins: - search - tags - - social # social embed cards - enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions) \ No newline at end of file + - social: # social embed cards + enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions) From a8f9bec65c51504f6215ed6defc9aa8fba333473 Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Sat, 7 Sep 2024 00:20:06 -0700 Subject: [PATCH 03/25] ci: route mkdocs `edit_uri` to `blob/main/docs` --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 3ff7fac9c..397829044 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,6 +13,7 @@ site_name: TagStudio site_description: "A User-Focused Photo & File Management System" site_url: https://tagstudiodev.github.io/tagstudio repo_url: https://github.com/TagStudioDev/TagStudio +edit_uri: blob/main/docs/ repo_name: TagStudioDev/TagStudio extra: From 686e803f3e687ee7a9340aef7a7fccf0c834ed00 Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Sat, 7 Sep 2024 00:35:19 -0700 Subject: [PATCH 04/25] ci: add CNAME `docs.tagstud.io` --- docs/CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/CNAME diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 000000000..181b63438 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +docs.tagstud.io \ No newline at end of file From d04d3b1f0aecec69356ca90aa2c610ad7ec0f2b5 Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Sat, 7 Sep 2024 07:30:08 -0700 Subject: [PATCH 05/25] ci: specify push paths for `publish_docs` --- .github/workflows/publish_docs.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_docs.yaml b/.github/workflows/publish_docs.yaml index 851ddd20a..974d9656c 100644 --- a/.github/workflows/publish_docs.yaml +++ b/.github/workflows/publish_docs.yaml @@ -3,7 +3,11 @@ name: Publish Docs on: push: branches: - - main + - main + paths: + - 'docs/**' + - 'mkdocs.yml' + - 'CHANGELOG.md' permissions: contents: write From 844dae0f58225ae211e608b5263f9e2d513ca252 Mon Sep 17 00:00:00 2001 From: CarterPillow Date: Sat, 7 Sep 2024 15:34:20 +0100 Subject: [PATCH 06/25] fix(ci): update `site_url` in mkdocs.yml (#467) Configure MkDocs to use the custom domain: `docs.tagstud.io` --- mkdocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 397829044..f34800d15 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,7 +11,7 @@ site_name: TagStudio site_description: "A User-Focused Photo & File Management System" -site_url: https://tagstudiodev.github.io/tagstudio +site_url: https://docs.tagstud.io/ repo_url: https://github.com/TagStudioDev/TagStudio edit_uri: blob/main/docs/ repo_name: TagStudioDev/TagStudio From f472d22e10ea6a700c0168a634aeb8c4ac64c9ec Mon Sep 17 00:00:00 2001 From: Travis Abendshien Date: Sat, 7 Sep 2024 07:39:28 -0700 Subject: [PATCH 07/25] ci: add `publish_docs.yaml` to `publish_docs` paths --- .github/workflows/publish_docs.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish_docs.yaml b/.github/workflows/publish_docs.yaml index 974d9656c..b5a67547d 100644 --- a/.github/workflows/publish_docs.yaml +++ b/.github/workflows/publish_docs.yaml @@ -8,6 +8,7 @@ on: - 'docs/**' - 'mkdocs.yml' - 'CHANGELOG.md' + - '.github/workflows/publish_docs.yaml' permissions: contents: write From 9b5c26a61edab144863ebc403767a410478cdb6f Mon Sep 17 00:00:00 2001 From: Knaughty <77908661+Kn4ughty@users.noreply.github.com> Date: Sun, 8 Sep 2024 13:34:11 +1000 Subject: [PATCH 08/25] fix(docs): correct grammar mistake in CONTRIBUTING.md. (#452) Removed unnecessary with in "- Lint code *with* by [doing x]" --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1316dfba7..87ab3392c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,7 +86,7 @@ A Python linter and code formatter. Ruff uses the `pyproject.toml` as its config #### Running Locally -- Lint code with by moving into the `/tagstudio` directory with `cd tagstudio` and running `ruff --config ../pyproject.toml`. +- Lint code by moving into the `/tagstudio` directory with `cd tagstudio` and running `ruff --config ../pyproject.toml`. - Format code with `ruff format` inside the repository directory Ruff is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff), PyCharm [plugin](https://plugins.jetbrains.com/plugin/20574-ruff), and [more](https://docs.astral.sh/ruff/integrations/). From d689d67000f87cdd2d9915a3ffe100bf1edc9b70 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 14 Jul 2024 16:51:56 +0700 Subject: [PATCH 09/25] use sqlite + sqlalchemy as a database backend --- .github/workflows/apprun.yaml | 51 - .github/workflows/mypy.yaml | 2 +- .github/workflows/pytest.yaml | 64 +- .github/workflows/ruff.yaml | 13 +- .pre-commit-config.yaml | 3 +- pyproject.toml | 28 +- requirements-dev.txt | 8 +- requirements.txt | 2 + tagstudio/src/cli/ts_cli.py | 3817 ----------------- tagstudio/src/core/constants.py | 52 +- tagstudio/src/core/enums.py | 30 +- tagstudio/src/core/json_typing.py | 42 - tagstudio/src/core/library/__init__.py | 1 + .../src/core/library/alchemy/__init__.py | 6 + tagstudio/src/core/library/alchemy/db.py | 48 + tagstudio/src/core/library/alchemy/enums.py | 116 + tagstudio/src/core/library/alchemy/fields.py | 175 + tagstudio/src/core/library/alchemy/joins.py | 20 + tagstudio/src/core/library/alchemy/library.py | 777 ++++ tagstudio/src/core/library/alchemy/models.py | 241 ++ tagstudio/src/core/library/json/__init__.py | 0 tagstudio/src/core/library/json/fields.py | 38 + .../src/core/{ => library/json}/library.py | 159 +- tagstudio/src/core/palette.py | 101 +- tagstudio/src/core/ts_core.py | 232 +- tagstudio/src/core/utils/dupe_files.py | 83 + tagstudio/src/core/utils/fs.py | 11 - tagstudio/src/core/utils/missing_files.py | 71 + tagstudio/src/core/utils/refresh_dir.py | 63 + tagstudio/src/qt/flowlayout.py | 48 +- tagstudio/src/qt/helpers/file_opener.py | 35 +- tagstudio/src/qt/helpers/function_iterator.py | 5 +- tagstudio/src/qt/main_window.py | 4 +- tagstudio/src/qt/modals/add_field.py | 43 +- tagstudio/src/qt/modals/build_tag.py | 146 +- tagstudio/src/qt/modals/delete_unlinked.py | 39 +- tagstudio/src/qt/modals/file_extension.py | 37 +- tagstudio/src/qt/modals/fix_dupes.py | 28 +- tagstudio/src/qt/modals/fix_unlinked.py | 138 +- tagstudio/src/qt/modals/folders_to_tags.py | 198 +- tagstudio/src/qt/modals/merge_dupe_entries.py | 8 +- tagstudio/src/qt/modals/mirror_entities.py | 54 +- tagstudio/src/qt/modals/relink_unlinked.py | 39 +- tagstudio/src/qt/modals/tag_database.py | 52 +- tagstudio/src/qt/modals/tag_search.py | 66 +- tagstudio/src/qt/pagination.py | 17 +- tagstudio/src/qt/resource_manager.py | 12 +- tagstudio/src/qt/ts_qt.py | 1273 ++---- tagstudio/src/qt/widgets/collage_icon.py | 80 +- tagstudio/src/qt/widgets/fields.py | 21 +- tagstudio/src/qt/widgets/item_thumb.py | 329 +- tagstudio/src/qt/widgets/landing.py | 2 +- tagstudio/src/qt/widgets/panel.py | 10 +- tagstudio/src/qt/widgets/preview_panel.py | 816 ++-- tagstudio/src/qt/widgets/tag.py | 80 +- tagstudio/src/qt/widgets/tag_box.py | 165 +- tagstudio/src/qt/widgets/thumb_renderer.py | 35 +- tagstudio/src/qt/widgets/video_player.py | 47 +- tagstudio/tag_studio.py | 33 +- tagstudio/tests/conftest.py | 110 +- .../test_library_search[--nomatch--].json | 1 - .../test_lib/test_library_search[First].json | 6 - .../test_lib/test_library_search[Second].json | 6 - .../test_lib/test_open_library.json | 4 - tagstudio/tests/core/test_lib.py | 18 - tagstudio/tests/core/test_tags.py | 8 - .../library/.TagStudio/ts_library.json | 69 - tagstudio/tests/fixtures/result.dupeguru | 10 + .../tests/fixtures/sidecar_newgrounds.json | 10 + tagstudio/tests/macros/test_dupe_entries.py | 26 + tagstudio/tests/macros/test_missing_files.py | 31 + tagstudio/tests/macros/test_refresh_dir.py | 19 + tagstudio/tests/macros/test_sidecar.py | 38 + .../__snapshots__/test_folders_to_tags.ambr | 4 + tagstudio/tests/qt/test_driver.py | 62 + tagstudio/tests/qt/test_flow_widget.py | 18 + tagstudio/tests/qt/test_folders_to_tags.py | 7 + tagstudio/tests/qt/test_item_thumb.py | 21 + tagstudio/tests/qt/test_preview_panel.py | 119 + tagstudio/tests/qt/test_tag_panel.py | 24 + tagstudio/tests/qt/test_tag_search_panel.py | 11 + tagstudio/tests/qt/test_tag_widget.py | 118 + tagstudio/tests/test_folders_tags.py | 7 + tagstudio/tests/test_library.py | 326 ++ 84 files changed, 4429 insertions(+), 6758 deletions(-) delete mode 100644 .github/workflows/apprun.yaml delete mode 100644 tagstudio/src/cli/ts_cli.py delete mode 100644 tagstudio/src/core/json_typing.py create mode 100644 tagstudio/src/core/library/__init__.py create mode 100644 tagstudio/src/core/library/alchemy/__init__.py create mode 100644 tagstudio/src/core/library/alchemy/db.py create mode 100644 tagstudio/src/core/library/alchemy/enums.py create mode 100644 tagstudio/src/core/library/alchemy/fields.py create mode 100644 tagstudio/src/core/library/alchemy/joins.py create mode 100644 tagstudio/src/core/library/alchemy/library.py create mode 100644 tagstudio/src/core/library/alchemy/models.py create mode 100644 tagstudio/src/core/library/json/__init__.py create mode 100644 tagstudio/src/core/library/json/fields.py rename tagstudio/src/core/{ => library/json}/library.py (95%) create mode 100644 tagstudio/src/core/utils/dupe_files.py delete mode 100644 tagstudio/src/core/utils/fs.py create mode 100644 tagstudio/src/core/utils/missing_files.py create mode 100644 tagstudio/src/core/utils/refresh_dir.py mode change 100644 => 100755 tagstudio/tag_studio.py delete mode 100644 tagstudio/tests/core/__snapshots__/test_lib/test_library_search[--nomatch--].json delete mode 100644 tagstudio/tests/core/__snapshots__/test_lib/test_library_search[First].json delete mode 100644 tagstudio/tests/core/__snapshots__/test_lib/test_library_search[Second].json delete mode 100644 tagstudio/tests/core/__snapshots__/test_lib/test_open_library.json delete mode 100644 tagstudio/tests/core/test_lib.py delete mode 100644 tagstudio/tests/core/test_tags.py delete mode 100644 tagstudio/tests/fixtures/library/.TagStudio/ts_library.json create mode 100644 tagstudio/tests/fixtures/result.dupeguru create mode 100644 tagstudio/tests/fixtures/sidecar_newgrounds.json create mode 100644 tagstudio/tests/macros/test_dupe_entries.py create mode 100644 tagstudio/tests/macros/test_missing_files.py create mode 100644 tagstudio/tests/macros/test_refresh_dir.py create mode 100644 tagstudio/tests/macros/test_sidecar.py create mode 100644 tagstudio/tests/qt/__snapshots__/test_folders_to_tags.ambr create mode 100644 tagstudio/tests/qt/test_driver.py create mode 100644 tagstudio/tests/qt/test_flow_widget.py create mode 100644 tagstudio/tests/qt/test_folders_to_tags.py create mode 100644 tagstudio/tests/qt/test_item_thumb.py create mode 100644 tagstudio/tests/qt/test_preview_panel.py create mode 100644 tagstudio/tests/qt/test_tag_panel.py create mode 100644 tagstudio/tests/qt/test_tag_search_panel.py create mode 100644 tagstudio/tests/qt/test_tag_widget.py create mode 100644 tagstudio/tests/test_folders_tags.py create mode 100644 tagstudio/tests/test_library.py diff --git a/.github/workflows/apprun.yaml b/.github/workflows/apprun.yaml deleted file mode 100644 index 9894f168e..000000000 --- a/.github/workflows/apprun.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: PySide App Test - -on: [ push, pull_request ] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - cache: 'pip' - - - name: Install system dependencies - run: | - # dont run update, it is slow - # sudo apt-get update - sudo apt-get install -y --no-install-recommends \ - libxkbcommon-x11-0 \ - x11-utils \ - libyaml-dev \ - libegl1-mesa \ - libxcb-icccm4 \ - libxcb-image0 \ - libxcb-keysyms1 \ - libxcb-randr0 \ - libxcb-render-util0 \ - libxcb-xinerama0 \ - libopengl0 \ - libxcb-cursor0 \ - libpulse0 - - - name: Install dependencies - run: | - pip install -Ur requirements.txt - - - name: Run TagStudio app and check exit code - run: | - xvfb-run --server-args="-screen 0, 1920x1200x24 -ac +extension GLX +render -noreset" python tagstudio/tag_studio.py --ci -o /tmp/ - exit_code=$? - if [ $exit_code -eq 0 ]; then - echo "TagStudio ran successfully" - else - echo "TagStudio failed with exit code $exit_code" - exit 1 - fi diff --git a/.github/workflows/mypy.yaml b/.github/workflows/mypy.yaml index 374a1c5e3..3d7b4c18e 100644 --- a/.github/workflows/mypy.yaml +++ b/.github/workflows/mypy.yaml @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: | pip install -r requirements.txt - pip install mypy==1.10.0 + pip install mypy==1.11.2 mkdir tagstudio/.mypy_cache - uses: tsuyoshicho/action-mypy@v4 diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index e8a458aa8..93f54e611 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -1,6 +1,6 @@ name: pytest -on: [push, pull_request] +on: [ push, pull_request ] jobs: pytest: @@ -11,12 +11,64 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install system dependencies + run: | + # dont run update, it is slow + # sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + libxkbcommon-x11-0 \ + x11-utils \ + libyaml-dev \ + libegl1-mesa \ + libxcb-icccm4 \ + libxcb-image0 \ + libxcb-keysyms1 \ + libxcb-randr0 \ + libxcb-render-util0 \ + libxcb-xinerama0 \ + libopengl0 \ + libxcb-cursor0 \ + libpulse0 + - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install -r requirements-dev.txt + python -m pip install --upgrade uv + uv pip install --system -r requirements.txt + uv pip install --system -r requirements-dev.txt - - name: Run tests + - name: Run pytest run: | - pytest tagstudio/tests/ + xvfb-run pytest --cov-report xml --cov=tagstudio + + - name: Store coverage + uses: actions/upload-artifact@v4 + with: + name: 'coverage' + path: 'coverage.xml' + + coverage: + name: Check Code Coverage + runs-on: ubuntu-latest + needs: pytest + + steps: + - name: Load coverage + uses: actions/download-artifact@v4 + with: + name: 'coverage' + + - name: Check Code Coverage + uses: yedpodtrzitko/coverage@main + with: + thresholdAll: 0.5 + thresholdNew: 0.5 + thresholdModified: 0.5 + coverageFile: coverage.xml + token: ${{ secrets.GITHUB_TOKEN }} + sourceDir: tagstudio/src diff --git a/.github/workflows/ruff.yaml b/.github/workflows/ruff.yaml index 730ab4412..897dbdc5d 100644 --- a/.github/workflows/ruff.yaml +++ b/.github/workflows/ruff.yaml @@ -1,11 +1,20 @@ name: Ruff on: [ push, pull_request ] jobs: - ruff: + ruff-format: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: chartboost/ruff-action@v1 with: - version: 0.4.2 + version: 0.6.4 args: 'format --check' + + ruff-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 + with: + version: 0.6.4 + args: 'check' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 291c3bdea..6d665667b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.4.2 + rev: v0.6.4 hooks: - id: ruff-format + - id: ruff diff --git a/pyproject.toml b/pyproject.toml index d60908da6..3fe8ffd04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,33 @@ [tool.ruff] exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"] +[tool.ruff.lint] +select = ["E", "F", "UP", "B", 'SIM'] +ignore = ["E402", "E501", "F541"] + [tool.mypy] strict_optional = false -disable_error_code = ["union-attr", "annotation-unchecked", "import-untyped"] +disable_error_code = ["func-returns-value", "import-untyped"] explicit_package_bases = true warn_unused_ignores = true -exclude = ['tests'] +check_untyped_defs = true + +[[tool.mypy.overrides]] +module = "tests.*" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "src.qt.main_window" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "src.qt.ui.home_ui" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "src.core.ts_core" +ignore_errors = true + +[tool.pytest.ini_options] +#addopts = "-m 'not qt'" +qt_api = "pyside6" diff --git a/requirements-dev.txt b/requirements-dev.txt index 81de1b330..b6b2c6e65 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,8 @@ -ruff==0.4.2 +ruff==0.6.4 pre-commit==3.7.0 pytest==8.2.0 Pyinstaller==6.6.0 -mypy==1.10.0 -syrupy==4.6.1 +mypy==1.11.2 +syrupy==4.7.1 +pytest-qt==4.4.0 +pytest-cov==5.0.0 diff --git a/requirements.txt b/requirements.txt index a353c70c8..18be43fab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,5 @@ numpy==1.26.4 rawpy==0.21.0 pillow-heif==0.16.0 chardet==5.2.0 +structlog==24.4.0 +SQLAlchemy==2.0.34 diff --git a/tagstudio/src/cli/ts_cli.py b/tagstudio/src/cli/ts_cli.py deleted file mode 100644 index fba30860d..000000000 --- a/tagstudio/src/cli/ts_cli.py +++ /dev/null @@ -1,3817 +0,0 @@ -# type: ignore -# Copyright (C) 2024 Travis Abendshien (CyanVoxel). -# Licensed under the GPL-3.0 License. -# Created for TagStudio: https://github.com/CyanVoxel/TagStudio - -"""DEPRECIATED: A basic CLI driver for TagStudio.""" - -import datetime -import math - -# from multiprocessing import Value -import os - -# import subprocess -import sys -import time -from PIL import Image, ImageChops, UnidentifiedImageError -from PIL.Image import DecompressionBombError - -# import pillow_avif -from pathlib import Path -import traceback -import cv2 - -# import climage -# import click -from datetime import datetime as dt -from src.core.ts_core import * -from src.core.utils.web import * -from src.core.utils.fs import * -from src.core.library import * -from src.qt.helpers.file_opener import open_file - -WHITE_FG = "\033[37m" -WHITE_BG = "\033[47m" -BRIGHT_WHITE_FG = "\033[97m" -BRIGHT_WHITE_BG = "\033[107m" -BLACK_FG = "\033[30m" -BRIGHT_CYAN_FG = "\033[96m" -BRIGHT_CYAN_BG = "\033[106m" -BRIGHT_MAGENTA_FG = "\033[95m" -BRIGHT_MAGENTA_BG = "\033[105m" -BRIGHT_GREEN_FG = "\033[92m" -BRIGHT_GREEN_BG = "\033[102m" -YELLOW_FG = "\033[33m" -YELLOW_BG = "\033[43m" -BRIGHT_YELLOW_FG = "\033[93m" -BRIGHT_YELLOW_BG = "\033[103m" -RED_BG = "\033[41m" -BRIGHT_RED_FG = "\033[91m" -BRIGHT_RED_BG = "\033[101m" -MAGENTA_FG = "\033[35m" -MAGENTA_BG = "\033[45m" -RESET = "\033[0m" -SAVE_SCREEN = "\033[?1049h\033[?47h\033[H" -RESTORE_SCREEN = "\033[?47l\033[?1049l" - -ERROR = f"{RED_BG}{BRIGHT_WHITE_FG}[ERROR]{RESET}" -WARNING = f"{RED_BG}{BRIGHT_WHITE_FG}[WARNING]{RESET}" -INFO = f"{BRIGHT_CYAN_BG}{BLACK_FG}[INFO]{RESET}" - - -def clear(): - """Clears the terminal screen.""" - - # Windows - if os.name == "nt": - _ = os.system("cls") - - # Unix - else: - _ = os.system("clear") - - -class CliDriver: - """A basic CLI driver for TagStudio.""" - - def __init__(self, core, args): - self.core: TagStudioCore = core - self.lib = self.core.lib - self.filtered_entries: list[tuple[ItemType, int]] = [] - self.args = args - self.first_open: bool = True - self.first_browse: bool = True - self.is_missing_count_init: bool = False - self.is_new_file_count_init: bool = False - self.is_dupe_entry_count_init: bool = False - self.is_dupe_file_count_init: bool = False - - self.external_preview_size: tuple[int, int] = (960, 960) - epd_path = ( - Path(__file__).parents[2] / "resources/cli/images/external_preview.png" - ) - self.external_preview_default: Image = ( - Image.open(epd_path) - if epd_path.exists() - else Image.new(mode="RGB", size=(self.external_preview_size)) - ) - self.external_preview_default.thumbnail(self.external_preview_size) - epb_path = Path(__file__).parents[3] / "resources/cli/images/no_preview.png" - self.external_preview_broken: Image = ( - Image.open(epb_path) - if epb_path.exists() - else Image.new(mode="RGB", size=(self.external_preview_size)) - ) - self.external_preview_broken.thumbnail(self.external_preview_size) - - self.branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" - self.base_title: str = f"TagStudio {VERSION}{self.branch} - CLI Mode" - self.title_text: str = self.base_title - self.buffer = {} - - def start(self): - """Enters the CLI.""" - print(SAVE_SCREEN, end="") - try: - self.scr_main_menu() - except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - self.exit(save=False, backup=False) - except KeyboardInterrupt: - # traceback.print_exc() - print("\nForce Quitting TagStudio...") - # if self.lib and self.lib.library_dir: - # self.backup_library() - # self.cleanup_before_exit() - # sys.exit() - self.exit(save=False, backup=False) - except: - traceback.print_exc() - print("\nPress Enter to Continue...") - input() - # if self.lib and self.lib.library_dir: - # self.backup_library() - # self.cleanup_before_exit() - # sys.exit() - self.exit(save=False, backup=True) - # except: - # print( - # '\nAn Unknown Exception in TagStudio has Occurred. Press Enter to Continue...') - # input() - # # if self.lib and self.lib.library_dir: - # # self.backup_library() - # # self.cleanup_before_exit() - # # sys.exit() - # self.quit(save=False, backup=True) - - def cleanup_before_exit(self, restore_screen=True): - """Things do be done on application exit.""" - try: - if self.args.external_preview: - self.close_external_preview() - except Exception: - traceback.print_exc() - print("\nCrashed on Cleanup! This is unusual... Press Enter to Continue...") - input() - self.backup_library() - - if restore_screen: - print(f"{RESET}{RESTORE_SCREEN}", end="") - - def exit(self, save: bool, backup: bool): - """Exists TagStudio, and optionally saves and/or backs up data.""" - - if save: - print(f"{INFO} Saving Library to disk...") - self.save_library(display_message=False) - if backup: - print(f"{INFO} Saving Library changes to Backups folder...") - self.backup_library(display_message=False) - - self.cleanup_before_exit() - - try: - sys.exit() - except SystemExit: - sys.exit() - - def format_title(self, str, color=f"{BRIGHT_WHITE_FG}{MAGENTA_BG}") -> str: - """Formats a string with title formatting.""" - # Floating Pill (Requires NerdFont) - # return f'◀ {str} ▶'.center(os.get_terminal_size()[0], " ").replace('◀', '\033[96m\033[0m\033[30m\033[106m').replace('▶', '\033[0m\033[96m\033[0m') - # Solid Background - return f'{color}{str.center(os.get_terminal_size()[0], " ")[:os.get_terminal_size()[0]]}{RESET}' - - def format_subtitle(self, str, color=BRIGHT_CYAN_FG) -> str: - """Formats a string with subtitle formatting.""" - return f'{color}{(" "+str+" ").center(os.get_terminal_size()[0], "═")[:os.get_terminal_size()[0]]}{RESET}' - - def format_h1(self, str, color=BRIGHT_MAGENTA_FG) -> str: - """Formats a string with h1 formatting.""" - return f'{color}{("┫ "+str+" ┣").center(os.get_terminal_size()[0], "━")[:os.get_terminal_size()[0]]}{RESET}' - - def format_h2(self, str, color=BRIGHT_GREEN_FG) -> str: - """Formats a string with h2 formatting.""" - return f'{color}{(" "+str+" ").center(os.get_terminal_size()[0], "·")[:os.get_terminal_size()[0]]}{RESET}' - - def get_file_color(self, ext: str): - if ext.lower().replace(".", "", 1) == "gif": - return BRIGHT_YELLOW_FG - if ext.lower().replace(".", "", 1) in IMAGE_TYPES: - return WHITE_FG - elif ext.lower().replace(".", "", 1) in VIDEO_TYPES: - return BRIGHT_CYAN_FG - elif ext.lower().replace(".", "", 1) in DOC_TYPES: - return BRIGHT_GREEN_FG - else: - return BRIGHT_WHITE_FG - - def get_tag_color(self, color: str) -> str: - if color.lower() == "black": - return "\033[48;2;17;16;24m" + "\033[38;2;183;182;190m" - # return '\033[48;5;233m' + BRIGHT_WHITE_FG - elif color.lower() == "dark gray": - return "\033[48;2;36;35;42m" + "\033[38;2;189;189;191m" - # return '\033[48;5;233m' + BRIGHT_WHITE_FG - elif color.lower() == "gray": - return "\033[48;2;83;82;90m" + "\033[38;2;203;202;210m" - # return '\033[48;5;246m' + BRIGHT_WHITE_FG - elif color.lower() == "light gray": - return "\033[48;2;170;169;176m" + "\033[38;2;34;33;40m" - # return '\033[48;5;250m' + BLACK_FG - elif color.lower() == "white": - return "\033[48;2;242;241;248m" + "\033[38;2;48;47;54m" - # return '\033[48;5;231m' + '\033[38;5;244m' - elif color.lower() == "light pink": - return "\033[48;2;255;143;190m" + "\033[38;2;108;43;57m" - # return '\033[48;5;212m' + '\033[38;5;88m' - elif color.lower() == "pink": - return "\033[48;2;250;74;117m" + "\033[38;2;91;23;35m" - # return '\033[48;5;204m' + '\033[38;5;224m' - elif color.lower() == "magenta": - return "\033[48;2;224;43;132m" + "\033[38;2;91;13;54m" - # return '\033[48;5;197m' + '\033[38;5;224m' - elif color.lower() == "red": - return "\033[48;2;226;44;60m" + "\033[38;2;68;13;18m" - # return '\033[48;5;196m' + '\033[38;5;224m' - elif color.lower() == "red orange": - return "\033[48;2;232;55;38m" + "\033[38;2;97;18;11m" - # return '\033[48;5;202m' + '\033[38;5;221m' - elif color.lower() == "salmon": - return "\033[48;2;246;88;72m" + "\033[38;2;111;27;22m" - # return '\033[48;5;203m' + '\033[38;5;88m' - elif color.lower() == "orange": - return "\033[48;2;237;96;34m" + "\033[38;2;85;30;10m" - # return '\033[48;5;208m' + '\033[38;5;229m' - elif color.lower() == "yellow orange": - return "\033[48;2;250;154;44m" + "\033[38;2;102;51;13m" - # return '\033[48;5;214m' + '\033[38;5;88m' - elif color.lower() == "yellow": - return "\033[48;2;255;214;61m" + "\033[38;2;117;67;18m" - # return '\033[48;5;220m' + '\033[38;5;88m' - elif color.lower() == "mint": - return "\033[48;2;74;237;144m" + "\033[38;2;22;79;62m" - # return '\033[48;5;84m' + '\033[38;5;17m' - elif color.lower() == "lime": - return "\033[48;2;149;227;69m" + "\033[38;2;65;84;21m" - # return '\033[48;5;154m' + '\033[38;5;17m' - elif color.lower() == "light green": - return "\033[48;2;138;236;125m" + "\033[38;2;44;85;38m" - # return '\033[48;5;40m' + '\033[38;5;17m' - elif color.lower() == "green": - return "\033[48;2;40;187;72m" + "\033[38;2;13;56;40m" - # return '\033[48;5;28m' + '\033[38;5;191m' - elif color.lower() == "teal": - return "\033[48;2;23;191;157m" + "\033[38;2;7;58;68m" - # return '\033[48;5;36m' + '\033[38;5;17m' - elif color.lower() == "cyan": - return "\033[48;2;60;222;196m" + "\033[38;2;12;64;66m" - # return '\033[48;5;50m' + '\033[38;5;17m' - elif color.lower() == "light blue": - return "\033[48;2;85;187;246m" + "\033[38;2;18;37;65m" - # return '\033[48;5;75m' + '\033[38;5;17m' - elif color.lower() == "blue": - return "\033[48;2;59;99;240m" + "\033[38;2;158;192;249m" - # return '\033[48;5;27m' + BRIGHT_WHITE_FG - elif color.lower() == "blue violet": - return "\033[48;2;93;88;241m" + "\033[38;2;149;176;249m" - # return '\033[48;5;63m' + BRIGHT_WHITE_FG - elif color.lower() == "violet": - return "\033[48;2;120;60;239m" + "\033[38;2;187;157;247m" - # return '\033[48;5;57m' + BRIGHT_WHITE_FG - elif color.lower() == "purple": - return "\033[48;2;155;79;240m" + "\033[38;2;73;24;98m" - # return '\033[48;5;135m' + BRIGHT_WHITE_FG - elif color.lower() == "peach": - return "\033[48;2;241;198;156m" + "\033[38;2;97;63;47m" - # return '\033[48;5;223m' + '\033[38;5;88m' - elif color.lower() == "brown": - return "\033[48;2;130;50;22m" + "\033[38;2;205;157;131m" - # return '\033[48;5;130m' + BRIGHT_WHITE_FG - elif color.lower() == "lavender": - return "\033[48;2;173;142;239m" + "\033[38;2;73;43;101m" - # return '\033[48;5;141m' + '\033[38;5;17m' - elif color.lower() == "blonde": - return "\033[48;2;239;198;100m" + "\033[38;2;109;70;30m" - # return '\033[48;5;221m' + '\033[38;5;88m' - elif color.lower() == "auburn": - return "\033[48;2;161;50;32m" + "\033[38;2;217;138;127m" - # return '\033[48;5;88m' + '\033[38;5;216m' - elif color.lower() == "light brown": - return "\033[48;2;190;91;45m" + "\033[38;2;76;41;14m" - elif color.lower() == "dark brown": - return "\033[48;2;76;35;21m" + "\033[38;2;183;129;113m" - # return '\033[48;5;172m' + BRIGHT_WHITE_FG - elif color.lower() == "cool gray": - return "\033[48;2;81;87;104m" + "\033[38;2;158;161;195m" - # return '\033[48;5;102m' + BRIGHT_WHITE_FG - elif color.lower() == "warm gray": - return "\033[48;2;98;88;80m" + "\033[38;2;192;171;146m" - # return '\033[48;5;59m' + BRIGHT_WHITE_FG - elif color.lower() == "olive": - return "\033[48;2;76;101;46m" + "\033[38;2;180;193;122m" - # return '\033[48;5;58m' + '\033[38;5;193m' - elif color.lower() == "berry": - return "\033[48;2;159;42;167m" + "\033[38;2;204;143;220m" - else: - return "" - - def copy_field_to_buffer(self, entry_field) -> None: - """Copies an Entry Field object into the internal buffer.""" - self.buffer = dict(entry_field) - - def paste_field_from_buffer(self, entry_id) -> None: - """Merges or adds the Entry Field object in the internal buffer to the Entry.""" - if self.buffer: - # entry: Entry = self.lib.entries[entry_index] - # entry = self.lib.get_entry(entry_id) - field_id: int = self.lib.get_field_attr(self.buffer, "id") - content = self.lib.get_field_attr(self.buffer, "content") - - # NOTE: This code is pretty much identical to the match_conditions code - # found in the core. Could this be made generic? Especially for merging Entries. - if self.lib.get_field_obj(int(field_id))["type"] == "tag_box": - existing_fields: list[int] = self.lib.get_field_index_in_entry( - entry_id, field_id - ) - if existing_fields: - self.lib.update_entry_field( - entry_id, existing_fields[0], content, "append" - ) - else: - self.lib.add_field_to_entry(entry_id, field_id) - self.lib.update_entry_field(entry_id, -1, content, "append") - - if self.lib.get_field_obj(int(field_id))["type"] in TEXT_FIELDS: - if not self.lib.does_field_content_exist(entry_id, field_id, content): - self.lib.add_field_to_entry(entry_id, field_id) - self.lib.update_entry_field(entry_id, -1, content, "replace") - - # existing_fields: list[int] = self.lib.get_field_index_in_entry(entry_index, field_id) - # if existing_fields: - # self.lib.update_entry_field(entry_index, existing_fields[0], content, 'append') - # else: - # self.lib.add_field_to_entry(entry_index, field_id) - # self.lib.update_entry_field(entry_index, -1, content, 'replace') - - def init_external_preview(self) -> None: - """Initialized the external preview image file.""" - if self.lib and self.lib.library_dir: - external_preview_path: Path = ( - self.lib.library_dir / TS_FOLDER_NAME / "external_preview.jpg" - ) - if not external_preview_path.is_file(): - temp = self.external_preview_default - temp.save(external_preview_path) - open_file(external_preview_path) - - def set_external_preview_default(self) -> None: - """Sets the external preview to its default image.""" - if self.lib and self.lib.library_dir: - external_preview_path: Path = ( - self.lib.library_dir / TS_FOLDER_NAME / "external_preview.jpg" - ) - if external_preview_path.is_file(): - temp = self.external_preview_default - temp.save(external_preview_path) - - def set_external_preview_broken(self) -> None: - """Sets the external preview image file to the 'broken' placeholder.""" - if self.lib and self.lib.library_dir: - external_preview_path: Path = ( - self.lib.library_dir / TS_FOLDER_NAME / "external_preview.jpg" - ) - if external_preview_path.is_file(): - temp = self.external_preview_broken - temp.save(external_preview_path) - - def close_external_preview(self) -> None: - """Destroys and closes the external preview image file.""" - if self.lib and self.lib.library_dir: - external_preview_path: Path = ( - self.lib.library_dir / TS_FOLDER_NAME / "external_preview.jpg" - ) - if external_preview_path.is_file(): - os.remove(external_preview_path) - - def scr_create_library(self, path=None): - """Screen for creating a new TagStudio library.""" - - subtitle = "Create Library" - - clear() - print(f"{self.format_title(self.title_text)}") - print(self.format_subtitle(subtitle)) - print("") - - if not path: - print("Enter Library Folder Path: \n> ", end="") - path = input() - - path = Path(path) - - if path.exists(): - print("") - print( - f'{INFO} Are you sure you want to create a new Library at "{path}"? (Y/N)\n> ', - end="", - ) - con = input().lower() - if con == "y" or con == "yes": - result = self.lib.create_library(path) - if result == 0: - print( - f'{INFO} Created new TagStudio Library at: "{path}"\nPress Enter to Return to Main Menu...' - ) - input() - # self.open_library(path) - elif result == 1: - print( - f'{ERROR} Could not create Library. Path: "{path}" is pointing inside an existing TagStudio Folder.\nPress Enter to Return to Main Menu...' - ) - input() - elif result == 2: - print( - f'{ERROR} Could not write inside path: "{path}"\nPress Enter to Return to Main Menu...' - ) - input() - else: - print( - f'{ERROR} Invalid Path: "{path}"\nPress Enter to Return to Main Menu...' - ) - input() - # if Core.open_library(path) == 1: - # self.library_name = path - # self.scr_library_home() - # else: - # print(f'[ERROR]: No existing TagStudio library found at \'{path}\'') - # self.scr_main_menu() - - def open_library(self, path): - """Opens a TagStudio library.""" - - return_code = self.lib.open_library(path) - if return_code == 1: - # self.lib = self.core.library - if self.args.external_preview: - self.init_external_preview() - - if len(self.lib.entries) <= 1000: - print( - f"{INFO} Checking for missing files in Library '{self.lib.library_dir}'..." - ) - self.lib.refresh_missing_files() - # else: - # print( - # f'{INFO} Automatic missing file refreshing is turned off for large libraries (1,000+ Entries)') - self.title_text: str = self.base_title + "" - self.scr_library_home() - else: - clear() - print(f"{ERROR} No existing TagStudio library found at '{path}'") - self.scr_main_menu(clear_scr=False) - - def close_library(self, save=True): - """ - Saves (by default) and clears the current Library as well as related operations. - Does *not* direct the navigation back to the main menu, that's not my job. - """ - if save: - self.lib.save_library_to_disk() - if self.args.external_preview: - self.close_external_preview() - self.lib.clear_internal_vars() - - def backup_library(self, display_message: bool = True) -> bool: - """Saves a backup copy of the Library file to disk. Returns True if successful.""" - if self.lib and self.lib.library_dir: - filename = self.lib.save_library_backup_to_disk() - location = self.lib.library_dir / TS_FOLDER_NAME / "backups" / filename - if display_message: - print(f'{INFO} Backup of Library saved at "{location}".') - return True - return False - - def save_library(self, display_message: bool = True) -> bool: - """Saves the Library file to disk. Returns True if successful.""" - if self.lib and self.lib.library_dir: - self.lib.save_library_to_disk() - if display_message: - print(f"{INFO} Library saved to disk.") - return True - return False - - def get_char_limit(self, text: str) -> int: - """ - Returns an estimated value for how many characters of a block of text should be allowed to display before being truncated. - """ - # char_limit: int = ( - # (os.get_terminal_size()[0] * os.get_terminal_size()[1]) // 6) - # char_limit -= (text.count('\n') + text.count('\r') * (os.get_terminal_size()[0] // 1.0)) - # char_limit = char_limit if char_limit > 0 else min(40, len(text)) - - char_limit: int = os.get_terminal_size()[0] * (os.get_terminal_size()[1] // 5) - char_limit -= (text.count("\n") + text.count("\r")) * ( - os.get_terminal_size()[0] // 2 - ) - char_limit = char_limit if char_limit > 0 else min((64), len(text)) - - # print(f'Char Limit: {char_limit}, Len: {len(text)}') - return char_limit - - def truncate_text(self, text: str) -> str: - """Returns a truncated string for displaying, calculated with `get_char_limit()`.""" - if len(text) > self.get_char_limit(text): - # print(f'Char Limit: {self.get_char_limit(text)}, Len: {len(text)}') - return f"{text[:int(self.get_char_limit(text) - 1)]} {WHITE_FG}[...]{RESET}" - else: - return text - - def print_fields(self, index) -> None: - """Prints an Entry's formatted fields to the screen.""" - entry = self.lib.entries[index] - - if entry and self.args.debug: - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} ID: {RESET} ", end="") - print(entry.id_) - - if entry and entry.fields: - for i, field in enumerate(entry.fields): - # Buffer between box fields below other fields if this isn't the first field - if ( - i != 0 - and self.lib.get_field_attr(field, "type") in BOX_FIELDS - and self.lib.get_field_attr(entry.fields[i - 1], "type") - not in BOX_FIELDS - ): - print("") - # Format the field title differently for box fields. - if self.lib.get_field_attr(field, "type") in BOX_FIELDS: - print( - f'{BRIGHT_WHITE_BG}{BLACK_FG} {self.lib.get_field_attr(field, "name")}: {RESET} ', - end="\n", - ) - else: - print( - f'{BRIGHT_WHITE_BG}{BLACK_FG} {self.lib.get_field_attr(field, "name")}: {RESET} ', - end="", - ) - if self.lib.get_field_attr(field, "type") == "tag_box": - char_count: int = 0 - for tag_id in self.lib.get_field_attr(field, "content"): - tag = self.lib.get_tag(tag_id) - # Properly wrap Tags on screen - char_count += len(f" {tag.display_name(self.lib)} ") + 1 - if char_count > os.get_terminal_size()[0]: - print("") - char_count = len(f" {tag.display_name(self.lib)} ") + 1 - print( - f"{self.get_tag_color(tag.color)} {tag.display_name(self.lib)} {RESET}", - end="", - ) - # If the tag isn't the last one, print a space for the next one. - if tag_id != self.lib.get_field_attr(field, "content")[-1]: - print(" ", end="") - else: - print("") - elif self.lib.get_field_attr(field, "type") in TEXT_FIELDS: - # Normalize line endings in any text content. - text: str = self.lib.get_field_attr(field, "content").replace( - "\r", "\n" - ) - print(self.truncate_text(text)) - elif self.lib.get_field_attr(field, "type") == "datetime": - try: - # TODO: Localize this and/or add preferences. - date = dt.strptime( - self.lib.get_field_attr(field, "content"), - "%Y-%m-%d %H:%M:%S", - ) - print(date.strftime("%D - %r")) - except: - print(self.lib.get_field_attr(field, "content")) - else: - print(self.lib.get_field_attr(field, "content")) - - # Buffer between box fields above other fields if this isn't the last field - if ( - entry.fields[i] != entry.fields[-1] - and self.lib.get_field_attr(field, "type") in BOX_FIELDS - ): - print("") - else: - # print(f'{MAGENTA_BG}{BRIGHT_WHITE_FG}[No Fields]{RESET}{WHITE_FG} (Run \'edit\', then \'add \' to add some!){RESET}') - print(f"{MAGENTA_BG}{BRIGHT_WHITE_FG}[No Fields]{RESET}{WHITE_FG}") - - def print_thumbnail( - self, index, filepath="", ignore_fields=False, max_width=-1 - ) -> None: - """ - Prints an Entry's formatted thumbnail to the screen. - Takes in either an Entry index or a direct filename. - """ - entry = None if index < 0 else self.lib.entries[index] - if entry: - filepath = self.lib.library_dir / entry.path / entry.filename - external_preview_path: Path = None - if self.args.external_preview: - external_preview_path = ( - self.lib.library_dir / TS_FOLDER_NAME / "external_preview.jpg" - ) - # thumb_width = min( - # os.get_terminal_size()[0]//2, - # math.floor(os.get_terminal_size()[1]*0.5)) - # thumb_width = math.floor(os.get_terminal_size()[1]*0.5) - - # if entry: - file_type = os.path.splitext(filepath)[1].lower()[1:] - if file_type in (IMAGE_TYPES + VIDEO_TYPES): - # TODO: Make the image-grabbing part try to get thumbnails. - - # Lots of calculations to determine an image width that works well. - w, h = (1, 1) - final_img_path = filepath - if file_type in IMAGE_TYPES: - try: - raw = Image.open(filepath) - w, h = raw.size - # NOTE: Temporary way to hack a non-terminal preview. - if self.args.external_preview: - raw = raw.convert("RGB") - # raw.thumbnail((512, 512)) - raw.thumbnail(self.external_preview_size) - raw.save(external_preview_path) - except ( - UnidentifiedImageError, - FileNotFoundError, - DecompressionBombError, - ) as e: - print(f'{ERROR} Could not load image "{filepath} due to {e}"') - if self.args.external_preview: - self.set_external_preview_broken() - elif file_type in VIDEO_TYPES: - try: - video = cv2.VideoCapture(filepath) - video.set( - cv2.CAP_PROP_POS_FRAMES, - (video.get(cv2.CAP_PROP_FRAME_COUNT) // 2), - ) - success, frame = video.read() - if not success: - # Depending on the video format, compression, and frame - # count, seeking halfway does not work and the thumb - # must be pulled from the earliest available frame. - video.set(cv2.CAP_PROP_POS_FRAMES, 0) - success, frame = video.read() - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - final_frame = Image.fromarray(frame) - w, h = final_frame.size - final_frame.save( - self.lib.library_dir / TS_FOLDER_NAME / "temp.jpg", quality=50 - ) - final_img_path = self.lib.library_dir / TS_FOLDER_NAME / "temp.jpg" - # NOTE: Temporary way to hack a non-terminal preview. - if self.args.external_preview and entry: - final_frame.thumbnail(self.external_preview_size) - final_frame.save(external_preview_path) - except SystemExit: - sys.exit() - except: - print(f'{ERROR} Could not load video thumbnail for "{filepath}"') - if self.args.external_preview and entry: - self.set_external_preview_broken() - pass - - img_ratio: float = w / h - term_ratio_norm: float = ( - os.get_terminal_size()[1] / os.get_terminal_size()[0] - ) * 2 - base_mod: float = 0.7 - field_cnt_mod: float = 0 - desc_len_mod: float = 0 - tag_cnt_mod: float = 0 - if entry and entry.fields and not ignore_fields: - field_cnt_mod = 1.5 * len(entry.fields) - for f in entry.fields: - if self.lib.get_field_attr(f, "type") == "tag_box": - tag_cnt_mod += 0.5 * len(self.lib.get_field_attr(f, "content")) - elif self.lib.get_field_attr(f, "type") == "text_box": - desc_len_mod += 0.07 * len( - self.truncate_text(self.lib.get_field_attr(f, "content")) - ) - desc_len_mod += 1.7 * self.truncate_text( - self.lib.get_field_attr(f, "content") - ).count("\n") - desc_len_mod += 1.7 * self.truncate_text( - self.lib.get_field_attr(f, "content") - ).count("\r") - try: - thumb_width = min( - math.floor( - ( - os.get_terminal_size()[0] - * img_ratio - * term_ratio_norm - * base_mod - ) - - ( - (field_cnt_mod + desc_len_mod + tag_cnt_mod) - * (img_ratio * 0.7) - ) - ), - os.get_terminal_size()[0], - ) - if max_width > 0: - thumb_width = max_width if thumb_width > max_width else thumb_width - # image = climage.convert(final_img_path, is_truecolor=True, is_256color=False, - # is_16color=False, is_8color=False, width=thumb_width) - # Center Alignment Hack - spacing = (os.get_terminal_size()[0] - thumb_width) // 2 - if not self.args.external_preview or not entry: - print(" " * spacing, end="") - print(image.replace("\n", ("\n" + " " * spacing))) - - if file_type in VIDEO_TYPES: - os.remove(self.lib.library_dir / TS_FOLDER_NAME / "temp.jpg") - except: - if not self.args.external_preview or not entry: - print( - f"{ERROR} Could not display preview. Is there enough screen space?" - ) - - def print_columns(self, content: list[object], add_enum: False) -> None: - """ - Prints content in a column format. - Content: A list of tuples list[(element, formatting)] - """ - try: - if content: - # This is an estimate based on the existing screen formatting. - margin: int = 7 - enum_padding: int = 0 - term_width: int = os.get_terminal_size()[0] - - num_width: int = len(str(len(content) + 1)) - if add_enum: - enum_padding = num_width + 2 - - longest_width: int = ( - len(max(content, key=lambda x: len(x[0]))[0]) + 1 - ) # + Padding - column_count: int = term_width // (longest_width + enum_padding + 3) - column_count: int = column_count if column_count > 0 else 1 - max_display: int = column_count * (os.get_terminal_size()[1] - margin) - displayable: int = min(max_display, len(content)) - - # Recalculate based on displayable items - num_width = len(str(len(content[:max_display]) + 1)) - if add_enum: - enum_padding = num_width + 2 - longest_width = ( - len(max(content[:max_display], key=lambda x: len(x[0]))[0]) + 1 - ) - column_count = term_width // (longest_width + enum_padding + 3) - column_count = column_count if column_count > 0 else 1 - max_display = column_count * (os.get_terminal_size()[1] - margin) - # displayable: int = min(max_display, len(content)) - - num_width = len(str(len(content[:max_display]) + 1)) - if add_enum: - enum_padding = num_width + 2 - # longest_width = len(max(content[:max_display], key=lambda x: len(x[0]))[0]) + 1 - # column_count = term_width // (longest_width + enum_padding + 3) - # column_count = column_count if column_count > 0 else 1 - # max_display = column_count * (os.get_terminal_size()[1]-margin) - - # print(num_width) - # print(term_width) - # print(longest_width) - # print(columns) - # print(max(content, key = lambda x : len(x[0]))) - # print(len(max(content, key = lambda x : len(x[0]))[0])) - - # # Prints out the list in a left-to-right tabular column form with color formatting. - # for i, element in enumerate(content): - # if i != 0 and i % (columns-1) == 0: - # print('') - # if add_enum: - # print(f'{element[1]}[{str(i+1).zfill(num_width)}] {element[0]} {RESET}', end='') - # else: - # print(f'{element[1]} {element[0]} {RESET}', end='') - # print(' ' * (longest_width - len(element[0])), end='') - - # Prints out the list in a top-down tabular column form with color formatting. - # This is my greatest achievement. - row_count: int = math.floor(len(content) / column_count) - table_size: int = row_count * column_count - table_size = table_size if table_size > 0 else 1 - # print(f'Rows:{max_rows}, Cols:{max_columns}') - row_num = 1 - col_num = 1 - for i, element in enumerate(content): - if i < max_display: - if row_count > 1: - row_number = i // column_count - index = (i * row_count) - (row_number * (table_size - 1)) - # col_number = index // math.ceil(len(content) / max_columns) - offset: int = 0 - if displayable % table_size == 1: - offset = ( - 1 - if (index >= row_count) - and (row_number != row_count) - else 0 - ) - elif displayable % table_size != 0: - if 1 < col_num <= displayable % table_size: - offset += col_num - 1 - elif col_num > 1 and col_num > displayable % table_size: - offset = displayable % table_size - - if ( - col_num > 1 - and (os.get_terminal_size()[1] - margin) < row_count - ): - offset -= ( - row_count - (os.get_terminal_size()[1] - margin) - ) * (col_num - 1) + (col_num - 1) - - # print(f'{row_count}/{(os.get_terminal_size()[1]-margin)}', end='') - - index += offset - # print(offset, end='') - # print(f'{row_num}-{col_num}', end='') - else: - index = i - if i != 0 and i % column_count == 0: - row_num += 1 - col_num = 1 - print("") - if index < len(content): - col_num += 1 - col_num = col_num if col_num <= column_count else 1 - if add_enum: - print( - f"{content[index][1]}[{str(index+1).zfill(num_width)}] {content[index][0]} {RESET}", - end="", - ) - else: - print( - f"{content[index][1]} {content[index][0]} {RESET}", - end="", - ) - if row_count > 0: - print( - " " * (longest_width - len(content[index][0])), - end="", - ) - else: - print(" ", end="") - else: - print( - "\n" - + self.format_h2(f"[{len(content) - max_display} More...]"), - end="", - ) - # print(WHITE_FG + '\n' + f'[{len(content) - max_display} More...]'.center(os.get_terminal_size()[0], " ")[:os.get_terminal_size()[0]]+RESET) - # print(f'\n{WHITE_FG}[{{RESET}', end='') - break - # print(f'Rows:{row_count}, Cols:{column_count}') - print("") - - except Exception: - traceback.print_exc() - print("\nPress Enter to Continue...") - input() - pass - - def run_macro(self, name: str, entry_id: int): - """Runs a specific Macro on an Entry given a Macro name.""" - # entry: Entry = self.lib.get_entry_from_index(entry_id) - entry = self.lib.get_entry(entry_id) - path = self.lib.library_dir / entry.path / entry.filename - source = path.split(os.sep)[1].lower() - if name == "sidecar": - self.lib.add_generic_data_to_entry( - self.core.get_gdl_sidecar(path, source), entry_id - ) - elif name == "autofill": - self.run_macro("sidecar", entry_id) - self.run_macro("build-url", entry_id) - self.run_macro("match", entry_id) - self.run_macro("clean-url", entry_id) - self.run_macro("sort-fields", entry_id) - elif name == "build-url": - data = {"source": self.core.build_url(entry_id, source)} - self.lib.add_generic_data_to_entry(data, entry_id) - elif name == "sort-fields": - order: list[int] = ( - [0] - + [1, 2] - + [9, 17, 18, 19, 20] - + [10, 14, 11, 12, 13, 22] - + [4, 5] - + [8, 7, 6] - + [3, 21] - ) - self.lib.sort_fields(entry_id, order) - elif name == "match": - self.core.match_conditions(entry_id) - elif name == "scrape": - self.core.scrape(entry_id) - elif name == "clean-url": - # entry = self.lib.get_entry_from_index(entry_id) - if entry.fields: - for i, field in enumerate(entry.fields, start=0): - if self.lib.get_field_attr(field, "type") == "text_line": - self.lib.update_entry_field( - entry_id=entry_id, - field_index=i, - content=strip_web_protocol( - self.lib.get_field_attr(field, "content") - ), - mode="replace", - ) - - def create_collage(self) -> str: - """Generates and saves an image collage based on Library Entries.""" - - run: bool = True - keep_aspect: bool = False - data_only_mode: bool = False - data_tint_mode: bool = False - - mode: int = self.scr_choose_option( - subtitle="Choose Collage Mode(s)", - choices=[ - ( - "Normal", - "Creates a standard square image collage made up of Library media files.", - ), - ( - "Data Tint", - "Tints the collage with a color representing data about the Library Entries/files.", - ), - ( - "Data Only", - "Ignores media files entirely and only outputs a collage of Library Entry/file data.", - ), - ("Normal & Data Only", "Creates both Normal and Data Only collages."), - ], - prompt="", - required=True, - ) - - if mode == 1: - data_tint_mode = True - - if mode == 2: - data_only_mode = True - - if mode in [0, 1, 3]: - keep_aspect = self.scr_choose_option( - subtitle="Choose Aspect Ratio Option", - choices=[ - ( - "Stretch to Fill", - "Stretches the media file to fill the entire collage square.", - ), - ( - "Keep Aspect Ratio", - "Keeps the original media file's aspect ratio, filling the rest of the square with black bars.", - ), - ], - prompt="", - required=True, - ) - - if mode in [1, 2, 3]: - # TODO: Choose data visualization options here. - pass - - full_thumb_size: int = 1 - - if mode in [0, 1, 3]: - full_thumb_size = self.scr_choose_option( - subtitle="Choose Thumbnail Size", - choices=[ - ("Tiny (32px)", ""), - ("Small (64px)", ""), - ("Medium (128px)", ""), - ("Large (256px)", ""), - ("Extra Large (512px)", ""), - ], - prompt="", - required=True, - ) - - thumb_size: int = ( - 32 - if (full_thumb_size == 0) - else 64 - if (full_thumb_size == 1) - else 128 - if (full_thumb_size == 2) - else 256 - if (full_thumb_size == 3) - else 512 - if (full_thumb_size == 4) - else 32 - ) - - # if len(com) > 1 and com[1] == 'keep-aspect': - # keep_aspect = True - # elif len(com) > 1 and com[1] == 'data-only': - # data_only_mode = True - # elif len(com) > 1 and com[1] == 'data-tint': - # data_tint_mode = True - grid_size = math.ceil(math.sqrt(len(self.lib.entries))) ** 2 - grid_len = math.floor(math.sqrt(grid_size)) - thumb_size = thumb_size if not data_only_mode else 1 - img_size = thumb_size * grid_len - - print( - f"Creating collage for {len(self.lib.entries)} Entries.\nGrid Size: {grid_size} ({grid_len}x{grid_len})\nIndividual Picture Size: ({thumb_size}x{thumb_size})" - ) - if keep_aspect: - print("Keeping original aspect ratios.") - if data_only_mode: - print("Visualizing Entry Data") - - if not data_only_mode: - time.sleep(5) - - collage = Image.new("RGB", (img_size, img_size)) - filename = ( - elf.lib.library_dir - / TS_FOLDER_NAME - / COLLAGE_FOLDER_NAME - / f'collage_{datetime.datetime.utcnow().strftime("%F_%T").replace(":", "")}.png' - ) - - i = 0 - for x in range(0, grid_len): - for y in range(0, grid_len): - try: - if i < len(self.lib.entries) and run: - # entry: Entry = self.lib.get_entry_from_index(i) - entry = self.lib.entries[i] - filepath = self.lib.library_dir / entry.path / entry.filename - color: str = "" - - if data_tint_mode or data_only_mode: - color = "#000000" # Black (Default) - - if entry.fields: - has_any_tags: bool = False - has_content_tags: bool = False - has_meta_tags: bool = False - for field in entry.fields: - if ( - self.lib.get_field_attr(field, "type") - == "tag_box" - ): - if self.lib.get_field_attr(field, "content"): - has_any_tags = True - if ( - self.lib.get_field_attr(field, "id") - == 7 - ): - has_content_tags = True - elif ( - self.lib.get_field_attr(field, "id") - == 8 - ): - has_meta_tags = True - if has_content_tags and has_meta_tags: - color = "#28bb48" # Green - elif has_any_tags: - color = "#ffd63d" # Yellow - # color = '#95e345' # Yellow-Green - else: - # color = '#fa9a2c' # Yellow-Orange - color = "#ed8022" # Orange - else: - color = "#e22c3c" # Red - - if data_only_mode: - pic: Image = Image.new( - "RGB", (thumb_size, thumb_size), color - ) - collage.paste(pic, (y * thumb_size, x * thumb_size)) - if not data_only_mode: - print( - f"\r{INFO} Combining [{i+1}/{len(self.lib.entries)}]: {self.get_file_color(filepath.suffix.lower())}{entry.path}{os.sep}{entry.filename}{RESET}" - ) - # sys.stdout.write(f'\r{INFO} Combining [{i+1}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}{RESET}') - # sys.stdout.flush() - - if filepath.suffix.lower() in IMAGE_TYPES: - try: - with Image.open( - self.lib.library_dir - / entry.path - / entry.filename - ) as pic: - if keep_aspect: - pic.thumbnail((thumb_size, thumb_size)) - else: - pic = pic.resize((thumb_size, thumb_size)) - if data_tint_mode and color: - pic = pic.convert(mode="RGB") - pic = ImageChops.hard_light( - pic, - Image.new( - "RGB", - (thumb_size, thumb_size), - color, - ), - ) - collage.paste( - pic, (y * thumb_size, x * thumb_size) - ) - except DecompressionBombError as e: - print( - f"[ERROR] One of the images was too big ({e})" - ) - - elif filepath.suffix.lower() in VIDEO_TYPES: - video = cv2.VideoCapture(filepath) - video.set( - cv2.CAP_PROP_POS_FRAMES, - (video.get(cv2.CAP_PROP_FRAME_COUNT) // 2), - ) - success, frame = video.read() - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - with Image.fromarray(frame, mode="RGB") as pic: - if keep_aspect: - pic.thumbnail((thumb_size, thumb_size)) - else: - pic = pic.resize((thumb_size, thumb_size)) - if data_tint_mode and color: - pic = ImageChops.hard_light( - pic, - Image.new( - "RGB", (thumb_size, thumb_size), color - ), - ) - collage.paste(pic, (y * thumb_size, x * thumb_size)) - except UnidentifiedImageError: - print(f"\n{ERROR} Couldn't read {entry.path / entry.filename}") - except KeyboardInterrupt: - # self.quit(save=False, backup=True) - run = False - clear() - print(f"{INFO} Collage operation cancelled.") - clear_scr = False - except: - print(f"{ERROR} {entry.path / entry.filename}") - traceback.print_exc() - print("Continuing...") - i = i + 1 - - if run: - self.lib.verify_ts_folders() - collage.save(filename) - return filename - return "" - - def global_commands(self, com: list[str]) -> tuple[bool, str]: - """ - Executes from a set of global commands.\n - Returns a (bool,str) tuple containing (was command executed?, optional command message) - """ - was_executed: bool = False - message: str = "" - com_name = com[0].lower() - - # Backup Library ======================================================= - if com_name == "backup": - self.backup_library(display_message=False) - was_executed = True - message = f"{INFO} Backed up Library to disk." - # Create Collage ======================================================= - elif com_name == "collage": - filename = self.create_collage() - if filename: - was_executed = True - message = f'{INFO} Saved collage to "{filename}".' - # Save Library ========================================================= - elif com_name in ("save", "write", "w"): - self.save_library(display_message=False) - was_executed = True - message = f"{INFO} Library saved to disk." - # Toggle Debug ========================================================= - elif com_name == "toggle-debug": - self.args.debug = not self.args.debug - was_executed = True - message = ( - f"{INFO} Debug Mode Active." - if self.args.debug - else f"{INFO} Debug Mode Deactivated." - ) - # Toggle External Preview ============================================== - elif com_name == "toggle-external-preview": - self.args.external_preview = not self.args.external_preview - if self.args.external_preview: - self.init_external_preview() - else: - self.close_external_preview() - was_executed = True - message = ( - f"{INFO} External Preview Enabled." - if self.args.external_preview - else f"{INFO} External Preview Disabled." - ) - # Quit ================================================================= - elif com_name in ("quit", "q"): - self.exit(save=True, backup=False) - was_executed = True - # Quit without Saving ================================================== - elif com_name in ("quit!", "q!"): - self.exit(save=False, backup=False) - was_executed = True - - return (was_executed, message) - - def scr_browse_help(self, prev) -> None: - """A Help screen for commands available during Library Browsing.""" - pass - - def scr_main_menu(self, clear_scr=True): - """The CLI main menu.""" - - while True: - if self.args.open and self.first_open: - self.first_open = False - self.open_library(self.args.open) - - if clear_scr: - clear() - clear_scr = True - print(f"{self.format_title(self.title_text)}") - print("") - print(f"\t{BRIGHT_WHITE_FG}{MAGENTA_BG} - Basic Commands - {RESET}") - print(f"\t\tOpen Library: {WHITE_FG}open | o {RESET}") - print(f"\t\tCreate New Library: {WHITE_FG}new | n {RESET}") - # print(f'\t\tHelp: {WHITE_FG}help | h{RESET}') - print("") - print(f"\t\tQuit TagStudio: {WHITE_FG}quit | q{RESET}") - print("") - print( - f"\t💡TIP: {WHITE_FG}TagStudio can be launched with the --open (or -o) option followed\n\t\tby to immediately open a library!{RESET}" - ) - print("") - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - if com[0].lower() == "open" or com[0].lower() == "o": - if len(com) > 1: - self.open_library(com[1]) - elif com[0].lower() == "new" or com[0].lower() == "n": - if len(com) > 1: - self.scr_create_library(com[1]) - # elif (com[0].lower() == 'toggle-debug'): - # self.args.debug = not self.args.debug - # elif com[0].lower() in ['quit', 'q', 'close', 'c']: - # sys.exit() - # elif com[0].lower() in ['quit!', 'q!']: - # sys.exit() - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - - def scr_library_home(self, clear_scr=True): - """Home screen for an opened Library.""" - - while True: - subtitle = f"Library '{self.lib.library_dir}'" - if self.lib.is_legacy_library: - subtitle += " (Legacy Format)" - if self.args.debug: - subtitle += " (Debug Mode Active)" - # Directory Info ------------------------------------------------------- - file_count: str = ( - f"{BRIGHT_YELLOW_FG}N/A (Run 'refresh dir' to update){RESET}" - if self.lib.dir_file_count == -1 - else f"{WHITE_FG}{self.lib.dir_file_count}{RESET}" - ) - - new_file_count: str = ( - f"{BRIGHT_YELLOW_FG}N/A (Run 'refresh dir' to update){RESET}" - if ( - self.lib.files_not_in_library == [] - and not self.is_new_file_count_init - ) - else f"{WHITE_FG}{len(self.lib.files_not_in_library)}{RESET}" - ) - - # Issues --------------------------------------------------------------- - missing_file_count: str = ( - f"{BRIGHT_YELLOW_FG}N/A (Run 'refresh missing' to update){RESET}" - if (self.lib.missing_files == [] and not self.is_missing_count_init) - else f"{BRIGHT_RED_FG}{len(self.lib.missing_files)}{RESET}" - ) - missing_file_count = ( - f"{BRIGHT_GREEN_FG}0{RESET}" - if (self.is_missing_count_init and len(self.lib.missing_files) == 0) - else missing_file_count - ) - - dupe_entry_count: str = ( - f"{BRIGHT_YELLOW_FG}N/A (Run 'refresh dupe entries' to update){RESET}" - if (self.lib.dupe_entries == [] and not self.is_dupe_entry_count_init) - else f"{BRIGHT_RED_FG}{len(self.lib.dupe_entries)}{RESET}" - ) - dupe_entry_count = ( - f"{BRIGHT_GREEN_FG}0{RESET}" - if (self.is_dupe_entry_count_init and len(self.lib.dupe_entries) == 0) - else dupe_entry_count - ) - - dupe_file_count: str = ( - f"{BRIGHT_YELLOW_FG}N/A (Run 'refresh dupe files' to update){RESET}" - if (self.lib.dupe_files == [] and not self.is_dupe_file_count_init) - else f"{BRIGHT_RED_FG}{len(self.lib.dupe_files)}{RESET}" - ) - dupe_file_count = ( - f"{BRIGHT_GREEN_FG}0{RESET}" - if (self.is_dupe_file_count_init and len(self.lib.dupe_files) == 0) - else dupe_file_count - ) - # fixed_file_count: str = 'N/A (Run \'fix missing\' to refresh)' if self.lib.fixed_files == [ - # ] else len(self.lib.fixed_files) - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(self.base_title)) - print(self.format_subtitle(subtitle)) - print("") - - if self.args.browse and self.first_browse: - self.first_browse = False - self.filtered_entries = self.lib.search_library() - self.scr_browse_entries_gallery(0) - else: - print(f"\t{BRIGHT_CYAN_BG}{BLACK_FG} - Library Info - {RESET}") - print(f"\t Entries: {WHITE_FG}{len(self.lib.entries)}{RESET}") - # print(f'\tCollations: {WHITE_FG}0{RESET}') - print(f"\t Tags: {WHITE_FG}{len(self.lib.tags)}{RESET}") - print(f"\t Fields: {WHITE_FG}{len(self.lib.default_fields)}{RESET}") - # print(f'\t Macros: {WHITE_FG}0{RESET}') - print("") - print(f"\t{BRIGHT_CYAN_BG}{BLACK_FG} - Directory Info - {RESET}") - print(f"\t Media Files: {file_count} (0 KB)") - print(f"\tNot in Library: {new_file_count} (0 KB)") - # print(f'\t Sidecar Files: 0 (0 KB)') - # print(f'\t Total Files: 0 (0 KB)') - print("") - print(f"\t{BRIGHT_CYAN_BG}{BLACK_FG} - Issues - {RESET}") - print(f"\t Missing Files: {missing_file_count}") - print(f"\tDuplicate Entries: {dupe_entry_count}") - print(f"\t Duplicate Files: {dupe_file_count}") - # print(f' Fixed Files: {WHITE_FG}{fixed_file_count}{RESET}') - print("") - print(f"\t{BRIGHT_WHITE_FG}{MAGENTA_BG} - Basic Commands - {RESET}") - - print(f"\tBrowse Library: {WHITE_FG}browse | b{RESET}") - print(f"\tSearch Library: {WHITE_FG}search | s < query >{RESET}") - print( - f"\tList Info: {WHITE_FG}list | ls < dir | entires | tags | fields | macros | new | missing >{RESET}" - ) - print(f"\tAdd New Files to Library: {WHITE_FG}add new{RESET}") - print( - f"\tRefresh Info: {WHITE_FG}refresh | r < dir | missing | dupe entries | dupe files >{RESET}" - ) - print( - f"\tFix Issues: {WHITE_FG}fix < missing | dupe entries | dupe files > {RESET}" - ) - # print(f'\tHelp: {WHITE_FG}help | h{RESET}') - - print("") - print(f"\tSave Library: {WHITE_FG}save | backup{RESET}") - print(f"\tClose Library: {WHITE_FG}close | c{RESET}") - print(f"\tQuit TagStudio: {WHITE_FG}quit | q{RESET}") - # print(f'Quit Without Saving: {WHITE_FG}quit! | q!{RESET}') - print("") - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # Refresh ============================================================== - if (com[0].lower() == "refresh" or com[0].lower() == "r") and len( - com - ) > 1: - if com[1].lower() == "files" or com[1].lower() == "dir": - print( - f"{INFO} Scanning for files in '{self.lib.library_dir}'..." - ) - self.lib.refresh_dir() - self.is_new_file_count_init = True - elif com[1].lower() == "missing": - print( - f"{INFO} Checking for missing files in '{self.lib.library_dir}'..." - ) - self.lib.refresh_missing_files() - self.is_missing_count_init = True - elif com[1].lower() == "duplicate" or com[1].lower() == "dupe": - if len(com) > 2: - if com[2].lower() == "entries" or com[2].lower() == "e": - print( - f"{INFO} Checking for duplicate entries in Library '{self.lib.library_dir}'..." - ) - self.lib.refresh_dupe_entries() - self.is_dupe_entry_count_init = True - elif com[2].lower() == "files" or com[2].lower() == "f": - print( - f"{WHITE_FG}Enter the filename for your DupeGuru results file:\n> {RESET}", - end="", - ) - dg_results_file = Path(input()) - print( - f"{INFO} Checking for duplicate files in Library '{self.lib.library_dir}'..." - ) - self.lib.refresh_dupe_files(dg_results_file) - self.is_dupe_file_count_init = True - else: - clear() - print( - f'{ERROR} Specify which duplicates to refresh (files, entries, all) \'{" ".join(com)}\'' - ) - clear_scr = False - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - # List ================================================================= - elif (com[0].lower() == "list" or com[0].lower() == "ls") and len( - com - ) > 1: - if com[1].lower() == "entries": - for i, e in enumerate(self.lib.entries, start=0): - title = f"[{i+1}/{len(self.lib.entries)}] {self.lib.entries[i].path / os.path.sep / self.lib.entries[i].filename}" - print( - self.format_subtitle( - title, - color=self.get_file_color( - os.path.splitext( - self.lib.entries[i].filename - )[1] - ), - ) - ) - self.print_fields(i) - print("") - time.sleep(0.05) - print("Press Enter to Continue...") - input() - elif com[1].lower() == "new": - for i in self.lib.files_not_in_library: - print(i) - time.sleep(0.1) - print("Press Enter to Continue...") - input() - elif com[1].lower() == "missing": - for i in self.lib.missing_files: - print(i) - time.sleep(0.1) - print("Press Enter to Continue...") - input() - elif com[1].lower() == "fixed": - for i in self.lib.fixed_files: - print(i) - time.sleep(0.1) - print("Press Enter to Continue...") - input() - elif com[1].lower() == "files" or com[1].lower() == "dir": - # NOTE: This doesn't actually print the directory files, it just prints - # files that are attached to Entries. Should be made consistent. - # print(self.lib.file_to_entry_index_map.keys()) - for key in self.lib.filename_to_entry_id_map.keys(): - print(key) - time.sleep(0.05) - print("Press Enter to Continue...") - input() - elif com[1].lower() == "duplicate" or com[1].lower() == "dupe": - if len(com) > 2: - if com[2].lower() == "entries" or com[2].lower() == "e": - for dupe in self.lib.dupe_entries: - print( - self.lib.entries[dupe[0]].path - / self.lib.entries[dupe[0]].filename - ) - for d in dupe[1]: - print( - f"\t-> {(self.lib.entries[d].path / self.lib.entries[d].filename)}" - ) - time.sleep(0.1) - print("Press Enter to Continue...") - input() - elif com[2].lower() == "files" or com[2].lower() == "f": - for dupe in self.lib.dupe_files: - print(dupe) - time.sleep(0.1) - print("Press Enter to Continue...") - input() - elif com[1].lower() == "tags": - self.scr_list_tags(tag_ids=self.lib.search_tags("")) - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - # Top ====================================================== - # Tags ----------------------------------------------------- - elif com[0].lower() == "top": - if len(com) > 1 and com[1].lower() == "tags": - self.lib.count_tag_entry_refs() - self.scr_top_tags() - # Browse =========================================================== - elif com[0].lower() == "browse" or com[0].lower() == "b": - if len(com) > 1: - if com[1].lower() == "entries": - self.filtered_entries = self.lib.search_library() - self.scr_browse_entries_gallery(0) - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - else: - self.filtered_entries = self.lib.search_library() - self.scr_browse_entries_gallery(0) - # Search =========================================================== - elif com[0].lower() == "search" or com[0].lower() == "s": - if len(com) > 1: - self.filtered_entries = self.lib.search_library( - " ".join(com[1:]) - ) - self.scr_browse_entries_gallery(0) - else: - self.scr_browse_entries_gallery(0) - # self.scr_library_home(clear_scr=False) - # Add New Entries ================================================== - elif " ".join(com) == "add new": - if not self.is_new_file_count_init: - print( - f"{INFO} Scanning for files in '{self.lib.library_dir}' (This may take a while)..." - ) - # if not self.lib.files_not_in_library: - self.lib.refresh_dir() - # self.is_new_file_count_init = False - new_ids: list[int] = self.lib.add_new_files_as_entries() - print( - f"{INFO} Running configured Macros on {len(new_ids)} new Entries..." - ) - for id in new_ids: - self.run_macro("autofill", id) - # print(f'{INFO} Scanning for files in \'{self.lib.library_dir}\' (This may take a while)...') - # self.lib.refresh_dir() - self.is_new_file_count_init = True - # self.scr_library_home() - # Fix ============================================================== - elif (com[0].lower() == "fix") and len(com) > 1: - if com[1].lower() == "missing": - subtitle = f"Fix Missing Files" - choices: list[(str, str)] = [ - ( - "Search with Manual & Automated Repair", - f"""Searches the Library directory ({self.lib.library_dir}) for files with the same name as the missing one(s), and automatically repairs Entries which only point to one matching file. If there are multiple filename matches for one Entry, a manual selection screen appears after any automatic repairing.\nRecommended if you moved files and don\'t have use strictly unique filenames in your Library directory.""", - ), - ( - "Search with Automated Repair Only", - "Same as above, only skipping the manual step.", - ), - ( - "Remove Entries", - """Removes Entries from the Library which point to missing files.\nOnly use if you know why a file is missing, and/or don\'t wish to keep that Entry\'s data.""", - ), - ] - prompt: str = "Choose how you want to repair Entries that point to missing files." - selection: int = self.scr_choose_option( - subtitle=subtitle, choices=choices, prompt=prompt - ) - - if selection >= 0 and not self.is_missing_count_init: - print( - f"{INFO} Checking for missing files in '{self.lib.library_dir}'..." - ) - self.lib.refresh_missing_files() - - if selection == 0: - print( - f"{INFO} Attempting to resolve {len(self.lib.missing_files)} missing files in '{self.lib.library_dir}' (This will take long for several results)..." - ) - self.lib.fix_missing_files() - - fixed_indices = [] - if self.lib.missing_matches: - clear() - for unresolved in self.lib.missing_matches: - res = self.scr_choose_missing_match( - self.lib.get_entry_id_from_filepath( - unresolved - ), - clear_scr=False, - ) - if res is not None and int(res) >= 0: - clear() - print( - f"{INFO} Updated {self.lib.entries[self.lib.get_entry_id_from_filepath(unresolved)].path} -> {self.lib.missing_matches[unresolved][res]}" - ) - self.lib.entries[ - self.lib.get_entry_id_from_filepath( - unresolved - ) - ].path = self.lib.missing_matches[ - unresolved - ][res] - fixed_indices.append(unresolved) - elif res and int(res) < 0: - clear() - print( - f"{INFO} Skipped match resolution selection.." - ) - if self.args.external_preview: - self.set_external_preview_default() - self.lib.remove_missing_matches(fixed_indices) - elif selection == 1: - print( - f"{INFO} Attempting to resolve missing files in '{self.lib.library_dir}' (This may take a LOOOONG while)..." - ) - self.lib.fix_missing_files() - elif selection == 2: - print( - f"{WARNING} Remove all Entries pointing to missing files? (Y/N)\n>{RESET} ", - end="", - ) - confirmation = input() - if ( - confirmation.lower() == "y" - or confirmation.lower() == "yes" - ): - deleted = [] - for i, missing in enumerate(self.lib.missing_files): - print( - f"Deleting {i}/{len(self.lib.missing_files)} Unlinked Entries" - ) - try: - id = self.lib.get_entry_id_from_filepath( - missing - ) - print( - f"Removing Entry ID {id}:\n\t{missing}" - ) - self.lib.remove_entry(id) - self.driver.purge_item_from_navigation( - ItemType.ENTRY, id - ) - deleted.append(missing) - except KeyError: - print( - f'{ERROR} "{id}" was reported as missing, but is not in the file_to_entry_id map.' - ) - for d in deleted: - self.lib.missing_files.remove(d) - # for missing in self.lib.missing_files: - # try: - # index = self.lib.get_entry_index_from_filename(missing) - # print(f'Removing Entry at Index [{index+1}/{len(self.lib.entries)}]:\n\t{missing}') - # self.lib.remove_entry(index) - # except KeyError: - # print( - # f'{ERROR} \"{index}\" was reported as missing, but is not in the file_to_entry_index map.') - - if selection >= 0: - print( - f"{INFO} Checking for missing files in '{self.lib.library_dir}'..." - ) - self.lib.refresh_missing_files() - self.is_missing_count_init = True - - # Fix Duplicates =============================================================== - elif com[1].lower() == "duplicate" or com[1].lower() == "dupe": - if len(com) > 2: - # Fix Duplicate Entries ---------------------------------------------------- - if com[2].lower() == "entries" or com[2].lower() == "e": - subtitle = f"Fix Duplicate Entries" - choices: list[(str, str)] = [ - ( - "Merge", - f"Each Entry pointing to the same file will have their data merged into a single remaining Entry.", - ) - ] - prompt: str = "Choose how you want to address groups of Entries which point to the same file." - selection: int = self.scr_choose_option( - subtitle=subtitle, - choices=choices, - prompt=prompt, - ) - - if selection == 0: - if self.is_dupe_entry_count_init: - print( - f"{WARNING} Are you sure you want to merge {len(self.lib.dupe_entries)} Entries? (Y/N)\n> ", - end="", - ) - else: - print( - f"{WARNING} Are you sure you want to merge any duplicate Entries? (Y/N)\n> ", - end="", - ) - confirmation = input() - if ( - confirmation.lower() == "y" - or confirmation.lower() == "yes" - ): - if not self.is_dupe_entry_count_init: - print( - f"{INFO} Checking for duplicate entries in Library '{self.lib.library_dir}'..." - ) - self.lib.refresh_dupe_entries() - self.lib.merge_dupe_entries() - self.is_dupe_entry_count_init = False - # Fix Duplicate Entries ---------------------------------------------------- - elif com[2].lower() == "files" or com[2].lower() == "f": - subtitle = f"Fix Duplicate Files" - choices: list[(str, str)] = [ - ( - "Mirror", - f"""For every predetermined duplicate file, mirror those files\' Entries with each other.\nMirroring involves merging all Entry field data together and then duplicating it across each Entry.\nThis process does not delete any Entries or files.""", - ) - ] - prompt: str = """Choose how you want to address handling data for files considered to be duplicates by an application such as DupeGuru. It\'s recommended that you mirror data here, then manually delete the duplicate files based on your own best judgement. Afterwards run \"fix missing\" and choose the \"Remove Entries\" option.""" - selection: int = self.scr_choose_option( - subtitle=subtitle, - choices=choices, - prompt=prompt, - ) - - if selection == 0: - if self.is_dupe_file_count_init: - print( - f"{WARNING} Are you sure you want to mirror Entry fields for {len(self.lib.dupe_files)} duplicate files? (Y/N)\n> ", - end="", - ) - else: - print( - f"{WARNING} Are you sure you want to mirror any Entry felids for duplicate files? (Y/N)\n> ", - end="", - ) - confirmation = input() - if ( - confirmation.lower() == "y" - or confirmation.lower() == "yes" - ): - print( - f"{INFO} Mirroring {len(self.lib.dupe_files)} Entries for duplicate files..." - ) - for i, dupe in enumerate( - self.lib.dupe_files - ): - entry_id_1 = ( - self.lib.get_entry_id_from_filepath( - dupe[0] - ) - ) - entry_id_2 = ( - self.lib.get_entry_id_from_filepath( - dupe[1] - ) - ) - self.lib.mirror_entry_fields( - [entry_id_1, entry_id_2] - ) - clear() - else: - clear() - print( - f'{ERROR} Invalid duplicate type "{" ".join(com[2:])}".' - ) - clear_scr = False - else: - clear() - print( - f'{ERROR} Specify which duplicates to fix (entries, files, etc) "{" ".join(com)}".' - ) - clear_scr = False - else: - clear() - print( - f'{ERROR} Invalid fix selection "{" ".join(com[1:])}". Try "fix missing", "fix dupe entries", etc.' - ) - clear_scr = False - # # Save to Disk ========================================================= - # elif com[0].lower() in ['save', 'write', 'w']: - # self.lib.save_library_to_disk() - # clear() - # print( - # f'{INFO} Library saved to disk.') - # clear_scr = False - # # Save Backup to Disk ========================================================= - # elif (com[0].lower() == 'backup'): - # self.backup_library() - # clear_scr = False - # Close ============================================================ - elif com[0].lower() == "close" or com[0].lower() == "c": - # self.core.clear_internal_vars() - self.close_library() - # clear() - return - # Unknown Command ================================================== - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - # self.scr_library_home(clear_scr=False) - - def scr_browse_entries_gallery(self, index, clear_scr=True, refresh=True): - """Gallery View for browsing Library Entries.""" - - branch = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - while True: - # try: - if refresh: - if clear_scr: - clear() - clear_scr = True - - print(self.format_title(title)) - - if self.filtered_entries: - # entry = self.lib.get_entry_from_index( - # self.filtered_entries[index]) - entry = self.lib.get_entry(self.filtered_entries[index][1]) - filename = self.lib.library_dir / entry.path / entry.filename - # if self.lib.is_legacy_library: - # title += ' (Legacy Format)' - h1 = f"[{index + 1}/{len(self.filtered_entries)}] {filename}" - - # print(self.format_subtitle(subtitle)) - print( - self.format_h1(h1, self.get_file_color(filename.suffix.lower())) - ) - print("") - - if not filename.is_file(): - print( - f"{RED_BG}{BRIGHT_WHITE_FG}[File Missing]{RESET}{BRIGHT_RED_FG} (Run 'fix missing' to resolve){RESET}" - ) - print("") - if self.args.external_preview: - self.set_external_preview_broken() - else: - self.print_thumbnail(self.filtered_entries[index][1]) - - self.print_fields(self.filtered_entries[index][1]) - else: - if self.lib.entries: - print( - self.format_h1( - "No Entry Results for Query", color=BRIGHT_RED_FG - ) - ) - self.set_external_preview_default() - else: - print( - self.format_h1("No Entries in Library", color=BRIGHT_RED_FG) - ) - self.set_external_preview_default() - print("") - - print("") - print( - self.format_subtitle( - "Prev Next Goto <#> Open File Search List Tags", - BRIGHT_MAGENTA_FG, - ) - ) - print( - self.format_subtitle( - "Add, Remove, Edit Remove Close Quit", - BRIGHT_MAGENTA_FG, - ) - ) - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - # except IndexError: - # clear() - # print(f'{INFO} No matches found for query') - # # self.scr_library_home(clear_scr=False) - # # clear_scr=False - # return - - # Previous ============================================================= - if ( - com[0].lower() == "prev" - or com[0].lower() == "p" - or com[0].lower() == "previous" - ): - if len(com) > 1: - try: - # self.scr_browse_entries_gallery( - # (index - int(com[1])) % len(self.filtered_entries)) - # return - index = (index - int(com[1])) % len( - self.filtered_entries - ) - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except (IndexError, ValueError): - clear() - print(f"{ERROR} Invalid \"Previous\" Index: '{com[1]}'") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - else: - # self.scr_browse_entries_gallery( - # (index - 1) % len(self.filtered_entries)) - # return - index = (index - 1) % len(self.filtered_entries) - # Next ================================================================= - elif com[0].lower() == "next" or com[0].lower() == "n": - if len(com) > 1: - try: - # NOTE: Will returning this as-is instead of after screw up the try-catch? - index = (index + int(com[1])) % len( - self.filtered_entries - ) - # self.scr_browse_entries_gallery( - # (index + int(com[1])) % len(self.filtered_entries)) - # return - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except (IndexError, ValueError): - clear() - print(f"{ERROR} Invalid \"Next\" Index: '{com[1]}'") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - else: - # self.scr_browse_entries_gallery( - # (index + 1) % len(self.filtered_entries)) - # return - index = (index + 1) % len(self.filtered_entries) - # Goto ================================================================= - elif (com[0].lower() == "goto" or com[0].lower() == "g") and len( - com - ) > 1: - try: - if int(com[1]) - 1 < 0: - raise IndexError - if int(com[1]) > len(self.filtered_entries): - raise IndexError - # self.scr_browse_entries_gallery(int(com[1])-1) - # return - index = int(com[1]) - 1 - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except (IndexError, ValueError): - clear() - print(f"{ERROR} Invalid \"Goto\" Index: '{com[1]}'") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # Search =============================================================== - elif com[0].lower() == "search" or com[0].lower() == "s": - if len(com) > 1: - self.filtered_entries = self.lib.search_library( - " ".join(com[1:]) - ) - # self.scr_browse_entries_gallery(0) - index = 0 - else: - self.filtered_entries = self.lib.search_library() - # self.scr_browse_entries_gallery(0) - index = 0 - # running = False - # return - # self.scr_library_home(clear_scr=False) - # return - # # Toggle Debug =========================================================== - # elif (com[0].lower() == 'toggle-debug'): - # self.args.debug = not self.args.debug - # Open with Default Application ======================================== - elif com[0].lower() == "open" or com[0].lower() == "o": - if len(com) > 1: - if com[1].lower() == "location" or com[1].lower() == "l": - open_file(filename, True) - else: - open_file(filename) - # refresh=False - # self.scr_browse_entries_gallery(index) - # Add Field ============================================================ - elif com[0].lower() == "add" or com[0].lower() == "a": - if len(com) > 1: - id_list = self.lib.filter_field_templates( - " ".join(com[1:]).lower() - ) - if id_list: - final_ids = [] - if len(id_list) == 1: - final_ids.append(id_list[0]) - else: - final_ids = self.scr_select_field_templates(id_list) - - for id in final_ids: - if id >= 0: - self.lib.add_field_to_entry( - self.filtered_entries[index][1], id - ) - # self.scr_browse_entries_gallery(index) - # return - # else: - # clear() - # print(f'{ERROR} Invalid selection.') - # return self.scr_browse_entries_gallery(index, clear_scr=False) - - else: - clear() - print(f"{INFO} Please specify a field to add.") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # self.scr_browse_entries_gallery(index) - # return - # Remove Field ========================================================= - elif com[0].lower() == "remove" or com[0].lower() == "rm": - if len(com) > 1: - # entry_fields = self.lib.get_entry_from_index( - # self.filtered_entries[index]).fields - entry_fields = self.lib.get_entry( - self.filtered_entries[index][1] - ).fields - field_indices: list[int] = [] - for i, f in enumerate(entry_fields): - if int( - self.lib.get_field_attr(f, "id") - ) in self.lib.filter_field_templates( - " ".join(com[1:]).lower() - ): - field_indices.append(i) - - try: - final_field_index = -1 - # if len(field_indices) == 1: - # final_index = field_indices[0] - # NOTE: The difference between this loop and Edit is that it always asks - # you to specify the field, even if there is only one option. - if len(field_indices) >= 1: - print(field_indices) - print(entry_fields) - print( - [ - self.lib.get_field_attr( - entry_fields[x], "id" - ) - for x in field_indices - ] - ) - final_field_index = field_indices[ - self.scr_select_field_templates( - [ - self.lib.get_field_attr( - entry_fields[x], "id" - ) - for x in field_indices - ], - allow_multiple=False, - mode="remove", - return_index=True, - )[0] - ] - else: - clear() - print( - f'{ERROR} Entry does not contain the field "{" ".join(com[1:])}".' - ) - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except IndexError: - pass - - if final_field_index >= 0: - self.lib.get_entry( - self.filtered_entries[index][1] - ).fields.pop(final_field_index) - # self.lib.entries[self.filtered_entries[index]].fields.pop( - # final_field_index) - else: - clear() - print(f"{INFO} Please specify a field to remove.") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # self.scr_browse_entries_gallery(index) - # return - # Edit Field =========================================================== - elif com[0].lower() == "edit" or com[0].lower() == "e": - if len(com) > 1: - # entry_fields = self.lib.get_entry_from_index( - # self.filtered_entries[index]).fields - entry_fields = self.lib.get_entry( - self.filtered_entries[index][1] - ).fields - field_indices: list[int] = [] - for i, f in enumerate(entry_fields): - if int( - self.lib.get_field_attr(f, "id") - ) in self.lib.filter_field_templates( - " ".join(com[1:]).lower() - ): - field_indices.append(i) - - try: - final_field_index = -1 - if len(field_indices) == 1: - final_field_index = field_indices[0] - elif len(field_indices) > 1: - print(field_indices) - print(entry_fields) - print( - [ - self.lib.get_field_attr( - entry_fields[x], "id" - ) - for x in field_indices - ] - ) - final_field_index = field_indices[ - self.scr_select_field_templates( - [ - self.lib.get_field_attr( - entry_fields[x], "id" - ) - for x in field_indices - ], - allow_multiple=False, - mode="edit", - return_index=True, - )[0] - ] - else: - clear() - print( - f'{ERROR} Entry does not contain the field "{" ".join(com[1:])}".' - ) - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except IndexError: - pass - - if final_field_index >= 0: - if ( - self.lib.get_field_attr( - entry_fields[final_field_index], "type" - ) - == "tag_box" - ): - self.scr_edit_entry_tag_box( - self.filtered_entries[index][1], - field_index=final_field_index, - ) - elif ( - self.lib.get_field_attr( - entry_fields[final_field_index], "type" - ) - == "text_line" - ): - self.scr_edit_entry_text( - self.filtered_entries[index][1], - field_index=final_field_index, - allow_newlines=False, - ) - elif ( - self.lib.get_field_attr( - entry_fields[final_field_index], "type" - ) - == "text_box" - ): - self.scr_edit_entry_text( - self.filtered_entries[index][1], - field_index=final_field_index, - ) - else: - clear() - print( - f'{INFO} Sorry, this type of field ({self.lib.get_field_attr(entry_fields[final_field_index], "type")}) isn\'t editable yet.' - ) - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - else: - clear() - print(f"{INFO} Please specify a field to edit.") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # self.scr_browse_entries_gallery(index) - # return - # Copy Field =========================================================== - elif com[0].lower() == "copy" or com[0].lower() == "cp": - # NOTE: Nearly identical code to the Edit section. - if len(com) > 1: - # entry_fields = self.lib.get_entry_from_index( - # self.filtered_entries[index]).fields - entry_fields = self.lib.get_entry( - self.filtered_entries[index][1] - ).fields - field_indices: list[int] = [] - for i, f in enumerate(entry_fields): - if int( - self.lib.get_field_attr(f, "id") - ) in self.lib.filter_field_templates( - " ".join(com[1:]).lower() - ): - field_indices.append(i) - - # try: - final_field_index = -1 - if len(field_indices) == 1: - final_field_index = field_indices[0] - elif len(field_indices) > 1: - print(field_indices) - print(entry_fields) - print( - [ - self.lib.get_field_attr(entry_fields[x], "id") - for x in field_indices - ] - ) - final_field_index = field_indices[ - self.scr_select_field_templates( - [ - self.lib.get_field_attr( - entry_fields[x], "id" - ) - for x in field_indices - ], - allow_multiple=False, - mode="edit", - return_index=True, - )[0] - ] - else: - clear() - print( - f'{ERROR} Entry does not contain the field "{" ".join(com[1:])}".' - ) - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - # except: - # pass - - if final_field_index >= 0: - self.copy_field_to_buffer( - entry.fields[final_field_index] - ) - # refresh = False - else: - clear() - print(f"{INFO} Please specify a field to copy.") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # self.scr_browse_entries_gallery(index) - # return - # Paste Field =========================================================== - elif com[0].lower() == "paste" or com[0].lower() == "ps": - self.paste_field_from_buffer(self.filtered_entries[index][1]) - # self.scr_browse_entries_gallery(index) - # return - # Run Macro ============================================================ - elif len(com) > 1 and com[0].lower() == "run": - if len(com) > 2 and com[1].lower() == "macro": - macro_name = (com[2]).lower() - if len(com) > 3: - # Run on all filtered Entries - if ( - com[-1].lower() == "--all" - or com[-1].lower() == "-a" - ): - clear() - print( - f'{INFO} Running Macro "{macro_name}" on {len(self.filtered_entries)} Entries...' - ) - for type, id in self.filtered_entries: - self.run_macro(name=macro_name, entry_id=id) - # self.scr_browse_entries_gallery(index) - else: - # Run on current Entry - self.run_macro( - name=macro_name, - entry_id=self.filtered_entries[index][1], - ) - # self.scr_browse_entries_gallery(index) - # return - else: - clear() - print(f"{ERROR} Please specify a Macro to run.") - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - # List Tags ============================================================ - elif (com[0].lower() == "list" or com[0].lower() == "ls") and len( - com - ) > 1: - if com[1].lower() == "tags": - clear() - self.scr_list_tags(tag_ids=self.lib.search_tags("")) - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - # self.scr_browse_entries_gallery(index, clear_scr=False) - - # return - # # Save to Disk ========================================================= - # elif (com[0].lower() == 'save' or com[0].lower() == 'write' or com[0].lower() == 'w'): - # self.lib.save_library_to_disk() - # clear() - # print( - # f'{INFO} Library saved to disk.') - # # self.scr_browse_entries_gallery(index, clear_scr=False) - # clear_scr = False - # # return - # # Save Backup to Disk ========================================================= - # elif (com[0].lower() == 'backup'): - # clear() - # self.backup_library() - # clear_scr = False - # Close View =========================================================== - elif com[0].lower() == "close" or com[0].lower() == "c": - if self.args.external_preview: - self.set_external_preview_default() - # self.scr_library_home() - clear() - return - # # Quit ================================================================= - # elif com[0].lower() == 'quit' or com[0].lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ================================================== - # elif com[0].lower() == 'quit!' or com[0].lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Unknown Command ====================================================== - elif com: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - # self.scr_browse_entries_gallery(index, clear_scr=False) - clear_scr = False - # return - - def scr_choose_option( - self, - subtitle: str, - choices: list, - prompt: str = "", - required=False, - clear_scr=True, - ) -> int: - """ - Screen for choosing one of a given set of generic options. - Takes in a list of (str,str) tuples which consist of (option name, option description), - with the description being optional. - Returns the index of the selected choice (starting at 0), or -1 if the choice was '0', 'Cancel', or 'C'. - """ - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - # invalid_input: bool = False - - while True: - if clear_scr: - clear() - clear_scr = True - - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_CYAN_BG}")) - print(self.format_subtitle(subtitle)) - # if invalid_input: - # print(self.format_h1( - # str='Please Enter a Valid Selection Number', color=BRIGHT_RED_FG)) - # invalid_input = False - print("") - if prompt: - print(prompt) - print("") - print("") - - for i, choice in enumerate(choices, start=1): - print( - f"{BRIGHT_WHITE_BG}{BLACK_FG}[{str(i).zfill(len(str(len(choices))))}]{RESET} {BRIGHT_WHITE_BG}{BLACK_FG} {choice[0]} {RESET}" - ) - if choice[1]: - print(f"{WHITE_FG}{choice[1]}{RESET}") - print("") - - if not required: - print("") - print( - f"{BRIGHT_WHITE_BG}{BLACK_FG}[0]{RESET} {BRIGHT_WHITE_BG}{BLACK_FG} Cancel {RESET}" - ) - - print("") - if not required: - print( - self.format_subtitle("<#> 0 or Cancel Quit", BRIGHT_CYAN_FG) - ) - else: - print(self.format_subtitle("<#> Quit", BRIGHT_CYAN_FG)) - print("> ", end="") - - com: list[str] = input().strip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - com_name = com[0].lower() - - try: - # # Quit ========================================================= - # if com.lower() == 'quit' or com.lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ========================================== - # elif com.lower() == 'quit!' or com.lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Cancel ======================================================= - if com_name in ("cancel", "c", "0") and not required: - clear() - return -1 - # Selection ==================================================== - elif com_name.isdigit() and 0 < int(com_name) <= len(choices): - clear() - return int(com_name) - 1 - else: - # invalid_input = True - # print(self.format_h1(str='Please Enter a Valid Selection Number', color=BRIGHT_RED_FG)) - clear() - print(f"{ERROR} Please Enter a Valid Selection Number/Option.") - clear_scr = False - except (TypeError, ValueError): - clear() - print(f"{ERROR} Please Enter a Valid Selection Number/Option.") - clear_scr = False - - def scr_choose_missing_match(self, index, clear_scr=True, refresh=True) -> int: - """ - Screen for manually resolving a missing file. - Returns the index of the choice made (starting at 0), or -1 if skipped. - """ - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - subtitle = f"Resolve Missing File Conflict" - - while True: - entry = self.lib.get_entry_from_index(index) - filename = self.lib.library_dir / entry.path / entry.filename - - if refresh: - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_CYAN_BG}")) - print(self.format_subtitle(subtitle)) - print("") - print(self.format_h1(filename, BRIGHT_RED_FG), end="\n\n") - - self.print_fields(index) - - for i, match in enumerate(self.lib.missing_matches[filename]): - print(self.format_h1(f"[{i+1}] {match}"), end="\n\n") - fn = self.lib.library_dir / match / entry.filename - self.print_thumbnail( - index=-1, - filepath=fn, - max_width=( - os.get_terminal_size()[1] - // len(self.lib.missing_matches[filename]) - - 2 - ), - ) - if fn in self.lib.filename_to_entry_id_map.keys(): - self.print_fields(self.lib.get_entry_id_from_filepath(fn)) - print("") - print( - self.format_subtitle( - "<#> 0 to Skip Open Files Quit", BRIGHT_CYAN_FG - ) - ) - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # Refresh ============================================================== - if com[0].lower() == "refresh" or com[0].lower() == "r": - # if (com[0].lower() == 'refresh' or com[0].lower() == 'r') and len(com) > 1: - # if com[1].lower() == 'files' or com[1].lower() == 'dir': - # clear() - # return self.scr_choose_missing_match(index) - # else: - # clear() - # print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - # self.scr_library_home(clear_scr=False) - # clear_scr=False - pass - # Open ============================================================= - elif com[0].lower() == "open" or com[0].lower() == "o": - for match in self.lib.missing_matches[filename]: - fn = self.lib.library_dir / match / entry.filename - open_file(fn) - refresh = False - # clear() - # return self.scr_choose_missing_match(index, clear_scr=False) - # # Quit ============================================================= - # elif com[0].lower() == 'quit' or com[0].lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ============================================== - # elif com[0].lower() == 'quit!' or com[0].lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Selection/Other ================================================== - else: - try: - i = int(com[0]) - 1 - if i < len(self.lib.missing_matches[filename]): - if i < -1: - return -1 - else: - return i - else: - raise IndexError - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except (ValueError, IndexError): - clear() - print(f'{ERROR} Invalid command \'{" ".join(com)}\'') - # return self.scr_choose_missing_match(index, clear_scr=False) - clear_scr = False - - def scr_resolve_dupe_files(self, index, clear_scr=True): - """Screen for manually resolving duplicate files.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - subtitle = f"Resolve Duplicate Files" - - while True: - dupe = self.lib.dupe_files[index] - - if dupe[0].exists() and dupe[1].exists(): - # entry = self.lib.get_entry_from_index(index_1) - entry_1_index = self.lib.get_entry_id_from_filepath(dupe[0]) - entry_2_index = self.lib.get_entry_id_from_filepath(dupe[1]) - - if clear_scr: - clear() - clear_scr = True - - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_CYAN_BG}")) - print(self.format_subtitle(subtitle)) - - print("") - print(f"{WHITE_BG}{BLACK_FG} Similarity: {RESET} ", end="") - print(f"{dupe[2]}%") - - # File 1 - print("") - print(self.format_h1(dupe[0], BRIGHT_RED_FG), end="\n\n") - print(f"{WHITE_BG}{BLACK_FG} File Size: {RESET} ", end="") - print(f"0 KB") - print(f"{WHITE_BG}{BLACK_FG} Resolution: {RESET} ", end="") - print(f"0x0") - if entry_1_index is not None: - print("") - self.print_fields(entry_1_index) - else: - print(f"{BRIGHT_RED_FG}No Library Entry for file.{RESET}") - - # File 2 - print("") - print(self.format_h1(dupe[1], BRIGHT_RED_FG), end="\n\n") - print(f"{WHITE_BG}{BLACK_FG} File Size: {RESET} ", end="") - print(f"0 KB") - print(f"{WHITE_BG}{BLACK_FG} Resolution: {RESET} ", end="") - print(f"0x0") - if entry_2_index is not None: - print("") - self.print_fields(entry_2_index) - else: - print(f"{BRIGHT_RED_FG}No Library Entry for file.{RESET}") - - # for i, match in enumerate(self.lib.missing_matches[filename]): - # print(self.format_h1(f'[{i+1}] {match}'), end='\n\n') - # fn = f'{os.path.normpath(self.lib.library_dir + "/" + match + "/" + entry_1.filename)}' - # self.print_thumbnail(self.lib.get_entry_from_filename(fn), - # max_width=(os.get_terminal_size()[1]//len(self.lib.missing_matches[filename])-2)) - # self.print_fields(self.lib.get_entry_from_filename(fn)) - print("") - print( - self.format_subtitle( - "Mirror Delete <#> Skip Close Open Files Quit", - BRIGHT_CYAN_FG, - ) - ) - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # Refresh ========================================================== - if (com[0].lower() == "refresh" or com[0].lower() == "r") and len( - com - ) > 1: - # if com[1].lower() == 'files' or com[1].lower() == 'dir': - # clear() - # return self.scr_resolve_dupe_files(index, clear_scr=True) - # else: - # clear() - # print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - # self.scr_library_home(clear_scr=False) - pass - # Open ============================================================= - elif com[0].lower() == "open" or com[0].lower() == "o": - # for match in self.lib.missing_matches[filename]: - # fn = f'{os.path.normpath(self.lib.library_dir + "/" + match + "/" + entry_1.filename)}' - # open_file(fn) - open_file(dupe[0]) - open_file(dupe[1]) - # clear() - # return self.scr_resolve_dupe_files(index, clear_scr=False) - # Mirror Entries =================================================== - elif com[0].lower() == "mirror" or com[0].lower() == "mir": - return com - # Skip ============================================================ - elif com[0].lower() == "skip": - return com - # Skip ============================================================ - elif ( - com[0].lower() == "close" - or com[0].lower() == "cancel" - or com[0].lower() == "c" - ): - return ["close"] - # Delete =========================================================== - elif com[0].lower() == "delete" or com[0].lower() == "del": - if len(com) > 1: - if com[1] == "1": - return ["del", 1] - elif com[1] == "2": - return ["del", 2] - else: - # return self.scr_resolve_dupe_files(index) - pass - else: - clear() - print( - f"{ERROR} Please specify which file (ex. delete 1, delete 2) to delete file." - ) - # return self.scr_resolve_dupe_files(index, clear_scr=False) - clear_scr = False - # # Quit ============================================================= - # elif com[0].lower() == 'quit' or com[0].lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ============================================== - # elif com[0].lower() == 'quit!' or com[0].lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Other ============================================================ - else: - # try: - # i = int(com[0]) - 1 - # if i < len(self.lib.missing_matches[filename]): - # return i - # else: - # raise IndexError - # except SystemExit: - # sys.exit() - # except: - clear() - print(f'{ERROR} Invalid command \'{" ".join(com)}\'') - # return self.scr_resolve_dupe_files(index, clear_scr=False) - clear_scr = False - - def scr_edit_entry_tag_box(self, entry_index, field_index, clear_scr=True): - """Screen for editing an Entry tag-box field.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - entry = self.lib.entries[entry_index] - filename = self.lib.library_dir / entry.path / entry.filename - field_name = self.lib.get_field_attr(entry.fields[field_index], "name") - subtitle = f'Editing "{field_name}" Field' - h1 = f"{filename}" - - while True: - if clear_scr: - clear() - clear_scr = True - - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_CYAN_BG}")) - print(self.format_subtitle(subtitle)) - print( - self.format_h1(h1, self.get_file_color(os.path.splitext(filename)[1])) - ) - print("") - - if not filename.is_file(): - print( - f"{RED_BG}{BRIGHT_WHITE_FG}[File Missing]{RESET}{BRIGHT_RED_FG} (Run 'fix missing' to resolve){RESET}" - ) - print("") - else: - self.print_thumbnail(entry_index) - - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} {field_name}: {RESET} ") - for i, tag_id in enumerate( - entry.fields[field_index][list(entry.fields[field_index].keys())[0]] - ): - tag = self.lib.get_tag(tag_id) - print( - f"{self.get_tag_color(tag.color)}[{i+1}]{RESET} {self.get_tag_color(tag.color)} {tag.display_name(self.lib)} {RESET}" - ) - # if tag_id != field[field_id][-1]: - # print(' ', end='') - print("") - - print( - self.format_subtitle( - "Add Remove <#> Open File Close/Done Quit" - ) - ) - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # Open with Default Application ======================================== - if com[0].lower() == "open" or com[0].lower() == "o": - open_file(filename) - # self.scr_edit_entry_tag_box(entry_index, field_index) - # return - # Close View =========================================================== - elif ( - com[0].lower() == "close" - or com[0].lower() == "c" - or com[0].lower() == "done" - ): - # self.scr_browse_entries_gallery() - clear() - return - # # Quit ================================================================= - # elif com[0].lower() == 'quit' or com[0].lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ================================================== - # elif com[0].lower() == 'quit!' or com[0].lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Add Tag ============================================================== - elif com[0].lower() == "add": - if len(com) > 1: - tag_list = self.lib.search_tags( - " ".join(com[1:]), include_cluster=True - ) - t: list[int] = [] - if len(tag_list) > 1: - t = self.scr_select_tags(tag_list) - else: - t = tag_list # Single Tag - if t: - self.lib.update_entry_field( - entry_index, field_index, content=t, mode="append" - ) - # self.scr_edit_entry_tag_box(entry_index, field_index) - # return - # Remove Tag =========================================================== - elif com[0].lower() == "remove" or com[0].lower() == "rm": - if len(com) > 1: - try: - selected_tag_ids: list[int] = [] - for c in com[1:]: - if (int(c) - 1) < 0: - raise IndexError - # print(self.lib.get_field_attr(entry.fields[field_index], 'content')) - # print(self.lib.get_field_attr(entry.fields[field_index], 'content')[int(c)-1]) - selected_tag_ids.append( - self.lib.get_field_attr( - entry.fields[field_index], "content" - )[int(c) - 1] - ) - # i = int(com[1]) - 1 - - # tag = entry.fields[field_index][list( - # entry.fields[field_index].keys())[0]][i] - self.lib.update_entry_field( - entry_index, - field_index, - content=selected_tag_ids, - mode="remove", - ) - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except: - clear() - print(f"{ERROR} Invalid Tag Selection '{com[1:]}'") - clear_scr = False - # self.scr_edit_entry_tag_box( - # entry_index, field_index, clear_scr=False) - # return - # self.scr_edit_entry_tag_box(entry_index, field_index) - # return - # Unknown Command ====================================================== - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - # self.scr_edit_entry_tag_box( - # entry_index, field_index, clear_scr=False) - # return - clear_scr = False - - def scr_select_tags(self, tag_ids: list[int], clear_scr=True) -> list[int]: - """Screen for selecting and returning one or more Tags. Used for Entry editing.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - subtitle = f"Select Tag(s) to Add" - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_GREEN_BG}")) - print(self.format_subtitle(subtitle, BRIGHT_GREEN_FG)) - # print(self.format_h1(h1, self.get_file_color( - # os.path.splitext(filename)[1]))) - print("") - - tag_tuple_list = [] - for tag_id in tag_ids: - tag = self.lib.get_tag(tag_id) - tag_tuple_list.append( - (tag.display_name(self.lib), self.get_tag_color(tag.color)) - ) - - self.print_columns(tag_tuple_list, add_enum=True) - print("") - - print(self.format_subtitle("Enter #(s) Cancel", BRIGHT_GREEN_FG)) - print("> ", end="") - - com: list[str] = input().rstrip().split(" ") - selected_ids: list[int] = [] - try: - for c in com: - selected_ids.append(tag_ids[int(c) - 1]) - except SystemExit: - self.cleanup_before_exit() - sys.exit() - except: - print(f"{ERROR} Invalid Tag Selection") - - return selected_ids - - # TODO: This can be replaced by the new scr_choose_option method. - def scr_select_field_templates( - self, - field_ids: list[int], - allow_multiple=True, - mode="add", - return_index=False, - clear_scr=True, - ) -> list[int]: - """ - Screen for selecting and returning one or more Field Templates. Used for Entry editing. - Allow Multiple: Lets the user select multiple items, returned in a list. If false, returns a list of only the first selected item. - Mode: 'add', 'edit', 'remove' - Changes prompt text and colors. - Return Index: Instead of returning the Field IDs that were selected, this returns the indices of the selected items from the given list. - """ - - branch = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" - title = ( - f"TagStudio {VERSION}{branch} - CLI Mode - Library '{self.lib.library_dir}'" - ) - subtitle = f"Select Field(s) to Add" - plural = "(s)" - - if not allow_multiple: - plural = "" - - fg_text_color = BLACK_FG - fg_color = BRIGHT_GREEN_FG - bg_color = BRIGHT_GREEN_BG - if mode == "edit": - fg_color = BRIGHT_CYAN_FG - bg_color = BRIGHT_CYAN_BG - subtitle = f"Select Field{plural} to Edit" - elif mode == "remove": - fg_color = BRIGHT_RED_FG - bg_color = BRIGHT_RED_BG - # fg_text_color = BRIGHT_WHITE_FG - subtitle = f"Select Field{plural} to Remove" - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title, color=f"{fg_text_color}{bg_color}")) - print(self.format_subtitle(subtitle, fg_color)) - # print(self.format_h1(h1, self.get_file_color( - # os.path.splitext(filename)[1]))) - print("") - - for i, field_id in enumerate(field_ids): - name = self.lib.get_field_obj(field_id)["name"] - type = self.lib.get_field_obj(field_id)["type"] - if i < (os.get_terminal_size()[1] - 7): - print( - f"{BRIGHT_WHITE_BG}{BLACK_FG}[{i+1}]{RESET} {BRIGHT_WHITE_BG}{BLACK_FG} {name} ({type}) {RESET}" - ) - else: - print(f"{WHITE_FG}[...]{RESET}") - break - print("") - - print(self.format_subtitle(f"Enter #{plural} Cancel", fg_color)) - print("> ", end="") - - com: list[str] = input().split(" ") - selected_ids: list[int] = [] - try: - for c in com: - if int(c) > 0: - if return_index: - selected_ids.append(int(c) - 1) - else: - selected_ids.append(field_ids[int(c) - 1]) - except SystemExit: - self.cleanup_before_exit() - sys.exit() - except: - print(f"{ERROR} Invalid Tag Selection") - - if not allow_multiple and selected_ids: - return [selected_ids[0]] - return selected_ids - - def scr_edit_entry_text( - self, entry_index, field_index, allow_newlines=True, clear_scr=True - ): - """Screen for editing an Entry text_line field.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - entry = self.lib.entries[entry_index] - filename = self.lib.library_dir / entry.path / entry.filename - field_name = self.lib.get_field_attr(entry.fields[field_index], "name") - subtitle = f'Editing "{field_name}" Field' - h1 = f"{filename}" - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_CYAN_BG}")) - print(self.format_subtitle(subtitle)) - print(self.format_h1(h1, self.get_file_color(os.path.splitext(filename)[1]))) - print("") - - if not filename.is_file(): - print( - f"{RED_BG}{BRIGHT_WHITE_FG}[File Missing]{RESET}{BRIGHT_RED_FG} (Run 'fix missing' to resolve){RESET}" - ) - print("") - else: - self.print_thumbnail(entry_index, ignore_fields=True) - - print( - self.format_title( - "Opened with Default Text Editor", f"{BLACK_FG}{BRIGHT_CYAN_BG}" - ) - ) - # print('') - # print( - # f'{BRIGHT_WHITE_BG}{BLACK_FG} {field_name}: {RESET} ') - # print(self.lib.get_field_attr(entry.fields[field_index], 'content')) - # for i, tag_id in enumerate(entry.fields[field_index][list(entry.fields[field_index].keys())[0]]): - # tag = self.lib.get_tag_from_id(tag_id) - # print( - # f'{self.get_tag_color(tag.color)}[{i+1}]{RESET} {self.get_tag_color(tag.color)} {tag.display_name(self.lib)} {RESET}') - # print('') - - # print(self.format_subtitle( - # 'Add Remove <#> Open File Close/Done Quit')) - - # new_content: str = click.edit(self.lib.get_field_attr( - # entry.fields[field_index], 'content')) - new_content: str = "" # NOTE: Removing - if new_content is not None: - if not allow_newlines: - new_content = new_content.replace("\r", "").replace("\n", "") - self.lib.update_entry_field( - entry_index, - field_index, - new_content.rstrip("\n").rstrip("\r"), - "replace", - ) - - def scr_list_tags( - self, query: str = "", tag_ids: list[int] = None, clear_scr=True - ) -> None: - """A screen for listing out and performing CRUD operations on Library Tags.""" - # NOTE: While a screen that just displays the first 40 or so random tags on your screen - # isn't really that useful, this is just a temporary measure to provide a launchpad - # screen for necessary commands such as adding and editing tags. - # A more useful screen presentation might look like a list of ranked occurrences, but - # that can be figured out and implemented later. - tag_ids = tag_ids or [] - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - while True: - h1 = f"{len(self.lib.tags)} Tags" - - if tag_ids: - if len(tag_ids) < len(self.lib.search_tags("")): - h1 = f"[{len(tag_ids)}/{len(self.lib.tags)}] Tags" - if query: - h1 += f" connected to '{query}'" - else: - h1 = f"No Tags" - if query: - h1 += f" connected to '{query}'" - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title)) - print(self.format_h1(h1)) - print("") - - tag_tuple_list = [] - for tag_id in tag_ids: - tag = self.lib.get_tag(tag_id) - if self.args.debug: - tag_tuple_list.append( - (tag.debug_name(), self.get_tag_color(tag.color)) - ) - else: - tag_tuple_list.append( - (tag.display_name(self.lib), self.get_tag_color(tag.color)) - ) - - self.print_columns(tag_tuple_list, add_enum=True) - - print("") - print( - self.format_subtitle( - "Create Edit <#> Delete <#> Search Close/Done", - BRIGHT_MAGENTA_FG, - ) - ) - print("> ", end="") - - com: list[str] = input().strip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - com_name = com[0].lower() - # Search Tags ========================================================== - if com_name in ("search", "s"): - if len(com) > 1: - new_query: str = " ".join(com[1:]) - # self.scr_list_tags(prev_scr, query=new_query, - # tag_ids=self.lib.filter_tags(new_query, include_cluster=True)) - query = new_query - tag_ids = self.lib.search_tags(new_query, include_cluster=True) - # return - else: - # self.scr_list_tags(prev_scr, tag_ids=self.lib.filter_tags('')) - tag_ids = self.lib.search_tags("") - # return - # Edit Tag =========================================================== - elif com_name in ("edit", "e"): - if len(com) > 1: - try: - index = int(com[1]) - 1 - if index < 0: - raise IndexError - self.scr_manage_tag(tag_ids[index]) - - # Refilter in case edits change results - tag_ids = self.lib.search_tags(query, include_cluster=True) - # self.scr_list_tags(prev_scr, query=query, tag_ids=tag_ids) - # return - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except (ValueError, IndexError): - clear() - print(f'{ERROR} Invalid Selection \'{" ".join(com[1])}\'') - clear_scr = False - # self.scr_list_tags(prev_scr, query=query, - # tag_ids=tag_ids, clear_scr=False) - # return - - # Create Tag ============================================================ - elif com_name in ("create", "mk"): - tag = Tag( - id=0, - name="New Tag", - shorthand="", - aliases=[], - subtags_ids=[], - color="", - ) - self.scr_manage_tag(self.lib.add_tag_to_library(tag), mode="create") - - tag_ids = self.lib.search_tags(query, include_cluster=True) - - # self.scr_list_tags(prev_scr, query=query, tag_ids=tag_ids) - # return - # Delete Tag =========================================================== - elif com_name in ("delete", "del"): - if len(com) > 1: - if len(com) > 1: - try: - index = int(com[1]) - 1 - if index < 0: - raise IndexError - deleted = self.scr_delete_tag(tag_ids[index]) - if deleted: - tag_ids.remove(tag_ids[index]) - tag_ids = self.lib.search_tags( - query, include_cluster=True - ) - # self.scr_list_tags( - # prev_scr, query=query, tag_ids=tag_ids) - # return - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except IndexError: - clear() - print( - f'{ERROR} Invalid Selection \'{" ".join(com[1])}\'' - ) - clear_scr = False - # self.scr_list_tags(prev_scr, query=query, - # tag_ids=tag_ids, clear_scr=False) - # return - # Close View =========================================================== - elif com_name in ("close", "c", "done"): - # prev_scr() - return - # # Quit ================================================================= - # elif com[0].lower() == 'quit' or com[0].lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ================================================== - # elif com[0].lower() == 'quit!' or com[0].lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Unknown Command ====================================================== - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - # self.scr_list_tags(prev_scr, query=query, - # tag_ids=tag_ids, clear_scr=False) - # return - clear_scr = False - - def scr_top_tags(self, clear_scr=True) -> None: - """A screen that lists out the top tags for the library.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - while True: - h1 = f"Top Tags" - - # if tag_ids: - # if len(tag_ids) < len(self.lib.filter_tags('')): - # h1 = f'[{len(tag_ids)}/{len(self.lib.tags)}] Tags' - # if query: - # h1 += f' connected to \'{query}\'' - # else: - # h1 = f'No Tags' - # if query: - # h1 += f' connected to \'{query}\'' - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title)) - print(self.format_h1(h1)) - print("") - - tag_tuple_list = [] - for tag_id, count in self.lib.tag_entry_refs: - tag = self.lib.get_tag(tag_id) - if self.args.debug: - tag_tuple_list.append( - (f"{tag.debug_name()} - {count}", self.get_tag_color(tag.color)) - ) - else: - tag_tuple_list.append( - ( - f"{tag.display_name(self.lib)} - {count}", - self.get_tag_color(tag.color), - ) - ) - - self.print_columns(tag_tuple_list, add_enum=True) - - print("") - print(self.format_subtitle("Close/Done", BRIGHT_MAGENTA_FG)) - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # Close View =================================================== - if ( - com[0].lower() == "close" - or com[0].lower() == "c" - or com[0].lower() == "done" - ): - return - # Unknown Command ============================================== - elif com[0]: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - - def scr_manage_tag(self, tag_id: int, mode="edit", clear_scr=True): - """Screen for editing fields of a Tag object.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - while True: - tag: Tag = self.lib.get_tag(tag_id) - subtitle = ( - f'Editing Tag "{self.lib.get_tag(tag_id).display_name(self.lib)}"' - ) - # h1 = f'{self.lib.tags[tag_index].display_name()}' - - fg_text_color = BLACK_FG - fg_color = BRIGHT_CYAN_FG - bg_color = BRIGHT_CYAN_BG - if mode == "create": - subtitle = ( - f'Creating Tag "{self.lib.get_tag(tag_id).display_name(self.lib)}"' - ) - fg_color = BRIGHT_GREEN_FG - bg_color = BRIGHT_GREEN_BG - # elif mode == 'remove': - # # TODO: Uhh is this ever going to get used? Delete this when you know. - # subtitle = f'Removing Tag \"{self.lib.get_tag_from_id(tag_id).display_name(self.lib)}\"' - # fg_color = BRIGHT_RED_FG - # bg_color = BRIGHT_RED_BG - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title, color=f"{fg_text_color}{bg_color}")) - print(self.format_subtitle(subtitle, fg_color)) - # print(self.format_h1(h1, self.get_file_color( - # os.path.splitext(filename)[1]))) - if self.args.debug: - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} ID: {RESET} ", end="") - print(tag.id) - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Name: {RESET} ", end="") - print(tag.name) - - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Shorthand: {RESET} ", end="") - print(tag.shorthand) - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Aliases: {RESET} ", end="\n") - for a in tag.aliases: - print(f"{a}") - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Subtags: {RESET} ", end="\n") - char_count: int = 0 - for id in tag.subtag_ids: - st = self.lib.get_tag(id) - # Properly wrap Tags on screen - char_count += len(f" {st.display_name(self.lib)} ") + 1 - if char_count > os.get_terminal_size()[0]: - print("") - char_count = len(f" {st.display_name(self.lib)} ") + 1 - print( - f"{self.get_tag_color(st.color)} {st.display_name(self.lib)} {RESET}", - end="", - ) - # If the tag isn't the last one, print a space for the next one. - if id != tag.subtag_ids[-1]: - print(" ", end="") - else: - print("") - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Color: {RESET} ", end="") - print(f"{self.get_tag_color(tag.color)} {tag.color.title()} {RESET}") - - print("") - print(self.format_subtitle("Edit Close/Done", fg_color)) - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # Edit Tag Field ======================================================= - if com[0].lower() == "edit" or com[0].lower() == "e": - if len(com) > 1: - selection: str = " ".join(com[1:]).lower() - if "id".startswith(selection) and self.args.debug: - clear() - print(f"{ERROR} Tag IDs are not editable.") - clear_scr = False - elif "name".startswith(selection): - new_name: str = self.scr_edit_text( - text=tag.name, field_name="Name", allow_newlines=False - ) - new_tag: Tag = Tag( - id=tag.id, - name=new_name, - shorthand=tag.shorthand, - aliases=tag.aliases, - subtags_ids=tag.subtag_ids, - color=tag.color, - ) - self.lib.update_tag(new_tag) - # self.scr_manage_tag(tag_id=tag_id, mode=mode) - # return - # clear_scr=False - elif "shorthand".startswith(selection): - new_shorthand: str = self.scr_edit_text( - text=tag.shorthand, - field_name="Shorthand", - allow_newlines=False, - ) - new_tag: Tag = Tag( - id=tag.id, - name=tag.name, - shorthand=new_shorthand, - aliases=tag.aliases, - subtags_ids=tag.subtag_ids, - color=tag.color, - ) - self.lib.update_tag(new_tag) - # self.scr_manage_tag(tag_id=tag_id, mode=mode) - # return - # clear_scr=False - elif "aliases".startswith(selection): - new_aliases: list[str] = self.scr_edit_text( - text="\n".join(tag.aliases), - field_name="Aliases", - note=f"# Tag Aliases Below Are Separated By Newlines", - allow_newlines=True, - ).split("\n") - new_tag: Tag = Tag( - id=tag.id, - name=tag.name, - shorthand=tag.shorthand, - aliases=new_aliases, - subtags_ids=tag.subtag_ids, - color=tag.color, - ) - self.lib.update_tag(new_tag) - # self.scr_manage_tag(tag_id=tag_id, mode=mode) - # return - # clear_scr=False - elif "subtags".startswith(selection): - new_subtag_ids: list[int] = self.scr_edit_generic_tag_box( - tag_ids=tag.subtag_ids, tag_box_name="Subtags" - ) - new_tag: Tag = Tag( - id=tag.id, - name=tag.name, - shorthand=tag.shorthand, - aliases=tag.aliases, - subtags_ids=new_subtag_ids, - color=tag.color, - ) - self.lib.update_tag(new_tag) - # self.scr_manage_tag(tag_id=tag_id, mode=mode) - # return - # clear_scr=False - elif "color".startswith(selection): - new_color: str = self.scr_tag_color_dropdown( - fallback=tag.color, colors=TAG_COLORS - ) - new_tag: Tag = Tag( - id=tag.id, - name=tag.name, - shorthand=tag.shorthand, - aliases=tag.aliases, - subtags_ids=tag.subtag_ids, - color=new_color, - ) - self.lib.update_tag(new_tag) - # self.scr_manage_tag(tag_id=tag_id, mode=mode) - # return - # clear_scr=False - else: - clear() - print(f'{ERROR} Unknown Tag field "{" ".join(com[1:])}".') - # self.scr_manage_tag(tag_id, mode, clear_scr=False) - # return - clear_scr = False - # Close View =========================================================== - elif ( - com[0].lower() == "close" - or com[0].lower() == "done" - or com[0].lower() == "c" - ): - return - # # Quit ================================================================= - # elif com[0].lower() == 'quit' or com[0].lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ================================================== - # elif com[0].lower() == 'quit!' or com[0].lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Unknown Command ====================================================== - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - clear_scr = False - # return self.scr_browse_entries_gallery(index, clear_scr=False) - - def scr_delete_tag(self, tag_id: int, clear_scr=True) -> bool: - """Screen for confirming the deletion of a Tag.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - tag: Tag = self.lib.get_tag(tag_id) - subtitle = f'Confirm Deletion of Tag "{self.lib.get_tag(tag_id).display_name(self.lib)}"' - # h1 = f'{self.lib.tags[tag_index].display_name()}' - entry_ref_count, subtag_ref_count = self.lib.get_tag_ref_count(tag_id) - - fg_text_color = BLACK_FG - fg_color = BRIGHT_RED_FG - bg_color = BRIGHT_RED_BG - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title, color=f"{fg_text_color}{bg_color}")) - print(self.format_subtitle(subtitle, fg_color)) - print("") - - print( - f"{INFO} {BRIGHT_WHITE_FG}This Tag is in {fg_color}{entry_ref_count}{RESET}{BRIGHT_WHITE_FG} Entries{RESET} ", - end="", - ) - print("") - - print( - f"{INFO} {BRIGHT_WHITE_FG}This Tag is a Subtag for {fg_color}{subtag_ref_count}{RESET}{BRIGHT_WHITE_FG} Tags{RESET} ", - end="", - ) - print("") - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Name: {RESET} ", end="") - print(tag.name) - - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Shorthand: {RESET} ", end="") - print(tag.shorthand) - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Aliases: {RESET} ", end="\n") - for a in tag.aliases: - print(f"{a}") - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Subtags: {RESET} ", end="\n") - char_count: int = 0 - for id in tag.subtag_ids: - st = self.lib.get_tag(id) - # Properly wrap Tags on screen - char_count += len(f" {st.display_name(self.lib)} ") + 1 - if char_count > os.get_terminal_size()[0]: - print("") - char_count = len(f" {st.display_name(self.lib)} ") + 1 - print( - f"{self.get_tag_color(st.color)} {st.display_name(self.lib)} {RESET}", - end="", - ) - # If the tag isn't the last one, print a space for the next one. - if id != tag.subtag_ids[-1]: - print(" ", end="") - else: - print("") - - print("") - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} Color: {RESET} ", end="") - print(f"{self.get_tag_color(tag.color)} {tag.color.title()} {RESET}") - - print("") - print(self.format_subtitle("Yes Cancel", fg_color)) - print("> ", end="") - - com: str = input().rstrip() - - if com.lower() == "yes" or com.lower() == "y": - self.lib.remove_tag(tag_id) - return True - - return False - - def scr_edit_text( - self, - text: str, - field_name: str, - note: str = "", - allow_newlines=True, - clear_scr=True, - ) -> str: - """ - Screen for editing generic text. Currently used in Tag editing.\n - `text`: The text to be edited and returned.\n - `field_name`: The name to display of what is being edited.\n - `note`: An optional help message to display on screen for users..\n - `allow_newlines`: Determines if the text should be allowed to contain newlines.\n - """ - # NOTE: This code is derived from scr_edit_entry_text, just without the - # specific entry stuff like filenames and preview images. There may be - # a good way to combine the methods in the future, but for now here's this. - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - subtitle = f'Editing "{field_name}"' - - if clear_scr: - clear() - clear_scr = True - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_CYAN_BG}")) - print(self.format_subtitle(subtitle)) - print("") - - print( - self.format_title( - "Opened with Default Text Editor", f"{BLACK_FG}{BRIGHT_CYAN_BG}" - ) - ) - - # new_text: str = click.edit(text) - new_text: str = input() - if new_text is not None: - if not allow_newlines: - new_text = new_text.replace("\r", "").replace("\n", "") - else: - new_text = new_text.rstrip("\n").rstrip("\r") - return new_text - return text - - def scr_tag_color_dropdown( - self, fallback: str, colors: list[str], clear_scr=True - ) -> str: - """ - Screen for selecting and returning a string of a color name. Used in Tag editing. - Fallback: The value to return if an invalid selection by the user was made. - """ - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - subtitle = f"Select Color" - - fg_text_color = BLACK_FG - fg_color = BRIGHT_CYAN_FG - bg_color = BRIGHT_CYAN_BG - - if clear_scr: - clear() - clear_scr = True - - print(self.format_title(title, color=f"{fg_text_color}{bg_color}")) - print(self.format_subtitle(subtitle, fg_color)) - print("") - - color_tuple_list = [] - for color in colors: - color_tuple_list.append((color.title(), self.get_tag_color(color))) - - self.print_columns(color_tuple_list, add_enum=True) - print("") - - # for i, color in enumerate(colors): - # if i < (os.get_terminal_size()[1] - 7): - # print( - # f'{self.get_tag_color(color)}[{i+1}]{RESET} {self.get_tag_color(color)} {color.title()} {RESET}') - # else: - # print(f'{WHITE_FG}[...]{RESET}') - # break - # print('') - - print(self.format_subtitle(f"Enter # Cancel", fg_color)) - print("> ", end="") - - selected: str = input() - try: - if selected.isdigit() and 0 < int(selected) <= len(colors): - selected = colors[int(selected) - 1] - return selected - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except: - print(f"{ERROR} Invalid Tag Selection") - - return fallback - - def scr_edit_generic_tag_box( - self, tag_ids: list[int], tag_box_name: str, clear_scr=True - ) -> list[int]: - """Screen for editing a generic tag_box. Used in Tag subtag modification.""" - - title = f"{self.base_title} - Library '{self.lib.library_dir}'" - - while True: - subtitle = f"Editing {tag_box_name}" - - if clear_scr: - clear() - clear_scr = True - - print(self.format_title(title, color=f"{BLACK_FG}{BRIGHT_CYAN_BG}")) - print(self.format_subtitle(subtitle)) - print("") - - print(f"{BRIGHT_WHITE_BG}{BLACK_FG} {tag_box_name}: {RESET} ") - for i, id in enumerate(tag_ids): - tag = self.lib.get_tag(id) - print( - f"{self.get_tag_color(tag.color)}[{i+1}]{RESET} {self.get_tag_color(tag.color)} {tag.display_name(self.lib)} {RESET}" - ) - print("") - - print( - self.format_subtitle( - "Add Remove <#> Close/Done Quit" - ) - ) - print("> ", end="") - - com: list[str] = input().lstrip().rstrip().split(" ") - gc, message = self.global_commands(com) - if gc: - if message: - clear() - print(message) - clear_scr = False - else: - # Add Tag ============================================================== - if com[0].lower() == "add": - if len(com) > 1: - tag_list = self.lib.search_tags( - " ".join(com[1:]), include_cluster=True - ) - selected_ids: list[int] = [] - if len(tag_list) > 1: - selected_ids = self.scr_select_tags(tag_list) - else: - selected_ids = tag_list # Single Tag - if selected_ids: - for id in selected_ids: - if id in tag_ids: - selected_ids.remove(id) - return self.scr_edit_generic_tag_box( - tag_ids + selected_ids, tag_box_name - ) - tag_ids = tag_ids + selected_ids - # else: - # return self.scr_edit_generic_tag_box(tag_ids, tag_box_name) - # Remove Tag =========================================================== - elif com[0].lower() == "remove" or com[0].lower() == "rm": - if len(com) > 1: - try: - # selected_tag_ids: list[int] = [] - # for c in com[1:]: - # if (int(c)-1) < 0: - # raise IndexError - # selected_tag_ids.append(tag_ids[int(c[1])-1]) - selected_id = tag_ids[int(com[1]) - 1] - tag_ids.remove(selected_id) - # return self.scr_edit_generic_tag_box(tag_ids, tag_box_name) - # except SystemExit: - # self.cleanup_before_exit() - # sys.exit() - except: - clear() - print(f"{ERROR} Invalid Tag Selection '{com[1:]}'") - # return self.scr_edit_generic_tag_box(tag_ids, tag_box_name, clear_scr=False) - clear_scr = False - # Close View =========================================================== - elif ( - com[0].lower() == "close" - or com[0].lower() == "c" - or com[0].lower() == "done" - ): - # clear() - # pass - return tag_ids - # # Quit ================================================================= - # elif com[0].lower() == 'quit' or com[0].lower() == 'q': - # self.lib.save_library_to_disk() - # # self.cleanup() - # sys.exit() - # # Quit without Saving ================================================== - # elif com[0].lower() == 'quit!' or com[0].lower() == 'q!': - # # self.cleanup() - # sys.exit() - # Unknown Command ====================================================== - else: - clear() - print(f'{ERROR} Unknown command \'{" ".join(com)}\'') - # return self.scr_edit_generic_tag_box(tag_ids, tag_box_name, clear_scr=False) - clear_scr = False - - # return tag_ids diff --git a/tagstudio/src/core/constants.py b/tagstudio/src/core/constants.py index 7efee56fd..64ff62142 100644 --- a/tagstudio/src/core/constants.py +++ b/tagstudio/src/core/constants.py @@ -1,3 +1,5 @@ +from enum import Enum + VERSION: str = "9.3.2" # Major.Minor.Patch VERSION_BRANCH: str = "" # Usually "" or "Pre-Release" @@ -120,49 +122,13 @@ + SHORTCUT_TYPES ) -BOX_FIELDS = ["tag_box", "text_box"] -TEXT_FIELDS = ["text_line", "text_box"] -DATE_FIELDS = ["datetime"] - -TAG_COLORS = [ - "", - "black", - "dark gray", - "gray", - "light gray", - "white", - "light pink", - "pink", - "red", - "red orange", - "orange", - "yellow orange", - "yellow", - "lime", - "light green", - "mint", - "green", - "teal", - "cyan", - "light blue", - "blue", - "blue violet", - "violet", - "purple", - "lavender", - "berry", - "magenta", - "salmon", - "auburn", - "dark brown", - "brown", - "light brown", - "blonde", - "peach", - "warm gray", - "cool gray", - "olive", -] TAG_FAVORITE = 1 TAG_ARCHIVED = 0 + + +class LibraryPrefs(Enum): + IS_EXCLUDE_LIST = True + EXTENSION_LIST: list[str] = [".json", ".xmp", ".aae"] + PAGE_SIZE: int = 500 + DB_VERSION: int = 1 diff --git a/tagstudio/src/core/enums.py b/tagstudio/src/core/enums.py index 7610a2ccc..8907ba179 100644 --- a/tagstudio/src/core/enums.py +++ b/tagstudio/src/core/enums.py @@ -19,21 +19,15 @@ class Theme(str, enum.Enum): COLOR_DISABLED_BG = "#65440D12" -class SearchMode(int, enum.Enum): - """Operational modes for item searching.""" - - AND = 0 - OR = 1 - - -class FieldID(int, enum.Enum): - TITLE = 0 - AUTHOR = 1 - ARTIST = 2 - DESCRIPTION = 4 - NOTES = 5 - TAGS = 6 - CONTENT_TAGS = 7 - META_TAGS = 8 - DATE_PUBLISHED = 14 - SOURCE = 21 +class OpenStatus(enum.IntEnum): + NOT_FOUND = 0 + SUCCESS = 1 + CORRUPTED = 2 + + +class MacroID(enum.Enum): + AUTOFILL = "autofill" + SIDECAR = "sidecar" + BUILD_URL = "build_url" + MATCH = "match" + CLEAN_URL = "clean_url" diff --git a/tagstudio/src/core/json_typing.py b/tagstudio/src/core/json_typing.py deleted file mode 100644 index 29ffdc35b..000000000 --- a/tagstudio/src/core/json_typing.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import TypedDict -from typing_extensions import NotRequired - - -class JsonLibary(TypedDict("", {"ts-version": str})): - # "ts-version": str - tags: "list[JsonTag]" - collations: "list[JsonCollation]" - fields: list # TODO - macros: "list[JsonMacro]" - entries: "list[JsonEntry]" - ext_list: list[str] - is_exclude_list: bool - ignored_extensions: NotRequired[list[str]] # deprecated - - -class JsonBase(TypedDict): - id: int - - -class JsonTag(JsonBase, total=False): - name: str - aliases: list[str] - color: str - shorthand: str - subtag_ids: list[int] - - -class JsonCollation(JsonBase, total=False): - title: str - e_ids_and_pages: list[list[int]] - sort_order: str - cover_id: int - - -class JsonEntry(JsonBase, total=False): - filename: str - path: str - fields: list[dict] # TODO - - -class JsonMacro(JsonBase, total=False): ... # TODO diff --git a/tagstudio/src/core/library/__init__.py b/tagstudio/src/core/library/__init__.py new file mode 100644 index 000000000..98b662e52 --- /dev/null +++ b/tagstudio/src/core/library/__init__.py @@ -0,0 +1 @@ +from .alchemy import * # noqa diff --git a/tagstudio/src/core/library/alchemy/__init__.py b/tagstudio/src/core/library/alchemy/__init__.py new file mode 100644 index 000000000..993e1aa0c --- /dev/null +++ b/tagstudio/src/core/library/alchemy/__init__.py @@ -0,0 +1,6 @@ +from .models import Entry +from .library import Library +from .models import Tag +from .enums import ItemType + +__all__ = ["Entry", "Library", "Tag", "ItemType"] diff --git a/tagstudio/src/core/library/alchemy/db.py b/tagstudio/src/core/library/alchemy/db.py new file mode 100644 index 000000000..f1a23f5d3 --- /dev/null +++ b/tagstudio/src/core/library/alchemy/db.py @@ -0,0 +1,48 @@ +from pathlib import Path + +import structlog +from sqlalchemy import Dialect, Engine, String, TypeDecorator, create_engine, text +from sqlalchemy.orm import DeclarativeBase + +logger = structlog.getLogger(__name__) + + +class PathType(TypeDecorator): + impl = String + cache_ok = True + + def process_bind_param(self, value: Path, dialect: Dialect): + if value is not None: + return Path(value).as_posix() + return None + + def process_result_value(self, value: str, dialect: Dialect): + if value is not None: + return Path(value) + return None + + +class Base(DeclarativeBase): + type_annotation_map = {Path: PathType} + + +def make_engine(connection_string: str) -> Engine: + return create_engine(connection_string) + + +def make_tables(engine: Engine) -> None: + logger.info("creating db tables") + Base.metadata.create_all(engine) + + # tag IDs < 1000 are reserved + # create tag and delete it to bump the autoincrement sequence + # TODO - find a better way + with engine.connect() as conn: + conn.execute(text("INSERT INTO tags (id, name, color) VALUES (999, 'temp', 1)")) + conn.execute(text("DELETE FROM tags WHERE id = 999")) + conn.commit() + + +def drop_tables(engine: Engine) -> None: + logger.info("dropping db tables") + Base.metadata.drop_all(engine) diff --git a/tagstudio/src/core/library/alchemy/enums.py b/tagstudio/src/core/library/alchemy/enums.py new file mode 100644 index 000000000..d1eea8aee --- /dev/null +++ b/tagstudio/src/core/library/alchemy/enums.py @@ -0,0 +1,116 @@ +import enum +from dataclasses import dataclass +from pathlib import Path + + +class TagColor(enum.IntEnum): + DEFAULT = 1 + BLACK = 2 + DARK_GRAY = 3 + GRAY = 4 + LIGHT_GRAY = 5 + WHITE = 6 + LIGHT_PINK = 7 + PINK = 8 + RED = 9 + RED_ORANGE = 10 + ORANGE = 11 + YELLOW_ORANGE = 12 + YELLOW = 13 + LIME = 14 + LIGHT_GREEN = 15 + MINT = 16 + GREEN = 17 + TEAL = 18 + CYAN = 19 + LIGHT_BLUE = 20 + BLUE = 21 + BLUE_VIOLET = 22 + VIOLET = 23 + PURPLE = 24 + LAVENDER = 25 + BERRY = 26 + MAGENTA = 27 + SALMON = 28 + AUBURN = 29 + DARK_BROWN = 30 + BROWN = 31 + LIGHT_BROWN = 32 + BLONDE = 33 + PEACH = 34 + WARM_GRAY = 35 + COOL_GRAY = 36 + OLIVE = 37 + + +class SearchMode(enum.IntEnum): + """Operational modes for item searching.""" + + AND = 0 + OR = 1 + + +class ItemType(enum.Enum): + ENTRY = 0 + COLLATION = 1 + TAG_GROUP = 2 + + +@dataclass +class FilterState: + """Represent a state of the Library grid view.""" + + page_index: int = 0 + page_size: int = 500 + tag: str | None = None + id: int | None = None + tag_id: int | None = None + path: str | Path | None = None + query: str | None = None + search_mode: SearchMode = SearchMode.AND # TODO - actually implement this + + def __post_init__(self): + # strip values automatically + if query := (self.query and self.query.strip()): + # parse the value + if ":" in query: + kind, _, value = query.partition(":") + else: + # default to tag search + kind, value = "tag", query + + if kind == "id": + self.id = int(value) + elif kind == "tag_id": + self.tag_id = int(value) + elif kind == "path": + self.path = value + elif kind == "tag": + self.tag = value + + else: + self.tag = self.tag and self.tag.strip() + self.id = int(self.id) if self.id is not None else None + self.tag_id = int(self.tag_id) if self.tag_id is not None else None + self.path = self.path and str(self.path) + + @property + def summary(self): + """Show query summary""" + return self.query or self.tag or self.id or self.tag_id or self.path + + @property + def limit(self): + return self.page_size + + @property + def offset(self): + return self.page_size * self.page_index + + +class FieldTypeEnum(enum.Enum): + TEXT_LINE = "Text Line" + TEXT_BOX = "Text Box" + TAGS = "Tags" + DATETIME = "Datetime" + BOOLEAN = "Checkbox" diff --git a/tagstudio/src/core/library/alchemy/fields.py b/tagstudio/src/core/library/alchemy/fields.py new file mode 100644 index 000000000..66d7ff892 --- /dev/null +++ b/tagstudio/src/core/library/alchemy/fields.py @@ -0,0 +1,175 @@ +from __future__ import annotations + +from dataclasses import dataclass +from enum import Enum +from typing import Union, Any, TYPE_CHECKING + +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .db import Base +from .enums import FieldTypeEnum + +if TYPE_CHECKING: + from .models import Entry, Tag, LibraryField + +Field = Union["TextField", "TagBoxField", "DatetimeField"] + + +class BooleanField(Base): + __tablename__ = "boolean_fields" + + id: Mapped[int] = mapped_column(primary_key=True) + type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) + type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) + + entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) + entry: Mapped[Entry] = relationship() + + value: Mapped[bool] + + def __key(self): + return (self.type, self.value) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, value) -> bool: + if isinstance(value, BooleanField): + return self.__key() == value.__key() + raise NotImplementedError + + +class TextField(Base): + __tablename__ = "text_fields" + + id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) + type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) + type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) + + entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) + entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) + + value: Mapped[str | None] + + def __key(self): + return (self.type, self.value) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, value) -> bool: + if isinstance(value, TextField): + return self.__key() == value.__key() + elif isinstance(value, (TagBoxField, DatetimeField)): + return False + raise NotImplementedError + + +class TagBoxField(Base): + __tablename__ = "tag_box_fields" + + id: Mapped[int] = mapped_column(primary_key=True) + type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) + type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) + + entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) + entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) + + tags: Mapped[set[Tag]] = relationship(secondary="tag_fields") + + def __key(self): + return ( + self.entry_id, + self.type_key, + ) + + @property + def value(self) -> None: + """For interface compatibility with other field types.""" + return None + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, value) -> bool: + if isinstance(value, TagBoxField): + return self.__key() == value.__key() + raise NotImplementedError + + +class DatetimeField(Base): + __tablename__ = "datetime_fields" + + id: Mapped[int] = mapped_column(primary_key=True) + type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) + type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) + + entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) + entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) + + value: Mapped[str | None] + + def __key(self): + return (self.type, self.value) + + def __hash__(self): + return hash(self.__key()) + + def __eq__(self, value) -> bool: + if isinstance(value, DatetimeField): + return self.__key() == value.__key() + raise NotImplementedError + + +@dataclass +class DefaultField: + id: int + name: str + type: Any # TextFieldTypes | TagBoxTypes | DateTimeTypes + + +class _FieldID(Enum): + """Only for bootstrapping content of DB table""" + + TITLE = DefaultField(id=0, name="Title", type=FieldTypeEnum.TEXT_LINE) + AUTHOR = DefaultField(id=1, name="Author", type=FieldTypeEnum.TEXT_LINE) + ARTIST = DefaultField(id=2, name="Artist", type=FieldTypeEnum.TEXT_LINE) + URL = DefaultField(id=3, name="URL", type=FieldTypeEnum.TEXT_LINE) + DESCRIPTION = DefaultField(id=4, name="Description", type=FieldTypeEnum.TEXT_LINE) + NOTES = DefaultField(id=5, name="Notes", type=FieldTypeEnum.TEXT_BOX) + TAGS = DefaultField(id=6, name="Tags", type=FieldTypeEnum.TAGS) + TAGS_CONTENT = DefaultField(id=7, name="Content Tags", type=FieldTypeEnum.TAGS) + TAGS_META = DefaultField(id=8, name="Meta Tags", type=FieldTypeEnum.TAGS) + COLLATION = DefaultField(id=9, name="Collation", type=FieldTypeEnum.TEXT_LINE) + DATE = DefaultField(id=10, name="Date", type=FieldTypeEnum.DATETIME) + DATE_CREATED = DefaultField(id=11, name="Date Created", type=FieldTypeEnum.DATETIME) + DATE_MODIFIED = DefaultField( + id=12, name="Date Modified", type=FieldTypeEnum.DATETIME + ) + DATE_TAKEN = DefaultField(id=13, name="Date Taken", type=FieldTypeEnum.DATETIME) + DATE_PUBLISHED = DefaultField( + id=14, name="Date Published", type=FieldTypeEnum.DATETIME + ) + # ARCHIVED = DefaultField(id=15, name="Archived", type=CheckboxField.checkbox) + # FAVORITE = DefaultField(id=16, name="Favorite", type=CheckboxField.checkbox) + BOOK = DefaultField(id=17, name="Book", type=FieldTypeEnum.TEXT_LINE) + COMIC = DefaultField(id=18, name="Comic", type=FieldTypeEnum.TEXT_LINE) + SERIES = DefaultField(id=19, name="Series", type=FieldTypeEnum.TEXT_LINE) + MANGA = DefaultField(id=20, name="Manga", type=FieldTypeEnum.TEXT_LINE) + SOURCE = DefaultField(id=21, name="Source", type=FieldTypeEnum.TEXT_LINE) + DATE_UPLOADED = DefaultField( + id=22, name="Date Uploaded", type=FieldTypeEnum.DATETIME + ) + DATE_RELEASED = DefaultField( + id=23, name="Date Released", type=FieldTypeEnum.DATETIME + ) + VOLUME = DefaultField(id=24, name="Volume", type=FieldTypeEnum.TEXT_LINE) + ANTHOLOGY = DefaultField(id=25, name="Anthology", type=FieldTypeEnum.TEXT_LINE) + MAGAZINE = DefaultField(id=26, name="Magazine", type=FieldTypeEnum.TEXT_LINE) + PUBLISHER = DefaultField(id=27, name="Publisher", type=FieldTypeEnum.TEXT_LINE) + GUEST_ARTIST = DefaultField( + id=28, name="Guest Artist", type=FieldTypeEnum.TEXT_LINE + ) + COMPOSER = DefaultField(id=29, name="Composer", type=FieldTypeEnum.TEXT_LINE) + COMMENTS = DefaultField(id=30, name="Comments", type=FieldTypeEnum.TEXT_LINE) diff --git a/tagstudio/src/core/library/alchemy/joins.py b/tagstudio/src/core/library/alchemy/joins.py new file mode 100644 index 000000000..b3f4b711f --- /dev/null +++ b/tagstudio/src/core/library/alchemy/joins.py @@ -0,0 +1,20 @@ +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column + +from .db import Base + + +class TagSubtag(Base): + __tablename__ = "tag_subtags" + + parent_id: Mapped[int] = mapped_column(ForeignKey("tags.id"), primary_key=True) + child_id: Mapped[int] = mapped_column(ForeignKey("tags.id"), primary_key=True) + + +class TagField(Base): + __tablename__ = "tag_fields" + + field_id: Mapped[int] = mapped_column( + ForeignKey("tag_box_fields.id"), primary_key=True + ) + tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id"), primary_key=True) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py new file mode 100644 index 000000000..933d1168b --- /dev/null +++ b/tagstudio/src/core/library/alchemy/library.py @@ -0,0 +1,777 @@ +from datetime import datetime, UTC +import shutil +from os import makedirs +from pathlib import Path +from typing import Iterator, Any + +import structlog +from sqlalchemy import ( + and_, + or_, + select, + create_engine, + Engine, + func, + update, + URL, + exists, +) +from sqlalchemy.exc import IntegrityError, InvalidRequestError +from sqlalchemy.orm import ( + Session, + contains_eager, + selectinload, + make_transient, +) +from typing import TYPE_CHECKING + +from .db import make_tables +from .enums import TagColor, FilterState, FieldTypeEnum +from .fields import ( + DatetimeField, + TagBoxField, + TextField, + _FieldID, + Field, +) +from .joins import TagSubtag, TagField +from .models import Entry, Preferences, Tag, TagAlias, LibraryField +from ...constants import ( + LibraryPrefs, + TS_FOLDER_NAME, + TAG_ARCHIVED, + TAG_FAVORITE, + BACKUP_FOLDER_NAME, +) + +if TYPE_CHECKING: + from ...utils.dupe_files import DupeRegistry + from ...utils.missing_files import MissingRegistry + +LIBRARY_FILENAME: str = "ts_library.sqlite" + +logger = structlog.get_logger(__name__) + +import re +import unicodedata + + +def slugify(input_string: str) -> str: + # Convert to lowercase and normalize unicode characters + slug = unicodedata.normalize("NFKD", input_string.lower()) + + # Remove non-word characters (except hyphens and spaces) + slug = re.sub(r"[^\w\s-]", "", slug).strip() + + # Replace spaces with hyphens + slug = re.sub(r"[-\s]+", "-", slug) + + return slug + + +def get_default_tags() -> tuple[Tag, ...]: + archive_tag = Tag( + id=TAG_ARCHIVED, + name="Archived", + aliases={TagAlias(name="Archive")}, + color=TagColor.RED, + ) + + favorite_tag = Tag( + id=TAG_FAVORITE, + name="Favorite", + aliases={ + TagAlias(name="Favorited"), + TagAlias(name="Favorites"), + }, + color=TagColor.YELLOW, + ) + + return archive_tag, favorite_tag + + +class Library: + """Class for the Library object, and all CRUD operations made upon it.""" + + library_dir: Path + storage_path: Path | str + engine: Engine | None + + ignored_extensions: list[str] + + missing_tracker: "MissingRegistry" + dupe_tracker: "DupeRegistry" + + def open_library( + self, library_dir: Path | str, storage_path: str | None = None + ) -> None: + if isinstance(library_dir, str): + library_dir = Path(library_dir) + + self.library_dir = library_dir + if storage_path == ":memory:": + self.storage_path = storage_path + else: + self.verify_ts_folders(self.library_dir) + self.storage_path = self.library_dir / TS_FOLDER_NAME / LIBRARY_FILENAME + + connection_string = URL.create( + drivername="sqlite", + database=str(self.storage_path), + ) + + logger.info("opening library", connection_string=connection_string) + self.engine = create_engine(connection_string) + with Session(self.engine) as session: + make_tables(self.engine) + + tags = get_default_tags() + try: + session.add_all(tags) + session.commit() + except IntegrityError: + # default tags may exist already + session.rollback() + + for pref in LibraryPrefs: + try: + session.add(Preferences(key=pref.name, value=pref.value)) + session.commit() + except IntegrityError: + logger.debug("preference already exists", pref=pref) + session.rollback() + + for field in _FieldID: + try: + session.add( + LibraryField( + name=field.value.name, + type=field.value.type, + order=field.value.id, + key=field.name, + ) + ) + session.commit() + except IntegrityError: + logger.debug("preference already exists", pref=pref) + session.rollback() + + # load ignored extensions + self.ignored_extensions = self.prefs(LibraryPrefs.EXTENSION_LIST) + + def delete_item(self, item): + logger.info("deleting item", item=item) + with Session(self.engine) as session: + session.delete(item) + session.commit() + + def remove_field_tag(self, entry: Entry, tag_id: int, field_key: str) -> bool: + assert isinstance(field_key, str), f"field_key is {type(field_key)}" + with Session(self.engine) as session: + # find field matching entry and field_type + field = session.scalars( + select(TagBoxField).where( + and_( + TagBoxField.entry_id == entry.id, + TagBoxField.type_key == field_key, + ) + ) + ).first() + + if not field: + logger.error("no field found", entry=entry, field=field) + return False + + try: + # find the record in `TagField` table and delete it + tag_field = session.scalars( + select(TagField).where( + and_( + TagField.tag_id == tag_id, + TagField.field_id == field.id, + ) + ) + ).first() + if tag_field: + session.delete(tag_field) + session.commit() + + return True + except IntegrityError as e: + logger.exception(e) + session.rollback() + return False + + def get_entry(self, entry_id: int) -> Entry | None: + """Load entry without joins.""" + with Session(self.engine) as session: + entry = session.scalar(select(Entry).where(Entry.id == entry_id)) + if not entry: + return None + session.expunge(entry) + make_transient(entry) + return entry + + @property + def entries_count(self) -> int: + with Session(self.engine) as session: + return session.scalar(select(func.count(Entry.id))) + + @property + def _entries(self) -> Iterator[Entry]: + """Load entries without joins.""" + with Session(self.engine) as session: + entries = session.execute(select(Entry).distinct()).scalars() # .unique() + for entry in entries: + yield entry + session.expunge(entry) + + @property + def _entries_full(self) -> Iterator[Entry]: + """Load entries with joins.""" + with Session(self.engine) as session: + stmt = ( + select(Entry) + .outerjoin(Entry.text_fields) + .outerjoin(Entry.datetime_fields) + .outerjoin(Entry.tag_box_fields) + .options( + contains_eager(Entry.text_fields), + contains_eager(Entry.datetime_fields), + contains_eager(Entry.tag_box_fields).selectinload(TagBoxField.tags), + ) + .distinct() + ) + + entries = session.execute(stmt).scalars().unique() # .all() + + for entry in entries: + yield entry + session.expunge(entry) + + @property + def tags(self) -> list[Tag]: + with Session(self.engine) as session: + # load all tags and join subtags + tags_query = select(Tag).options(selectinload(Tag.subtags)) + tags = session.scalars(tags_query).unique() + tags_list = list(tags) + + for tag in tags_list: + session.expunge(tag) + for subtag in tag.subtags: + session.expunge(subtag) + + return list(tags_list) + + def verify_ts_folders(self, library_dir: Path) -> None: + """Verify/create folders required by TagStudio.""" + if library_dir is None: + raise ValueError("No path set.") + + if not library_dir.exists(): + raise ValueError("Invalid library directory.") + + full_ts_path = library_dir / TS_FOLDER_NAME + if not full_ts_path.exists(): + logger.info("creating library directory", dir=full_ts_path) + full_ts_path.mkdir(parents=True, exist_ok=True) + + def add_entries(self, items: list[Entry]) -> list[int]: + """Add multiple Entry records to the Library.""" + assert items + + with Session(self.engine) as session: + # add all items + session.add_all(items) + session.flush() + + new_ids = [item.id for item in items] + + session.expunge_all() + + session.commit() + + return new_ids + + def remove_entries(self, entry_ids: list[int]) -> None: + """Remove Entry items matching supplied IDs from the Library.""" + with Session(self.engine) as session: + session.query(Entry).where(Entry.id.in_(entry_ids)).delete() + session.commit() + + def has_path_entry(self, path: Path) -> bool: + """Check if item with given path is in library already.""" + with Session(self.engine) as session: + return session.query(exists().where(Entry.path == path)).scalar() + + def search_library( + self, + search: FilterState, + ) -> tuple[int, list[Entry]]: + """Filter library by search query. + + :return: number of entries matching the query and one page of results. + """ + assert isinstance(search, FilterState) + assert self.engine + + with Session(self.engine, expire_on_commit=False) as session: + statement = select(Entry) + + if search.tag: + statement = ( + statement.join(Entry.tag_box_fields) + .join(TagBoxField.tags) + .where( + or_( + Tag.name.ilike(search.tag), + Tag.shorthand.ilike(search.tag), + ) + ) + ) + + elif search.id: + statement = statement.where(Entry.id == search.id) + elif search.tag_id: + statement = statement.where(Tag.id == search.tag_id) + elif search.path: + statement = statement.where(Entry.path.ilike(search.path)) + + extensions = self.prefs(LibraryPrefs.EXTENSION_LIST) + is_exclude_list = self.prefs(LibraryPrefs.IS_EXCLUDE_LIST) + if extensions and is_exclude_list: + statement = statement.where( + Entry.path.notilike(f"%.{','.join(extensions)}") + ) + elif extensions: + statement = statement.where( + Entry.path.ilike(f"%.{','.join(extensions)}") + ) + + statement = statement.options( + selectinload(Entry.text_fields), + selectinload(Entry.datetime_fields), + selectinload(Entry.tag_box_fields) + .joinedload(TagBoxField.tags) + .options(selectinload(Tag.aliases), selectinload(Tag.subtags)), + ) + + query_count = select(func.count()).select_from(statement.alias("entries")) + count_all: int = session.execute(query_count).scalar() + + statement = statement.limit(search.limit).offset(search.offset) + + logger.info( + "searching library", + filter=search, + query_full=str( + statement.compile(compile_kwargs={"literal_binds": True}) + ), + ) + + entries_ = list(session.scalars(statement).unique()) + + session.expunge_all() + + return count_all, entries_ + + def search_tags( + self, + search: FilterState, + ) -> list[Tag]: + """Return a list of Tag records matching the query.""" + + with Session(self.engine) as session: + query = select(Tag) + query = query.options( + selectinload(Tag.subtags), + selectinload(Tag.aliases), + ) + + if search.tag: + query = query.where( + or_( + Tag.name.ilike(search.tag), + Tag.shorthand.ilike(search.tag), + ) + ) + + tags = session.scalars(query) + + res = list(tags) + + logger.info( + "searching tags", + search=search, + statement=str(query), + results=len(res), + ) + + session.expunge_all() + return res + + def get_all_child_tag_ids(self, tag_id: int) -> list[int]: + """Recursively traverse a Tag's subtags and return a list of all children tags.""" + + all_subtags: set[int] = {tag_id} + + with Session(self.engine) as session: + tag = session.scalar(select(Tag).where(Tag.id == tag_id)) + if tag is None: + raise ValueError(f"No tag found with id {tag_id}.") + + subtag_ids = tag.subtag_ids + + all_subtags.update(subtag_ids) + + for sub_id in subtag_ids: + all_subtags.update(self.get_all_child_tag_ids(sub_id)) + + return list(all_subtags) + + def update_entry_path(self, entry_id: int | Entry, path: Path) -> None: + if isinstance(entry_id, Entry): + entry_id = entry_id.id + + with Session(self.engine) as session: + update_stmt = ( + update(Entry) + .where( + and_( + Entry.id == entry_id, + ) + ) + .values(path=path) + ) + + session.execute(update_stmt) + session.commit() + + def remove_tag_from_field(self, tag: Tag, field: TagBoxField) -> None: + with Session(self.engine) as session: + field_ = session.scalars( + select(TagBoxField).where(TagBoxField.id == field.id) + ).one() + + tag = session.scalars(select(Tag).where(Tag.id == tag.id)).one() + + field_.tags.remove(tag) + session.add(field_) + session.commit() + + def remove_entry_field( + self, + field: Field, + entry_ids: list[int], + ) -> None: + field_class = type(field) + + with Session(self.engine) as session: + session.query(field_class).where( + and_( + field_class.entry_id.in_(entry_ids), + field_class.type == field.type, + ) + ).delete() + session.commit() + + def update_entry_field( + self, + entry_ids: list[int] | int, + field: Field, + content: str | datetime | set[Tag], + ): + if isinstance(entry_ids, int): + entry_ids = [entry_ids] + + field_class = type(field) + + with Session(self.engine) as session: + update_stmt = ( + update(field_class) + .where( + and_( + field_class.type == field.type, + field_class.entry_id.in_(entry_ids), + ) + ) + .values(value=content) + ) + + session.execute(update_stmt) + session.commit() + + @property + def field_types(self) -> dict[str, LibraryField]: + with Session(self.engine) as session: + return {x.key: x for x in session.scalars(select(LibraryField)).all()} + + def get_library_field(self, field_key: str) -> LibraryField: + with Session(self.engine) as session: + return session.scalar( + select(LibraryField).where(LibraryField.key == field_key) + ) + + def add_entry_field_type( + self, + entry_ids: list[int] | int, + *, + field: LibraryField | None = None, + field_id: _FieldID | str | None = None, + value: str | datetime | list[str] | None = None, + ) -> bool: + logger.info( + "add_field_to_entry", + entry_ids=entry_ids, + field_type=field, + field_id=field_id, + value=value, + ) + # supply only instance or ID, not both + assert bool(field) != (field_id is not None) + + if isinstance(entry_ids, int): + entry_ids = [entry_ids] + + if not field: + if isinstance(field_id, _FieldID): + field_id = field_id.name + field = self.get_library_field(field_id) + + field_model: TextField | DatetimeField | TagBoxField + if field.type in (FieldTypeEnum.TEXT_LINE, FieldTypeEnum.TEXT_BOX): + field_model = TextField( + type=field, + value=value or "", + ) + elif field.type == FieldTypeEnum.TAGS: + field_model = TagBoxField( + type=field, + ) + + if value: + assert isinstance(value, list) + for tag in value: + field_model.tags.add(Tag(name=tag)) + + elif field.type == FieldTypeEnum.DATETIME: + field_model = DatetimeField( + type=field, + value=value, + ) + else: + raise NotImplementedError(f"field type not implemented: {field.type}") + + with Session(self.engine) as session: + try: + for entry_id in entry_ids: + field_model.entry_id = entry_id + session.add(field_model) + session.flush() + + session.commit() + return True + except IntegrityError as e: + logger.exception(e) + session.rollback() + return False + # TODO - trigger error signal + + def add_tag(self, tag: Tag, subtag_ids: list[int] | None = None) -> Tag | None: + with Session(self.engine, expire_on_commit=False) as session: + try: + session.add(tag) + session.flush() + + for subtag_id in subtag_ids or []: + subtag = TagSubtag( + parent_id=tag.id, + child_id=subtag_id, + ) + session.add(subtag) + + session.commit() + + session.expunge(tag) + return tag + + except IntegrityError as e: + logger.exception(e) + session.rollback() + return None + + def add_field_tag( + self, + entry: Entry, + tag: Tag, + field_key: str = _FieldID.TAGS.name, + create_field: bool = False, + ) -> bool: + assert isinstance(field_key, str), f"field_key is {type(field_key)}" + + with Session(self.engine) as session: + # find field matching entry and field_type + field = session.scalars( + select(TagBoxField).where( + and_( + TagBoxField.entry_id == entry.id, + TagBoxField.type_key == field_key, + ) + ) + ).first() + + if not field and not create_field: + logger.error("no field found", entry=entry, field_key=field_key) + return False + + try: + if not field: + field = TagBoxField( + type_key=field_key, + entry_id=entry.id, + ) + + field.tags = field.tags | {tag} + session.add(field) + session.commit() + logger.info( + "tag added to field", tag=tag, field=field, entry_id=entry.id + ) + + return True + except InvalidRequestError as e: + logger.exception(e) + session.rollback() + return False + + def save_library_backup_to_disk(self) -> Path: + assert isinstance(self.library_dir, Path) + makedirs( + str(self.library_dir / TS_FOLDER_NAME / BACKUP_FOLDER_NAME), exist_ok=True + ) + + filename = ( + f'ts_library_backup_{datetime.now(UTC).strftime("%Y_%m_%d_%H%M%S")}.sqlite' + ) + + target_path = self.library_dir / TS_FOLDER_NAME / BACKUP_FOLDER_NAME / filename + + shutil.copy2( + self.library_dir / TS_FOLDER_NAME / LIBRARY_FILENAME, + target_path, + ) + + return target_path + + def get_tag(self, tag_id: int) -> Tag: + with Session(self.engine) as session: + tags_query = select(Tag).options(selectinload(Tag.subtags)) + tag = session.scalar(tags_query.where(Tag.id == tag_id)) + + session.expunge(tag) + for subtag in tag.subtags: + session.expunge(subtag) + + return tag + + def add_subtag(self, base_id: int, new_tag_id: int) -> bool: + # open session and save as parent tag + with Session(self.engine) as session: + tag = TagSubtag( + parent_id=base_id, + child_id=new_tag_id, + ) + + try: + session.add(tag) + session.commit() + return True + except IntegrityError: + session.rollback() + logger.exception("IntegrityError") + return False + + def update_tag(self, tag: Tag, subtag_ids: list[int]) -> None: + """ + Edit a Tag in the Library. + """ + # TODO - maybe merge this with add_tag? + + if tag.shorthand: + tag.shorthand = slugify(tag.shorthand) + + if tag.aliases: + # TODO + ... + + # save the tag + with Session(self.engine) as session: + try: + # update the existing tag + session.add(tag) + session.flush() + + # load all tag's subtag to know which to remove + prev_subtags = session.scalars( + select(TagSubtag).where(TagSubtag.parent_id == tag.id) + ).all() + + for subtag in prev_subtags: + if subtag.child_id not in subtag_ids: + session.delete(subtag) + else: + # no change, remove from list + subtag_ids.remove(subtag.child_id) + + # create remaining items + for subtag_id in subtag_ids: + # add new subtag + subtag = TagSubtag( + parent_id=tag.id, + child_id=subtag_id, + ) + session.add(subtag) + + session.commit() + except IntegrityError: + session.rollback() + logger.exception("IntegrityError") + + def prefs(self, key: LibraryPrefs) -> Any: + # load given item from Preferences table + with Session(self.engine) as session: + return session.scalar( + select(Preferences).where(Preferences.key == key.name) + ).value + + def set_prefs(self, key: LibraryPrefs, value: Any) -> None: + # set given item in Preferences table + with Session(self.engine) as session: + # load existing preference and update value + pref = session.scalar( + select(Preferences).where(Preferences.key == key.name) + ) + pref.value = value + session.add(pref) + session.commit() + # TODO - try/except + + def mirror_entry_fields(self, *entries: Entry) -> None: + """Mirror fields among multiple Entry items.""" + fields = {} + # load all fields + existing_fields = {field.type_key for field in entries[0].fields} + for entry in entries: + for entry_field in entry.fields: + fields[entry_field.type_key] = entry_field + + # assign the field to all entries + for entry in entries: + for field_key, field in fields.items(): + if field_key not in existing_fields: + self.add_entry_field_type( + entry_ids=entry.id, + field_id=field.type_key, + value=field.value, + ) diff --git a/tagstudio/src/core/library/alchemy/models.py b/tagstudio/src/core/library/alchemy/models.py new file mode 100644 index 000000000..ce43ce69e --- /dev/null +++ b/tagstudio/src/core/library/alchemy/models.py @@ -0,0 +1,241 @@ +from pathlib import Path +from typing import Optional + +from sqlalchemy import JSON, ForeignKey, Integer, event +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from .db import Base, PathType +from .enums import TagColor +from .fields import ( + DatetimeField, + Field, + TagBoxField, + TextField, + FieldTypeEnum, + _FieldID, +) +from .joins import TagSubtag +from ...constants import TAG_FAVORITE, TAG_ARCHIVED + + +class TagAlias(Base): + __tablename__ = "tag_aliases" + + id: Mapped[int] = mapped_column(primary_key=True) + + name: Mapped[str] + + tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id")) + tag: Mapped["Tag"] = relationship(back_populates="aliases") + + def __init__(self, name: str, tag: Optional["Tag"] = None): + self.name = name + + if tag: + self.tag = tag + + super().__init__() + + +class Tag(Base): + __tablename__ = "tags" + __table_args__ = {"sqlite_autoincrement": True} + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + + name: Mapped[str] = mapped_column(unique=True) + shorthand: Mapped[str | None] + color: Mapped[TagColor] + icon: Mapped[str | None] + + aliases: Mapped[set[TagAlias]] = relationship(back_populates="tag") + + parent_tags: Mapped[set["Tag"]] = relationship( + secondary=TagSubtag.__tablename__, + primaryjoin="Tag.id == TagSubtag.child_id", + secondaryjoin="Tag.id == TagSubtag.parent_id", + back_populates="subtags", + ) + + subtags: Mapped[set["Tag"]] = relationship( + secondary=TagSubtag.__tablename__, + primaryjoin="Tag.id == TagSubtag.parent_id", + secondaryjoin="Tag.id == TagSubtag.child_id", + back_populates="parent_tags", + ) + + @property + def subtag_ids(self) -> list[int]: + return [tag.id for tag in self.subtags] + + @property + def alias_strings(self) -> list[str]: + return [alias.name for alias in self.aliases] + + def __init__( + self, + name: str, + shorthand: str | None = None, + aliases: set[TagAlias] | None = None, + parent_tags: set["Tag"] | None = None, + subtags: set["Tag"] | None = None, + icon: str | None = None, + color: TagColor = TagColor.DEFAULT, + id: int | None = None, + ): + self.name = name + self.aliases = aliases or set() + self.parent_tags = parent_tags or set() + self.subtags = subtags or set() + self.color = color + self.icon = icon + self.shorthand = shorthand + assert not self.id + self.id = id + super().__init__() + + def __str__(self) -> str: + return f"" + + def __repr__(self) -> str: + return self.__str__() + + +class Entry(Base): + __tablename__ = "entries" + + id: Mapped[int] = mapped_column(primary_key=True) + + path: Mapped[Path] = mapped_column(PathType, unique=True) + + text_fields: Mapped[list[TextField]] = relationship( + back_populates="entry", + cascade="all, delete", + ) + datetime_fields: Mapped[list[DatetimeField]] = relationship( + back_populates="entry", + cascade="all, delete", + ) + tag_box_fields: Mapped[list[TagBoxField]] = relationship( + back_populates="entry", + cascade="all, delete", + ) + + @property + def fields(self) -> list[Field]: + fields: list[Field] = [] + fields.extend(self.tag_box_fields) + fields.extend(self.text_fields) + fields.extend(self.datetime_fields) + fields = sorted(fields, key=lambda field: field.type.order) + return fields + + @property + def tags(self) -> set[Tag]: + tag_set: set[Tag] = set() + for tag_box_field in self.tag_box_fields: + tag_set.update(tag_box_field.tags) + return tag_set + + @property + def is_favorited(self) -> bool: + for tag_box_field in self.tag_box_fields: + if tag_box_field.type_key == _FieldID.TAGS_META.name: + for tag in tag_box_field.tags: + if tag.id == TAG_FAVORITE: + return True + return False + + @property + def is_archived(self) -> bool: + for tag_box_field in self.tag_box_fields: + if tag_box_field.type_key == _FieldID.TAGS_META.name: + for tag in tag_box_field.tags: + if tag.id == TAG_ARCHIVED: + return True + return False + + def __init__( + self, + path: Path, + fields: list[Field] | None = None, + ) -> None: + self.path = path + + if fields is None: + fields = [ + TagBoxField(type_key=_FieldID.TAGS_META.name), + TagBoxField(type_key=_FieldID.TAGS_CONTENT.name), + TextField(type_key=_FieldID.TITLE.name), + ] + + for field in fields: + if isinstance(field, TextField): + self.text_fields.append(field) + elif isinstance(field, DatetimeField): + self.datetime_fields.append(field) + elif isinstance(field, TagBoxField): + self.tag_box_fields.append(field) + else: + raise ValueError(f"Invalid field type: {field}") + + def has_tag(self, tag: Tag) -> bool: + return tag in self.tags + + def remove_tag(self, tag: Tag, field: TagBoxField | None = None) -> None: + """ + Removes a Tag from the Entry. If given a field index, the given Tag will + only be removed from that index. If left blank, all instances of that + Tag will be removed from the Entry. + """ + if field: + field.tags.remove(tag) + return + + for tag_box_field in self.tag_box_fields: + tag_box_field.tags.remove(tag) + + +class LibraryField(Base): + """Define Field Types in the Library. + + Example: + key: content_tags (this field is slugified `name`) + name: Content Tags (this field is human readable name) + kind: type of content (Text Line, Text Box, Tags, Datetime, Checkbox) + + """ + + __tablename__ = "library_fields" + + key: Mapped[str] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(nullable=False) + type: Mapped[FieldTypeEnum] = mapped_column(default=FieldTypeEnum.TEXT_LINE) + order: Mapped[int] = mapped_column(default=0) + + # add relations to other tables + text_fields: Mapped[list[TextField]] = relationship( + "TextField", back_populates="type" + ) + datetime_fields: Mapped[list[DatetimeField]] = relationship( + "DatetimeField", back_populates="type" + ) + tag_box_fields: Mapped[list[TagBoxField]] = relationship( + "TagBoxField", back_populates="type" + ) + + +@event.listens_for(LibraryField, "before_insert") +def slugify_field_key(mapper, connection, target): + """Slugify the field key before inserting into the database.""" + if not target.key: + from .library import slugify + + target.key = slugify(target.tag) + + +class Preferences(Base): + __tablename__ = "preferences" + + key: Mapped[str] = mapped_column(primary_key=True) + value: Mapped[dict] = mapped_column(JSON, nullable=False) diff --git a/tagstudio/src/core/library/json/__init__.py b/tagstudio/src/core/library/json/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tagstudio/src/core/library/json/fields.py b/tagstudio/src/core/library/json/fields.py new file mode 100644 index 000000000..5e3509e40 --- /dev/null +++ b/tagstudio/src/core/library/json/fields.py @@ -0,0 +1,38 @@ +BOX_FIELDS = ["tag_box", "text_box"] +TEXT_FIELDS = ["text_line", "text_box"] +DATE_FIELDS = ["datetime"] + + +DEFAULT_FIELDS: list[dict] = [ + {"id": 0, "name": "Title", "type": "text_line"}, + {"id": 1, "name": "Author", "type": "text_line"}, + {"id": 2, "name": "Artist", "type": "text_line"}, + {"id": 3, "name": "URL", "type": "text_line"}, + {"id": 4, "name": "Description", "type": "text_box"}, + {"id": 5, "name": "Notes", "type": "text_box"}, + {"id": 6, "name": "Tags", "type": "tag_box"}, + {"id": 7, "name": "Content Tags", "type": "tag_box"}, + {"id": 8, "name": "Meta Tags", "type": "tag_box"}, + {"id": 9, "name": "Collation", "type": "collation"}, + {"id": 10, "name": "Date", "type": "datetime"}, + {"id": 11, "name": "Date Created", "type": "datetime"}, + {"id": 12, "name": "Date Modified", "type": "datetime"}, + {"id": 13, "name": "Date Taken", "type": "datetime"}, + {"id": 14, "name": "Date Published", "type": "datetime"}, + {"id": 15, "name": "Archived", "type": "checkbox"}, + {"id": 16, "name": "Favorite", "type": "checkbox"}, + {"id": 17, "name": "Book", "type": "collation"}, + {"id": 18, "name": "Comic", "type": "collation"}, + {"id": 19, "name": "Series", "type": "collation"}, + {"id": 20, "name": "Manga", "type": "collation"}, + {"id": 21, "name": "Source", "type": "text_line"}, + {"id": 22, "name": "Date Uploaded", "type": "datetime"}, + {"id": 23, "name": "Date Released", "type": "datetime"}, + {"id": 24, "name": "Volume", "type": "collation"}, + {"id": 25, "name": "Anthology", "type": "collation"}, + {"id": 26, "name": "Magazine", "type": "collation"}, + {"id": 27, "name": "Publisher", "type": "text_line"}, + {"id": 28, "name": "Guest Artist", "type": "text_line"}, + {"id": 29, "name": "Composer", "type": "text_line"}, + {"id": 30, "name": "Comments", "type": "text_box"}, +] diff --git a/tagstudio/src/core/library.py b/tagstudio/src/core/library/json/library.py similarity index 95% rename from tagstudio/src/core/library.py rename to tagstudio/src/core/library/json/library.py index 7eedb8b82..78870d955 100644 --- a/tagstudio/src/core/library.py +++ b/tagstudio/src/core/library/json/library.py @@ -1,3 +1,5 @@ +# type: ignore +# ruff: noqa # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio @@ -5,11 +7,12 @@ """The Library object and related methods for TagStudio.""" import datetime -import logging import os import time import traceback import xml.etree.ElementTree as ET + +import structlog import ujson from enum import Enum @@ -17,15 +20,13 @@ from typing import cast, Generator from typing_extensions import Self -from src.core.enums import FieldID -from src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag +from .fields import DEFAULT_FIELDS, TEXT_FIELDS +from src.core.enums import OpenStatus from src.core.utils.str import strip_punctuation from src.core.utils.web import strip_web_protocol -from src.core.enums import SearchMode from src.core.constants import ( BACKUP_FOLDER_NAME, COLLAGE_FOLDER_NAME, - TEXT_FIELDS, TS_FOLDER_NAME, VERSION, ) @@ -40,7 +41,7 @@ class ItemType(Enum): TAG_GROUP = 2 -logging.basicConfig(format="%(message)s", level=logging.INFO) +logger = structlog.get_logger(__name__) class Entry: @@ -94,12 +95,12 @@ def __eq__(self, __value: object) -> bool: and self.fields == __value.fields ) - def compressed_dict(self) -> JsonEntry: + def compressed_dict(self): """ An alternative to __dict__ that only includes fields containing non-default data. """ - obj: JsonEntry = {"id": self.id} + obj = {"id": self.id} if self.filename: obj["filename"] = str(self.filename) if self.path: @@ -128,7 +129,7 @@ def remove_tag(self, library: "Library", tag_id: int, field_index=-1): if library.get_field_attr(f, "type") == "tag_box": if field_index >= 0 and field_index == i: t: list[int] = library.get_field_attr(f, "content") - logging.info( + logger.info( f't:{tag_id}, i:{i}, idx:{field_index}, c:{library.get_field_attr(f, "content")}' ) t.remove(tag_id) @@ -142,30 +143,30 @@ def add_tag( ): # if self.fields: # if field_index != -1: - # logging.info(f'[LIBRARY] ADD TAG to E:{self.id}, F-DI:{field_id}, F-INDEX:{field_index}') + # logger.info(f'[LIBRARY] ADD TAG to E:{self.id}, F-DI:{field_id}, F-INDEX:{field_index}') for i, f in enumerate(self.fields): if library.get_field_attr(f, "id") == field_id: field_index = i - # logging.info(f'[LIBRARY] FOUND F-INDEX:{field_index}') + # logger.info(f'[LIBRARY] FOUND F-INDEX:{field_index}') break if field_index == -1: library.add_field_to_entry(self.id, field_id) - # logging.info(f'[LIBRARY] USING NEWEST F-INDEX:{field_index}') + # logger.info(f'[LIBRARY] USING NEWEST F-INDEX:{field_index}') - # logging.info(list(self.fields[field_index].keys())) + # logger.info(list(self.fields[field_index].keys())) field_id = list(self.fields[field_index].keys())[0] - # logging.info(f'Entry Field ID: {field_id}, Index: {field_index}') + # logger.info(f'Entry Field ID: {field_id}, Index: {field_index}') tags: list[int] = self.fields[field_index][field_id] if tag_id not in tags: - # logging.info(f'Adding Tag: {tag_id}') + # logger.info(f'Adding Tag: {tag_id}') tags.append(tag_id) self.fields[field_index][field_id] = sorted( tags, key=lambda t: library.get_tag(t).display_name(library) ) - # logging.info(f'Tags: {self.fields[field_index][field_id]}') + # logger.info(f'Tags: {self.fields[field_index][field_id]}') class Tag: @@ -219,12 +220,12 @@ def display_name(self, library: "Library") -> str: else: return f"{self.name}" - def compressed_dict(self) -> JsonTag: + def compressed_dict(self): """ An alternative to __dict__ that only includes fields containing non-default data. """ - obj: JsonTag = {"id": self.id} + obj = {"id": self.id} if self.name: obj["name"] = self.name if self.shorthand: @@ -281,12 +282,12 @@ def __eq__(self, __value: object) -> bool: __value = cast(Self, __value) return int(self.id) == int(__value.id) and self.fields == __value.fields - def compressed_dict(self) -> JsonCollation: + def compressed_dict(self): """ An alternative to __dict__ that only includes fields containing non-default data. """ - obj: JsonCollation = {"id": self.id} + obj = {"id": self.id} if self.title: obj["title"] = self.title if self.e_ids_and_pages: @@ -368,7 +369,7 @@ def __init__(self) -> None: # Map of every Tag ID to the index of the Tag in self.tags. self._tag_id_to_index_map: dict[int, int] = {} - self.default_tags: list[JsonTag] = [ + self.default_tags: list = [ {"id": 0, "name": "Archived", "aliases": ["Archive"], "color": "Red"}, { "id": 1, @@ -383,40 +384,6 @@ def __init__(self) -> None: # Tag(id=1, name='Favorite', shorthand='', aliases=['Favorited, Favorites, Likes, Liked, Loved'], subtags_ids=[], color='yellow'), # ] - self.default_fields: list[dict] = [ - {"id": 0, "name": "Title", "type": "text_line"}, - {"id": 1, "name": "Author", "type": "text_line"}, - {"id": 2, "name": "Artist", "type": "text_line"}, - {"id": 3, "name": "URL", "type": "text_line"}, - {"id": 4, "name": "Description", "type": "text_box"}, - {"id": 5, "name": "Notes", "type": "text_box"}, - {"id": 6, "name": "Tags", "type": "tag_box"}, - {"id": 7, "name": "Content Tags", "type": "tag_box"}, - {"id": 8, "name": "Meta Tags", "type": "tag_box"}, - {"id": 9, "name": "Collation", "type": "collation"}, - {"id": 10, "name": "Date", "type": "datetime"}, - {"id": 11, "name": "Date Created", "type": "datetime"}, - {"id": 12, "name": "Date Modified", "type": "datetime"}, - {"id": 13, "name": "Date Taken", "type": "datetime"}, - {"id": 14, "name": "Date Published", "type": "datetime"}, - {"id": 15, "name": "Archived", "type": "checkbox"}, - {"id": 16, "name": "Favorite", "type": "checkbox"}, - {"id": 17, "name": "Book", "type": "collation"}, - {"id": 18, "name": "Comic", "type": "collation"}, - {"id": 19, "name": "Series", "type": "collation"}, - {"id": 20, "name": "Manga", "type": "collation"}, - {"id": 21, "name": "Source", "type": "text_line"}, - {"id": 22, "name": "Date Uploaded", "type": "datetime"}, - {"id": 23, "name": "Date Released", "type": "datetime"}, - {"id": 24, "name": "Volume", "type": "collation"}, - {"id": 25, "name": "Anthology", "type": "collation"}, - {"id": 26, "name": "Magazine", "type": "collation"}, - {"id": 27, "name": "Publisher", "type": "text_line"}, - {"id": 28, "name": "Guest Artist", "type": "text_line"}, - {"id": 29, "name": "Composer", "type": "text_line"}, - {"id": 30, "name": "Comments", "type": "text_box"}, - ] - def create_library(self, path: Path) -> int: """ Creates a TagStudio library in the given directory.\n @@ -433,7 +400,7 @@ def create_library(self, path: Path) -> int: self.verify_ts_folders() self.save_library_to_disk() self.open_library(self.library_dir) - except: + except Exception: traceback.print_exc() return 2 @@ -443,7 +410,7 @@ def _fix_lib_path(self, path) -> Path: """If '.TagStudio' is included in the path, trim the path up to it.""" path = Path(path) paths = [x for x in [path, *path.parents] if x.stem == TS_FOLDER_NAME] - if len(paths) > 0: + if paths: return paths[0].parent return path @@ -463,12 +430,12 @@ def verify_ts_folders(self) -> None: if not os.path.isdir(full_collage_path): os.mkdir(full_collage_path) - def verify_default_tags(self, tag_list: list[JsonTag]) -> list[JsonTag]: + def verify_default_tags(self, tag_list: list) -> list: """ Ensures that the default builtin tags are present in the Library's save file. Takes in and returns the tag dictionary from the JSON file. """ - missing: list[JsonTag] = [] + missing: list = [] for dt in self.default_tags: if dt["id"] not in [t["id"] for t in tag_list]: @@ -479,16 +446,14 @@ def verify_default_tags(self, tag_list: list[JsonTag]) -> list[JsonTag]: return tag_list - def open_library(self, path: str | Path) -> int: + def open_library(self, path: str | Path) -> OpenStatus: """ - Opens a TagStudio v9+ Library. - Returns 0 if library does not exist, 1 if successfully opened, 2 if corrupted. + Open a TagStudio v9+ Library. """ - - return_code: int = 2 + return_code = OpenStatus.CORRUPTED _path: Path = self._fix_lib_path(path) - + logger.info("opening library", path=_path) if (_path / TS_FOLDER_NAME / "ts_library.json").exists(): try: with open( @@ -496,7 +461,7 @@ def open_library(self, path: str | Path) -> int: "r", encoding="utf-8", ) as file: - json_dump: JsonLibary = ujson.load(file) + json_dump = ujson.load(file) self.library_dir = Path(_path) self.verify_ts_folders() major, minor, patch = json_dump["ts-version"].split(".") @@ -525,7 +490,7 @@ def open_library(self, path: str | Path) -> int: self.is_exclude_list = json_dump.get("is_exclude_list", True) end_time = time.time() - logging.info( + logger.info( f"[LIBRARY] Extension list loaded in {(end_time - start_time):.3f} seconds" ) @@ -570,7 +535,7 @@ def open_library(self, path: str | Path) -> int: self._map_tag_id_to_index(t, -1) self._map_tag_strings_to_tag_id(t) else: - logging.info( + logger.info( f"[LIBRARY]Skipping Tag with duplicate ID: {tag}" ) @@ -579,7 +544,7 @@ def open_library(self, path: str | Path) -> int: self._map_tag_id_to_cluster(t) end_time = time.time() - logging.info( + logger.info( f"[LIBRARY] Tags loaded in {(end_time - start_time):.3f} seconds" ) @@ -680,8 +645,8 @@ def open_library(self, path: str | Path) -> int: self._map_entry_id_to_index(e, -1) end_time = time.time() - logging.info( - f"[LIBRARY] Entries loaded in {(end_time - start_time):.3f} seconds" + logger.info( + f"[LIBRARY] Entries loaded", load_time=end_time - start_time ) # Parse Collations ----------------------------------------- @@ -704,7 +669,7 @@ def open_library(self, path: str | Path) -> int: c = Collation( id=id, title=title, - e_ids_and_pages=e_ids_and_pages, # type: ignore + e_ids_and_pages=e_ids_and_pages, sort_order=sort_order, cover_id=cover_id, ) @@ -716,16 +681,16 @@ def open_library(self, path: str | Path) -> int: self.collations.append(c) self._map_collation_id_to_index(c, -1) end_time = time.time() - logging.info( + logger.info( f"[LIBRARY] Collations loaded in {(end_time - start_time):.3f} seconds" ) - return_code = 1 + return_code = OpenStatus.SUCCESS except ujson.JSONDecodeError: - logging.info("[LIBRARY][ERROR]: Empty JSON file!") + logger.info("[LIBRARY][ERROR]: Empty JSON file!") # If the Library is loaded, continue other processes. - if return_code == 1: + if return_code == OpenStatus.SUCCESS: (self.library_dir / TS_FOLDER_NAME).mkdir(parents=True, exist_ok=True) self._map_filenames_to_entry_ids() @@ -759,7 +724,7 @@ def to_json(self): Used in saving the library to disk. """ - file_to_save: JsonLibary = { + file_to_save = { "ts-version": VERSION, "ext_list": [i for i in self.ext_list if i], "is_exclude_list": self.is_exclude_list, @@ -790,7 +755,7 @@ def to_json(self): def save_library_to_disk(self): """Saves the Library to disk at the default TagStudio folder location.""" - logging.info(f"[LIBRARY] Saving Library to Disk...") + logger.info(f"[LIBRARY] Saving Library to Disk...") start_time = time.time() filename = "ts_library.json" @@ -808,7 +773,7 @@ def save_library_to_disk(self): ) # , indent=4 <-- How to prettyprint dump end_time = time.time() - logging.info( + logger.info( f"[LIBRARY] Library saved to disk in {(end_time - start_time):.3f} seconds" ) @@ -817,7 +782,7 @@ def save_library_backup_to_disk(self) -> str: Saves a backup file of the Library to disk at the default TagStudio folder location. Returns the filename used, including the date and time.""" - logging.info(f"[LIBRARY] Saving Library Backup to Disk...") + logger.info(f"[LIBRARY] Saving Library Backup to Disk...") start_time = time.time() filename = f'ts_library_backup_{datetime.datetime.utcnow().strftime("%F_%T").replace(":", "")}.json' @@ -835,7 +800,7 @@ def save_library_backup_to_disk(self) -> str: escape_forward_slashes=False, ) end_time = time.time() - logging.info( + logger.info( f"[LIBRARY] Library backup saved to disk in {(end_time - start_time):.3f} seconds" ) return filename @@ -908,7 +873,7 @@ def refresh_dir(self) -> Generator: # print(file) self.files_not_in_library.append(file) except PermissionError: - logging.info( + logger.info( f"The File/Folder {f} cannot be accessed, because it requires higher permission!" ) end_time = time.time() @@ -951,7 +916,7 @@ def remove_entry(self, entry_id: int) -> None: # Remove this Entry from the Entries list. entry = self.get_entry(entry_id) path = entry.path / entry.filename - # logging.info(f'Removing path: {path}') + # logger.info(f'Removing path: {path}') del self.filename_to_entry_id_map[path] @@ -1000,9 +965,9 @@ def refresh_dupe_entries(self): for k, v in registered.items(): if len(v) > 1: self.dupe_entries.append((v[0], v[1:])) - # logging.info(f"DUPLICATE FOUND: {(v[0], v[1:])}") + # logger.info(f"DUPLICATE FOUND: {(v[0], v[1:])}") # for id in v: - # logging.info(f"\t{(Path()/self.get_entry(id).path/self.get_entry(id).filename)}") + # logger.info(f"\t{(Path()/self.get_entry(id).path/self.get_entry(id).filename)}") yield len(self.entries) @@ -1014,7 +979,7 @@ def merge_dupe_entries(self): `dupe_entries = tuple(int, list[int])` """ - logging.info("[LIBRARY] Mirroring Duplicate Entries...") + logger.info("[LIBRARY] Mirroring Duplicate Entries...") id_to_entry_map: dict = {} for dupe in self.dupe_entries: @@ -1026,7 +991,7 @@ def merge_dupe_entries(self): id_to_entry_map[id] = self.get_entry(id) self.mirror_entry_fields([dupe[0]] + dupe[1]) - logging.info( + logger.info( "[LIBRARY] Consolidating Entries... (This may take a while for larger libraries)" ) for i, dupe in enumerate(self.dupe_entries): @@ -1037,7 +1002,7 @@ def merge_dupe_entries(self): # takes but in a batch-friendly way here. # NOTE: Couldn't use get_entry(id) because that relies on the # entry's index in the list, which is currently being messed up. - logging.info(f"[LIBRARY] Removing Unneeded Entry {id}") + logger.info(f"[LIBRARY] Removing Unneeded Entry {id}") self.entries.remove(id_to_entry_map[id]) yield i - 1 # The -1 waits for the next step to finish @@ -1107,12 +1072,12 @@ def remove_missing_files(self): # pb.setLabelText(f'Deleting {i}/{len(self.lib.missing_files)} Unlinked Entries') try: id = self.get_entry_id_from_filepath(missing) - logging.info(f"Removing Entry ID {id}:\n\t{missing}") + logger.info(f"Removing Entry ID {id}:\n\t{missing}") self.remove_entry(id) # self.driver.purge_item_from_navigation(ItemType.ENTRY, id) deleted.append(missing) except KeyError: - logging.info( + logger.info( f'[LIBRARY][ERROR]: "{id}" was reported as missing, but is not in the file_to_entry_id map.' ) yield (i, id) @@ -1331,7 +1296,7 @@ def search_library( entries=True, collations=True, tag_groups=True, - search_mode=SearchMode.AND, + search_mode=0, # AND ) -> list[tuple[ItemType, int]]: """ Uses a search query to generate a filtered results list. @@ -1473,7 +1438,7 @@ def add_entry(entry: Entry): if not added: results.append((ItemType.ENTRY, entry.id)) - if search_mode == SearchMode.AND: # Include all terms + if search_mode == 0: # AND # Include all terms # For each verified, extracted Tag term. failure_to_union_terms = False for term in all_tag_terms: @@ -1507,7 +1472,7 @@ def add_entry(entry: Entry): if all_tag_terms and not failure_to_union_terms: add_entry(entry) - if search_mode == SearchMode.OR: # Include any terms + if search_mode == 1: # OR # Include any terms # For each verified, extracted Tag term. for term in all_tag_terms: # Add the immediate associated Tags to the set (ex. Name, Alias hits) @@ -1771,7 +1736,7 @@ def filter_field_templates(self, query: str) -> list[int]: """Returns a list of Field Template IDs returned from a string query.""" matches: list[int] = [] - for ft in self.default_fields: + for ft in DEFAULT_FIELDS: if ft["name"].lower().startswith(query.lower()): matches.append(ft["id"]) @@ -2104,7 +2069,7 @@ def add_field_to_entry(self, entry_id: int, field_id: int) -> None: elif field_type == "datetime": entry.fields.append({int(field_id): ""}) else: - logging.info( + logger.info( f"[LIBRARY][ERROR]: Unknown field id attempted to be added to entry: {field_id}" ) @@ -2181,8 +2146,8 @@ def get_field_obj(self, field_id: int) -> dict: Returns a field template object associated with a field ID. The objects have "id", "name", and "type" fields. """ - if int(field_id) < len(self.default_fields): - return self.default_fields[int(field_id)] + if int(field_id) < len(DEFAULT_FIELDS): + return DEFAULT_FIELDS[int(field_id)] else: return {"id": -1, "name": "Unknown Field", "type": "unknown"} diff --git a/tagstudio/src/core/palette.py b/tagstudio/src/core/palette.py index 886e0bd6c..74a30fc0d 100644 --- a/tagstudio/src/core/palette.py +++ b/tagstudio/src/core/palette.py @@ -1,11 +1,18 @@ # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio +import traceback +from enum import IntEnum +from typing import Any -from enum import Enum +import structlog +from src.core.library.alchemy.enums import TagColor -class ColorType(int, Enum): +logger = structlog.get_logger(__name__) + + +class ColorType(IntEnum): PRIMARY = 0 TEXT = 1 BORDER = 2 @@ -13,71 +20,71 @@ class ColorType(int, Enum): DARK_ACCENT = 4 -_TAG_COLORS = { - "": { +TAG_COLORS: dict[TagColor, dict[ColorType, Any]] = { + TagColor.DEFAULT: { ColorType.PRIMARY: "#1e1e1e", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#333333", ColorType.LIGHT_ACCENT: "#FFFFFF", ColorType.DARK_ACCENT: "#222222", }, - "black": { + TagColor.BLACK: { ColorType.PRIMARY: "#111018", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#18171e", ColorType.LIGHT_ACCENT: "#b7b6be", ColorType.DARK_ACCENT: "#03020a", }, - "dark gray": { + TagColor.DARK_GRAY: { ColorType.PRIMARY: "#24232a", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#2a2930", ColorType.LIGHT_ACCENT: "#bdbcc4", ColorType.DARK_ACCENT: "#07060e", }, - "gray": { + TagColor.GRAY: { ColorType.PRIMARY: "#53525a", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#5b5a62", ColorType.LIGHT_ACCENT: "#cbcad2", ColorType.DARK_ACCENT: "#191820", }, - "light gray": { + TagColor.LIGHT_GRAY: { ColorType.PRIMARY: "#aaa9b0", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#b6b4bc", ColorType.LIGHT_ACCENT: "#cbcad2", ColorType.DARK_ACCENT: "#191820", }, - "white": { + TagColor.WHITE: { ColorType.PRIMARY: "#f2f1f8", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#fefeff", ColorType.LIGHT_ACCENT: "#ffffff", ColorType.DARK_ACCENT: "#302f36", }, - "light pink": { + TagColor.LIGHT_PINK: { ColorType.PRIMARY: "#ff99c4", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#ffaad0", ColorType.LIGHT_ACCENT: "#ffcbe7", ColorType.DARK_ACCENT: "#6c2e3b", }, - "pink": { + TagColor.PINK: { ColorType.PRIMARY: "#ff99c4", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#ffaad0", ColorType.LIGHT_ACCENT: "#ffcbe7", ColorType.DARK_ACCENT: "#6c2e3b", }, - "magenta": { + TagColor.MAGENTA: { ColorType.PRIMARY: "#f6466f", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#f7587f", ColorType.LIGHT_ACCENT: "#fba4bf", ColorType.DARK_ACCENT: "#61152f", }, - "red": { + TagColor.RED: { ColorType.PRIMARY: "#e22c3c", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#b21f2d", @@ -85,35 +92,35 @@ class ColorType(int, Enum): ColorType.LIGHT_ACCENT: "#f39caa", ColorType.DARK_ACCENT: "#440d12", }, - "red orange": { + TagColor.RED_ORANGE: { ColorType.PRIMARY: "#e83726", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#ea4b3b", ColorType.LIGHT_ACCENT: "#f5a59d", ColorType.DARK_ACCENT: "#61120b", }, - "salmon": { + TagColor.SALMON: { ColorType.PRIMARY: "#f65848", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#f76c5f", ColorType.LIGHT_ACCENT: "#fcadaa", ColorType.DARK_ACCENT: "#6f1b16", }, - "orange": { + TagColor.ORANGE: { ColorType.PRIMARY: "#ed6022", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#ef7038", ColorType.LIGHT_ACCENT: "#f7b79b", ColorType.DARK_ACCENT: "#551e0a", }, - "yellow orange": { + TagColor.YELLOW_ORANGE: { ColorType.PRIMARY: "#fa9a2c", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#fba94b", ColorType.LIGHT_ACCENT: "#fdd7ab", ColorType.DARK_ACCENT: "#66330d", }, - "yellow": { + TagColor.YELLOW: { ColorType.PRIMARY: "#ffd63d", ColorType.TEXT: ColorType.DARK_ACCENT, # ColorType.BORDER: '#ffe071', @@ -121,154 +128,154 @@ class ColorType(int, Enum): ColorType.LIGHT_ACCENT: "#fff3c4", ColorType.DARK_ACCENT: "#754312", }, - "mint": { + TagColor.MINT: { ColorType.PRIMARY: "#4aed90", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#79f2b1", ColorType.LIGHT_ACCENT: "#c8fbe9", ColorType.DARK_ACCENT: "#164f3e", }, - "lime": { + TagColor.LIME: { ColorType.PRIMARY: "#92e649", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#b2ed72", ColorType.LIGHT_ACCENT: "#e9f9b7", ColorType.DARK_ACCENT: "#405516", }, - "light green": { + TagColor.LIGHT_GREEN: { ColorType.PRIMARY: "#85ec76", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#a3f198", ColorType.LIGHT_ACCENT: "#e7fbe4", ColorType.DARK_ACCENT: "#2b5524", }, - "green": { + TagColor.GREEN: { ColorType.PRIMARY: "#28bb48", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#43c568", ColorType.LIGHT_ACCENT: "#93e2c8", ColorType.DARK_ACCENT: "#0d3828", }, - "teal": { + TagColor.TEAL: { ColorType.PRIMARY: "#1ad9b2", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#4de3c7", ColorType.LIGHT_ACCENT: "#a0f3e8", ColorType.DARK_ACCENT: "#08424b", }, - "cyan": { + TagColor.CYAN: { ColorType.PRIMARY: "#49e4d5", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#76ebdf", ColorType.LIGHT_ACCENT: "#bff5f0", ColorType.DARK_ACCENT: "#0f4246", }, - "light blue": { + TagColor.LIGHT_BLUE: { ColorType.PRIMARY: "#55bbf6", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#70c6f7", ColorType.LIGHT_ACCENT: "#bbe4fb", ColorType.DARK_ACCENT: "#122541", }, - "blue": { + TagColor.BLUE: { ColorType.PRIMARY: "#3b87f0", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#4e95f2", ColorType.LIGHT_ACCENT: "#aedbfa", ColorType.DARK_ACCENT: "#122948", }, - "blue violet": { + TagColor.BLUE_VIOLET: { ColorType.PRIMARY: "#5948f2", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#6258f3", ColorType.LIGHT_ACCENT: "#9cb8fb", ColorType.DARK_ACCENT: "#1b1649", }, - "violet": { + TagColor.VIOLET: { ColorType.PRIMARY: "#874ff5", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#9360f6", ColorType.LIGHT_ACCENT: "#c9b0fa", ColorType.DARK_ACCENT: "#3a1860", }, - "purple": { + TagColor.PURPLE: { ColorType.PRIMARY: "#bb4ff0", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#c364f2", ColorType.LIGHT_ACCENT: "#dda7f7", ColorType.DARK_ACCENT: "#531862", }, - "peach": { + TagColor.PEACH: { ColorType.PRIMARY: "#f1c69c", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#f4d4b4", ColorType.LIGHT_ACCENT: "#fbeee1", ColorType.DARK_ACCENT: "#613f2f", }, - "brown": { + TagColor.BROWN: { ColorType.PRIMARY: "#823216", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#8a3e22", ColorType.LIGHT_ACCENT: "#cd9d83", ColorType.DARK_ACCENT: "#3a1804", }, - "lavender": { + TagColor.LAVENDER: { ColorType.PRIMARY: "#ad8eef", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#b99ef2", ColorType.LIGHT_ACCENT: "#d5c7fa", ColorType.DARK_ACCENT: "#492b65", }, - "blonde": { + TagColor.BLONDE: { ColorType.PRIMARY: "#efc664", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#f3d387", ColorType.LIGHT_ACCENT: "#faebc6", ColorType.DARK_ACCENT: "#6d461e", }, - "auburn": { + TagColor.AUBURN: { ColorType.PRIMARY: "#a13220", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#aa402f", ColorType.LIGHT_ACCENT: "#d98a7f", ColorType.DARK_ACCENT: "#3d100a", }, - "light brown": { + TagColor.LIGHT_BROWN: { ColorType.PRIMARY: "#be5b2d", ColorType.TEXT: ColorType.DARK_ACCENT, ColorType.BORDER: "#c4693d", ColorType.LIGHT_ACCENT: "#e5b38c", ColorType.DARK_ACCENT: "#4c290e", }, - "dark brown": { + TagColor.DARK_BROWN: { ColorType.PRIMARY: "#4c2315", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#542a1c", ColorType.LIGHT_ACCENT: "#b78171", ColorType.DARK_ACCENT: "#211006", }, - "cool gray": { + TagColor.COOL_GRAY: { ColorType.PRIMARY: "#515768", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#5b6174", ColorType.LIGHT_ACCENT: "#9ea1c3", ColorType.DARK_ACCENT: "#181a37", }, - "warm gray": { + TagColor.WARM_GRAY: { ColorType.PRIMARY: "#625550", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#6c5e57", ColorType.LIGHT_ACCENT: "#c0a392", ColorType.DARK_ACCENT: "#371d18", }, - "olive": { + TagColor.OLIVE: { ColorType.PRIMARY: "#4c652e", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#586f36", ColorType.LIGHT_ACCENT: "#b4c17a", ColorType.DARK_ACCENT: "#23300e", }, - "berry": { + TagColor.BERRY: { ColorType.PRIMARY: "#9f2aa7", ColorType.TEXT: ColorType.LIGHT_ACCENT, ColorType.BORDER: "#aa43b4", @@ -278,12 +285,14 @@ class ColorType(int, Enum): } -def get_tag_color(type, color): - color = color.lower() +def get_tag_color(color_type: ColorType, color_id: TagColor) -> str: try: - if type == ColorType.TEXT: - return get_tag_color(_TAG_COLORS[color][type], color) - else: - return _TAG_COLORS[color][type] + if color_type == ColorType.TEXT: + text_account: ColorType = TAG_COLORS[color_id][color_type] + return get_tag_color(text_account, color_id) + + return TAG_COLORS[color_id][color_type] except KeyError: + traceback.print_stack() + logger.error("Color not found", color_id=color_id) return "#FF00FF" diff --git a/tagstudio/src/core/ts_core.py b/tagstudio/src/core/ts_core.py index 63ac30e63..aeed64094 100644 --- a/tagstudio/src/core/ts_core.py +++ b/tagstudio/src/core/ts_core.py @@ -5,78 +5,73 @@ """The core classes and methods of TagStudio.""" import json -import os from pathlib import Path -from enum import Enum from src.core.library import Entry, Library -from src.core.constants import TS_FOLDER_NAME, TEXT_FIELDS +from src.core.constants import TS_FOLDER_NAME +from src.core.library.alchemy.fields import _FieldID +from src.core.utils.missing_files import logger class TagStudioCore: - """ - Instantiate this to establish a TagStudio session. - Holds all TagStudio session data and provides methods to manage it. - """ - def __init__(self): self.lib: Library = Library() - def get_gdl_sidecar(self, filepath: str | Path, source: str = "") -> dict: + @classmethod + def get_gdl_sidecar(cls, filepath: Path, source: str = "") -> dict: """ - Attempts to open and dump a Gallery-DL Sidecar sidecar file for - the filepath.\n Returns a formatted object with notable values or an - empty object if none is found. + Attempt to open and dump a Gallery-DL Sidecar file for the filepath. + + Return a formatted object with notable values or an empty object if none is found. """ - json_dump = {} info = {} - _filepath: Path = Path(filepath) - _filepath = _filepath.parent / (_filepath.stem + ".json") + _filepath = filepath.parent / (filepath.stem + ".json") # NOTE: This fixes an unknown (recent?) bug in Gallery-DL where Instagram sidecar # files may be downloaded with indices starting at 1 rather than 0, unlike the posts. # This may only occur with sidecar files that are downloaded separate from posts. - if source == "instagram": - if not _filepath.is_file(): - newstem = _filepath.stem[:-16] + "1" + _filepath.stem[-15:] - _filepath = _filepath.parent / (newstem + ".json") + if source == "instagram" and not _filepath.is_file(): + newstem = _filepath.stem[:-16] + "1" + _filepath.stem[-15:] + _filepath = _filepath.parent / (newstem + ".json") + + logger.info( + "get_gdl_sidecar", filepath=filepath, source=source, sidecar=_filepath + ) try: - with open(_filepath, "r", encoding="utf8") as f: + with open(_filepath, encoding="utf8") as f: json_dump = json.load(f) - - if json_dump: - if source == "twitter": - info["content"] = json_dump["content"].strip() - info["date_published"] = json_dump["date"] - elif source == "instagram": - info["description"] = json_dump["description"].strip() - info["date_published"] = json_dump["date"] - elif source == "artstation": - info["title"] = json_dump["title"].strip() - info["artist"] = json_dump["user"]["full_name"].strip() - info["description"] = json_dump["description"].strip() - info["tags"] = json_dump["tags"] - # info["tags"] = [x for x in json_dump["mediums"]["name"]] - info["date_published"] = json_dump["date"] - elif source == "newgrounds": - # info["title"] = json_dump["title"] - # info["artist"] = json_dump["artist"] - # info["description"] = json_dump["description"] - info["tags"] = json_dump["tags"] - info["date_published"] = json_dump["date"] - info["artist"] = json_dump["user"].strip() - info["description"] = json_dump["description"].strip() - info["source"] = json_dump["post_url"].strip() + if not json_dump: + return {} + + if source == "twitter": + info[_FieldID.DESCRIPTION] = json_dump["content"].strip() + info[_FieldID.DATE_PUBLISHED] = json_dump["date"] + elif source == "instagram": + info[_FieldID.DESCRIPTION] = json_dump["description"].strip() + info[_FieldID.DATE_PUBLISHED] = json_dump["date"] + elif source == "artstation": + info[_FieldID.TITLE] = json_dump["title"].strip() + info[_FieldID.ARTIST] = json_dump["user"]["full_name"].strip() + info[_FieldID.DESCRIPTION] = json_dump["description"].strip() + info[_FieldID.TAGS] = json_dump["tags"] + # info["tags"] = [x for x in json_dump["mediums"]["name"]] + info[_FieldID.DATE_PUBLISHED] = json_dump["date"] + elif source == "newgrounds": + # info["title"] = json_dump["title"] + # info["artist"] = json_dump["artist"] + # info["description"] = json_dump["description"] + info[_FieldID.TAGS] = json_dump["tags"] + info[_FieldID.DATE_PUBLISHED] = json_dump["date"] + info[_FieldID.ARTIST] = json_dump["user"].strip() + info[_FieldID.DESCRIPTION] = json_dump["description"].strip() + info[_FieldID.SOURCE] = json_dump["post_url"].strip() # else: # print( # f'[INFO]: TagStudio does not currently support sidecar files for "{source}"') - # except FileNotFoundError: - except: - # print( - # f'[INFO]: No sidecar file found at "{os.path.normpath(file_path + ".json")}"') - pass + except Exception: + logger.exception("Error handling sidecar file.", path=_filepath) return info @@ -103,102 +98,86 @@ def get_gdl_sidecar(self, filepath: str | Path, source: str = "") -> dict: # # # print("Could not resolve URL.") # # pass - def match_conditions(self, entry_id: int) -> None: - """Matches defined conditions against a file to add Entry data.""" + @classmethod + def match_conditions(cls, lib: Library, entry_id: int) -> bool: + """Match defined conditions against a file to add Entry data.""" - cond_file = self.lib.library_dir / TS_FOLDER_NAME / "conditions.json" + # TODO - what even is this file format? # TODO: Make this stored somewhere better instead of temporarily in this JSON file. - entry: Entry = self.lib.get_entry(entry_id) + cond_file = lib.library_dir / TS_FOLDER_NAME / "conditions.json" + if not cond_file.is_file(): + return False + + entry: Entry = lib.get_entry(entry_id) + try: - if cond_file.is_file(): - with open(cond_file, "r", encoding="utf8") as f: - json_dump = json.load(f) - for c in json_dump["conditions"]: - match: bool = False - for path_c in c["path_conditions"]: - if str(Path(path_c).resolve()) in str(entry.path): - match = True - break - if match: - if fields := c.get("fields"): - for field in fields: - field_id = self.lib.get_field_attr(field, "id") - content = field[field_id] - - if ( - self.lib.get_field_obj(int(field_id))["type"] - == "tag_box" - ): - existing_fields: list[int] = ( - self.lib.get_field_index_in_entry( - entry, field_id - ) - ) - if existing_fields: - self.lib.update_entry_field( - entry_id, - existing_fields[0], - content, - "append", - ) - else: - self.lib.add_field_to_entry( - entry_id, field_id - ) - self.lib.update_entry_field( - entry_id, -1, content, "append" - ) - - if ( - self.lib.get_field_obj(int(field_id))["type"] - in TEXT_FIELDS - ): - if not self.lib.does_field_content_exist( - entry_id, field_id, content - ): - self.lib.add_field_to_entry( - entry_id, field_id - ) - self.lib.update_entry_field( - entry_id, -1, content, "replace" - ) - except: - print("Error in match_conditions...") - # input() - pass - - def build_url(self, entry_id: int, source: str): - """Tries to rebuild a source URL given a specific filename structure.""" + with open(cond_file, encoding="utf8") as f: + json_dump = json.load(f) + for c in json_dump["conditions"]: + match: bool = False + for path_c in c["path_conditions"]: + if Path(path_c).is_relative_to(entry.path): + match = True + break + + if not match: + return False + + if not c.get("fields"): + return False + + fields = c["fields"] + entry_field_types = { + field.type_key: field for field in entry.fields + } + + for field in fields: + is_new = field["id"] not in entry_field_types + field_key = field["id"] + if is_new: + lib.add_entry_field_type( + entry.id, field_key, field["value"] + ) + else: + lib.update_entry_field(entry.id, field_key, field["value"]) + + except Exception: + logger.exception("Error matching conditions.", entry=entry) + + return False + + @classmethod + def build_url(cls, entry: Entry, source: str): + """Try to rebuild a source URL given a specific filename structure.""" source = source.lower().replace("-", " ").replace("_", " ") if "twitter" in source: - return self._build_twitter_url(entry_id) + return cls._build_twitter_url(entry) elif "instagram" in source: - return self._build_instagram_url(entry_id) + return cls._build_instagram_url(entry) - def _build_twitter_url(self, entry_id: int): + @classmethod + def _build_twitter_url(cls, entry: Entry): """ - Builds an Twitter URL given a specific filename structure. + Build a Twitter URL given a specific filename structure. Method expects filename to be formatted as 'USERNAME_TWEET-ID_INDEX_YEAR-MM-DD' """ try: - entry = self.lib.get_entry(entry_id) - stubs = str(entry.filename).rsplit("_", 3) - # print(stubs) - # source, author = os.path.split(entry.path) + stubs = str(entry.path.name).rsplit("_", 3) url = f"www.twitter.com/{stubs[0]}/status/{stubs[-3]}/photo/{stubs[-2]}" return url - except: + except Exception: + logger.exception("Error building Twitter URL.", entry=entry) return "" - def _build_instagram_url(self, entry_id: int): + @classmethod + def _build_instagram_url(cls, entry: Entry): """ - Builds an Instagram URL given a specific filename structure. + Build an Instagram URL given a specific filename structure. Method expects filename to be formatted as 'USERNAME_POST-ID_INDEX_YEAR-MM-DD' """ try: - entry = self.lib.get_entry(entry_id) - stubs = str(entry.filename).rsplit("_", 2) + stubs = str(entry.path.name).rsplit("_", 2) # stubs[0] = stubs[0].replace(f"{author}_", '', 1) # print(stubs) # NOTE: Both Instagram usernames AND their ID can have underscores in them, @@ -207,5 +186,6 @@ def _build_instagram_url(self, entry_id: int): # seems to more or less be the case... for now... url = f"www.instagram.com/p/{stubs[-3][-11:]}" return url - except: + except Exception: + logger.exception("Error building Instagram URL.", entry=entry) return "" diff --git a/tagstudio/src/core/utils/dupe_files.py b/tagstudio/src/core/utils/dupe_files.py new file mode 100644 index 000000000..719470189 --- /dev/null +++ b/tagstudio/src/core/utils/dupe_files.py @@ -0,0 +1,83 @@ +from dataclasses import dataclass, field +from pathlib import Path +import xml.etree.ElementTree as ET + +import structlog + +from src.core.library import Library, Entry +from src.core.library.alchemy.enums import FilterState + +logger = structlog.get_logger() + + +@dataclass +class DupeRegistry: + """State handler for DupeGuru results.""" + + library: Library + groups: list[list[Entry]] = field(default_factory=list) + + @property + def groups_count(self) -> int: + return len(self.groups) + + def refresh_dupe_files(self, results_filepath: str | Path): + """ + Refresh the list of duplicate files. + A duplicate file is defined as an identical or near-identical file as determined + by a DupeGuru results file. + """ + library_dir = self.library.library_dir + if not isinstance(results_filepath, Path): + results_filepath = Path(results_filepath) + + if not results_filepath.is_file(): + raise ValueError("invalid file path") + + self.groups.clear() + tree = ET.parse(results_filepath) + root = tree.getroot() + for group in root: + # print(f'-------------------- Match Group {i}---------------------') + files: list[Entry] = [] + for element in group: + if element.tag == "file": + file_path = Path(element.attrib.get("path")) + + try: + path_relative = file_path.relative_to(library_dir) + except ValueError: + # The file is not in the library directory + continue + + _, entries = self.library.search_library( + FilterState(path=path_relative), + ) + + if not entries: + # file not in library + continue + + files.append(entries[0]) + + if not len(files) > 1: + # only one file in the group, nothing to do + continue + + self.groups.append(files) + + def merge_dupe_entries(self): + """ + Merge the duplicate Entry items. + A duplicate Entry is defined as an Entry pointing to a file that one or more other Entries are also pointing to + """ + logger.info( + "Consolidating Entries... (This may take a while for larger libraries)", + groups=len(self.groups), + ) + + for i, entries in enumerate(self.groups): + remove_ids = [x.id for x in entries[1:]] + logger.info("Removing entries group", ids=remove_ids) + self.library.remove_entries(remove_ids) + yield i - 1 # The -1 waits for the next step to finish diff --git a/tagstudio/src/core/utils/fs.py b/tagstudio/src/core/utils/fs.py deleted file mode 100644 index 44ba1e88a..000000000 --- a/tagstudio/src/core/utils/fs.py +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright (C) 2024 Travis Abendshien (CyanVoxel). -# Licensed under the GPL-3.0 License. -# Created for TagStudio: https://github.com/CyanVoxel/TagStudio - - -def clean_folder_name(folder_name: str) -> str: - cleaned_name = folder_name - invalid_chars = '<>:"/\\|?*.' - for char in invalid_chars: - cleaned_name = cleaned_name.replace(char, "_") - return cleaned_name diff --git a/tagstudio/src/core/utils/missing_files.py b/tagstudio/src/core/utils/missing_files.py new file mode 100644 index 000000000..86f3714e1 --- /dev/null +++ b/tagstudio/src/core/utils/missing_files.py @@ -0,0 +1,71 @@ +from collections.abc import Iterator +from dataclasses import field, dataclass +from pathlib import Path + +import structlog + +from src.core.library import Library, Entry + +IGNORE_ITEMS = [ + "$recycle.bin", +] + +logger = structlog.get_logger() + + +@dataclass +class MissingRegistry: + """State tracker for unlinked and moved files.""" + + library: Library + files_fixed_count: int = 0 + missing_files: list[Entry] = field(default_factory=list) + + @property + def missing_files_count(self) -> int: + return len(self.missing_files) + + def refresh_missing_files(self) -> Iterator[int]: + """Track the number of Entries that point to an invalid file path.""" + logger.info("refresh_missing_files running") + self.missing_files = [] + for i, entry in enumerate(self.library._entries): + full_path = self.library.library_dir / entry.path + if not full_path.exists() or not full_path.is_file(): + self.missing_files.append(entry) + yield i + + def match_missing_file(self, match_item: Entry) -> list[Path]: + """ + Try to find missing entry files within the library directory. + Works if files were just moved to different subfolders and don't have duplicate names. + """ + + matches = [] + for item in self.library.library_dir.glob(f"**/{match_item.path.name}"): + if item.name == match_item.path.name: # TODO - implement IGNORE_ITEMS + new_path = Path(item).relative_to(self.library.library_dir) + matches.append(new_path) + + return matches + + def fix_missing_files(self) -> Iterator[int]: + """Attempt to fix missing files by finding a match in the library directory.""" + self.files_fixed_count = 0 + for i, entry in enumerate(self.missing_files, start=1): + item_matches = self.match_missing_file(entry) + if len(item_matches) == 1: + logger.info("fix_missing_files", entry=entry, item_matches=item_matches) + self.library.update_entry_path(entry.id, item_matches[0]) + self.files_fixed_count += 1 + # remove fixed file + self.missing_files.remove(entry) + yield i + + def execute_deletion(self) -> Iterator[int]: + for i, missing in enumerate(self.missing_files, start=1): + # TODO - optimize this by removing multiple entries at once + self.library.remove_entries([missing.id]) + yield i + + self.missing_files = [] diff --git a/tagstudio/src/core/utils/refresh_dir.py b/tagstudio/src/core/utils/refresh_dir.py new file mode 100644 index 000000000..1c0a3c191 --- /dev/null +++ b/tagstudio/src/core/utils/refresh_dir.py @@ -0,0 +1,63 @@ +import time +from collections.abc import Iterator +from dataclasses import dataclass, field +from pathlib import Path + +from src.core.constants import TS_FOLDER_NAME +from src.core.library import Library, Entry + + +@dataclass +class RefreshDirTracker: + library: Library + dir_file_count: int = 0 + files_not_in_library: list[Path] = field(default_factory=list) + + @property + def files_count(self) -> int: + return len(self.files_not_in_library) + + def save_new_files(self) -> Iterator[int]: + """Save the list of files that are not in the library.""" + if not self.files_not_in_library: + yield 0 + + for idx, entry_path in enumerate(self.files_not_in_library): + self.library.add_entries([Entry(path=entry_path)]) + yield idx + + self.files_not_in_library = [] + + def refresh_dir(self) -> Iterator[int]: + """Scan a directory for files, and add those relative filenames to internal variables.""" + if self.library.library_dir is None: + raise ValueError("No library path set.") + + start_time = time.time() + self.files_not_in_library = [] + self.dir_file_count = 0 + + for path in self.library.library_dir.glob("**/*"): + str_path = str(path) + if ( + path.is_dir() + or "$RECYCLE.BIN" in str_path + or TS_FOLDER_NAME in str_path + or "tagstudio_thumbs" in str_path + ): + continue + + suffix = path.suffix.lower().lstrip(".") + if suffix in self.library.ignored_extensions: + continue + + self.dir_file_count += 1 + relative_path = path.relative_to(self.library.library_dir) + # TODO - load these in batch somehow + if not self.library.has_path_entry(relative_path): + self.files_not_in_library.append(relative_path) + + end_time = time.time() + # Yield output every 1/30 of a second + if (end_time - start_time) > 0.034: + yield self.dir_file_count diff --git a/tagstudio/src/qt/flowlayout.py b/tagstudio/src/qt/flowlayout.py index 7e3662214..05bcc1b09 100644 --- a/tagstudio/src/qt/flowlayout.py +++ b/tagstudio/src/qt/flowlayout.py @@ -72,14 +72,12 @@ def heightForWidth(self, width): return height def setGeometry(self, rect): - super(FlowLayout, self).setGeometry(rect) + super().setGeometry(rect) self._do_layout(rect, False) - def setGridEfficiency(self, bool): - """ - Enables or Disables efficiencies when all objects are equally sized. - """ - self.grid_efficiency = bool + def setGridEfficiency(self, value: bool): + """Enable or Disable efficiencies when all objects are equally sized.""" + self.grid_efficiency = value def sizeHint(self): return self.minimumSize() @@ -101,27 +99,29 @@ def minimumSize(self): ) return size - def _do_layout(self, rect, test_only): + def _do_layout(self, rect: QRect, test_only: bool) -> float: x = rect.x() y = rect.y() line_height = 0 spacing = self.spacing() - item = None - style = None layout_spacing_x = None layout_spacing_y = None - if self.grid_efficiency: - if self._item_list: - item = self._item_list[0] - style = item.widget().style() - layout_spacing_x = style.layoutSpacing( - QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal - ) - layout_spacing_y = style.layoutSpacing( - QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical - ) - for i, item in enumerate(self._item_list): + if self.grid_efficiency and self._item_list: + item = self._item_list[0] + style = item.widget().style() + layout_spacing_x = style.layoutSpacing( + QSizePolicy.ControlType.PushButton, + QSizePolicy.ControlType.PushButton, + Qt.Orientation.Horizontal, + ) + layout_spacing_y = style.layoutSpacing( + QSizePolicy.ControlType.PushButton, + QSizePolicy.ControlType.PushButton, + Qt.Orientation.Vertical, + ) + + for item in self._item_list: # print(issubclass(type(item.widget()), FlowWidget)) # print(item.widget().ignore_size) skip_count = 0 @@ -139,10 +139,14 @@ def _do_layout(self, rect, test_only): if not self.grid_efficiency: style = item.widget().style() layout_spacing_x = style.layoutSpacing( - QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal + QSizePolicy.ControlType.PushButton, + QSizePolicy.ControlType.PushButton, + Qt.Orientation.Horizontal, ) layout_spacing_y = style.layoutSpacing( - QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical + QSizePolicy.ControlType.PushButton, + QSizePolicy.ControlType.PushButton, + Qt.Orientation.Vertical, ) space_x = spacing + layout_spacing_x space_y = spacing + layout_spacing_y diff --git a/tagstudio/src/qt/helpers/file_opener.py b/tagstudio/src/qt/helpers/file_opener.py index 76ef36666..d9afcd738 100644 --- a/tagstudio/src/qt/helpers/file_opener.py +++ b/tagstudio/src/qt/helpers/file_opener.py @@ -2,22 +2,18 @@ # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging -import os import subprocess import shutil import sys import traceback from pathlib import Path +import structlog from PySide6.QtWidgets import QLabel from PySide6.QtCore import Qt -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" -logging.basicConfig(format="%(message)s", level=logging.INFO) +logger = structlog.get_logger(__name__) def open_file(path: str | Path, file_manager: bool = False): @@ -28,14 +24,15 @@ def open_file(path: str | Path, file_manager: bool = False): file_manager (bool, optional): Whether to open the file in the file manager (e.g. Finder on macOS). Defaults to False. """ - _path = str(path) - logging.info(f"Opening file: {_path}") - if not os.path.exists(_path): - logging.error(f"File not found: {_path}") + path = Path(path) + logger.info("Opening file", path=path) + if not path.exists(): + logger.error("File not found", path=path) return + try: if sys.platform == "win32": - normpath = os.path.normpath(_path) + normpath = Path(path).resolve().as_posix() if file_manager: command_name = "explorer" command_args = '/select,"' + normpath + '"' @@ -61,7 +58,7 @@ def open_file(path: str | Path, file_manager: bool = False): else: if sys.platform == "darwin": command_name = "open" - command_args = [_path] + command_args = [str(path)] if file_manager: # will reveal in Finder command_args.append("-R") @@ -75,18 +72,20 @@ def open_file(path: str | Path, file_manager: bool = False): "--type=method_call", "/org/freedesktop/FileManager1", "org.freedesktop.FileManager1.ShowItems", - f"array:string:file://{_path}", + f"array:string:file://{str(path)}", "string:", ] else: command_name = "xdg-open" - command_args = [_path] + command_args = [str(path)] command = shutil.which(command_name) if command is not None: subprocess.Popen([command] + command_args, close_fds=True) else: - logging.info(f"Could not find {command_name} on system PATH") - except: + logger.info( + "Could not find command on system PATH", command=command_name + ) + except Exception: traceback.print_exc() @@ -144,9 +143,9 @@ def mousePressEvent(self, event): """ super().mousePressEvent(event) - if event.button() == Qt.LeftButton: + if event.button() == Qt.MouseButton.LeftButton: opener = FileOpenerHelper(self.filepath) opener.open_explorer() - elif event.button() == Qt.RightButton: + elif event.button() == Qt.MouseButton.RightButton: # Show context menu pass diff --git a/tagstudio/src/qt/helpers/function_iterator.py b/tagstudio/src/qt/helpers/function_iterator.py index 197f90a62..770e2846b 100644 --- a/tagstudio/src/qt/helpers/function_iterator.py +++ b/tagstudio/src/qt/helpers/function_iterator.py @@ -1,14 +1,13 @@ # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio - +from collections.abc import Callable from PySide6.QtCore import Signal, QObject -from typing import Callable class FunctionIterator(QObject): - """Iterates over a yielding function and emits progress as the 'value' signal.\n\nThread-Safe Guarantee™""" + """Iterate over a yielding function and emit progress as the 'value' signal.""" value = Signal(object) diff --git a/tagstudio/src/qt/main_window.py b/tagstudio/src/qt/main_window.py index a77f87447..ce6b1e338 100644 --- a/tagstudio/src/qt/main_window.py +++ b/tagstudio/src/qt/main_window.py @@ -36,7 +36,7 @@ class Ui_MainWindow(QMainWindow): def __init__(self, driver: "QtDriver", parent=None) -> None: super().__init__(parent) - self.driver: "QtDriver" = driver + self.driver = driver self.setupUi(self) # NOTE: These are old attempts to allow for a translucent/acrylic @@ -235,4 +235,4 @@ def toggle_landing_page(self, enabled: bool): else: self.landing_widget.setHidden(True) self.landing_widget.set_status_label("") - self.scrollArea.setHidden(False) \ No newline at end of file + self.scrollArea.setHidden(False) diff --git a/tagstudio/src/qt/modals/add_field.py b/tagstudio/src/qt/modals/add_field.py index c0137da2d..5f6ceacb9 100644 --- a/tagstudio/src/qt/modals/add_field.py +++ b/tagstudio/src/qt/modals/add_field.py @@ -10,23 +10,24 @@ QHBoxLayout, QLabel, QPushButton, - QComboBox, + QListWidget, + QListWidgetItem, ) from src.core.library import Library class AddFieldModal(QWidget): - done = Signal(int) + done = Signal(list) - def __init__(self, library: "Library"): + def __init__(self, library: Library): # [Done] # - OR - # [Cancel] [Save] super().__init__() self.is_connected = False self.lib = library - self.setWindowTitle(f"Add Field") + self.setWindowTitle("Add Field") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) @@ -43,17 +44,7 @@ def __init__(self, library: "Library"): self.title_widget.setText("Add Field") self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) - self.combo_box = QComboBox() - self.combo_box.setEditable(False) - # self.combo_box.setMaxVisibleItems(5) - self.combo_box.setStyleSheet("combobox-popup:0;") - self.combo_box.view().setVerticalScrollBarPolicy( - Qt.ScrollBarPolicy.ScrollBarAsNeeded - ) - for df in self.lib.default_fields: - self.combo_box.addItem( - f'{df["name"]} ({df["type"].replace("_", " ").title()})' - ) + self.list_widget = QListWidget() self.button_container = QWidget() self.button_layout = QHBoxLayout(self.button_container) @@ -75,17 +66,25 @@ def __init__(self, library: "Library"): self.save_button.setDefault(True) self.save_button.clicked.connect(self.hide) self.save_button.clicked.connect( - lambda: self.done.emit(self.combo_box.currentIndex()) + lambda: ( + # get userData for each selected item + self.done.emit(self.list_widget.selectedItems()) + ) ) - # self.save_button.clicked.connect(lambda: save_callback(widget.get_content())) self.button_layout.addWidget(self.save_button) - # self.returnPressed.connect(lambda: self.done.emit(self.combo_box.currentIndex())) - - # self.done.connect(lambda x: callback(x)) - self.root_layout.addWidget(self.title_widget) - self.root_layout.addWidget(self.combo_box) + self.root_layout.addWidget(self.list_widget) # self.root_layout.setStretch(1,2) + self.root_layout.addStretch(1) self.root_layout.addWidget(self.button_container) + + def show(self): + self.list_widget.clear() + for df in self.lib.field_types.values(): + item = QListWidgetItem(f"{df.name} ({df.type})") + item.setData(Qt.ItemDataRole.UserRole, df.key) + self.list_widget.addItem(item) + + super().show() diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index c7fa543e1..78769f51b 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -3,8 +3,7 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging - +import structlog from PySide6.QtCore import Signal, Qt from PySide6.QtWidgets import ( QWidget, @@ -18,30 +17,28 @@ QComboBox, ) -from src.core.library import Library, Tag +from src.core.library import Tag, Library +from src.core.library.alchemy.enums import TagColor from src.core.palette import ColorType, get_tag_color -from src.core.constants import TAG_COLORS + from src.qt.widgets.panel import PanelWidget, PanelModal from src.qt.widgets.tag import TagWidget from src.qt.modals.tag_search import TagSearchPanel - -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" - -logging.basicConfig(format="%(message)s", level=logging.INFO) +logger = structlog.get_logger(__name__) class BuildTagPanel(PanelWidget): on_edit = Signal(Tag) - def __init__(self, library, tag_id: int = -1): + def __init__( + self, library: Library, tag_id: int | None = None, tag: Tag | None = None + ): super().__init__() - self.lib: Library = library + self.lib = library # self.callback = callback # self.tag_id = tag_id - self.tag = None + self.setMinimumSize(300, 400) self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 0, 6, 0) @@ -95,6 +92,7 @@ def __init__(self, library, tag_id: int = -1): self.subtags_layout.setContentsMargins(0, 0, 0, 0) self.subtags_layout.setSpacing(0) self.subtags_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) + self.subtags_title = QLabel() self.subtags_title.setText("Parent Tags") self.subtags_layout.addWidget(self.subtags_title) @@ -140,15 +138,18 @@ def __init__(self, library, tag_id: int = -1): self.color_field.setEditable(False) self.color_field.setMaxVisibleItems(10) self.color_field.setStyleSheet("combobox-popup:0;") - for color in TAG_COLORS: - self.color_field.addItem(color.title()) + for color in TagColor: + self.color_field.addItem(color.name, userData=color.value) # self.color_field.setProperty("appearance", "flat") - self.color_field.currentTextChanged.connect( - lambda c: self.color_field.setStyleSheet(f"""combobox-popup:0; - font-weight:600; - color:{get_tag_color(ColorType.TEXT, c.lower())}; - background-color:{get_tag_color(ColorType.PRIMARY, c.lower())}; - """) + self.color_field.currentIndexChanged.connect( + lambda c: ( + self.color_field.setStyleSheet( + "combobox-popup:0;" + "font-weight:600;" + f"color:{get_tag_color(ColorType.TEXT, self.color_field.currentData())};" + f"background-color:{get_tag_color(ColorType.PRIMARY, self.color_field.currentData())};" + ) + ) ) self.color_layout.addWidget(self.color_field) @@ -160,86 +161,59 @@ def __init__(self, library, tag_id: int = -1): self.root_layout.addWidget(self.color_widget) # self.parent().done.connect(self.update_tag) - if tag_id >= 0: - self.tag = self.lib.get_tag(tag_id) - else: - self.tag = Tag(-1, "New Tag", "", [], [], "") - self.set_tag(self.tag) + if tag_id is not None: + tag = self.lib.get_tag(tag_id) + elif not tag: + tag = Tag(name="New Tag") + + # TODO - fill subtags + self.subtags: set[int] = set() + self.set_tag(tag) def add_subtag_callback(self, tag_id: int): - logging.info(f"adding {tag_id}") - # tag = self.lib.get_tag(self.tag_id) - # TODO: Create a single way to update tags and refresh library data - # new = self.build_tag() - self.tag.add_subtag(tag_id) - # self.tag = new - # self.lib.update_tag(new) + logger.info("add_subtag_callback", tag_id=tag_id) + self.subtags.add(tag_id) self.set_subtags() - # self.on_edit.emit(self.build_tag()) def remove_subtag_callback(self, tag_id: int): - logging.info(f"removing {tag_id}") - # tag = self.lib.get_tag(self.tag_id) - # TODO: Create a single way to update tags and refresh library data - # new = self.build_tag() - self.tag.remove_subtag(tag_id) - # self.tag = new - # self.lib.update_tag(new) + logger.info("removing subtag", tag_id=tag_id) + self.subtags.remove(tag_id) self.set_subtags() - # self.on_edit.emit(self.build_tag()) def set_subtags(self): while self.scroll_layout.itemAt(0): self.scroll_layout.takeAt(0).widget().deleteLater() - logging.info(f"Setting {self.tag.subtag_ids}") + c = QWidget() - l = QVBoxLayout(c) - l.setContentsMargins(0, 0, 0, 0) - l.setSpacing(3) - for tag_id in self.tag.subtag_ids: - tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, True) - tw.on_remove.connect( - lambda checked=False, t=tag_id: self.remove_subtag_callback(t) - ) - l.addWidget(tw) + layout = QVBoxLayout(c) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + for tag_id in self.subtags: + tag = self.lib.get_tag(tag_id) + tw = TagWidget(tag, False, True) + tw.on_remove.connect(lambda t=tag_id: self.remove_subtag_callback(t)) + layout.addWidget(tw) self.scroll_layout.addWidget(c) def set_tag(self, tag: Tag): - # tag = self.lib.get_tag(tag_id) + logger.info("setting tag", tag=tag) + self.name_field.setText(tag.name) - self.shorthand_field.setText(tag.shorthand) - self.aliases_field.setText("\n".join(tag.aliases)) + self.shorthand_field.setText(tag.shorthand or "") + # TODO: Implement aliases + # self.aliases_field.setText("\n".join(tag.aliases)) self.set_subtags() - self.color_field.setCurrentIndex(TAG_COLORS.index(tag.color.lower())) - # self.tag_id = tag.id + self.color_field.setCurrentIndex(tag.color.value) + self.tag = tag def build_tag(self) -> Tag: - # tag: Tag = self.tag - # if self.tag_id >= 0: - # tag = self.lib.get_tag(self.tag_id) - # else: - # tag = Tag(-1, '', '', [], [], '') - new_tag: Tag = Tag( - id=self.tag.id, - name=self.name_field.text(), - shorthand=self.shorthand_field.text(), - aliases=self.aliases_field.toPlainText().split("\n"), - subtags_ids=self.tag.subtag_ids, - color=self.color_field.currentText().lower(), - ) - logging.info(f"built {new_tag}") - return new_tag - - # NOTE: The callback and signal do the same thing, I'm currently - # transitioning from using callbacks to the Qt method of using signals. - # self.tag_updated.emit(new_tag) - # self.callback(new_tag) - - # def on_return(self, callback, text:str): - # if text and self.first_tag_id >= 0: - # callback(self.first_tag_id) - # self.search_field.setText('') - # self.update_tags('') - # else: - # self.search_field.setFocus() - # self.parentWidget().hide() + color = self.color_field.currentData() or TagColor.DEFAULT + + tag = self.tag + + tag.name = self.name_field.text() + tag.shorthand = self.shorthand_field.text() + tag.color = color + + logger.info("built tag", tag=tag) + return tag diff --git a/tagstudio/src/qt/modals/delete_unlinked.py b/tagstudio/src/qt/modals/delete_unlinked.py index 453148f91..19a3af08f 100644 --- a/tagstudio/src/qt/modals/delete_unlinked.py +++ b/tagstudio/src/qt/modals/delete_unlinked.py @@ -15,7 +15,7 @@ QListView, ) -from src.core.library import ItemType, Library +from src.core.utils.missing_files import MissingRegistry from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.helpers.function_iterator import FunctionIterator from src.qt.widgets.progress import ProgressWidget @@ -28,10 +28,10 @@ class DeleteUnlinkedEntriesModal(QWidget): done = Signal() - def __init__(self, library: "Library", driver: "QtDriver"): + def __init__(self, driver: "QtDriver", tracker: MissingRegistry): super().__init__() - self.lib = library self.driver = driver + self.tracker = tracker self.setWindowTitle("Delete Unlinked Entries") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) @@ -42,7 +42,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) self.desc_widget.setText(f""" - Are you sure you want to delete the following {len(self.lib.missing_files)} entries? + Are you sure you want to delete the following {self.tracker.missing_files_count} entries? """) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) @@ -73,35 +73,38 @@ def __init__(self, library: "Library", driver: "QtDriver"): def refresh_list(self): self.desc_widget.setText(f""" - Are you sure you want to delete the following {len(self.lib.missing_files)} entries? + Are you sure you want to delete the following {self.tracker.missing_files_count} entries? """) self.model.clear() - for i in self.lib.missing_files: - self.model.appendRow(QStandardItem(str(i))) + for i in self.tracker.missing_files: + self.model.appendRow(QStandardItem(str(i.path))) def delete_entries(self): - iterator = FunctionIterator(self.lib.remove_missing_files) - pw = ProgressWidget( window_title="Deleting Entries", label_text="", cancel_button_text=None, minimum=0, - maximum=len(self.lib.missing_files), + maximum=self.tracker.missing_files_count, ) pw.show() - iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) + iterator = FunctionIterator(self.tracker.execute_deletion) + files_count = self.tracker.missing_files_count iterator.value.connect( - lambda x: pw.update_label( - f"Deleting {x[0]+1}/{len(self.lib.missing_files)} Unlinked Entries" + lambda idx: ( + pw.update_progress(idx), + pw.update_label(f"Deleting {idx}/{files_count} Unlinked Entries"), ) ) - iterator.value.connect( - lambda x: self.driver.purge_item_from_navigation(ItemType.ENTRY, x[1]) - ) - r = CustomRunnable(lambda: iterator.run()) + r = CustomRunnable(iterator.run) QThreadPool.globalInstance().start(r) - r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.done.emit())) + r.done.connect( + lambda: ( + pw.hide(), + pw.deleteLater(), + self.done.emit(), + ) + ) diff --git a/tagstudio/src/qt/modals/file_extension.py b/tagstudio/src/qt/modals/file_extension.py index c95b520bb..c631cd50b 100644 --- a/tagstudio/src/qt/modals/file_extension.py +++ b/tagstudio/src/qt/modals/file_extension.py @@ -19,13 +19,17 @@ from src.core.library import Library from src.qt.widgets.panel import PanelWidget +from src.core.constants import LibraryPrefs class FileExtensionItemDelegate(QStyledItemDelegate): def setModelData(self, editor, model, index): - if isinstance(editor, QLineEdit): - if editor.text() and not editor.text().startswith("."): - editor.setText(f".{editor.text()}") + if ( + isinstance(editor, QLineEdit) + and editor.text() + and not editor.text().startswith(".") + ): + editor.setText(f".{editor.text()}") super().setModelData(editor, model, index) @@ -43,7 +47,7 @@ def __init__(self, library: "Library"): self.root_layout.setContentsMargins(6, 6, 6, 6) # Create Table Widget -------------------------------------------------- - self.table = QTableWidget(len(self.lib.ext_list), 1) + self.table = QTableWidget(len(self.lib.prefs(LibraryPrefs.EXTENSION_LIST)), 1) self.table.horizontalHeader().setVisible(False) self.table.verticalHeader().setVisible(False) self.table.horizontalHeader().setStretchLastSection(True) @@ -65,9 +69,12 @@ def __init__(self, library: "Library"): self.mode_label.setText("List Mode:") self.mode_combobox = QComboBox() self.mode_combobox.setEditable(False) - self.mode_combobox.addItem("Exclude") self.mode_combobox.addItem("Include") - self.mode_combobox.setCurrentIndex(0 if self.lib.is_exclude_list else 1) + self.mode_combobox.addItem("Exclude") + + is_exclude_list = int(bool(self.lib.prefs(LibraryPrefs.IS_EXCLUDE_LIST))) + + self.mode_combobox.setCurrentIndex(is_exclude_list) self.mode_combobox.currentIndexChanged.connect( lambda i: self.update_list_mode(i) ) @@ -91,23 +98,23 @@ def update_list_mode(self, mode: int): Args: mode (int): The list mode, given by the index of the mode inside - the mode combobox. 0 for "Exclude", 1 for "Include". + the mode combobox. 1 for "Exclude", 0 for "Include". """ - if mode == 0: - self.lib.is_exclude_list = True - elif mode == 1: - self.lib.is_exclude_list = False + self.lib.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, bool(mode)) def refresh_list(self): - for i, ext in enumerate(self.lib.ext_list): + for i, ext in enumerate(self.lib.prefs(LibraryPrefs.EXTENSION_LIST)): self.table.setItem(i, 0, QTableWidgetItem(ext)) def add_item(self): self.table.insertRow(self.table.rowCount()) def save(self): - self.lib.ext_list.clear() + extensions = [] for i in range(self.table.rowCount()): ext = self.table.item(i, 0) - if ext and ext.text(): - self.lib.ext_list.append(ext.text().lower()) + if ext and ext.text().strip(): + extensions.append(ext.text().strip().lower()) + + # save preference + self.lib.set_prefs(LibraryPrefs.EXTENSION_LIST, extensions) diff --git a/tagstudio/src/qt/modals/fix_dupes.py b/tagstudio/src/qt/modals/fix_dupes.py index b471f0763..e323c20e3 100644 --- a/tagstudio/src/qt/modals/fix_dupes.py +++ b/tagstudio/src/qt/modals/fix_dupes.py @@ -3,7 +3,6 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import os import typing from PySide6.QtCore import Qt @@ -17,6 +16,7 @@ ) from src.core.library import Library +from src.core.utils.dupe_files import DupeRegistry from src.qt.modals.mirror_entities import MirrorEntriesModal # Only import for type checking/autocompletion, will not be imported at runtime. @@ -32,12 +32,14 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.driver = driver self.count = -1 self.filename = "" - self.setWindowTitle(f"Fix Duplicate Files") + self.setWindowTitle("Fix Duplicate Files") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(400, 300) self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 6, 6, 6) + self.tracker = DupeRegistry(library=self.lib) + self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) @@ -80,13 +82,14 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.open_button = QPushButton() self.open_button.setText("&Load DupeGuru File") - self.open_button.clicked.connect(lambda: self.select_file()) + self.open_button.clicked.connect(self.select_file) + + self.mirror_modal = MirrorEntriesModal(self.driver, self.tracker) + self.mirror_modal.done.connect(self.refresh_dupes) self.mirror_button = QPushButton() - self.mirror_modal = MirrorEntriesModal(self.lib, self.driver) - self.mirror_modal.done.connect(lambda: self.refresh_dupes()) self.mirror_button.setText("&Mirror Entries") - self.mirror_button.clicked.connect(lambda: self.mirror_modal.show()) + self.mirror_button.clicked.connect(self.mirror_modal.show) self.mirror_desc = QLabel() self.mirror_desc.setWordWrap(True) self.mirror_desc.setText( @@ -134,7 +137,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.root_layout.addStretch(1) self.root_layout.addWidget(self.button_container) - self.set_dupe_count(self.count) + self.set_dupe_count(-1) def select_file(self): qfd = QFileDialog(self, "Open DupeGuru Results File", str(self.lib.library_dir)) @@ -155,15 +158,14 @@ def set_filename(self, filename: str): self.mirror_modal.refresh_list() def refresh_dupes(self): - self.lib.refresh_dupe_files(self.filename) - self.set_dupe_count(len(self.lib.dupe_files)) + self.tracker.refresh_dupe_files(self.filename) + self.set_dupe_count(self.tracker.groups_count) def set_dupe_count(self, count: int): - self.count = count - if self.count < 0: + if count < 0: self.mirror_button.setDisabled(True) - self.dupe_count.setText(f"Duplicate File Matches: N/A") - elif self.count == 0: + self.dupe_count.setText("Duplicate File Matches: N/A") + elif count == 0: self.mirror_button.setDisabled(True) self.dupe_count.setText(f"Duplicate File Matches: {count}") else: diff --git a/tagstudio/src/qt/modals/fix_unlinked.py b/tagstudio/src/qt/modals/fix_unlinked.py index 72d115cda..b933aa6d3 100644 --- a/tagstudio/src/qt/modals/fix_unlinked.py +++ b/tagstudio/src/qt/modals/fix_unlinked.py @@ -3,13 +3,13 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging import typing from PySide6.QtCore import Qt, QThreadPool from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton from src.core.library import Library +from src.core.utils.missing_files import MissingRegistry from src.qt.helpers.function_iterator import FunctionIterator from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal @@ -22,18 +22,14 @@ from src.qt.ts_qt import QtDriver -ERROR = "[ERROR]" -WARNING = "[WARNING]" -INFO = "[INFO]" - -logging.basicConfig(format="%(message)s", level=logging.INFO) - - class FixUnlinkedEntriesModal(QWidget): def __init__(self, library: "Library", driver: "QtDriver"): super().__init__() self.lib = library self.driver = driver + + self.tracker = MissingRegistry(library=self.lib) + self.missing_count = -1 self.dupe_count = -1 self.setWindowTitle("Fix Unlinked Entries") @@ -50,14 +46,6 @@ def __init__(self, library: "Library", driver: "QtDriver"): """Each library entry is linked to a file in one of your directories. If a file linked to an entry is moved or deleted outside of TagStudio, it is then considered unlinked. Unlinked entries may be automatically relinked via searching your directories, manually relinked by the user, or deleted if desired.""" ) - self.dupe_desc_widget = QLabel() - self.dupe_desc_widget.setObjectName("dupeDescriptionLabel") - self.dupe_desc_widget.setWordWrap(True) - self.dupe_desc_widget.setStyleSheet("text-align:left;") - self.dupe_desc_widget.setText( - """Duplicate entries are defined as multiple entries which point to the same file on disk. Merging these will combine the tags and metadata from all duplicates into a single consolidated entry. These are not to be confused with "duplicate files", which are duplicates of your files themselves outside of TagStudio.""" - ) - self.missing_count_label = QLabel() self.missing_count_label.setObjectName("missingCountLabel") self.missing_count_label.setStyleSheet("font-weight:bold;" "font-size:14px;") @@ -70,42 +58,37 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.refresh_unlinked_button = QPushButton() self.refresh_unlinked_button.setText("&Refresh All") - self.refresh_unlinked_button.clicked.connect( - lambda: self.refresh_missing_files() - ) + self.refresh_unlinked_button.clicked.connect(self.refresh_missing_files) self.merge_class = MergeDuplicateEntries(self.lib, self.driver) - self.relink_class = RelinkUnlinkedEntries(self.lib, self.driver) + self.relink_class = RelinkUnlinkedEntries(self.tracker) self.search_button = QPushButton() self.search_button.setText("&Search && Relink") self.relink_class.done.connect( - lambda: self.refresh_and_repair_dupe_entries(self.merge_class) + # refresh the grid + lambda: ( + self.driver.filter_items(), + self.refresh_missing_files(), + ) ) - self.search_button.clicked.connect(lambda: self.relink_class.repair_entries()) - - self.refresh_dupe_button = QPushButton() - self.refresh_dupe_button.setText("Refresh Duplicate Entries") - self.refresh_dupe_button.clicked.connect(lambda: self.refresh_dupe_entries()) - - self.merge_dupe_button = QPushButton() - self.merge_dupe_button.setText("&Merge Duplicate Entries") - self.merge_class.done.connect(lambda: self.set_dupe_count(-1)) - self.merge_class.done.connect(lambda: self.set_missing_count(-1)) - self.merge_class.done.connect(lambda: self.driver.filter_items()) - self.merge_dupe_button.clicked.connect(lambda: self.merge_class.merge_entries()) + self.search_button.clicked.connect(self.relink_class.repair_entries) self.manual_button = QPushButton() self.manual_button.setText("&Manual Relink") + self.manual_button.setHidden(True) self.delete_button = QPushButton() - self.delete_modal = DeleteUnlinkedEntriesModal(self.lib, self.driver) + self.delete_modal = DeleteUnlinkedEntriesModal(self.driver, self.tracker) self.delete_modal.done.connect( - lambda: self.set_missing_count(len(self.lib.missing_files)) + lambda: ( + self.set_missing_count(self.tracker.missing_files_count), + # refresh the grid + self.driver.filter_items(), + ) ) - self.delete_modal.done.connect(lambda: self.driver.update_thumbs()) self.delete_button.setText("De&lete Unlinked Entries") - self.delete_button.clicked.connect(lambda: self.delete_modal.show()) + self.delete_button.clicked.connect(self.delete_modal.show) self.button_container = QWidget() self.button_layout = QHBoxLayout(self.button_container) @@ -122,83 +105,35 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.root_layout.addWidget(self.unlinked_desc_widget) self.root_layout.addWidget(self.refresh_unlinked_button) self.root_layout.addWidget(self.search_button) - self.manual_button.setHidden(True) self.root_layout.addWidget(self.manual_button) self.root_layout.addWidget(self.delete_button) self.root_layout.addStretch(1) - self.root_layout.addWidget(self.dupe_count_label) - self.root_layout.addWidget(self.dupe_desc_widget) - self.root_layout.addWidget(self.refresh_dupe_button) - self.root_layout.addWidget(self.merge_dupe_button) self.root_layout.addStretch(2) self.root_layout.addWidget(self.button_container) self.set_missing_count(self.missing_count) - self.set_dupe_count(self.dupe_count) def refresh_missing_files(self): - iterator = FunctionIterator(self.lib.refresh_missing_files) pw = ProgressWidget( window_title="Scanning Library", label_text="Scanning Library for Unlinked Entries...", cancel_button_text=None, minimum=0, - maximum=len(self.lib.entries), - ) - pw.show() - iterator.value.connect(lambda v: pw.update_progress(v + 1)) - r = CustomRunnable(lambda: iterator.run()) - QThreadPool.globalInstance().start(r) - r.done.connect( - lambda: ( - pw.hide(), - pw.deleteLater(), - self.set_missing_count(len(self.lib.missing_files)), - self.delete_modal.refresh_list(), - self.refresh_dupe_entries(), - ) + maximum=self.lib.entries_count, ) - def refresh_dupe_entries(self): - iterator = FunctionIterator(self.lib.refresh_dupe_entries) - pw = ProgressWidget( - window_title="Scanning Library", - label_text="Scanning Library for Duplicate Entries...", - cancel_button_text=None, - minimum=0, - maximum=len(self.lib.entries), - ) pw.show() + + iterator = FunctionIterator(self.tracker.refresh_missing_files) iterator.value.connect(lambda v: pw.update_progress(v + 1)) - r = CustomRunnable(lambda: iterator.run()) + r = CustomRunnable(iterator.run) QThreadPool.globalInstance().start(r) r.done.connect( lambda: ( pw.hide(), pw.deleteLater(), - self.set_dupe_count(len(self.lib.dupe_entries)), - ) - ) - - def refresh_and_repair_dupe_entries(self, merge_class: MergeDuplicateEntries): - iterator = FunctionIterator(self.lib.refresh_dupe_entries) - pw = ProgressWidget( - window_title="Scanning Library", - label_text="Scanning Library for Duplicate Entries...", - cancel_button_text=None, - minimum=0, - maximum=len(self.lib.entries), - ) - pw.show() - iterator.value.connect(lambda v: pw.update_progress(v + 1)) - r = CustomRunnable(lambda: iterator.run()) - QThreadPool.globalInstance().start(r) - r.done.connect( - lambda: ( - pw.hide(), # type: ignore - pw.deleteLater(), # type: ignore - self.set_dupe_count(len(self.lib.dupe_entries)), - merge_class.merge_entries(), + self.set_missing_count(self.tracker.missing_files_count), + self.delete_modal.refresh_list(), ) ) @@ -208,23 +143,8 @@ def set_missing_count(self, count: int): self.search_button.setDisabled(True) self.delete_button.setDisabled(True) self.missing_count_label.setText("Unlinked Entries: N/A") - elif self.missing_count == 0: - self.search_button.setDisabled(True) - self.delete_button.setDisabled(True) - self.missing_count_label.setText(f"Unlinked Entries: {count}") else: - self.search_button.setDisabled(False) - self.delete_button.setDisabled(False) + # disable buttons if there are no files to fix + self.search_button.setDisabled(self.missing_count == 0) + self.delete_button.setDisabled(self.missing_count == 0) self.missing_count_label.setText(f"Unlinked Entries: {count}") - - def set_dupe_count(self, count: int): - self.dupe_count = count - if self.dupe_count < 0: - self.dupe_count_label.setText("Duplicate Entries: N/A") - self.merge_dupe_button.setDisabled(True) - elif self.dupe_count == 0: - self.dupe_count_label.setText(f"Duplicate Entries: {count}") - self.merge_dupe_button.setDisabled(True) - else: - self.dupe_count_label.setText(f"Duplicate Entries: {count}") - self.merge_dupe_button.setDisabled(False) diff --git a/tagstudio/src/qt/modals/folders_to_tags.py b/tagstudio/src/qt/modals/folders_to_tags.py index 2486a76e9..d779899b5 100644 --- a/tagstudio/src/qt/modals/folders_to_tags.py +++ b/tagstudio/src/qt/modals/folders_to_tags.py @@ -3,10 +3,11 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging import math import typing +from dataclasses import dataclass, field +import structlog from PySide6.QtCore import Qt from PySide6.QtWidgets import ( QWidget, @@ -18,142 +19,143 @@ QFrame, ) -from src.core.enums import FieldID -from src.core.library import Library, Tag +from src.core.constants import TAG_FAVORITE, TAG_ARCHIVED +from src.core.library import Tag, Library +from src.core.library.alchemy.fields import _FieldID from src.core.palette import ColorType, get_tag_color from src.qt.flowlayout import FlowLayout -# Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver +logger = structlog.get_logger(__name__) -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" -logging.basicConfig(format="%(message)s", level=logging.INFO) +@dataclass +class BranchData: + dirs: dict[str, "BranchData"] = field(default_factory=dict) + files: list[str] = field(default_factory=list) + tag: Tag | None = None + + +def add_folders_to_tree( + library: Library, tree: BranchData, items: tuple[str, ...] +) -> BranchData: + branch = tree + for folder in items: + if folder not in branch.dirs: + # TODO - subtags + new_tag = Tag(name=folder) + library.add_tag(new_tag) + branch.dirs[folder] = BranchData(tag=new_tag) + branch.tag = new_tag + branch = branch.dirs[folder] + return branch def folders_to_tags(library: Library): - logging.info("Converting folders to Tags") - tree: dict = dict(dirs={}) + logger.info("Converting folders to Tags") + tree = BranchData() def add_tag_to_tree(items: list[Tag]): branch = tree for tag in items: - if tag.name not in branch["dirs"]: - branch["dirs"][tag.name] = dict(dirs={}, tag=tag) - branch = branch["dirs"][tag.name] - - def add_folders_to_tree(items: list[str]) -> Tag: - branch: dict = tree - for folder in items: - if folder not in branch["dirs"]: - new_tag = Tag( - -1, - folder, - "", - [], - ([branch["tag"].id] if "tag" in branch else []), - "", - ) - library.add_tag_to_library(new_tag) - branch["dirs"][folder] = dict(dirs={}, tag=new_tag) - branch = branch["dirs"][folder] - return branch.get("tag") + if tag.name not in branch.dirs: + branch.dirs[tag.name] = BranchData() + branch = branch.dirs[tag.name] for tag in library.tags: reversed_tag = reverse_tag(library, tag, None) add_tag_to_tree(reversed_tag) - for entry in library.entries: - folders = list(entry.path.parts) - if len(folders) == 1 and folders[0] == "": + for entry in library._entries: + folders = entry.path.parts[1:-1] + if not folders: continue - tag = add_folders_to_tree(folders) - if tag: - if not entry.has_tag(library, tag.id): - entry.add_tag(library, tag.id, FieldID.TAGS) - logging.info("Done") + tag = add_folders_to_tree(library, tree, folders).tag + if tag and not entry.has_tag(tag): + library.add_field_tag(entry, tag, _FieldID.TAGS.name, create_field=True) + + logger.info("Done") -def reverse_tag(library: Library, tag: Tag, list: list[Tag]) -> list[Tag]: - if list is not None: - list.append(tag) - else: - list = [tag] +def reverse_tag(library: Library, tag: Tag, items: list[Tag] | None) -> list[Tag]: + items = items or [] + items.append(tag) - if len(tag.subtag_ids) == 0: - list.reverse() - return list - else: - for subtag_id in tag.subtag_ids: - subtag = library.get_tag(subtag_id) - return reverse_tag(library, subtag, list) + if not tag.subtag_ids: + items.reverse() + return items + + for subtag_id in tag.subtag_ids: + subtag = library.get_tag(subtag_id) + return reverse_tag(library, subtag, items) # =========== UI =========== -def generate_preview_data(library: Library): - tree: dict = dict(dirs={}, files=[]) +def generate_preview_data(library: Library) -> BranchData: + tree = BranchData() def add_tag_to_tree(items: list[Tag]): - branch: dict = tree + branch = tree for tag in items: - if tag.name not in branch["dirs"]: - branch["dirs"][tag.name] = dict(dirs={}, tag=tag, files=[]) - branch = branch["dirs"][tag.name] + if tag.name not in branch.dirs: + branch.dirs[tag.name] = BranchData(tag=tag) + branch = branch.dirs[tag.name] - def add_folders_to_tree(items: list[str]) -> dict: - branch: dict = tree + def _add_folders_to_tree(items: typing.Sequence[str]) -> BranchData: + branch = tree for folder in items: - if folder not in branch["dirs"]: - new_tag = Tag(-1, folder, "", [], [], "green") - branch["dirs"][folder] = dict(dirs={}, tag=new_tag, files=[]) - branch = branch["dirs"][folder] + if folder not in branch.dirs: + new_tag = Tag(name=folder) + branch.dirs[folder] = BranchData(tag=new_tag) + branch = branch.dirs[folder] return branch for tag in library.tags: + if tag.id in (TAG_FAVORITE, TAG_ARCHIVED): + continue reversed_tag = reverse_tag(library, tag, None) add_tag_to_tree(reversed_tag) - for entry in library.entries: - folders = list(entry.path.parts) - if len(folders) == 1 and folders[0] == "": + for entry in library._entries: + folders = entry.path.parts[1:-1] + if not folders: continue - branch = add_folders_to_tree(folders) + + branch = _add_folders_to_tree(folders) if branch: - field_indexes = library.get_field_index_in_entry(entry, 6) has_tag = False - for index in field_indexes: - content = library.get_field_attr(entry.fields[index], "content") - for tag_id in content: - tag = library.get_tag(tag_id) - if tag.name == branch["tag"].name: + for tag_field in entry.tag_box_fields: + for tag in tag_field.tags: + if tag.name == branch.tag.name: has_tag = True break if not has_tag: - branch["files"].append(entry.filename) + branch.files.append(entry.path.name) - def cut_branches_adding_nothing(branch: dict): - folders = set(branch["dirs"].keys()) + def cut_branches_adding_nothing(branch: BranchData) -> bool: + folders = list(branch.dirs.keys()) for folder in folders: - cut = cut_branches_adding_nothing(branch["dirs"][folder]) + cut = cut_branches_adding_nothing(branch.dirs[folder]) if cut: - branch["dirs"].pop(folder) + branch.dirs.pop(folder) - if "tag" not in branch: - return - if branch["tag"].id == -1 or len(branch["files"]) > 0: # Needs to be first + if not branch.tag: return False - if len(branch["dirs"].keys()) == 0: - return True - cut_branches_adding_nothing(tree) + if not branch.tag.id: + return False + + if branch.files: + return False + + return not bool(branch.dirs) + cut_branches_adding_nothing(tree) return tree @@ -166,7 +168,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.count = -1 self.filename = "" - self.setWindowTitle(f"Create Tags From Folders") + self.setWindowTitle("Create Tags From Folders") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(640, 640) self.root_layout = QVBoxLayout(self) @@ -242,19 +244,19 @@ def on_open(self, event): data = generate_preview_data(self.library) - for folder in data["dirs"].values(): - test = TreeItem(folder, None) + for folder in data.dirs.values(): + test = TreeItem(folder) self.scroll_layout.addWidget(test) def set_all_branches(self, hidden: bool): for i in reversed(range(self.scroll_layout.count())): child = self.scroll_layout.itemAt(i).widget() - if type(child) == TreeItem: + if isinstance(child, TreeItem): child.set_all_branches(hidden) class TreeItem(QWidget): - def __init__(self, data: dict, parentTag: Tag): + def __init__(self, data: BranchData, parent_tag: Tag | None = None): super().__init__() self.setStyleSheet("QLabel{font-size: 13px}") @@ -270,7 +272,7 @@ def __init__(self, data: dict, parentTag: Tag): self.label = QLabel() self.tag_layout.addWidget(self.label) - self.tag_widget = ModifiedTagWidget(data["tag"], parentTag) + self.tag_widget = ModifiedTagWidget(data.tag, parent_tag) self.tag_widget.bg_button.clicked.connect(lambda: self.hide_show()) self.tag_layout.addWidget(self.tag_widget) @@ -284,24 +286,24 @@ def hide_show(self): self.children_widget.setHidden(not self.children_widget.isHidden()) self.label.setText(">" if self.children_widget.isHidden() else "v") - def populate(self, data: dict): - for folder in data["dirs"].values(): - item = TreeItem(folder, data["tag"]) + def populate(self, data: BranchData): + for folder in data.dirs.values(): + item = TreeItem(folder, data.tag) self.children_layout.addWidget(item) - for file in data["files"]: + for file in data.files: label = QLabel() label.setText(" -> " + str(file)) self.children_layout.addWidget(label) - if len(data["files"]) == 0 and len(data["dirs"].values()) == 0: - self.hide_show() - else: + if data.files or data.dirs: self.label.setText("v") + else: + self.hide_show() def set_all_branches(self, hidden: bool): for i in reversed(range(self.children_layout.count())): child = self.children_layout.itemAt(i).widget() - if type(child) == TreeItem: + if isinstance(child, TreeItem): child.set_all_branches(hidden) self.children_widget.setHidden(hidden) @@ -343,7 +345,7 @@ def __init__(self, tag: Tag, parentTag: Tag) -> None: f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};" f"border-radius: 6px;" f"border-style:inset;" - f"border-width: {math.ceil(1*self.devicePixelRatio())}px;" + f"border-width: {math.ceil(self.devicePixelRatio())}px;" f"padding-right: 4px;" f"padding-bottom: 1px;" f"padding-left: 4px;" diff --git a/tagstudio/src/qt/modals/merge_dupe_entries.py b/tagstudio/src/qt/modals/merge_dupe_entries.py index 249e6d113..470512334 100644 --- a/tagstudio/src/qt/modals/merge_dupe_entries.py +++ b/tagstudio/src/qt/modals/merge_dupe_entries.py @@ -7,6 +7,7 @@ from PySide6.QtCore import QObject, Signal, QThreadPool from src.core.library import Library +from src.core.utils.dupe_files import DupeRegistry from src.qt.helpers.function_iterator import FunctionIterator from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.widgets.progress import ProgressWidget @@ -23,16 +24,17 @@ def __init__(self, library: "Library", driver: "QtDriver"): super().__init__() self.lib = library self.driver = driver + self.tracker = DupeRegistry(library=self.lib) def merge_entries(self): - iterator = FunctionIterator(self.lib.merge_dupe_entries) + iterator = FunctionIterator(self.tracker.merge_dupe_entries) pw = ProgressWidget( window_title="Merging Duplicate Entries", label_text="", cancel_button_text=None, minimum=0, - maximum=len(self.lib.dupe_entries), + maximum=self.tracker.groups_count, ) pw.show() @@ -41,6 +43,6 @@ def merge_entries(self): lambda: (pw.update_label("Merging Duplicate Entries...")) ) - r = CustomRunnable(lambda: iterator.run()) + r = CustomRunnable(iterator.run) r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.done.emit())) QThreadPool.globalInstance().start(r) diff --git a/tagstudio/src/qt/modals/mirror_entities.py b/tagstudio/src/qt/modals/mirror_entities.py index 09e4bab0c..97a11357b 100644 --- a/tagstudio/src/qt/modals/mirror_entities.py +++ b/tagstudio/src/qt/modals/mirror_entities.py @@ -17,7 +17,7 @@ QListView, ) -from src.core.library import Library +from src.core.utils.dupe_files import DupeRegistry from src.qt.helpers.function_iterator import FunctionIterator from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.widgets.progress import ProgressWidget @@ -30,21 +30,22 @@ class MirrorEntriesModal(QWidget): done = Signal() - def __init__(self, library: "Library", driver: "QtDriver"): + def __init__(self, driver: "QtDriver", tracker: DupeRegistry): super().__init__() - self.lib = library self.driver = driver - self.setWindowTitle(f"Mirror Entries") + self.setWindowTitle("Mirror Entries") self.setWindowModality(Qt.WindowModality.ApplicationModal) self.setMinimumSize(500, 400) self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(6, 6, 6, 6) + self.tracker = tracker self.desc_widget = QLabel() self.desc_widget.setObjectName("descriptionLabel") self.desc_widget.setWordWrap(True) + self.desc_widget.setText(f""" - Are you sure you want to mirror the following {len(self.lib.dupe_files)} Entries? + Are you sure you want to mirror the following {self.tracker.groups_count} Entries? """) self.desc_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) @@ -66,7 +67,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.mirror_button = QPushButton() self.mirror_button.setText("&Mirror") self.mirror_button.clicked.connect(self.hide) - self.mirror_button.clicked.connect(lambda: self.mirror_entries()) + self.mirror_button.clicked.connect(self.mirror_entries) self.button_layout.addWidget(self.mirror_button) self.root_layout.addWidget(self.desc_widget) @@ -75,45 +76,30 @@ def __init__(self, library: "Library", driver: "QtDriver"): def refresh_list(self): self.desc_widget.setText(f""" - Are you sure you want to mirror the following {len(self.lib.dupe_files)} Entries? + Are you sure you want to mirror the following {self.tracker.groups_count} Entries? """) self.model.clear() - for i in self.lib.dupe_files: + for i in self.tracker.groups: self.model.appendRow(QStandardItem(str(i))) def mirror_entries(self): - # pb = QProgressDialog('', None, 0, len(self.lib.dupe_files)) - # # pb.setMaximum(len(self.lib.missing_files)) - # pb.setFixedSize(432, 112) - # pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint) - # pb.setWindowTitle('Mirroring Entries') - # pb.setWindowModality(Qt.WindowModality.ApplicationModal) - # pb.show() - - # r = CustomRunnable(lambda: self.mirror_entries_runnable(pb)) - # r.done.connect(lambda: self.done.emit()) - # r.done.connect(lambda: self.driver.preview_panel.refresh()) - # # r.done.connect(lambda: self.model.clear()) - # # QThreadPool.globalInstance().start(r) - # r.run() - iterator = FunctionIterator(self.mirror_entries_runnable) pw = ProgressWidget( window_title="Mirroring Entries", - label_text=f"Mirroring 1/{len(self.lib.dupe_files)} Entries...", + label_text=f"Mirroring 1/{self.tracker.groups_count} Entries...", cancel_button_text=None, minimum=0, - maximum=len(self.lib.dupe_files), + maximum=self.tracker.groups_count, ) pw.show() iterator.value.connect(lambda x: pw.update_progress(x + 1)) iterator.value.connect( lambda x: pw.update_label( - f"Mirroring {x+1}/{len(self.lib.dupe_files)} Entries..." + f"Mirroring {x + 1}/{self.tracker.groups_count} Entries..." ) ) - r = CustomRunnable(lambda: iterator.run()) + r = CustomRunnable(iterator.run) QThreadPool.globalInstance().start(r) r.done.connect( lambda: ( @@ -126,15 +112,11 @@ def mirror_entries(self): def mirror_entries_runnable(self): mirrored: list = [] - for i, dupe in enumerate(self.lib.dupe_files): - # pb.setValue(i) - # pb.setLabelText(f'Mirroring {i}/{len(self.lib.dupe_files)} Entries') - entry_id_1 = self.lib.get_entry_id_from_filepath(dupe[0]) - entry_id_2 = self.lib.get_entry_id_from_filepath(dupe[1]) - self.lib.mirror_entry_fields([entry_id_1, entry_id_2]) + lib = self.driver.lib + for i, entries in enumerate(self.tracker.groups): + lib.mirror_entry_fields(*entries) sleep(0.005) yield i + for d in mirrored: - self.lib.dupe_files.remove(d) - # self.driver.filter_items('') - # self.done.emit() + self.tracker.groups.remove(d) diff --git a/tagstudio/src/qt/modals/relink_unlinked.py b/tagstudio/src/qt/modals/relink_unlinked.py index 15af5cd3f..36e8da822 100644 --- a/tagstudio/src/qt/modals/relink_unlinked.py +++ b/tagstudio/src/qt/modals/relink_unlinked.py @@ -2,60 +2,51 @@ # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import typing from PySide6.QtCore import QObject, Signal, QThreadPool -from src.core.library import Library +from src.core.utils.missing_files import MissingRegistry from src.qt.helpers.function_iterator import FunctionIterator from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.widgets.progress import ProgressWidget -# Only import for type checking/autocompletion, will not be imported at runtime. -if typing.TYPE_CHECKING: - from src.qt.ts_qt import QtDriver - class RelinkUnlinkedEntries(QObject): done = Signal() - def __init__(self, library: "Library", driver: "QtDriver"): + def __init__(self, tracker: MissingRegistry): super().__init__() - self.lib = library - self.driver = driver - self.fixed = 0 + self.tracker = tracker def repair_entries(self): - iterator = FunctionIterator(self.lib.fix_missing_files) + iterator = FunctionIterator(self.tracker.fix_missing_files) pw = ProgressWidget( window_title="Relinking Entries", label_text="", cancel_button_text=None, minimum=0, - maximum=len(self.lib.missing_files), + maximum=self.tracker.missing_files_count, ) pw.show() - iterator.value.connect(lambda x: pw.update_progress(x[0] + 1)) iterator.value.connect( - lambda x: ( - self.increment_fixed() if x[1] else (), + lambda idx: ( + pw.update_progress(idx), pw.update_label( - f"Attempting to Relink {x[0]+1}/{len(self.lib.missing_files)} Entries, {self.fixed} Successfully Relinked" + f"Attempting to Relink {idx}/{self.tracker.missing_files_count} Entries. " + f"{self.tracker.files_fixed_count} Successfully Relinked." ), ) ) - r = CustomRunnable(lambda: iterator.run()) + r = CustomRunnable(iterator.run) r.done.connect( - lambda: (pw.hide(), pw.deleteLater(), self.done.emit(), self.reset_fixed()) + lambda: ( + pw.hide(), + pw.deleteLater(), + self.done.emit(), + ) ) QThreadPool.globalInstance().start(r) - - def increment_fixed(self): - self.fixed += 1 - - def reset_fixed(self): - self.fixed = 0 diff --git a/tagstudio/src/qt/modals/tag_database.py b/tagstudio/src/qt/modals/tag_database.py index 6101b737a..9b9f11e4c 100644 --- a/tagstudio/src/qt/modals/tag_database.py +++ b/tagstudio/src/qt/modals/tag_database.py @@ -12,7 +12,8 @@ QFrame, ) -from src.core.library import Library +from src.core.library import Library, Tag +from src.core.library.alchemy.enums import FilterState from src.qt.widgets.panel import PanelWidget, PanelModal from src.qt.widgets.tag import TagWidget from src.qt.modals.build_tag import BuildTagPanel @@ -21,7 +22,7 @@ class TagDatabasePanel(PanelWidget): tag_chosen = Signal(int) - def __init__(self, library): + def __init__(self, library: Library): super().__init__() self.lib: Library = library # self.callback = callback @@ -38,7 +39,7 @@ def __init__(self, library): self.search_field.setMinimumSize(QSize(0, 32)) self.search_field.setPlaceholderText("Search Tags") self.search_field.textEdited.connect( - lambda x=self.search_field.text(): self.update_tags(x) + lambda: self.update_tags(self.search_field.text()) ) self.search_field.returnPressed.connect( lambda checked=False: self.on_return(self.search_field.text()) @@ -73,7 +74,7 @@ def __init__(self, library): self.root_layout.addWidget(self.search_field) self.root_layout.addWidget(self.scroll_area) - self.update_tags("") + self.update_tags() # def reset(self): # self.search_field.setText('') @@ -84,60 +85,47 @@ def on_return(self, text: str): if text and self.first_tag_id >= 0: # callback(self.first_tag_id) self.search_field.setText("") - self.update_tags("") + self.update_tags() else: self.search_field.setFocus() self.parentWidget().hide() - def update_tags(self, query: str): + def update_tags(self, query: str | None = None): # TODO: Look at recycling rather than deleting and reinitializing while self.scroll_layout.itemAt(0): self.scroll_layout.takeAt(0).widget().deleteLater() - # If there is a query, get a list of tag_ids that match, otherwise return all - if query: - tags = self.lib.search_tags(query, include_cluster=True)[ - : self.tag_limit - 1 - ] - else: - # Get tag ids to keep this behaviorally identical - tags = [t.id for t in self.lib.tags] - - first_id_set = False - for tag_id in tags: - if not first_id_set: - self.first_tag_id = tag_id - first_id_set = True + tags = self.lib.search_tags(FilterState(path=query, page_size=self.tag_limit)) + + for tag in tags: container = QWidget() row = QHBoxLayout(container) row.setContentsMargins(0, 0, 0, 0) row.setSpacing(3) - tw = TagWidget(self.lib, self.lib.get_tag(tag_id), True, False) - tw.on_edit.connect( - lambda checked=False, t=self.lib.get_tag(tag_id): (self.edit_tag(t.id)) - ) - row.addWidget(tw) + tag_widget = TagWidget(tag, True, False) + tag_widget.on_edit.connect(lambda checked=False, t=tag: self.edit_tag(t)) + row.addWidget(tag_widget) self.scroll_layout.addWidget(container) self.search_field.setFocus() - def edit_tag(self, tag_id: int): - btp = BuildTagPanel(self.lib, tag_id) - # btp.on_edit.connect(lambda x: self.edit_tag_callback(x)) + def edit_tag(self, tag: Tag): + build_tag_panel = BuildTagPanel(self.lib, tag=tag) + self.edit_modal = PanelModal( - btp, - self.lib.get_tag(tag_id).display_name(self.lib), + build_tag_panel, + tag.name, "Edit Tag", done_callback=(self.update_tags(self.search_field.text())), has_save=True, ) # self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t)) # TODO Check Warning: Expected type 'BuildTagPanel', got 'PanelWidget' instead - self.edit_modal.saved.connect(lambda: self.edit_tag_callback(btp)) + self.edit_modal.saved.connect(lambda: self.edit_tag_callback(build_tag_panel)) self.edit_modal.show() def edit_tag_callback(self, btp: BuildTagPanel): - self.lib.update_tag(btp.build_tag()) + self.lib.add_tag(btp.build_tag()) self.update_tags(self.search_field.text()) # def enterEvent(self, event: QEnterEvent) -> None: diff --git a/tagstudio/src/qt/modals/tag_search.py b/tagstudio/src/qt/modals/tag_search.py index 896f634f7..8459f11a1 100644 --- a/tagstudio/src/qt/modals/tag_search.py +++ b/tagstudio/src/qt/modals/tag_search.py @@ -3,9 +3,9 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging import math +import structlog from PySide6.QtCore import Signal, Qt, QSize from PySide6.QtWidgets import ( QWidget, @@ -18,24 +18,20 @@ ) from src.core.library import Library +from src.core.library.alchemy.enums import FilterState from src.core.palette import ColorType, get_tag_color from src.qt.widgets.panel import PanelWidget from src.qt.widgets.tag import TagWidget - -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" - -logging.basicConfig(format="%(message)s", level=logging.INFO) +logger = structlog.get_logger(__name__) class TagSearchPanel(PanelWidget): tag_chosen = Signal(int) - def __init__(self, library): + def __init__(self, library: Library): super().__init__() - self.lib: Library = library + self.lib = library # self.callback = callback self.first_tag_id = None self.tag_limit = 100 @@ -49,7 +45,7 @@ def __init__(self, library): self.search_field.setMinimumSize(QSize(0, 32)) self.search_field.setPlaceholderText("Search Tags") self.search_field.textEdited.connect( - lambda x=self.search_field.text(): self.update_tags(x) + lambda: self.update_tags(self.search_field.text()) ) self.search_field.returnPressed.connect( lambda checked=False: self.on_return(self.search_field.text()) @@ -84,7 +80,7 @@ def __init__(self, library): self.root_layout.addWidget(self.search_field) self.root_layout.addWidget(self.scroll_area) - self.update_tags("") + self.update_tags() # def reset(self): # self.search_field.setText('') @@ -101,55 +97,51 @@ def on_return(self, text: str): self.search_field.setFocus() self.parentWidget().hide() - def update_tags(self, query: str = ""): - # for c in self.scroll_layout.children(): - # c.widget().deleteLater() + def update_tags(self, name: str | None = None): while self.scroll_layout.count(): - # logging.info(f"I'm deleting { self.scroll_layout.itemAt(0).widget()}") self.scroll_layout.takeAt(0).widget().deleteLater() - found_tags = self.lib.search_tags(query, include_cluster=True)[: self.tag_limit] - self.first_tag_id = found_tags[0] if found_tags else None + found_tags = self.lib.search_tags( + FilterState( + path=name, + page_size=self.tag_limit, + ) + ) - for tag_id in found_tags: + for tag in found_tags: c = QWidget() - l = QHBoxLayout(c) - l.setContentsMargins(0, 0, 0, 0) - l.setSpacing(3) - tw = TagWidget(self.lib, self.lib.get_tag(tag_id), False, False) + layout = QHBoxLayout(c) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + tw = TagWidget(tag, False, False) ab = QPushButton() ab.setMinimumSize(23, 23) ab.setMaximumSize(23, 23) ab.setText("+") ab.setStyleSheet( f"QPushButton{{" - f"background: {get_tag_color(ColorType.PRIMARY, self.lib.get_tag(tag_id).color)};" - # f'background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 {get_tag_color(ColorType.PRIMARY, tag.color)}, stop:1.0 {get_tag_color(ColorType.BORDER, tag.color)});' - # f"border-color:{get_tag_color(ColorType.PRIMARY, tag.color)};" - f"color: {get_tag_color(ColorType.TEXT, self.lib.get_tag(tag_id).color)};" + f"background: {get_tag_color(ColorType.PRIMARY, tag.color)};" + f"color: {get_tag_color(ColorType.TEXT, tag.color)};" f"font-weight: 600;" - f"border-color:{get_tag_color(ColorType.BORDER, self.lib.get_tag(tag_id).color)};" + f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};" f"border-radius: 6px;" f"border-style:solid;" - f"border-width: {math.ceil(1*self.devicePixelRatio())}px;" - # f'padding-top: 1.5px;' - # f'padding-right: 4px;' + f"border-width: {math.ceil(self.devicePixelRatio())}px;" f"padding-bottom: 5px;" - # f'padding-left: 4px;' f"font-size: 20px;" f"}}" f"QPushButton::hover" f"{{" - f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};" - f"color: {get_tag_color(ColorType.DARK_ACCENT, self.lib.get_tag(tag_id).color)};" - f"background: {get_tag_color(ColorType.LIGHT_ACCENT, self.lib.get_tag(tag_id).color)};" + f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};" + f"color: {get_tag_color(ColorType.DARK_ACCENT, tag.color)};" + f"background: {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};" f"}}" ) - ab.clicked.connect(lambda checked=False, x=tag_id: self.tag_chosen.emit(x)) + ab.clicked.connect(lambda checked=False, x=tag.id: self.tag_chosen.emit(x)) - l.addWidget(tw) - l.addWidget(ab) + layout.addWidget(tw) + layout.addWidget(ab) self.scroll_layout.addWidget(c) self.search_field.setFocus() diff --git a/tagstudio/src/qt/pagination.py b/tagstudio/src/qt/pagination.py index 904dd19f0..174bc8452 100644 --- a/tagstudio/src/qt/pagination.py +++ b/tagstudio/src/qt/pagination.py @@ -10,7 +10,6 @@ from PySide6.QtWidgets import ( QWidget, QHBoxLayout, - QPushButton, QLabel, QLineEdit, QSizePolicy, @@ -274,7 +273,7 @@ def update_buttons(self, page_count: int, index: int, emit: bool = True): if self.end_buffer_layout.itemAt(i): self.end_buffer_layout.itemAt(i).widget().setHidden(True) sbc += 1 - self.current_page_field.setText((str(i + 1))) + self.current_page_field.setText(str(i + 1)) # elif index == page_count-1: # self.start_button.setText(str(page_count)) @@ -419,7 +418,6 @@ def update_buttons(self, page_count: int, index: int, emit: bool = True): self.validator.setTop(page_count) # if self.current_page_index != index: if emit: - print(f"[PAGINATION] Emitting {index}") self.index.emit(index) self.current_page_index = index self.page_count = page_count @@ -435,7 +433,7 @@ def _assign_click(self, button: QPushButtonWrapper, index): button.is_connected = True def _populate_buffer_buttons(self): - for i in range(max(self.buffer_page_count * 2, 5)): + for _ in range(max(self.buffer_page_count * 2, 5)): button = QPushButtonWrapper() button.setMinimumSize(self.button_size) button.setMaximumSize(self.button_size) @@ -443,13 +441,12 @@ def _populate_buffer_buttons(self): # button.setMaximumHeight(self.button_size.height()) self.start_buffer_layout.addWidget(button) - for i in range(max(self.buffer_page_count * 2, 5)): - button = QPushButtonWrapper() - button.setMinimumSize(self.button_size) - button.setMaximumSize(self.button_size) - button.setHidden(True) + end_button = QPushButtonWrapper() + end_button.setMinimumSize(self.button_size) + end_button.setMaximumSize(self.button_size) + end_button.setHidden(True) # button.setMaximumHeight(self.button_size.height()) - self.end_buffer_layout.addWidget(button) + self.end_buffer_layout.addWidget(end_button) class Validator(QIntValidator): diff --git a/tagstudio/src/qt/resource_manager.py b/tagstudio/src/qt/resource_manager.py index 0db8bb194..137363bb9 100644 --- a/tagstudio/src/qt/resource_manager.py +++ b/tagstudio/src/qt/resource_manager.py @@ -2,13 +2,13 @@ # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging from pathlib import Path from typing import Any +import structlog import ujson -logging.basicConfig(format="%(message)s", level=logging.INFO) +logger = structlog.get_logger(__name__) class ResourceManager: @@ -21,12 +21,10 @@ class ResourceManager: def __init__(self) -> None: # Load JSON resource map if not ResourceManager._initialized: - with open( - Path(__file__).parent / "resources.json", mode="r", encoding="utf-8" - ) as f: + with open(Path(__file__).parent / "resources.json", encoding="utf-8") as f: ResourceManager._map = ujson.load(f) - logging.info( - f"[ResourceManager] {len(ResourceManager._map.items())} resources registered" + logger.info( + "resources registered", count=len(ResourceManager._map.items()) ) ResourceManager._initialized = True diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index be7403683..a4bbec311 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -8,18 +8,18 @@ """A Qt driver for TagStudio.""" import ctypes -import logging +import dataclasses import math import os import sys import time -import typing import webbrowser -from datetime import datetime as dt +from collections.abc import Sequence +from itertools import zip_longest from pathlib import Path from queue import Queue -from typing import Optional -from PIL import Image + +import structlog from PySide6 import QtCore from PySide6.QtCore import ( QObject, @@ -43,7 +43,6 @@ from PySide6.QtWidgets import ( QApplication, QWidget, - QHBoxLayout, QPushButton, QLineEdit, QScrollArea, @@ -55,30 +54,36 @@ ) from humanfriendly import format_timespan -from src.core.enums import SettingItems, SearchMode -from src.core.library import ItemType -from src.core.ts_core import TagStudioCore +from src.core.enums import SettingItems, MacroID + from src.core.constants import ( - COLLAGE_FOLDER_NAME, - BACKUP_FOLDER_NAME, TS_FOLDER_NAME, VERSION_BRANCH, VERSION, - TAG_FAVORITE, + LibraryPrefs, TAG_ARCHIVED, + TAG_FAVORITE, +) +from src.core.library.alchemy.enums import ( + SearchMode, + FilterState, + ItemType, + FieldTypeEnum, ) +from src.core.library.alchemy.fields import _FieldID +from src.core.ts_core import TagStudioCore +from src.core.utils.refresh_dir import RefreshDirTracker from src.core.utils.web import strip_web_protocol from src.qt.flowlayout import FlowLayout from src.qt.main_window import Ui_MainWindow from src.qt.helpers.function_iterator import FunctionIterator from src.qt.helpers.custom_runnable import CustomRunnable from src.qt.resource_manager import ResourceManager -from src.qt.widgets.collage_icon import CollageIconRenderer from src.qt.widgets.panel import PanelModal from src.qt.widgets.thumb_renderer import ThumbRenderer from src.qt.widgets.progress import ProgressWidget from src.qt.widgets.preview_panel import PreviewPanel -from src.qt.widgets.item_thumb import ItemThumb +from src.qt.widgets.item_thumb import ItemThumb, BadgeType from src.qt.modals.build_tag import BuildTagPanel from src.qt.modals.tag_database import TagDatabasePanel from src.qt.modals.file_extension import FileExtensionModal @@ -87,7 +92,7 @@ from src.qt.modals.folders_to_tags import FoldersToTagsModal # this import has side-effect of import PySide resources -import src.qt.resources_rc # pylint: disable=unused-import +import src.qt.resources_rc # noqa: F401 # SIGQUIT is not defined on Windows if sys.platform == "win32": @@ -97,33 +102,7 @@ else: from signal import signal, SIGINT, SIGTERM, SIGQUIT -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" - -logging.basicConfig(format="%(message)s", level=logging.INFO) - - -class NavigationState: - """Represents a state of the Library grid view.""" - - def __init__( - self, - contents, - scrollbar_pos: int, - page_index: int, - page_count: int, - search_text: str | None = None, - thumb_size=None, - spacing=None, - ) -> None: - self.contents = contents - self.scrollbar_pos = scrollbar_pos - self.page_index = page_index - self.page_count = page_count - self.search_text = search_text - self.thumb_size = thumb_size - self.spacing = spacing +logger = structlog.get_logger(__name__) class Consumer(QThread): @@ -143,18 +122,6 @@ def run(self): except RuntimeError: pass - def set_page_count(self, count: int): - self.page_count = count - - def jump_to_page(self, index: int): - pass - - def nav_back(self): - pass - - def nav_forward(self): - pass - class QtDriver(QObject): """A Qt GUI frontend driver for TagStudio.""" @@ -163,20 +130,20 @@ class QtDriver(QObject): preview_panel: PreviewPanel - def __init__(self, core: TagStudioCore, args): + def __init__(self, backend, args): super().__init__() - self.core: TagStudioCore = core - self.lib = self.core.lib + # prevent recursive badges update when multiple items selected + self.badge_update_lock = False + self.lib = backend.Library() self.rm: ResourceManager = ResourceManager() self.args = args - self.frame_dict: dict = {} - self.nav_frames: list[NavigationState] = [] - self.cur_frame_idx: int = -1 - - self.search_mode = SearchMode.AND + self.frame_content = [] + self.filter = FilterState() + self.pages_count = 0 - # self.main_window = None - # self.main_window = Ui_MainWindow() + self.scrollbar_pos = 0 + self.thumb_size = 128 + self.spacing = None self.branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" self.base_title: str = f"TagStudio Alpha {VERSION}{self.branch}" @@ -185,18 +152,17 @@ def __init__(self, core: TagStudioCore, args): self.thumb_job_queue: Queue = Queue() self.thumb_threads: list[Consumer] = [] self.thumb_cutoff: float = time.time() - # self.selected: list[tuple[int,int]] = [] # (Thumb Index, Page Index) - self.selected: list[tuple[ItemType, int]] = [] # (Item Type, Item ID) + + # grid indexes of selected items + self.selected: list[int] = [] self.SIGTERM.connect(self.handleSIGTERM) if self.args.config_file: path = Path(self.args.config_file) if not path.exists(): - logging.warning( - f"[QT DRIVER] Config File does not exist creating {str(path)}" - ) - logging.info(f"[QT DRIVER] Using Config File {str(path)}") + logger.warning("Config File does not exist creating", path=path) + logger.info("Using Config File", path=path) self.settings = QSettings(str(path), QSettings.Format.IniFormat) else: self.settings = QSettings( @@ -205,14 +171,12 @@ def __init__(self, core: TagStudioCore, args): "TagStudio", "TagStudio", ) - logging.info( - f"[QT DRIVER] Config File not specified, defaulting to {self.settings.fileName()}" + logger.info( + "Config File not specified, using default one", + filename=self.settings.fileName(), ) max_threads = os.cpu_count() - if args.ci: - # spawn only single worker in CI environment - max_threads = 1 for i in range(max_threads): # thread = threading.Thread(target=self.consumer, name=f'ThumbRenderer_{i}',args=(), daemon=True) # thread.start() @@ -223,7 +187,10 @@ def __init__(self, core: TagStudioCore, args): def open_library_from_dialog(self): dir = QFileDialog.getExistingDirectory( - None, "Open/Create Library", "/", QFileDialog.ShowDirsOnly + None, + "Open/Create Library", + "/", + QFileDialog.Option.ShowDirsOnly, ) if dir not in (None, ""): self.open_library(Path(dir)) @@ -238,11 +205,12 @@ def setup_signals(self): signal(SIGQUIT, self.signal_handler) def start(self) -> None: - """Launches the main Qt window.""" + """Launch the main Qt window.""" - loader = QUiLoader() + _ = QUiLoader() if os.name == "nt": sys.argv += ["-platform", "windows:darkmode=2"] + app = QApplication(sys.argv) app.setStyle("Fusion") # pal: QPalette = app.palette() @@ -251,7 +219,7 @@ def start(self) -> None: # pal.setColor(QPalette.ColorGroup.Normal, # QPalette.ColorRole.Window, QColor('#110F1B')) # app.setPalette(pal) - home_path = Path(__file__).parent / "ui/home.ui" + # home_path = Path(__file__).parent / "ui/home.ui" icon_path = Path(__file__).parents[2] / "resources/icon.png" # Handle OS signals @@ -280,7 +248,7 @@ def start(self) -> None: splash_pixmap = QPixmap(":/images/splash.png") splash_pixmap.setDevicePixelRatio(self.main_window.devicePixelRatio()) - self.splash = QSplashScreen(splash_pixmap, Qt.WindowStaysOnTopHint) # type: ignore + self.splash = QSplashScreen(splash_pixmap, Qt.WindowType.WindowStaysOnTopHint) # self.splash.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) self.splash.show() @@ -319,19 +287,6 @@ def start(self) -> None: open_library_action.setToolTip("Ctrl+O") file_menu.addAction(open_library_action) - save_library_action = QAction("&Save Library", menu_bar) - save_library_action.triggered.connect( - lambda: self.callback_library_needed_check(self.save_library) - ) - save_library_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier), - QtCore.Qt.Key.Key_S, - ) - ) - save_library_action.setStatusTip("Ctrl+S") - file_menu.addAction(save_library_action) - save_library_backup_action = QAction("&Save Library Backup", menu_bar) save_library_backup_action.triggered.connect( lambda: self.callback_library_needed_check(self.backup_library) @@ -365,7 +320,6 @@ def start(self) -> None: add_new_files_action.setStatusTip("Ctrl+R") # file_menu.addAction(refresh_lib_action) file_menu.addAction(add_new_files_action) - file_menu.addSeparator() close_library_action = QAction("&Close Library", menu_bar) @@ -406,9 +360,7 @@ def start(self) -> None: edit_menu.addSeparator() manage_file_extensions_action = QAction("Manage File Extensions", menu_bar) - manage_file_extensions_action.triggered.connect( - lambda: self.show_file_extension_modal() - ) + manage_file_extensions_action.triggered.connect(self.show_file_extension_modal) edit_menu.addAction(manage_file_extensions_action) tag_database_action = QAction("Manage Tags", menu_bar) @@ -418,7 +370,7 @@ def start(self) -> None: check_action = QAction("Open library on start", self) check_action.setCheckable(True) check_action.setChecked( - self.settings.value(SettingItems.START_LOAD_LAST, True, type=bool) # type: ignore + bool(self.settings.value(SettingItems.START_LOAD_LAST, True, type=bool)) ) check_action.triggered.connect( lambda checked: self.settings.setValue( @@ -428,67 +380,58 @@ def start(self) -> None: window_menu.addAction(check_action) # Tools Menu =========================================================== + def create_fix_unlinked_entries_modal(): + if not hasattr(self, "unlinked_modal"): + self.unlinked_modal = FixUnlinkedEntriesModal(self.lib, self) + self.unlinked_modal.show() + fix_unlinked_entries_action = QAction("Fix &Unlinked Entries", menu_bar) - fue_modal = FixUnlinkedEntriesModal(self.lib, self) - fix_unlinked_entries_action.triggered.connect(lambda: fue_modal.show()) + fix_unlinked_entries_action.triggered.connect(create_fix_unlinked_entries_modal) tools_menu.addAction(fix_unlinked_entries_action) + def create_dupe_files_modal(): + if not hasattr(self, "dupe_modal"): + self.dupe_modal = FixDupeFilesModal(self.lib, self) + self.dupe_modal.show() + fix_dupe_files_action = QAction("Fix Duplicate &Files", menu_bar) - fdf_modal = FixDupeFilesModal(self.lib, self) - fix_dupe_files_action.triggered.connect(lambda: fdf_modal.show()) + fix_dupe_files_action.triggered.connect(create_dupe_files_modal) tools_menu.addAction(fix_dupe_files_action) - create_collage_action = QAction("Create Collage", menu_bar) - create_collage_action.triggered.connect(lambda: self.create_collage()) - tools_menu.addAction(create_collage_action) + # create_collage_action = QAction("Create Collage", menu_bar) + # create_collage_action.triggered.connect(lambda: self.create_collage()) + # tools_menu.addAction(create_collage_action) # Macros Menu ========================================================== self.autofill_action = QAction("Autofill", menu_bar) self.autofill_action.triggered.connect( lambda: ( - self.run_macros( - "autofill", [x[1] for x in self.selected if x[0] == ItemType.ENTRY] - ), + self.run_macros(MacroID.AUTOFILL, self.selected), self.preview_panel.update_widgets(), ) ) macros_menu.addAction(self.autofill_action) - self.sort_fields_action = QAction("&Sort Fields", menu_bar) - self.sort_fields_action.triggered.connect( - lambda: ( - self.run_macros( - "sort-fields", - [x[1] for x in self.selected if x[0] == ItemType.ENTRY], - ), - self.preview_panel.update_widgets(), - ) - ) - self.sort_fields_action.setShortcut( - QtCore.QKeyCombination( - QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.AltModifier), - QtCore.Qt.Key.Key_S, - ) - ) - self.sort_fields_action.setToolTip("Alt+S") - macros_menu.addAction(self.sort_fields_action) - show_libs_list_action = QAction("Show Recent Libraries", menu_bar) show_libs_list_action.setCheckable(True) show_libs_list_action.setChecked( - self.settings.value(SettingItems.WINDOW_SHOW_LIBS, True, type=bool) # type: ignore + bool(self.settings.value(SettingItems.WINDOW_SHOW_LIBS, True, type=bool)) ) show_libs_list_action.triggered.connect( lambda checked: ( - self.settings.setValue(SettingItems.WINDOW_SHOW_LIBS, checked), # type: ignore + self.settings.setValue(SettingItems.WINDOW_SHOW_LIBS, checked), self.toggle_libs_list(checked), ) ) window_menu.addAction(show_libs_list_action) + def create_folders_tags_modal(): + if not hasattr(self, "folders_modal"): + self.folders_modal = FoldersToTagsModal(self.lib, self) + self.folders_modal.show() + folders_to_tags_action = QAction("Folders to Tags", menu_bar) - ftt_modal = FoldersToTagsModal(self.lib, self) - folders_to_tags_action.triggered.connect(lambda: ftt_modal.show()) + folders_to_tags_action.triggered.connect(create_folders_tags_modal) macros_menu.addAction(folders_to_tags_action) # Help Menu ========================================================== @@ -507,30 +450,28 @@ def start(self) -> None: menu_bar.addMenu(help_menu) self.preview_panel = PreviewPanel(self.lib, self) - l: QHBoxLayout = self.main_window.splitter - l.addWidget(self.preview_panel) + splitter = self.main_window.splitter + splitter.addWidget(self.preview_panel) QFontDatabase.addApplicationFont( str(Path(__file__).parents[2] / "resources/qt/fonts/Oxanium-Bold.ttf") ) self.thumb_size = 128 - self.max_results = 500 self.item_thumbs: list[ItemThumb] = [] self.thumb_renderers: list[ThumbRenderer] = [] - self.collation_thumb_size = math.ceil(self.thumb_size * 2) self.init_library_window() - lib = None + lib: str | None = None if self.args.open: lib = self.args.open elif self.settings.value(SettingItems.START_LOAD_LAST, True, type=bool): - lib = self.settings.value(SettingItems.LAST_LIBRARY) + lib = str(self.settings.value(SettingItems.LAST_LIBRARY)) # TODO: Remove this check if the library is no longer saved with files if lib and not (Path(lib) / TS_FOLDER_NAME).exists(): - logging.error( + logger.error( f"[QT DRIVER] {TS_FOLDER_NAME} folder in {lib} does not exist." ) self.settings.setValue(SettingItems.LAST_LIBRARY, "") @@ -542,11 +483,7 @@ def start(self) -> None: int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), QColor("#9782ff"), ) - self.open_library(Path(lib)) - - if self.args.ci: - # gracefully terminate the app in CI environment - self.thumb_job_queue.put((self.SIGTERM.emit, [])) + self.open_library(lib) app.exec() @@ -560,15 +497,19 @@ def init_library_window(self): # so the resource isn't being used, then store the specific size variations # in a global dict for methods to access for different DPIs. # adj_font_size = math.floor(12 * self.main_window.devicePixelRatio()) - # self.ext_font = ImageFont.truetype(os.path.normpath(f'{Path(__file__).parents[2]}/resources/qt/fonts/Oxanium-Bold.ttf'), adj_font_size) search_button: QPushButton = self.main_window.searchButton search_button.clicked.connect( - lambda: self.filter_items(self.main_window.searchField.text()) + lambda: self.filter_items( + FilterState(query=self.main_window.searchField.text()) + ) ) search_field: QLineEdit = self.main_window.searchField search_field.returnPressed.connect( - lambda: self.filter_items(self.main_window.searchField.text()) + # TODO - parse search field for filters + lambda: self.filter_items( + FilterState(query=self.main_window.searchField.text()) + ) ) search_type_selector: QComboBox = self.main_window.comboBox_2 search_type_selector.currentIndexChanged.connect( @@ -578,9 +519,9 @@ def init_library_window(self): ) back_button: QPushButton = self.main_window.backButton - back_button.clicked.connect(self.nav_back) + back_button.clicked.connect(lambda: self.page_move(-1)) forward_button: QPushButton = self.main_window.forwardButton - forward_button.clicked.connect(self.nav_forward) + forward_button.clicked.connect(lambda: self.page_move(1)) # NOTE: Putting this early will result in a white non-responsive # window until everything is loaded. Consider adding a splash screen @@ -589,28 +530,8 @@ def init_library_window(self): self.main_window.activateWindow() self.main_window.toggle_landing_page(True) - self.frame_dict = {} - self.main_window.pagination.index.connect( - lambda i: ( - self.nav_forward( - *self.get_frame_contents( - i, self.nav_frames[self.cur_frame_idx].search_text - ) - ), - logging.info(f"emitted {i}"), - ) - ) - - self.nav_frames = [] - self.cur_frame_idx = -1 - self.cur_query = "" - self.filter_items() - # self.update_thumbs() + self.main_window.pagination.index.connect(lambda i: self.page_move(page_id=i)) - # self.render_times: list = [] - # self.main_window.setWindowFlag(Qt.FramelessWindowHint) - - # self.main_window.raise_() self.splash.finish(self.main_window) self.preview_panel.update_widgets() @@ -632,10 +553,9 @@ def handleSIGTERM(self): def shutdown(self): """Save Library on Application Exit""" if self.lib.library_dir: - self.save_library() self.settings.setValue(SettingItems.LAST_LIBRARY, self.lib.library_dir) self.settings.sync() - logging.info("[SHUTDOWN] Ending Thumbnail Threads...") + logger.info("[SHUTDOWN] Ending Thumbnail Threads...") for _ in self.thumb_threads: self.thumb_job_queue.put(Consumer.MARKER_QUIT) @@ -646,89 +566,63 @@ def shutdown(self): QApplication.quit() - def save_library(self, show_status=True): - logging.info(f"Saving Library...") - if show_status: - self.main_window.statusbar.showMessage(f"Saving Library...") - start_time = time.time() - # This might still be able to error, if the selected directory deletes in a race condition - # or something silly like that. Hence the loop, but if this is considered overkill, thats fair. - while True: - try: - self.lib.save_library_to_disk() - break - # If the parent directory got moved, or deleted, prompt user for where to save. - except FileNotFoundError: - logging.info( - "Library parent directory not found, prompting user to select the directory" - ) - dir = QFileDialog.getExistingDirectory( - None, - "Library Location not found, please select location to save Library", - "/", - QFileDialog.ShowDirsOnly, - ) - if dir not in (None, ""): - self.lib.library_dir = dir - if show_status: - end_time = time.time() - self.main_window.statusbar.showMessage( - f"Library Saved! ({format_timespan(end_time - start_time)})" - ) - def close_library(self): - if self.lib.library_dir: - logging.info(f"Closing Library...") - self.main_window.statusbar.showMessage(f"Closing & Saving Library...") - start_time = time.time() - self.save_library(show_status=False) - self.settings.setValue(SettingItems.LAST_LIBRARY, self.lib.library_dir) - self.settings.sync() + if not self.lib.library_dir: + return - self.lib.clear_internal_vars() - title_text = f"{self.base_title}" - self.main_window.setWindowTitle(title_text) + logger.info("Closing Library...") + self.main_window.statusbar.showMessage("Closing & Saving Library...") + start_time = time.time() + self.settings.setValue(SettingItems.LAST_LIBRARY, self.lib.library_dir) + self.settings.sync() - self.nav_frames = [] - self.cur_frame_idx = -1 - self.cur_query = "" - self.selected.clear() - self.preview_panel.update_widgets() - self.filter_items() - self.main_window.toggle_landing_page(True) + self.lib.clear_internal_vars() + title_text = f"{self.base_title}" + self.main_window.setWindowTitle(title_text) - end_time = time.time() - self.main_window.statusbar.showMessage( - f"Library Saved and Closed! ({format_timespan(end_time - start_time)})" - ) + self.selected.clear() + self.preview_panel.update_widgets() + self.main_window.toggle_landing_page(True) + + self.main_window.pagination.setHidden(True) + + end_time = time.time() + self.main_window.statusbar.showMessage( + f"Library Closed ({format_timespan(end_time - start_time)})" + ) def backup_library(self): - logging.info(f"Backing Up Library...") - self.main_window.statusbar.showMessage(f"Saving Library...") + logger.info("Backing Up Library...") + self.main_window.statusbar.showMessage("Saving Library...") start_time = time.time() - fn = self.lib.save_library_backup_to_disk() + target_path = self.lib.save_library_backup_to_disk() end_time = time.time() self.main_window.statusbar.showMessage( - f'Library Backup Saved at: "{ self.lib.library_dir / TS_FOLDER_NAME / BACKUP_FOLDER_NAME / fn}" ({format_timespan(end_time - start_time)})' + f'Library Backup Saved at: "{target_path}" ({format_timespan(end_time - start_time)})' ) def add_tag_action_callback(self): self.modal = PanelModal( - BuildTagPanel(self.lib), "New Tag", "Add Tag", has_save=True + BuildTagPanel(self.lib), + "New Tag", + "Add Tag", + has_save=True, ) - # self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t)) + panel: BuildTagPanel = self.modal.widget self.modal.saved.connect( - lambda: (self.lib.add_tag_to_library(panel.build_tag()), self.modal.hide()) + lambda: ( + self.lib.add_tag(panel.build_tag(), panel.subtags), + self.modal.hide(), + ) ) - # panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag)) self.modal.show() def select_all_action_callback(self): - for item in self.item_thumbs: - if item.mode and (item.mode, item.item_id) not in self.selected: - self.selected.append((item.mode, item.item_id)) - item.thumb_button.set_selected(True) + self.selected = list(range(0, len(self.frame_content))) + + for grid_idx in self.selected: + self.item_thumbs[grid_idx].thumb_button.set_selected(True) self.set_macro_menu_viability() self.preview_panel.update_widgets() @@ -755,54 +649,15 @@ def show_file_extension_modal(self): "File Extensions", has_save=True, ) - self.modal.saved.connect(lambda: (panel.save(), self.filter_items(""))) + + self.modal.saved.connect(lambda: (panel.save(), self.filter_items())) self.modal.show() def add_new_files_callback(self): - """Runs when user initiates adding new files to the Library.""" - # # if self.lib.files_not_in_library: - # # mb = QMessageBox() - # # mb.setText(f'Would you like to refresh the directory before adding {len(self.lib.files_not_in_library)} new files to the library?\nThis will add any additional files that have been moved to the directory since the last refresh.') - # # mb.setWindowTitle('Refresh Library') - # # mb.setIcon(QMessageBox.Icon.Information) - # # mb.setStandardButtons(QMessageBox.StandardButton.No) - # # refresh_button = mb.addButton('Refresh', QMessageBox.ButtonRole.AcceptRole) - # # mb.setDefaultButton(refresh_button) - # # result = mb.exec_() - # # # logging.info(result) - # # if result == 0: - # # self.main_window.statusbar.showMessage(f'Refreshing Library...', 3) - # # self.lib.refresh_dir() - # # else: - # pb = QProgressDialog('Scanning Directories for New Files...\nPreparing...', None, 0,0) + """Run when user initiates adding new files to the Library.""" + + tracker = RefreshDirTracker(self.lib) - # pb.setFixedSize(432, 112) - # pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint) - # # pb.setLabelText('Scanning Directories...') - # pb.setWindowTitle('Scanning Directories') - # pb.setWindowModality(Qt.WindowModality.ApplicationModal) - # # pb.setMinimum(0) - # # pb.setMaximum(0) - # # pb.setValue(0) - # pb.show() - # self.main_window.statusbar.showMessage(f'Refreshing Library...', 3) - # # self.lib.refresh_dir() - # r = CustomRunnable(lambda: self.runnable(pb)) - # logging.info(f'Main: {QThread.currentThread()}') - # r.done.connect(lambda: (pb.hide(), pb.deleteLater(), self.add_new_files_runnable())) - # QThreadPool.globalInstance().start(r) - # # r.run() - - # # new_ids: list[int] = self.lib.add_new_files_as_entries() - # # # logging.info(f'{INFO} Running configured Macros on {len(new_ids)} new Entries...') - # # # self.main_window.statusbar.showMessage(f'Running configured Macros on {len(new_ids)} new Entries...', 3) - # # # for id in new_ids: - # # # self.run_macro('autofill', id) - - # # self.main_window.statusbar.showMessage('', 3) - # # self.filter_entries('') - - iterator = FunctionIterator(self.lib.refresh_dir) pw = ProgressWidget( window_title="Refreshing Directories", label_text="Scanning Directories for New Files...\nPreparing...", @@ -811,31 +666,31 @@ def add_new_files_callback(self): maximum=0, ) pw.show() - iterator.value.connect(lambda x: pw.update_progress(x + 1)) + + iterator = FunctionIterator(tracker.refresh_dir) iterator.value.connect( - lambda x: pw.update_label( - f'Scanning Directories for New Files...\n{x + 1} File{"s" if x + 1 != 1 else ""} Searched, {len(self.lib.files_not_in_library)} New Files Found' + lambda x: ( + pw.update_progress(x + 1), + pw.update_label( + f'Scanning Directories for New Files...\n{x + 1} File{"s" if x + 1 != 1 else ""} Searched, {tracker.files_count} New Files Found' + ), ) ) - r = CustomRunnable(lambda: iterator.run()) - # r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.filter_items(''))) - # vvv This one runs the macros when adding new files to the library. + r = CustomRunnable(iterator.run) r.done.connect( - lambda: (pw.hide(), pw.deleteLater(), self.add_new_files_runnable()) + lambda: ( + pw.hide(), + pw.deleteLater(), + self.add_new_files_runnable(tracker), + ) ) QThreadPool.globalInstance().start(r) - # def runnable(self, pb:QProgressDialog): - # for i in self.lib.refresh_dir(): - # pb.setLabelText(f'Scanning Directories for New Files...\n{i} File{"s" if i != 1 else ""} Searched, {len(self.lib.files_not_in_library)} New Files Found') - - def add_new_files_runnable(self): + def add_new_files_runnable(self, tracker: RefreshDirTracker): """ Threaded method that adds any known new files to the library and initiates running default macros on them. """ - # logging.info(f'Start ANF: {QThread.currentThread()}') - new_ids: list[int] = self.lib.add_new_files_as_entries() # pb = QProgressDialog(f'Running Configured Macros on 1/{len(new_ids)} New Entries', None, 0,len(new_ids)) # pb.setFixedSize(432, 112) # pb.setWindowFlags(pb.windowFlags() & ~Qt.WindowType.WindowCloseButtonHint) @@ -848,34 +703,44 @@ def add_new_files_runnable(self): # r.run() # # QThreadPool.globalInstance().start(r) - # # logging.info(f'{INFO} Running configured Macros on {len(new_ids)} new Entries...') # # self.main_window.statusbar.showMessage(f'Running configured Macros on {len(new_ids)} new Entries...', 3) # # pb.hide() - iterator = FunctionIterator(lambda: self.new_file_macros_runnable(new_ids)) + files_count = tracker.files_count + + # iterator = FunctionIterator(lambda: self.new_file_macros_runnable(tracker.files_not_in_library)) + iterator = FunctionIterator(tracker.save_new_files) pw = ProgressWidget( window_title="Running Macros on New Entries", - label_text=f"Running Configured Macros on 1/{len(new_ids)} New Entries", + label_text=f"Running Configured Macros on 1/{files_count} New Entries", cancel_button_text=None, minimum=0, - maximum=0, + maximum=files_count, ) pw.show() - iterator.value.connect(lambda x: pw.update_progress(x + 1)) iterator.value.connect( - lambda x: pw.update_label( - f"Running Configured Macros on {x + 1}/{len(new_ids)} New Entries" + lambda x: ( + pw.update_progress(x + 1), + pw.update_label( + f"Running Configured Macros on {x + 1}/{files_count} New Entries" + ), + ) + ) + r = CustomRunnable(iterator.run) + r.done.connect( + lambda: ( + pw.hide(), + pw.deleteLater(), + # refresh the library only when new items are added + files_count and self.filter_items(), ) ) - r = CustomRunnable(lambda: iterator.run()) - r.done.connect(lambda: (pw.hide(), pw.deleteLater(), self.filter_items(""))) QThreadPool.globalInstance().start(r) def new_file_macros_runnable(self, new_ids): """Threaded method that runs macros on a set of Entry IDs.""" # sleep(1) - # logging.info(f'ANFR: {QThread.currentThread()}') # for i, id in enumerate(new_ids): # # pb.setValue(i) # # pb.setLabelText(f'Running Configured Macros on {i}/{len(new_ids)} New Entries') @@ -890,75 +755,66 @@ def new_file_macros_runnable(self, new_ids): # sleep(5) # pb.deleteLater() - def run_macros(self, name: str, entry_ids: list[int]): - """Runs a specific Macro on a group of given entry_ids.""" - for id in entry_ids: - self.run_macro(name, id) + def run_macros(self, name: MacroID, grid_idx: list[int]): + """Run a specific Macro on a group of given entry_ids.""" + for gid in grid_idx: + self.run_macro(name, gid) - def run_macro(self, name: str, entry_id: int): - """Runs a specific Macro on an Entry given a Macro name.""" - entry = self.lib.get_entry(entry_id) - path = self.lib.library_dir / entry.path / entry.filename + def run_macro(self, name: MacroID, grid_idx: int): + """Run a specific Macro on an Entry given a Macro name.""" + entry = self.frame_content[grid_idx] + ful_path = self.lib.library_dir / entry.path source = entry.path.parts[0] - if name == "sidecar": - self.lib.add_generic_data_to_entry( - self.core.get_gdl_sidecar(path, source), entry_id - ) - elif name == "autofill": - self.run_macro("sidecar", entry_id) - self.run_macro("build-url", entry_id) - self.run_macro("match", entry_id) - self.run_macro("clean-url", entry_id) - self.run_macro("sort-fields", entry_id) - elif name == "build-url": - data = {"source": self.core.build_url(entry_id, source)} - self.lib.add_generic_data_to_entry(data, entry_id) - elif name == "sort-fields": - order: list[int] = ( - [0] - + [1, 2] - + [9, 17, 18, 19, 20] - + [8, 7, 6] - + [4] - + [3, 21] - + [10, 14, 11, 12, 13, 22] - + [5] - ) - self.lib.sort_fields(entry_id, order) - elif name == "match": - self.core.match_conditions(entry_id) - # elif name == 'scrape': - # self.core.scrape(entry_id) - elif name == "clean-url": - # entry = self.lib.get_entry_from_index(entry_id) - if entry.fields: - for i, field in enumerate(entry.fields, start=0): - if self.lib.get_field_attr(field, "type") == "text_line": - self.lib.update_entry_field( - entry_id=entry_id, - field_index=i, - content=strip_web_protocol( - self.lib.get_field_attr(field, "content") - ), - mode="replace", - ) + + logger.info( + "running macro", + source=source, + macro=name, + entry_id=entry.id, + grid_idx=grid_idx, + ) + + if name == MacroID.AUTOFILL: + for macro_id in MacroID: + if macro_id == MacroID.AUTOFILL: + continue + self.run_macro(macro_id, entry.id) + + elif name == MacroID.SIDECAR: + parsed_items = TagStudioCore.get_gdl_sidecar(ful_path, source) + for field_id, value in parsed_items.items(): + self.lib.add_entry_field_type( + entry.id, + field_id=field_id, + value=value, + ) + + elif name == MacroID.BUILD_URL: + url = TagStudioCore.build_url(entry.id, source) + self.lib.add_entry_field_type(entry.id, field_id=_FieldID.SOURCE, value=url) + elif name == MacroID.MATCH: + TagStudioCore.match_conditions(self.lib, entry.id) + elif name == MacroID.CLEAN_URL: + for field in entry.text_fields: + if field.type.type == FieldTypeEnum.TEXT_LINE and field.value: + self.lib.update_entry_field( + entry_ids=entry.id, + content=strip_web_protocol(field.value), + ) def mouse_navigation(self, event: QMouseEvent): # print(event.button()) if event.button() == Qt.MouseButton.ForwardButton: - self.nav_forward() + self.page_move(1) elif event.button() == Qt.MouseButton.BackButton: - self.nav_back() - - def nav_forward( - self, - frame_content: Optional[list[tuple[ItemType, int]]] = None, - page_index: int = 0, - page_count: int = 0, - ): - """Navigates a step further into the navigation stack.""" - logging.info( - f"Calling NavForward with Content:{False if not frame_content else frame_content[0]}, Index:{page_index}, PageCount:{page_count}" + self.page_move(-1) + + def page_move(self, delta: int = None, page_id: int = None) -> None: + """Navigate a step further into the navigation stack.""" + logger.info( + "page_move", + delta=delta, + page_id=page_id, ) # Ex. User visits | A ->[B] | @@ -967,135 +823,21 @@ def nav_forward( # |[A]<- B C | Previous routes still exist # | A ->[D] | Stack is cut from [:A] on new route - # Moving forward (w/ or wo/ new content) in the middle of the stack - original_pos = self.cur_frame_idx - sb: QScrollArea = self.main_window.scrollArea - sb_pos = sb.verticalScrollBar().value() - search_text = self.main_window.searchField.text() - - trimmed = False - if len(self.nav_frames) > self.cur_frame_idx + 1: - if frame_content is not None: - # Trim the nav stack if user is taking a new route. - self.nav_frames = self.nav_frames[: self.cur_frame_idx + 1] - if self.nav_frames and not self.nav_frames[self.cur_frame_idx].contents: - self.nav_frames.pop() - trimmed = True - self.nav_frames.append( - NavigationState( - frame_content, 0, page_index, page_count, search_text - ) - ) - # logging.info(f'Saving Text: {search_text}') - # Update the last frame's scroll_pos - self.nav_frames[self.cur_frame_idx].scrollbar_pos = sb_pos - self.cur_frame_idx += 1 if not trimmed else 0 - # Moving forward at the end of the stack with new content - elif frame_content is not None: - # If the current page is empty, don't include it in the new stack. - if self.nav_frames and not self.nav_frames[self.cur_frame_idx].contents: - self.nav_frames.pop() - trimmed = True - self.nav_frames.append( - NavigationState(frame_content, 0, page_index, page_count, search_text) - ) - # logging.info(f'Saving Text: {search_text}') - self.nav_frames[self.cur_frame_idx].scrollbar_pos = sb_pos - self.cur_frame_idx += 1 if not trimmed else 0 - - # if self.nav_stack[self.cur_page_idx].contents: - if (self.cur_frame_idx != original_pos) or (frame_content is not None): - self.update_thumbs() - sb.verticalScrollBar().setValue( - self.nav_frames[self.cur_frame_idx].scrollbar_pos - ) - self.main_window.searchField.setText( - self.nav_frames[self.cur_frame_idx].search_text - ) - self.main_window.pagination.update_buttons( - self.nav_frames[self.cur_frame_idx].page_count, - self.nav_frames[self.cur_frame_idx].page_index, - emit=False, - ) - # logging.info(f'Setting Text: {self.nav_stack[self.cur_page_idx].search_text}') - # else: - # self.nav_stack.pop() - # self.cur_page_idx -= 1 - # self.update_thumbs() - # sb.verticalScrollBar().setValue(self.nav_stack[self.cur_page_idx].scrollbar_pos) - - # logging.info(f'Forward: {[len(x.contents) for x in self.nav_stack]}, Index {self.cur_page_idx}, SB {self.nav_stack[self.cur_page_idx].scrollbar_pos}') - - def nav_back(self): - """Navigates a step backwards in the navigation stack.""" - - original_pos = self.cur_frame_idx - sb: QScrollArea = self.main_window.scrollArea - sb_pos = sb.verticalScrollBar().value() - - if self.cur_frame_idx > 0: - self.nav_frames[self.cur_frame_idx].scrollbar_pos = sb_pos - self.cur_frame_idx -= 1 - if self.cur_frame_idx != original_pos: - self.update_thumbs() - sb.verticalScrollBar().setValue( - self.nav_frames[self.cur_frame_idx].scrollbar_pos - ) - self.main_window.searchField.setText( - self.nav_frames[self.cur_frame_idx].search_text - ) - self.main_window.pagination.update_buttons( - self.nav_frames[self.cur_frame_idx].page_count, - self.nav_frames[self.cur_frame_idx].page_index, - emit=False, - ) - # logging.info(f'Setting Text: {self.nav_stack[self.cur_page_idx].search_text}') - # logging.info(f'Back: {[len(x.contents) for x in self.nav_stack]}, Index {self.cur_page_idx}, SB {self.nav_stack[self.cur_page_idx].scrollbar_pos}') - - def refresh_frame( - self, - frame_content: list[tuple[ItemType, int]], - page_index: int = 0, - page_count: int = 0, - ): - """ - Refreshes the current navigation contents without altering the - navigation stack order. - """ - if self.nav_frames: - self.nav_frames[self.cur_frame_idx] = NavigationState( - frame_content, - 0, - self.nav_frames[self.cur_frame_idx].page_index, - self.nav_frames[self.cur_frame_idx].page_count, - self.main_window.searchField.text(), - ) - else: - self.nav_forward(frame_content, page_index, page_count) - self.update_thumbs() - # logging.info(f'Refresh: {[len(x.contents) for x in self.nav_stack]}, Index {self.cur_page_idx}') - - @typing.no_type_check - def purge_item_from_navigation(self, type: ItemType, id: int): - # logging.info(self.nav_frames) - # TODO - types here are ambiguous - for i, frame in enumerate(self.nav_frames, start=0): - while (type, id) in frame.contents: - logging.info(f"Removing {id} from nav stack frame {i}") - frame.contents.remove((type, id)) - - for i, key in enumerate(self.frame_dict.keys(), start=0): - for frame in self.frame_dict[key]: - while (type, id) in frame: - logging.info(f"Removing {id} from frame dict item {i}") - frame.remove((type, id)) - - while (type, id) in self.selected: - logging.info(f"Removing {id} from frame selected") - self.selected.remove((type, id)) + # sb: QScrollArea = self.main_window.scrollArea + # sb_pos = sb.verticalScrollBar().value() + + page_index = page_id if page_id is not None else self.filter.page_index + delta + page_index = max(0, min(page_index, self.pages_count - 1)) + + self.filter.page_index = page_index + self.filter_items() + + def remove_grid_item(self, grid_idx: int): + self.frame_content[grid_idx] = None + self.item_thumbs[grid_idx].hide() def _init_thumb_grid(self): - # logging.info('Initializing Thumbnail Grid...') + # logger.info('Initializing Thumbnail Grid...') layout = FlowLayout() layout.setGridEfficiency(True) # layout.setContentsMargins(0,0,0,0) @@ -1105,10 +847,10 @@ def _init_thumb_grid(self): # layout = QListView() # layout.setViewMode(QListView.ViewMode.IconMode) - col_size = 28 - for i in range(0, self.max_results): + # TODO - init after library is loaded, it can have different page_size + for grid_idx in range(self.filter.page_size): item_thumb = ItemThumb( - None, self.lib, self.preview_panel, (self.thumb_size, self.thumb_size) + None, self.lib, self, (self.thumb_size, self.thumb_size), grid_idx ) layout.addWidget(item_thumb) self.item_thumbs.append(item_thumb) @@ -1122,62 +864,37 @@ def _init_thumb_grid(self): sa.setWidgetResizable(True) sa.setWidget(self.flow_container) - def select_item(self, type: ItemType, id: int, append: bool, bridge: bool): - """Selects one or more items in the Thumbnail Grid.""" + def select_item(self, grid_index: int, append: bool, bridge: bool): + """Select one or more items in the Thumbnail Grid.""" + logger.info( + "selecting item", grid_index=grid_index, append=append, bridge=bridge + ) if append: - # self.selected.append((thumb_index, page_index)) - if ((type, id)) not in self.selected: - self.selected.append((type, id)) - for it in self.item_thumbs: - if it.mode == type and it.item_id == id: - it.thumb_button.set_selected(True) + if grid_index not in self.selected: + self.selected.append(grid_index) + self.item_thumbs[grid_index].thumb_button.set_selected(True) else: - self.selected.remove((type, id)) - for it in self.item_thumbs: - if it.mode == type and it.item_id == id: - it.thumb_button.set_selected(False) - # self.item_thumbs[thumb_index].thumb_button.set_selected(True) + self.selected.remove(grid_index) + self.item_thumbs[grid_index].thumb_button.set_selected(False) elif bridge and self.selected: - logging.info(f"Last Selected: {self.selected[-1]}") - contents = self.nav_frames[self.cur_frame_idx].contents - last_index = self.nav_frames[self.cur_frame_idx].contents.index( - self.selected[-1] - ) - current_index = self.nav_frames[self.cur_frame_idx].contents.index( - (type, id) - ) - index_range: list = contents[ - min(last_index, current_index) : max(last_index, current_index) + 1 - ] - # Preserve bridge direction for correct appending order. - if last_index < current_index: - index_range.reverse() - - # logging.info(f'Current Frame Contents: {len(self.nav_frames[self.cur_frame_idx].contents)}') - # logging.info(f'Last Selected Index: {last_index}') - # logging.info(f'Current Selected Index: {current_index}') - # logging.info(f'Index Range: {index_range}') - - for c_type, c_id in index_range: - for it in self.item_thumbs: - if it.mode == c_type and it.item_id == c_id: - it.thumb_button.set_selected(True) - if ((c_type, c_id)) not in self.selected: - self.selected.append((c_type, c_id)) + select_from = min(self.selected) + select_to = max(self.selected) + + if select_to < grid_index: + index_range = range(select_from, grid_index + 1) + else: + index_range = range(grid_index, select_to + 1) + + self.selected = list(index_range) + + for selected_idx in self.selected: + self.item_thumbs[selected_idx].thumb_button.set_selected(True) else: - # for i in self.selected: - # if i[1] == self.cur_frame_idx: - # self.item_thumbs[i[0]].thumb_button.set_selected(False) - self.selected.clear() - # self.selected.append((thumb_index, page_index)) - self.selected.append((type, id)) - # self.item_thumbs[thumb_index].thumb_button.set_selected(True) - for it in self.item_thumbs: - if it.mode == type and it.item_id == id: - it.thumb_button.set_selected(True) - else: - it.thumb_button.set_selected(False) + self.selected = [grid_index] + for thumb_idx, item_thumb in enumerate(self.item_thumbs): + item_matched = thumb_idx == grid_index + item_thumb.thumb_button.set_selected(item_matched) # NOTE: By using the preview panel's "set_tags_updated_slot" method, # only the last of multiple identical item selections are connected. @@ -1185,24 +902,19 @@ def select_item(self, type: ItemType, id: int, append: bool, bridge: bool): # just bypass the method and manually disconnect and connect the slots. if len(self.selected) == 1: for it in self.item_thumbs: - if it.mode == type and it.item_id == id: - self.preview_panel.set_tags_updated_slot(it.update_badges) + if it.item_id == id: + self.preview_panel.set_tags_updated_slot(it.refresh_badge) self.set_macro_menu_viability() self.preview_panel.update_widgets() def set_macro_menu_viability(self): - if len([x[1] for x in self.selected if x[0] == ItemType.ENTRY]) == 0: - self.autofill_action.setDisabled(True) - self.sort_fields_action.setDisabled(True) - else: - self.autofill_action.setDisabled(False) - self.sort_fields_action.setDisabled(False) + self.autofill_action.setDisabled(not self.selected) def update_thumbs(self): - """Updates search thumbnails.""" + """Update search thumbnails.""" # start_time = time.time() - # logging.info(f'Current Page: {self.cur_page_idx}, Stack Length:{len(self.nav_stack)}') + # logger.info(f'Current Page: {self.cur_page_idx}, Stack Length:{len(self.nav_stack)}') with self.thumb_job_queue.mutex: # Cancels all thumb jobs waiting to be started self.thumb_job_queue.queue.clear() @@ -1214,195 +926,118 @@ def update_thumbs(self): ratio: float = self.main_window.devicePixelRatio() base_size: tuple[int, int] = (self.thumb_size, self.thumb_size) - for i, item_thumb in enumerate(self.item_thumbs, start=0): - if i < len(self.nav_frames[self.cur_frame_idx].contents): - # Set new item type modes - # logging.info(f'[UPDATE] Setting Mode To: {self.nav_stack[self.cur_page_idx].contents[i][0]}') - item_thumb.set_mode(self.nav_frames[self.cur_frame_idx].contents[i][0]) - item_thumb.ignore_size = False - # logging.info(f'[UPDATE] Set Mode To: {item.mode}') - # Set thumbnails to loading (will always finish if rendering) - self.thumb_job_queue.put( - ( - item_thumb.renderer.render, - (sys.float_info.max, "", base_size, ratio, True, True), - ) - ) - # # Restore Selected Borders - # if (item_thumb.mode, item_thumb.item_id) in self.selected: - # item_thumb.thumb_button.set_selected(True) - # else: - # item_thumb.thumb_button.set_selected(False) - else: - item_thumb.ignore_size = True - item_thumb.set_mode(None) - item_thumb.set_item_id(-1) - item_thumb.thumb_button.set_selected(False) - # scrollbar: QScrollArea = self.main_window.scrollArea # scrollbar.verticalScrollBar().setValue(scrollbar_pos) self.flow_container.layout().update() self.main_window.update() - for i, item_thumb in enumerate(self.item_thumbs, start=0): - if i < len(self.nav_frames[self.cur_frame_idx].contents): - filepath = "" - if self.nav_frames[self.cur_frame_idx].contents[i][0] == ItemType.ENTRY: - entry = self.lib.get_entry( - self.nav_frames[self.cur_frame_idx].contents[i][1] - ) - filepath = self.lib.library_dir / entry.path / entry.filename - - item_thumb.set_item_id(entry.id) - item_thumb.assign_archived(entry.has_tag(self.lib, TAG_ARCHIVED)) - item_thumb.assign_favorite(entry.has_tag(self.lib, TAG_FAVORITE)) - # ctrl_down = True if QGuiApplication.keyboardModifiers() else False - # TODO: Change how this works. The click function - # for collations a few lines down should NOT be allowed during modifier keys. - item_thumb.update_clickable( - clickable=( - lambda checked=False, entry=entry: self.select_item( - ItemType.ENTRY, - entry.id, - append=True - if QGuiApplication.keyboardModifiers() - == Qt.KeyboardModifier.ControlModifier - else False, - bridge=True - if QGuiApplication.keyboardModifiers() - == Qt.KeyboardModifier.ShiftModifier - else False, - ) - ) - ) - # item_thumb.update_clickable(clickable=( - # lambda checked=False, filepath=filepath, entry=entry, - # item_t=item_thumb, i=i, page=self.cur_frame_idx: ( - # self.preview_panel.update_widgets(entry), - # self.select_item(ItemType.ENTRY, entry.id, - # append=True if QGuiApplication.keyboardModifiers() == Qt.KeyboardModifier.ControlModifier else False, - # bridge=True if QGuiApplication.keyboardModifiers() == Qt.KeyboardModifier.ShiftModifier else False)))) - # item.dumpObjectTree() - elif ( - self.nav_frames[self.cur_frame_idx].contents[i][0] - == ItemType.COLLATION - ): - collation = self.lib.get_collation( - self.nav_frames[self.cur_frame_idx].contents[i][1] - ) - cover_id = ( - collation.cover_id - if collation.cover_id >= 0 - else collation.e_ids_and_pages[0][0] - ) - cover_e = self.lib.get_entry(cover_id) - filepath = self.lib.library_dir / cover_e.path / cover_e.filename - item_thumb.set_count(str(len(collation.e_ids_and_pages))) - item_thumb.update_clickable( - clickable=( - lambda checked=False, - filepath=filepath, - entry=cover_e, - collation=collation: ( - self.expand_collation(collation.e_ids_and_pages) - ) - ) - ) - # item.setHidden(False) - - # Restore Selected Borders - if (item_thumb.mode, item_thumb.item_id) in self.selected: - item_thumb.thumb_button.set_selected(True) - else: - item_thumb.thumb_button.set_selected(False) - - self.thumb_job_queue.put( - ( - item_thumb.renderer.render, - (time.time(), filepath, base_size, ratio, False, True), + for idx, (entry, item_thumb) in enumerate( + zip_longest(self.frame_content, self.item_thumbs) + ): + if not entry: + item_thumb.hide() + continue + + filepath = self.lib.library_dir / entry.path + item_thumb = self.item_thumbs[idx] + item_thumb.set_mode(ItemType.ENTRY) + item_thumb.set_item_id(entry) + + # TODO - show after item is rendered + item_thumb.show() + + self.thumb_job_queue.put( + ( + item_thumb.renderer.render, + (sys.float_info.max, "", base_size, ratio, True, True), + ) + ) + + entry_tag_ids = {tag.id for tag in entry.tags} + item_thumb.assign_badge(BadgeType.ARCHIVED, TAG_ARCHIVED in entry_tag_ids) + item_thumb.assign_badge(BadgeType.FAVORITE, TAG_FAVORITE in entry_tag_ids) + item_thumb.update_clickable( + clickable=( + lambda checked=False, index=idx: self.select_item( + index, + append=( + QGuiApplication.keyboardModifiers() + == Qt.KeyboardModifier.ControlModifier + ), + bridge=( + QGuiApplication.keyboardModifiers() + == Qt.KeyboardModifier.ShiftModifier + ), ) ) - else: - # item.setHidden(True) - pass - # update_widget_clickable(widget=item.bg_button, clickable=()) - # self.thumb_job_queue.put( - # (item.renderer.render, ('', base_size, ratio, False))) - - # end_time = time.time() - # logging.info( - # f'[MAIN] Elements thumbs updated in {(end_time - start_time):.3f} seconds') - - def update_badges(self): - for i, item_thumb in enumerate(self.item_thumbs, start=0): - item_thumb.update_badges() - - def expand_collation(self, collation_entries: list[tuple[int, int]]): - self.nav_forward([(ItemType.ENTRY, x[0]) for x in collation_entries]) - # self.update_thumbs() - - def get_frame_contents(self, index=0, query: str = ""): - return ( - [] if not self.frame_dict[query] else self.frame_dict[query][index], - index, - len(self.frame_dict[query]), + ) + + # Restore Selected Borders + is_selected = (item_thumb.mode, item_thumb.item_id) in self.selected + item_thumb.thumb_button.set_selected(is_selected) + + self.thumb_job_queue.put( + ( + item_thumb.renderer.render, + (time.time(), filepath, base_size, ratio, False, True), + ) + ) + + def update_badges(self, grid_item_ids: Sequence[int] = None): + if not grid_item_ids: + # no items passed, update all items in grid + grid_item_ids = range(min(len(self.item_thumbs), len(self.frame_content))) + + logger.info("updating badges for items", grid_item_ids=grid_item_ids) + + for grid_idx in grid_item_ids: + # get the entry from grid to avoid loading from db again + entry = self.frame_content[grid_idx] + self.item_thumbs[grid_idx].refresh_badge(entry) + + def filter_items(self, filter: FilterState | None = None) -> None: + assert self.lib.engine + + if filter: + self.filter = dataclasses.replace(self.filter, **dataclasses.asdict(filter)) + + self.main_window.statusbar.showMessage( + f'Searching Library: "{self.filter.summary}"' ) + self.main_window.statusbar.repaint() + start_time = time.time() + + query_count, page_items = self.lib.search_library(self.filter) + + logger.info("items to render", count=len(page_items)) - def filter_items(self, query: str = ""): - if self.lib: - # logging.info('Filtering...') + end_time = time.time() + if self.filter.summary: self.main_window.statusbar.showMessage( - f'Searching Library for "{query}"...' + f'{query_count} Results Found for "{self.filter.summary}" ({format_timespan(end_time - start_time)})' + ) + else: + self.main_window.statusbar.showMessage( + f"{query_count} Results ({format_timespan(end_time - start_time)})" ) - self.main_window.statusbar.repaint() - start_time = time.time() - - # self.filtered_items = self.lib.search_library(query) - # 73601 Entries at 500 size should be 246 - all_items = self.lib.search_library(query, search_mode=self.search_mode) - frames: list[list[tuple[ItemType, int]]] = [] - frame_count = math.ceil(len(all_items) / self.max_results) - for i in range(0, frame_count): - frames.append( - all_items[ - min(len(all_items) - 1, (i) * self.max_results) : min( - len(all_items), (i + 1) * self.max_results - ) - ] - ) - for i, f in enumerate(frames): - logging.info(f"Query:{query}, Frame: {i}, Length: {len(f)}") - self.frame_dict[query] = frames - # self.frame_dict[query] = [all_items] - - if self.cur_query == query: - # self.refresh_frame(self.lib.search_library(query)) - # NOTE: Trying to refresh instead of navigating forward here - # now creates a bug when the page counts differ on refresh. - # If refreshing is absolutely desired, see how to update - # page counts where they need to be updated. - self.nav_forward(*self.get_frame_contents(0, query)) - else: - # self.nav_forward(self.lib.search_library(query)) - self.nav_forward(*self.get_frame_contents(0, query)) - self.cur_query = query - - end_time = time.time() - if query: - self.main_window.statusbar.showMessage( - f'{len(all_items)} Results Found for "{query}" ({format_timespan(end_time - start_time)})' - ) - else: - self.main_window.statusbar.showMessage( - f"{len(all_items)} Results ({format_timespan(end_time - start_time)})" - ) - # logging.info(f'Done Filtering! ({(end_time - start_time):.3f}) seconds') - # self.update_thumbs() + # update page content + self.frame_content = list(page_items) + self.update_thumbs() + + # update pagination + self.pages_count = math.ceil(query_count / self.filter.page_size) + self.main_window.pagination.update_buttons( + self.pages_count, self.filter.page_index, emit=False + ) - def set_search_type(self, mode=SearchMode.AND): - self.search_mode = mode - self.filter_items(self.main_window.searchField.text()) + def set_search_type(self, mode: SearchMode = SearchMode.AND): + self.filter_items( + FilterState( + search_mode=mode, + path=self.main_window.searchField.text(), + ) + ) def remove_recent_library(self, item_key: str): self.settings.beginGroup(SettingItems.LIBS_LIST) @@ -1410,8 +1045,7 @@ def remove_recent_library(self, item_key: str): self.settings.endGroup() self.settings.sync() - @typing.no_type_check - def update_libs_list(self, path: Path): + def update_libs_list(self, path: Path | str): """add library to list in SettingItems.LIBS_LIST""" ITEMS_LIMIT = 5 path = Path(path) @@ -1421,193 +1055,44 @@ def update_libs_list(self, path: Path): all_libs = {str(time.time()): str(path)} for item_key in self.settings.allKeys(): - item_path = self.settings.value(item_key) + item_path = str(self.settings.value(item_key, type=str)) if Path(item_path) != path: all_libs[item_key] = item_path # sort items, most recent first - all_libs = sorted(all_libs.items(), key=lambda item: item[0], reverse=True) + all_libs_list = sorted(all_libs.items(), key=lambda item: item[0], reverse=True) # remove previously saved items self.settings.clear() - for item_key, item_value in all_libs[:ITEMS_LIMIT]: + for item_key, item_value in all_libs_list[:ITEMS_LIMIT]: self.settings.setValue(item_key, item_value) self.settings.endGroup() self.settings.sync() - def open_library(self, path: Path): + def open_library(self, path: Path | str): """Opens a TagStudio library.""" open_message: str = f'Opening Library "{str(path)}"...' self.main_window.landing_widget.set_status_label(open_message) self.main_window.statusbar.showMessage(open_message, 3) self.main_window.repaint() - if self.lib.library_dir: - self.save_library() - self.lib.clear_internal_vars() + self.lib.open_library(path) - return_code = self.lib.open_library(path) - if return_code == 1: - pass - else: - logging.info( - f"{ERROR} No existing TagStudio library found at '{path}'. Creating one." - ) - print(f"Library Creation Return Code: {self.lib.create_library(path)}") - self.add_new_files_callback() + self.filter.page_size = self.lib.prefs(LibraryPrefs.PAGE_SIZE) + + # TODO - make this call optional + self.add_new_files_callback() self.update_libs_list(path) title_text = f"{self.base_title} - Library '{self.lib.library_dir}'" self.main_window.setWindowTitle(title_text) - self.nav_frames = [] - self.cur_frame_idx = -1 - self.cur_query = "" self.selected.clear() self.preview_panel.update_widgets() - self.filter_items() - self.main_window.toggle_landing_page(False) - def create_collage(self) -> None: - """Generates and saves an image collage based on Library Entries.""" - - run: bool = True - keep_aspect: bool = False - data_only_mode: bool = False - data_tint_mode: bool = False - - self.main_window.statusbar.showMessage(f"Creating Library Collage...") - self.collage_start_time = time.time() - - # mode:int = self.scr_choose_option(subtitle='Choose Collage Mode(s)', - # choices=[ - # ('Normal','Creates a standard square image collage made up of Library media files.'), - # ('Data Tint','Tints the collage with a color representing data about the Library Entries/files.'), - # ('Data Only','Ignores media files entirely and only outputs a collage of Library Entry/file data.'), - # ('Normal & Data Only','Creates both Normal and Data Only collages.'), - # ], prompt='', required=True) - mode = 0 - - if mode == 1: - data_tint_mode = True - - if mode == 2: - data_only_mode = True - - if mode in [0, 1, 3]: - # keep_aspect = self.scr_choose_option( - # subtitle='Choose Aspect Ratio Option', - # choices=[ - # ('Stretch to Fill','Stretches the media file to fill the entire collage square.'), - # ('Keep Aspect Ratio','Keeps the original media file\'s aspect ratio, filling the rest of the square with black bars.') - # ], prompt='', required=True) - keep_aspect = False - - if mode in [1, 2, 3]: - # TODO: Choose data visualization options here. - pass - - full_thumb_size: int = 1 - - if mode in [0, 1, 3]: - # full_thumb_size = self.scr_choose_option( - # subtitle='Choose Thumbnail Size', - # choices=[ - # ('Tiny (32px)',''), - # ('Small (64px)',''), - # ('Medium (128px)',''), - # ('Large (256px)',''), - # ('Extra Large (512px)','') - # ], prompt='', required=True) - full_thumb_size = 0 - - thumb_size: int = ( - 32 - if (full_thumb_size == 0) - else 64 - if (full_thumb_size == 1) - else 128 - if (full_thumb_size == 2) - else 256 - if (full_thumb_size == 3) - else 512 - if (full_thumb_size == 4) - else 32 - ) - thumb_size = 16 - - # if len(com) > 1 and com[1] == 'keep-aspect': - # keep_aspect = True - # elif len(com) > 1 and com[1] == 'data-only': - # data_only_mode = True - # elif len(com) > 1 and com[1] == 'data-tint': - # data_tint_mode = True - grid_size = math.ceil(math.sqrt(len(self.lib.entries))) ** 2 - grid_len = math.floor(math.sqrt(grid_size)) - thumb_size = thumb_size if not data_only_mode else 1 - img_size = thumb_size * grid_len - - logging.info( - f"Creating collage for {len(self.lib.entries)} Entries.\nGrid Size: {grid_size} ({grid_len}x{grid_len})\nIndividual Picture Size: ({thumb_size}x{thumb_size})" - ) - if keep_aspect: - logging.info("Keeping original aspect ratios.") - if data_only_mode: - logging.info("Visualizing Entry Data") - - if not data_only_mode: - time.sleep(5) - - self.collage = Image.new("RGB", (img_size, img_size)) - i = 0 - self.completed = 0 - for x in range(0, grid_len): - for y in range(0, grid_len): - if i < len(self.lib.entries) and run: - # if i < 5 and run: - - entry_id = self.lib.entries[i].id - renderer = CollageIconRenderer(self.lib) - renderer.rendered.connect( - lambda image, x=x, y=y: self.collage.paste( - image, (y * thumb_size, x * thumb_size) - ) - ) - renderer.done.connect(lambda: self.try_save_collage(True)) - self.thumb_job_queue.put( - ( - renderer.render, - ( - entry_id, - (thumb_size, thumb_size), - data_tint_mode, - data_only_mode, - keep_aspect, - ), - ) - ) - i = i + 1 - - def try_save_collage(self, increment_progress: bool): - if increment_progress: - self.completed += 1 - # logging.info(f'threshold:{len(self.lib.entries}, completed:{self.completed}') - if self.completed == len(self.lib.entries): - filename = ( - self.lib.library_dir - / TS_FOLDER_NAME - / COLLAGE_FOLDER_NAME - / f'collage_{dt.utcnow().strftime("%F_%T").replace(":", "")}.png' - ) - self.collage.save(filename) - self.collage = None + # page (re)rendering, extract eventually + self.filter_items() - end_time = time.time() - self.main_window.statusbar.showMessage( - f'Collage Saved at "{filename}" ({format_timespan(end_time - self.collage_start_time)})' - ) - logging.info( - f'Collage Saved at "{filename}" ({format_timespan(end_time - self.collage_start_time)})' - ) + self.main_window.toggle_landing_page(False) diff --git a/tagstudio/src/qt/widgets/collage_icon.py b/tagstudio/src/qt/widgets/collage_icon.py index b9234d7d2..b8382a493 100644 --- a/tagstudio/src/qt/widgets/collage_icon.py +++ b/tagstudio/src/qt/widgets/collage_icon.py @@ -2,37 +2,22 @@ # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging -import os -import traceback from pathlib import Path import cv2 +import structlog from PIL import Image, ImageChops, UnidentifiedImageError from PIL.Image import DecompressionBombError from PySide6.QtCore import ( QObject, - QThread, Signal, - QRunnable, - Qt, - QThreadPool, - QSize, - QEvent, - QTimer, - QSettings, ) -from src.core.library import Library from src.core.constants import DOC_TYPES, VIDEO_TYPES, IMAGE_TYPES +from src.core.library import Library +from src.core.library.alchemy.fields import _FieldID - -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" - - -logging.basicConfig(format="%(message)s", level=logging.INFO) +logger = structlog.get_logger(__name__) class CollageIconRenderer(QObject): @@ -52,26 +37,22 @@ def render( keep_aspect, ): entry = self.lib.get_entry(entry_id) - filepath = self.lib.library_dir / entry.path / entry.filename - file_type = os.path.splitext(filepath)[1].lower()[1:] + filepath = self.lib.library_dir / entry.path color: str = "" try: if data_tint_mode or data_only_mode: - color = "#000000" # Black (Default) - if entry.fields: has_any_tags: bool = False has_content_tags: bool = False has_meta_tags: bool = False - for field in entry.fields: - if self.lib.get_field_attr(field, "type") == "tag_box": - if self.lib.get_field_attr(field, "content"): - has_any_tags = True - if self.lib.get_field_attr(field, "id") == 7: - has_content_tags = True - elif self.lib.get_field_attr(field, "id") == 8: - has_meta_tags = True + for field in entry.tag_box_fields: + if field.tags: + has_any_tags = True + if field.type_key == _FieldID.TAGS_CONTENT.name: + has_content_tags = True + elif field.type_key == _FieldID.TAGS_META.name: + has_meta_tags = True if has_content_tags and has_meta_tags: color = "#28bb48" # Green elif has_any_tags: @@ -88,16 +69,15 @@ def render( # collage.paste(pic, (y*thumb_size, x*thumb_size)) self.rendered.emit(pic) if not data_only_mode: - logging.info( - f"\r{INFO} Combining [ID:{entry_id}/{len(self.lib.entries)}]: {self.get_file_color(filepath.suffix.lower())}{entry.path}{os.sep}{entry.filename}\033[0m" + logger.info( + "Combining icons", + entry=entry, + color=self.get_file_color(filepath.suffix.lower()), ) - # sys.stdout.write(f'\r{INFO} Combining [{i+1}/{len(self.lib.entries)}]: {self.get_file_color(file_type)}{entry.path}{os.sep}{entry.filename}{RESET}') - # sys.stdout.flush() + if filepath.suffix.lower() in IMAGE_TYPES: try: - with Image.open( - str(self.lib.library_dir / entry.path / entry.filename) - ) as pic: + with Image.open(str(self.lib.library_dir / entry.path)) as pic: if keep_aspect: pic.thumbnail(size) else: @@ -109,8 +89,10 @@ def render( ) # collage.paste(pic, (y*thumb_size, x*thumb_size)) self.rendered.emit(pic) - except DecompressionBombError as e: - logging.info(f"[ERROR] One of the images was too big ({e})") + except DecompressionBombError: + logger.exception( + "One of the images was too big", entry=entry.path + ) elif filepath.suffix.lower() in VIDEO_TYPES: video = cv2.VideoCapture(str(filepath)) video.set( @@ -137,9 +119,7 @@ def render( # collage.paste(pic, (y*thumb_size, x*thumb_size)) self.rendered.emit(pic) except (UnidentifiedImageError, FileNotFoundError): - logging.info( - f"\n{ERROR} Couldn't read {entry.path}{os.sep}{entry.filename}" - ) + logger.error("Couldn't read entry", entry=entry.path) with Image.open( str( Path(__file__).parents[2] @@ -153,19 +133,11 @@ def render( # collage.paste(pic, (y*thumb_size, x*thumb_size)) self.rendered.emit(pic) except KeyboardInterrupt: - # self.quit(save=False, backup=True) - run = False - # clear() - logging.info("\n") - logging.info(f"{INFO} Collage operation cancelled.") - clear_scr = False - except: - logging.info(f"{ERROR} {entry.path}{os.sep}{entry.filename}") - traceback.print_exc() - logging.info("Continuing...") + logger.info("Collage operation cancelled.") + except Exception: + logger.exception("render failed", entry=entry.path) self.done.emit() - # logging.info('Done!') def get_file_color(self, ext: str): if ext.lower().replace(".", "", 1) == "gif": diff --git a/tagstudio/src/qt/widgets/fields.py b/tagstudio/src/qt/widgets/fields.py index 355a0fa94..fc7a1fd89 100644 --- a/tagstudio/src/qt/widgets/fields.py +++ b/tagstudio/src/qt/widgets/fields.py @@ -4,15 +4,14 @@ import math -import os -from types import FunctionType, MethodType +from types import MethodType from pathlib import Path -from typing import Optional, cast, Callable, Any +from typing import Optional, Callable from PIL import Image, ImageQt from PySide6.QtCore import Qt, QEvent from PySide6.QtGui import QPixmap, QEnterEvent -from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton +from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper @@ -41,8 +40,8 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.title: str = title self.inline: bool = inline # self.editable:bool = editable - self.copy_callback: FunctionType = None - self.edit_callback: FunctionType = None + self.copy_callback: Callable = None + self.edit_callback: Callable = None self.remove_callback: Callable = None button_size = 24 # self.setStyleSheet('border-style:solid;border-color:#1e1a33;border-radius:8px;border-width:2px;') @@ -133,7 +132,7 @@ def set_copy_callback(self, callback: Optional[MethodType]): if callback is not None: self.copy_button.is_connected = True - def set_edit_callback(self, callback: Optional[MethodType]): + def set_edit_callback(self, callback: Callable): if self.edit_button.is_connected: self.edit_button.clicked.disconnect() @@ -142,7 +141,7 @@ def set_edit_callback(self, callback: Optional[MethodType]): if callback is not None: self.edit_button.is_connected = True - def set_remove_callback(self, callback: Optional[Callable]): + def set_remove_callback(self, callback: Callable): if self.remove_button.is_connected: self.remove_button.clicked.disconnect() @@ -160,9 +159,9 @@ def set_inner_widget(self, widget: "FieldWidget"): self.field_layout.itemAt(0).widget().deleteLater() self.field_layout.addWidget(widget) - def get_inner_widget(self) -> Optional["FieldWidget"]: + def get_inner_widget(self): if self.field_layout.itemAt(0): - return cast(FieldWidget, self.field_layout.itemAt(0).widget()) + return self.field_layout.itemAt(0).widget() return None def set_title(self, title: str): @@ -198,8 +197,6 @@ def leaveEvent(self, event: QEvent) -> None: class FieldWidget(QWidget): - field = dict - def __init__(self, title) -> None: super().__init__() # self.item = item diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index 5be2e3fbe..aae012f34 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -1,14 +1,14 @@ # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import contextlib -import logging -import os import time import typing +from enum import Enum +from functools import wraps from pathlib import Path -from typing import Optional +from typing import TYPE_CHECKING +import structlog from PIL import Image, ImageQt from PySide6.QtCore import Qt, QSize, QEvent from PySide6.QtGui import QPixmap, QEnterEvent, QAction @@ -21,8 +21,6 @@ QCheckBox, ) -from src.core.enums import FieldID -from src.core.library import ItemType, Library, Entry from src.core.constants import ( AUDIO_TYPES, VIDEO_TYPES, @@ -30,20 +28,49 @@ TAG_FAVORITE, TAG_ARCHIVED, ) +from src.core.library import ItemType, Entry, Library +from src.core.library.alchemy.enums import FilterState +from src.core.library.alchemy.fields import _FieldID + from src.qt.flowlayout import FlowWidget from src.qt.helpers.file_opener import FileOpenerHelper from src.qt.widgets.thumb_renderer import ThumbRenderer from src.qt.widgets.thumb_button import ThumbButton -if typing.TYPE_CHECKING: - from src.qt.widgets.preview_panel import PreviewPanel +if TYPE_CHECKING: + from src.qt.ts_qt import QtDriver + +logger = structlog.get_logger(__name__) + + +class BadgeType(Enum): + FAVORITE = "Favorite" + ARCHIVED = "Archived" + + +BADGE_TAGS = { + BadgeType.FAVORITE: TAG_FAVORITE, + BadgeType.ARCHIVED: TAG_ARCHIVED, +} -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" +def badge_update_lock(func): + """Prevent recursively triggering badge updates.""" -logging.basicConfig(format="%(message)s", level=logging.INFO) + @wraps(func) + def wrapper(self, *args, **kwargs): + if self.driver.badge_update_lock: + return + + self.driver.badge_update_lock = True + try: + func(self, *args, **kwargs) + except Exception: + raise + finally: + self.driver.badge_update_lock = False + + return wrapper class ItemThumb(FlowWidget): @@ -89,19 +116,18 @@ class ItemThumb(FlowWidget): def __init__( self, - mode: Optional[ItemType], + mode: ItemType, library: Library, - panel: "PreviewPanel", + driver: "QtDriver", thumb_size: tuple[int, int], + grid_idx: int, ): - """Modes: entry, collation, tag_group""" super().__init__() + self.grid_idx = grid_idx self.lib = library - self.panel = panel - self.mode = mode - self.item_id: int = -1 - self.isFavorite: bool = False - self.isArchived: bool = False + self.mode: ItemType = mode + self.driver = driver + self.item_id: int | None = None self.thumb_size: tuple[int, int] = thumb_size self.setMinimumSize(*thumb_size) self.setMaximumSize(*thumb_size) @@ -179,7 +205,7 @@ def __init__( lambda ts, i, s, ext: ( self.update_thumb(ts, image=i), self.update_size(ts, size=s), - self.set_extension(ext), # type: ignore + self.set_extension(ext), ) ) self.thumb_button.setFlat(True) @@ -263,54 +289,52 @@ def __init__( # self.root_layout.addWidget(self.check_badges, 0, 2) self.top_layout.addWidget(self.cb_container) - # Favorite Badge ------------------------------------------------------- - self.favorite_badge = QCheckBox() - self.favorite_badge.setObjectName("favBadge") - self.favorite_badge.setToolTip("Favorite") - self.favorite_badge.setStyleSheet( - f"QCheckBox::indicator{{width: {check_size}px;height: {check_size}px;}}" - f"QCheckBox::indicator::unchecked{{image: url(:/images/star_icon_empty_128.png)}}" - f"QCheckBox::indicator::checked{{image: url(:/images/star_icon_filled_128.png)}}" - # f'QCheckBox{{background-color:yellow;}}' - ) - self.favorite_badge.setMinimumSize(check_size, check_size) - self.favorite_badge.setMaximumSize(check_size, check_size) - self.favorite_badge.stateChanged.connect( - lambda x=self.favorite_badge.isChecked(): self.on_favorite_check(bool(x)) - ) + self.badge_active: dict[BadgeType, bool] = { + BadgeType.FAVORITE: False, + BadgeType.ARCHIVED: False, + } + + self.badges: dict[BadgeType, QCheckBox] = {} + badge_icons = { + BadgeType.FAVORITE: ( + ":/images/star_icon_empty_128.png", + ":/images/star_icon_filled_128.png", + ), + BadgeType.ARCHIVED: ( + ":/images/box_icon_empty_128.png", + ":/images/box_icon_filled_128.png", + ), + } + for badge_type in BadgeType: + icon_empty, icon_checked = badge_icons[badge_type] + badge = QCheckBox() + badge.setObjectName(badge_type.name) + badge.setToolTip(badge_type.value) + badge.setStyleSheet( + f"QCheckBox::indicator{{width: {check_size}px;height: {check_size}px;}}" + f"QCheckBox::indicator::unchecked{{image: url({icon_empty})}}" + f"QCheckBox::indicator::checked{{image: url({icon_checked})}}" + ) + badge.setMinimumSize(check_size, check_size) + badge.setMaximumSize(check_size, check_size) + badge.setHidden(True) - # self.fav_badge.setContentsMargins(0,0,0,0) - # tr_layout.addWidget(self.fav_badge) - # root_layout.addWidget(self.fav_badge, 0, 2) - self.cb_layout.addWidget(self.favorite_badge) - self.favorite_badge.setHidden(True) - - # Archive Badge -------------------------------------------------------- - self.archived_badge = QCheckBox() - self.archived_badge.setObjectName("archiveBadge") - self.archived_badge.setToolTip("Archive") - self.archived_badge.setStyleSheet( - f"QCheckBox::indicator{{width: {check_size}px;height: {check_size}px;}}" - f"QCheckBox::indicator::unchecked{{image: url(:/images/box_icon_empty_128.png)}}" - f"QCheckBox::indicator::checked{{image: url(:/images/box_icon_filled_128.png)}}" - # f'QCheckBox{{background-color:red;}}' - ) - self.archived_badge.setMinimumSize(check_size, check_size) - self.archived_badge.setMaximumSize(check_size, check_size) - # self.archived_badge.clicked.connect(lambda x: self.assign_archived(x)) - self.archived_badge.stateChanged.connect( - lambda x=self.archived_badge.isChecked(): self.on_archived_check(bool(x)) - ) + badge.stateChanged.connect(lambda x, bt=badge_type: self.on_badge_check(bt)) - # tr_layout.addWidget(self.archive_badge) - self.cb_layout.addWidget(self.archived_badge) - self.archived_badge.setHidden(True) - # root_layout.addWidget(self.archive_badge, 0, 2) - # self.dumpObjectTree() + self.badges[badge_type] = badge + self.cb_layout.addWidget(badge) self.set_mode(mode) - def set_mode(self, mode: Optional[ItemType]) -> None: + @property + def is_favorite(self) -> bool: + return self.badge_active[BadgeType.FAVORITE] + + @property + def is_archived(self): + return self.badge_active[BadgeType.ARCHIVED] + + def set_mode(self, mode: ItemType | None) -> None: if mode is None: self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, True) self.unsetCursor() @@ -318,7 +342,6 @@ def set_mode(self, mode: Optional[ItemType]) -> None: # self.check_badges.setHidden(True) # self.ext_badge.setHidden(True) # self.item_type_badge.setHidden(True) - pass elif mode == ItemType.ENTRY and self.mode != ItemType.ENTRY: self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, False) self.setCursor(Qt.CursorShape.PointingHandCursor) @@ -349,11 +372,6 @@ def set_mode(self, mode: Optional[ItemType]) -> None: self.mode = mode # logging.info(f'Set Mode To: {self.mode}') - # def update_(self, thumb: QPixmap, size:QSize, ext:str, badges:list[QPixmap]) -> None: - # """Updates the ItemThumb's visuals.""" - # if thumb: - # pass - def set_extension(self, ext: str) -> None: if ext and ext.startswith(".") is False: ext = "." + ext @@ -376,8 +394,8 @@ def set_count(self, count: str) -> None: self.ext_badge.setHidden(True) self.count_badge.setHidden(True) - def update_thumb(self, timestamp: float, image: QPixmap = None): - """Updates attributes of a thumbnail element.""" + def update_thumb(self, timestamp: float, image: QPixmap | None = None): + """Update attributes of a thumbnail element.""" # logging.info(f'[GUI] Updating Thumbnail for element {id(element)}: {id(image) if image else None}') if timestamp > ItemThumb.update_cutoff: self.thumb_button.setIcon(image if image else QPixmap()) @@ -386,11 +404,10 @@ def update_thumb(self, timestamp: float, image: QPixmap = None): def update_size(self, timestamp: float, size: QSize): """Updates attributes of a thumbnail element.""" # logging.info(f'[GUI] Updating size for element {id(element)}: {size.__str__()}') - if timestamp > ItemThumb.update_cutoff: - if self.thumb_button.iconSize != size: - self.thumb_button.setIconSize(size) - self.thumb_button.setMinimumSize(size) - self.thumb_button.setMaximumSize(size) + if timestamp > ItemThumb.update_cutoff and self.thumb_button.iconSize != size: + self.thumb_button.setIconSize(size) + self.thumb_button.setMinimumSize(size) + self.thumb_button.setMaximumSize(size) def update_clickable(self, clickable: typing.Callable): """Updates attributes of a thumbnail element.""" @@ -401,58 +418,41 @@ def update_clickable(self, clickable: typing.Callable): self.thumb_button.clicked.connect(clickable) self.thumb_button.is_connected = True - def update_badges(self): - if self.mode == ItemType.ENTRY: - # logging.info(f'[UPDATE BADGES] ENTRY: {self.lib.get_entry(self.item_id)}') - # logging.info(f'[UPDATE BADGES] ARCH: {self.lib.get_entry(self.item_id).has_tag(self.lib, 0)}, FAV: {self.lib.get_entry(self.item_id).has_tag(self.lib, 1)}') - self.assign_archived( - self.lib.get_entry(self.item_id).has_tag(self.lib, TAG_ARCHIVED) - ) - self.assign_favorite( - self.lib.get_entry(self.item_id).has_tag(self.lib, TAG_FAVORITE) - ) + def refresh_badge(self, entry: Entry | None = None): + if not entry: + if not self.item_id: + logger.error("missing both entry and item_id") + return None - def set_item_id(self, id: int): - """ - also sets the filepath for the file opener - """ - self.item_id = id - if id == -1: - return - entry = self.lib.get_entry(self.item_id) - filepath = self.lib.library_dir / entry.path / entry.filename + entry = self.lib.get_entry(self.item_id) + if not entry: + logger.error("Entry not found", item_id=self.item_id) + return + + self.assign_badge(BadgeType.ARCHIVED, entry.is_archived) + self.assign_badge(BadgeType.FAVORITE, entry.is_favorited) + + def set_item_id(self, entry: Entry): + filepath = self.lib.library_dir / entry.path self.opener.set_filepath(filepath) + self.item_id = entry.id - def assign_favorite(self, value: bool): - # Switching mode to None to bypass mode-specific operations when the - # checkbox's state changes. - mode = self.mode - self.mode = None - self.isFavorite = value - self.favorite_badge.setChecked(value) - if not self.thumb_button.underMouse(): - self.favorite_badge.setHidden(not self.isFavorite) - self.mode = mode + def assign_badge(self, badge_type: BadgeType, value: bool) -> None: + # mode = self.mode + # self.mode = None + badge = self.badges[badge_type] + self.badge_active[badge_type] = value + if badge.isChecked() != value: + badge.setChecked(value) + badge.setHidden(not value) - def assign_archived(self, value: bool): - # Switching mode to None to bypass mode-specific operations when the - # checkbox's state changes. - mode = self.mode - self.mode = None - self.isArchived = value - self.archived_badge.setChecked(value) - if not self.thumb_button.underMouse(): - self.archived_badge.setHidden(not self.isArchived) - self.mode = mode + # self.mode = mode def show_check_badges(self, show: bool): if self.mode != ItemType.TAG_GROUP: - self.favorite_badge.setHidden( - True if (not show and not self.isFavorite) else False - ) - self.archived_badge.setHidden( - True if (not show and not self.isArchived) else False - ) + for badge_type, badge in self.badges.items(): + is_hidden = not (show or self.badge_active[badge_type]) + badge.setHidden(is_hidden) def enterEvent(self, event: QEnterEvent) -> None: self.show_check_badges(True) @@ -462,40 +462,55 @@ def leaveEvent(self, event: QEvent) -> None: self.show_check_badges(False) return super().leaveEvent(event) - def on_archived_check(self, toggle_value: bool): - if self.mode == ItemType.ENTRY: - self.isArchived = toggle_value - self.toggle_item_tag(toggle_value, TAG_ARCHIVED) - - def on_favorite_check(self, toggle_value: bool): - if self.mode == ItemType.ENTRY: - self.isFavorite = toggle_value - self.toggle_item_tag(toggle_value, TAG_FAVORITE) - - def toggle_item_tag(self, toggle_value: bool, tag_id: int): - def toggle_tag(entry: Entry): - if toggle_value: - self.favorite_badge.setHidden(False) - entry.add_tag( - self.panel.driver.lib, - tag_id, - field_id=FieldID.META_TAGS, - field_index=-1, - ) - else: - entry.remove_tag(self.panel.driver.lib, tag_id) - - # Is the badge a part of the selection? - if (ItemType.ENTRY, self.item_id) in self.panel.driver.selected: - # Yes, add chosen tag to all selected. - for _, item_id in self.panel.driver.selected: - entry = self.lib.get_entry(item_id) - toggle_tag(entry) + @badge_update_lock + def on_badge_check(self, badge_type: BadgeType): + if self.mode is None: + return + + toggle_value = self.badges[badge_type].isChecked() + + self.badge_active[badge_type] = toggle_value + tag_id = BADGE_TAGS[badge_type] + + # check if current item is selected. if so, update all selected items + if self.grid_idx in self.driver.selected: + update_items = self.driver.selected else: - # No, add tag to the entry this badge is on. - entry = self.lib.get_entry(self.item_id) - toggle_tag(entry) + update_items = [self.grid_idx] + + for idx in update_items: + entry = self.driver.frame_content[idx] + self.toggle_item_tag( + entry, toggle_value, tag_id, _FieldID.TAGS_META.name, True + ) + # update the entry + self.driver.frame_content[idx] = self.lib.search_library( + FilterState(id=entry.id) + )[1][0] + + self.driver.update_badges(update_items) + + def toggle_item_tag( + self, + entry: Entry, + toggle_value: bool, + tag_id: int, + field_key: str, + create_field: bool = False, + ): + logger.info( + "toggle_item_tag", + entry_id=entry.id, + toggle_value=toggle_value, + tag_id=tag_id, + field_key=field_key, + ) + + tag = self.lib.get_tag(tag_id) + if toggle_value: + self.lib.add_field_tag(entry, tag, field_key, create_field) + else: + self.lib.remove_field_tag(entry, tag.id, field_key) - if self.panel.isOpen: - self.panel.update_widgets() - self.panel.driver.update_badges() + if self.driver.preview_panel.is_open: + self.driver.preview_panel.update_widgets() diff --git a/tagstudio/src/qt/widgets/landing.py b/tagstudio/src/qt/widgets/landing.py index 9df3fa4d6..a44a49c6d 100644 --- a/tagstudio/src/qt/widgets/landing.py +++ b/tagstudio/src/qt/widgets/landing.py @@ -24,7 +24,7 @@ class LandingWidget(QWidget): def __init__(self, driver: "QtDriver", pixel_ratio: float): super().__init__() - self.driver: "QtDriver" = driver + self.driver = driver self.logo_label: ClickableLabel = ClickableLabel() self._pixel_ratio: float = pixel_ratio self._logo_width: int = int(480 * pixel_ratio) diff --git a/tagstudio/src/qt/widgets/panel.py b/tagstudio/src/qt/widgets/panel.py index 2d2538b27..1448d73e1 100644 --- a/tagstudio/src/qt/widgets/panel.py +++ b/tagstudio/src/qt/widgets/panel.py @@ -2,7 +2,6 @@ # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio import logging -from types import FunctionType from typing import Callable from PySide6.QtCore import Signal, Qt @@ -16,12 +15,11 @@ class PanelModal(QWidget): # figure out what you want from this. def __init__( self, - widget: "PanelWidget", + widget, title: str, window_title: str, - done_callback: Callable = None, - # cancel_callback:FunctionType=None, - save_callback: Callable = None, + done_callback: Callable | None = None, + save_callback: Callable | None = None, has_save: bool = False, ): # [Done] @@ -76,10 +74,12 @@ def __init__( if done_callback: self.save_button.clicked.connect(done_callback) + if save_callback: self.save_button.clicked.connect( lambda: save_callback(widget.get_content()) ) + self.button_layout.addWidget(self.save_button) # trigger save button actions when pressing enter in the widget diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 27320aabf..b8311cf28 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -1,8 +1,8 @@ # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio - -import logging +import sys +from collections.abc import Callable from pathlib import Path import time import typing @@ -10,6 +10,7 @@ import cv2 import rawpy +import structlog from PIL import Image, UnidentifiedImageError from PIL.Image import DecompressionBombError from PySide6.QtCore import Signal, Qt, QSize @@ -29,8 +30,16 @@ from humanfriendly import format_size from src.core.enums import SettingItems, Theme -from src.core.library import Entry, ItemType, Library from src.core.constants import VIDEO_TYPES, IMAGE_TYPES, RAW_IMAGE_TYPES, TS_FOLDER_NAME +from src.core.library.alchemy.enums import FilterState +from src.core.library.alchemy.fields import ( + TagBoxField, + DatetimeField, + FieldTypeEnum, + Field, + _FieldID, + TextField, +) from src.qt.helpers.file_opener import FileOpenerLabel, FileOpenerHelper, open_file from src.qt.modals.add_field import AddFieldModal from src.qt.widgets.thumb_renderer import ThumbRenderer @@ -42,17 +51,24 @@ from src.qt.widgets.text_line_edit import EditTextLine from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper from src.qt.widgets.video_player import VideoPlayer +from src.core.library.alchemy.library import Library - -# Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver -ERROR = "[ERROR]" -WARNING = "[WARNING]" -INFO = "[INFO]" +logger = structlog.get_logger(__name__) + -logging.basicConfig(format="%(message)s", level=logging.INFO) +def update_selected_entry(driver: "QtDriver"): + for grid_idx in driver.selected: + entry = driver.frame_content[grid_idx] + # reload entry + _, entries = driver.lib.search_library(FilterState(id=entry.id)) + logger.info( + "found item", entries=entries, grid_idx=grid_idx, lookup_id=entry.id + ) + assert entries, f"Entry not found: {entry.id}" + driver.frame_content[grid_idx] = entries[0] class PreviewPanel(QWidget): @@ -66,14 +82,14 @@ def __init__(self, library: Library, driver: "QtDriver"): self.lib = library self.driver: QtDriver = driver self.initialized = False - self.isOpen: bool = False + self.is_open: bool = False # self.filepath = None # self.item = None # DEPRECATED, USE self.selected self.common_fields: list = [] self.mixed_fields: list = [] - self.selected: list[tuple[ItemType, int]] = [] # New way of tracking items + self.selected: list[int] = [] # New way of tracking items self.tag_callback = None - self.containers: list[QWidget] = [] + self.containers: list[FieldContainer] = [] self.img_button_size: tuple[int, int] = (266, 266) self.image_ratio: float = 1.0 @@ -226,7 +242,7 @@ def __init__(self, library: Library, driver: "QtDriver"): self.add_field_button.setMaximumSize(96, 28) self.add_field_button.setText("Add Field") self.afb_layout.addWidget(self.add_field_button) - self.afm = AddFieldModal(self.lib) + self.add_field_modal = AddFieldModal(self.lib) self.place_add_field_button() self.update_image_size( (self.image_container.size().width(), self.image_container.size().height()) @@ -236,12 +252,15 @@ def __init__(self, library: Library, driver: "QtDriver"): root_layout.setContentsMargins(0, 0, 0, 0) root_layout.addWidget(splitter) + def remove_field_prompt(self, name: str) -> str: + return f'Are you sure you want to remove field "{name}"?' + def fill_libs_widget(self, layout: QVBoxLayout): settings = self.driver.settings settings.beginGroup(SettingItems.LIBS_LIST) lib_items: dict[str, tuple[str, str]] = {} for item_tstamp in settings.allKeys(): - val: str = settings.value(item_tstamp) # type: ignore + val = str(settings.value(item_tstamp, type=str)) cut_val = val if len(val) > 45: cut_val = f"{val[0:10]} ... {val[-10:]}" @@ -275,7 +294,7 @@ def clear_layout(layout_item: QVBoxLayout): clear_layout(layout) label = QLabel("Recent Libraries") - label.setAlignment(Qt.AlignCenter) # type: ignore + label.setAlignment(Qt.AlignmentFlag.AlignCenter) row_layout = QHBoxLayout() row_layout.addWidget(label) @@ -296,14 +315,12 @@ def set_button_style( full_style_rows = base_style + (extras or []) btn.setStyleSheet( - ( - "QPushButton{" - f"{''.join(full_style_rows)}" - "}" - f"QPushButton::hover{{background-color:{Theme.COLOR_HOVER.value};}}" - f"QPushButton::pressed{{background-color:{Theme.COLOR_PRESSED.value};}}" - f"QPushButton::disabled{{background-color:{Theme.COLOR_DISABLED_BG.value};}}" - ) + "QPushButton{" + f"{''.join(full_style_rows)}" + "}" + f"QPushButton::hover{{background-color:{Theme.COLOR_HOVER.value};}}" + f"QPushButton::pressed{{background-color:{Theme.COLOR_PRESSED.value};}}" + f"QPushButton::disabled{{background-color:{Theme.COLOR_DISABLED_BG.value};}}" ) btn.setCursor(Qt.CursorShape.PointingHandCursor) @@ -395,59 +412,50 @@ def update_image_size(self, size: tuple[int, int], ratio: float = None): self.preview_vid.setMinimumSize(adj_size) # self.preview_img.setMinimumSize(adj_size) - # if self.preview_img.iconSize().toTuple()[0] < self.preview_img.size().toTuple()[0] + 10: - # if type(self.item) == Entry: - # filepath = os.path.normpath(f'{self.lib.library_dir}/{self.item.path}/{self.item.filename}') - # self.thumb_renderer.render(time.time(), filepath, self.preview_img.size().toTuple(), self.devicePixelRatio(),update_on_ratio_change=True) - - # logging.info(f' Img Aspect Ratio: {self.image_ratio}') - # logging.info(f' Max Button Size: {size}') - # logging.info(f'Container Size: {(self.image_container.size().width(), self.image_container.size().height())}') - # logging.info(f'Final Button Size: {(adj_width, adj_height)}') - # logging.info(f'') - # logging.info(f' Icon Size: {self.preview_img.icon().actualSize().toTuple()}') - # logging.info(f'Button Size: {self.preview_img.size().toTuple()}') - def place_add_field_button(self): self.scroll_layout.addWidget(self.afb_container) self.scroll_layout.setAlignment( self.afb_container, Qt.AlignmentFlag.AlignHCenter ) - if self.afm.is_connected: - self.afm.done.disconnect() + if self.add_field_modal.is_connected: + self.add_field_modal.done.disconnect() if self.add_field_button.is_connected: self.add_field_button.clicked.disconnect() # self.afm.done.connect(lambda f: (self.lib.add_field_to_entry(self.selected[0][1], f), self.update_widgets())) - self.afm.done.connect( - lambda f: (self.add_field_to_selected(f), self.update_widgets()) + self.add_field_modal.done.connect( + lambda items: ( + self.add_field_to_selected(items), + update_selected_entry(self.driver), + self.update_widgets(), + ) ) - self.afm.is_connected = True - self.add_field_button.clicked.connect(self.afm.show) - - def add_field_to_selected(self, field_id: int): - """Adds an entry field to one or more selected items.""" - added = set() - for item_pair in self.selected: - if item_pair[0] == ItemType.ENTRY and item_pair[1] not in added: - self.lib.add_field_to_entry(item_pair[1], field_id) - added.add(item_pair[1]) - - # def update_widgets(self, item: Union[Entry, Collation, Tag]): - def update_widgets(self): + self.add_field_modal.is_connected = True + self.add_field_button.clicked.connect(self.add_field_modal.show) + + def add_field_to_selected(self, field_list: list): + """Add list of entry fields to one or more selected items.""" + logger.info("add_field_to_selected", selected=self.selected, fields=field_list) + for grid_idx in self.selected: + entry = self.driver.frame_content[grid_idx] + for field_item in field_list: + self.lib.add_entry_field_type( + entry.id, field_id=field_item.currentData() + ) + + def update_widgets(self) -> bool: """ - Renders the panel's widgets with the newest data from the Library. + Render the panel widgets with the newest data from the Library. """ - logging.info(f"[ENTRY PANEL] UPDATE WIDGETS ({self.driver.selected})") - self.isOpen = True + logger.info("update_widgets", selected=self.driver.selected) + self.is_open = True # self.tag_callback = tag_callback if tag_callback else None window_title = "" # update list of libraries self.fill_libs_widget(self.libs_layout) - # 0 Selected Items if not self.driver.selected: if self.selected or not self.initialized: self.file_label.setText("No Items Selected") @@ -460,7 +468,7 @@ def update_widgets(self): ) self.preview_img.setCursor(Qt.CursorShape.ArrowCursor) - ratio: float = self.devicePixelRatio() + ratio = self.devicePixelRatio() self.thumb_renderer.render( time.time(), "", @@ -471,7 +479,7 @@ def update_widgets(self): ) if self.preview_img.is_connected: self.preview_img.clicked.disconnect() - for i, c in enumerate(self.containers): + for c in self.containers: c.setHidden(True) self.preview_img.show() self.preview_vid.stop() @@ -479,141 +487,145 @@ def update_widgets(self): self.selected = list(self.driver.selected) self.add_field_button.setHidden(True) - # 1 Selected Item - elif len(self.driver.selected) == 1: + # common code + self.initialized = True + self.setWindowTitle(window_title) + self.show() + return True + + # reload entry and fill it into the grid again + # TODO - do this more granular + for grid_idx in self.driver.selected: + entry = self.driver.frame_content[grid_idx] + _, entries = self.lib.search_library(FilterState(id=entry.id)) + logger.info( + "found item", entries=entries, grid_idx=grid_idx, lookup_id=entry.id + ) + self.driver.frame_content[grid_idx] = entries[0] + + if len(self.driver.selected) == 1: # 1 Selected Entry - if self.driver.selected[0][0] == ItemType.ENTRY: - self.preview_img.show() - self.preview_vid.stop() - self.preview_vid.hide() - item: Entry = self.lib.get_entry(self.driver.selected[0][1]) - # If a new selection is made, update the thumbnail and filepath. - if not self.selected or self.selected != self.driver.selected: - filepath = self.lib.library_dir / item.path / item.filename - self.file_label.setFilePath(filepath) - window_title = str(filepath) - ratio: float = self.devicePixelRatio() - self.thumb_renderer.render( - time.time(), - filepath, - (512, 512), - ratio, - update_on_ratio_change=True, - ) - self.file_label.setText("\u200b".join(str(filepath))) - self.file_label.setCursor(Qt.CursorShape.PointingHandCursor) + selected_idx = self.driver.selected[0] + item = self.driver.frame_content[selected_idx] - self.preview_img.setContextMenuPolicy( - Qt.ContextMenuPolicy.ActionsContextMenu - ) - self.preview_img.setCursor(Qt.CursorShape.PointingHandCursor) + self.preview_img.show() + self.preview_vid.stop() + self.preview_vid.hide() - self.opener = FileOpenerHelper(filepath) - self.open_file_action.triggered.connect(self.opener.open_file) - self.open_explorer_action.triggered.connect( - self.opener.open_explorer - ) + # If a new selection is made, update the thumbnail and filepath. + if not self.selected or self.selected != self.driver.selected: + filepath = self.lib.library_dir / item.path + self.file_label.setFilePath(filepath) + ratio = self.devicePixelRatio() + self.thumb_renderer.render( + time.time(), + filepath, + (512, 512), + ratio, + update_on_ratio_change=True, + ) + self.file_label.setText("\u200b".join(str(filepath))) + self.file_label.setCursor(Qt.CursorShape.PointingHandCursor) - # TODO: Do this somewhere else, this is just here temporarily. - try: - image = None - if filepath.suffix.lower() in IMAGE_TYPES: - image = Image.open(str(filepath)) - elif filepath.suffix.lower() in RAW_IMAGE_TYPES: - try: - with rawpy.imread(str(filepath)) as raw: - rgb = raw.postprocess() - image = Image.new( - "L", (rgb.shape[1], rgb.shape[0]), color="black" - ) - except ( - rawpy._rawpy.LibRawIOError, - rawpy._rawpy.LibRawFileUnsupportedError, - ): - pass - elif filepath.suffix.lower() in VIDEO_TYPES: - video = cv2.VideoCapture(str(filepath)) - if video.get(cv2.CAP_PROP_FRAME_COUNT) <= 0: - raise cv2.error("File is invalid or has 0 frames") - video.set(cv2.CAP_PROP_POS_FRAMES, 0) - success, frame = video.read() - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) - image = Image.fromarray(frame) - if success: - self.preview_img.hide() - self.preview_vid.play( - filepath, QSize(image.width, image.height) - ) - self.resizeEvent( - QResizeEvent( - QSize(image.width, image.height), - QSize(image.width, image.height), - ) - ) - self.preview_vid.show() + self.preview_img.setContextMenuPolicy( + Qt.ContextMenuPolicy.ActionsContextMenu + ) + self.preview_img.setCursor(Qt.CursorShape.PointingHandCursor) - # Stats for specific file types are displayed here. - if image and filepath.suffix.lower() in ( - IMAGE_TYPES + VIDEO_TYPES + RAW_IMAGE_TYPES + self.opener = FileOpenerHelper(filepath) + self.open_file_action.triggered.connect(self.opener.open_file) + self.open_explorer_action.triggered.connect(self.opener.open_explorer) + + # TODO: Do this somewhere else, this is just here temporarily. + try: + image = None + if filepath.suffix.lower() in IMAGE_TYPES: + image = Image.open(str(filepath)) + elif filepath.suffix.lower() in RAW_IMAGE_TYPES: + try: + with rawpy.imread(str(filepath)) as raw: + rgb = raw.postprocess() + image = Image.new( + "L", (rgb.shape[1], rgb.shape[0]), color="black" + ) + except ( + rawpy._rawpy.LibRawIOError, + rawpy._rawpy.LibRawFileUnsupportedError, ): - self.dimensions_label.setText( - f"{filepath.suffix.upper()[1:]} • {format_size(filepath.stat().st_size)}\n{image.width} x {image.height} px" + pass + elif filepath.suffix.lower() in VIDEO_TYPES: + video = cv2.VideoCapture(str(filepath)) + if video.get(cv2.CAP_PROP_FRAME_COUNT) <= 0: + raise cv2.error("File is invalid or has 0 frames") + video.set(cv2.CAP_PROP_POS_FRAMES, 0) + success, frame = video.read() + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + image = Image.fromarray(frame) + if success: + self.preview_img.hide() + self.preview_vid.play( + filepath, QSize(image.width, image.height) ) - else: - self.dimensions_label.setText( - f"{filepath.suffix.upper()[1:]} • {format_size(filepath.stat().st_size)}" + self.resizeEvent( + QResizeEvent( + QSize(image.width, image.height), + QSize(image.width, image.height), + ) ) + self.preview_vid.show() - if not filepath.is_file(): - raise FileNotFoundError - - except FileNotFoundError as e: - self.dimensions_label.setText(f"{filepath.suffix.upper()[1:]}") - logging.info( - f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})" - ) - - except (FileNotFoundError, cv2.error) as e: - self.dimensions_label.setText(f"{filepath.suffix.upper()}") - logging.info( - f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})" + # Stats for specific file types are displayed here. + if image and filepath.suffix.lower() in ( + IMAGE_TYPES + VIDEO_TYPES + RAW_IMAGE_TYPES + ): + self.dimensions_label.setText( + f"{filepath.suffix.upper()[1:]} • {format_size(filepath.stat().st_size)}\n{image.width} x {image.height} px" ) - except ( - UnidentifiedImageError, - DecompressionBombError, - ) as e: + else: self.dimensions_label.setText( f"{filepath.suffix.upper()[1:]} • {format_size(filepath.stat().st_size)}" ) - logging.info( - f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})" - ) - if self.preview_img.is_connected: - self.preview_img.clicked.disconnect() - self.preview_img.clicked.connect( - lambda checked=False, filepath=filepath: open_file(filepath) + if not filepath.is_file(): + raise FileNotFoundError + + except (FileNotFoundError, cv2.error) as e: + self.dimensions_label.setText(f"{filepath.suffix.upper()}") + logger.error( + "Couldn't Render thumbnail", filepath=filepath, error=e + ) + + except ( + UnidentifiedImageError, + DecompressionBombError, + ) as e: + self.dimensions_label.setText( + f"{filepath.suffix.upper()[1:]} • {format_size(filepath.stat().st_size)}" + ) + logger.error( + "Couldn't Render thumbnail", filepath=filepath, error=e ) - self.preview_img.is_connected = True - self.selected = list(self.driver.selected) - for i, f in enumerate(item.fields): - self.write_container(i, f) - # Hide leftover containers - if len(self.containers) > len(item.fields): - for i, c in enumerate(self.containers): - if i > (len(item.fields) - 1): - c.setHidden(True) + if self.preview_img.is_connected: + self.preview_img.clicked.disconnect() + self.preview_img.clicked.connect( + lambda checked=False, pth=filepath: open_file(pth) + ) + self.preview_img.is_connected = True - self.add_field_button.setHidden(False) + self.selected = self.driver.selected + logger.info("rendering item fields", item=item, fields=item.tag_box_fields) + for idx, field in enumerate(item.fields): + logger.info("write container in update_widgets", idx=idx, field=field) + self.write_container(idx, field) - # 1 Selected Collation - elif self.driver.selected[0][0] == ItemType.COLLATION: - pass + # Hide leftover containers + if len(self.containers) > len(item.fields): + for i, c in enumerate(self.containers): + if i > (len(item.fields) - 1): + c.setHidden(True) - # 1 Selected Tag - elif self.driver.selected[0][0] == ItemType.TAG_GROUP: - pass + self.add_field_button.setHidden(False) # Multiple Selected Items elif len(self.driver.selected) > 1: @@ -631,7 +643,7 @@ def update_widgets(self): ) self.preview_img.setCursor(Qt.CursorShape.ArrowCursor) - ratio: float = self.devicePixelRatio() + ratio = self.devicePixelRatio() self.thumb_renderer.render( time.time(), "", @@ -644,63 +656,38 @@ def update_widgets(self): self.preview_img.clicked.disconnect() self.preview_img.is_connected = False - self.common_fields = [] - self.mixed_fields = [] - for i, item_pair in enumerate(self.driver.selected): - if item_pair[0] == ItemType.ENTRY: - item = self.lib.get_entry(item_pair[1]) - if i == 0: - for f in item.fields: - self.common_fields.append(f) - else: - common_to_remove = [] - for f in self.common_fields: - # Common field found (Same ID, identical content) - if f not in item.fields: - common_to_remove.append(f) - - # Mixed field found (Same ID, different content) - if self.lib.get_field_index_in_entry( - item, self.lib.get_field_attr(f, "id") - ): - # if self.lib.get_field_attr(f, 'type') == ('tag_box'): - # pass - # logging.info(f) - # logging.info(type(f)) - f_stripped = { - self.lib.get_field_attr(f, "id"): None - } - if f_stripped not in self.mixed_fields and ( - f not in self.common_fields - or f in common_to_remove - ): - # and (f not in self.common_fields or f in common_to_remove) - self.mixed_fields.append(f_stripped) - self.common_fields = [ - f for f in self.common_fields if f not in common_to_remove - ] - order: list[int] = ( - [0] - + [1, 2] - + [9, 17, 18, 19, 20] - + [8, 7, 6] - + [4] - + [3, 21] - + [10, 14, 11, 12, 13, 22] - + [5] - ) - self.mixed_fields = sorted( - self.mixed_fields, - key=lambda x: order.index(self.lib.get_field_attr(x, "id")), - ) + # fill shared fields from first item + first_item = self.driver.frame_content[self.driver.selected[0]] + common_fields = [f for f in first_item.fields] + mixed_fields = [] + + # iterate through other items + for grid_idx in self.driver.selected[1:]: + item = self.driver.frame_content[grid_idx] + item_field_types = {f.type_key for f in item.fields} + for f in common_fields[:]: + if f.type_key not in item_field_types: + common_fields.remove(f) + mixed_fields.append(f) + + self.common_fields = common_fields + self.mixed_fields = sorted(mixed_fields, key=lambda x: x.type.order) self.selected = list(self.driver.selected) + logger.info( + "update_widgets common_fields", + common_fields=self.common_fields, + ) for i, f in enumerate(self.common_fields): - logging.info(f"ci:{i}, f:{f}") self.write_container(i, f) + + logger.info( + "update_widgets mixed_fields", + mixed_fields=self.mixed_fields, + start=len(self.common_fields), + ) for i, f in enumerate(self.mixed_fields, start=len(self.common_fields)): - logging.info(f"mi:{i}, f:{f}") - self.write_container(i, f, mixed=True) + self.write_container(i, f, is_mixed=True) # Hide leftover containers if len(self.containers) > len(self.common_fields) + len(self.mixed_fields): @@ -712,60 +699,9 @@ def update_widgets(self): self.initialized = True - # # Uninitialized or New Item: - # if not self.item or self.item.id != item.id: - # # logging.info(f'Uninitialized or New Item ({item.id})') - # if type(item) == Entry: - # # New Entry: Render preview and update filename label - # filepath = os.path.normpath(f'{self.lib.library_dir}/{item.path}/{item.filename}') - # window_title = filepath - # ratio: float = self.devicePixelRatio() - # self.thumb_renderer.render(time.time(), filepath, (512, 512), ratio,update_on_ratio_change=True) - # self.file_label.setText("\u200b".join(filepath)) - - # # TODO: Deal with this later. - # # https://stackoverflow.com/questions/64252654/pyqt5-drag-and-drop-into-system-file-explorer-with-delayed-encoding - # # https://doc.qt.io/qtforpython-5/PySide2/QtCore/QMimeData.html#more - # # drag = QDrag(self.preview_img) - # # mime = QMimeData() - # # mime.setUrls([filepath]) - # # drag.setMimeData(mime) - # # drag.exec_(Qt.DropAction.CopyAction) - - # try: - # self.preview_img.clicked.disconnect() - # except RuntimeError: - # pass - # self.preview_img.clicked.connect( - # lambda checked=False, filepath=filepath: open_file(filepath)) - - # for i, f in enumerate(item.fields): - # self.write_container(item, i, f) - - # self.item = item - - # # try: - # # self.tags_updated.disconnect() - # # except RuntimeError: - # # pass - # # if self.tag_callback: - # # # logging.info(f'[UPDATE CONTAINER] Updating Callback for {item.id}: {self.tag_callback}') - # # self.tags_updated.connect(self.tag_callback) - - # # Initialized, Updating: - # elif self.item and self.item.id == item.id: - # # logging.info(f'Initialized Item, Updating! ({item.id})') - # for i, f in enumerate(item.fields): - # self.write_container(item, i, f) - - # # Hide leftover containers - # if len(self.containers) > len(self.item.fields): - # for i, c in enumerate(self.containers): - # if i > (len(self.item.fields) - 1): - # c.setHidden(True) - self.setWindowTitle(window_title) self.show() + return True def set_tags_updated_slot(self, slot: object): """ @@ -774,103 +710,104 @@ def set_tags_updated_slot(self, slot: object): if self.is_connected: self.tags_updated.disconnect() - logging.info("[UPDATE CONTAINER] Setting tags updated slot") + logger.info("[UPDATE CONTAINER] Setting tags updated slot") self.tags_updated.connect(slot) self.is_connected = True - # def write_container(self, item:Union[Entry, Collation, Tag], index, field): - def write_container(self, index, field, mixed=False): - """Updates/Creates data for a FieldContainer.""" - # logging.info(f'[ENTRY PANEL] WRITE CONTAINER') + def write_container(self, index: int, field: Field, is_mixed: bool = False): + """Update/Create data for a FieldContainer. + + :param is_mixed: Relevant when multiple items are selected. If True, field is not present in all selected items + """ # Remove 'Add Field' button from scroll_layout, to be re-added later. self.scroll_layout.takeAt(self.scroll_layout.count() - 1).widget() - container: FieldContainer = None if len(self.containers) < (index + 1): container = FieldContainer() self.containers.append(container) self.scroll_layout.addWidget(container) else: container = self.containers[index] - # container.inner_layout.removeItem(container.inner_layout.itemAt(1)) - # container.setHidden(False) - if self.lib.get_field_attr(field, "type") == "tag_box": - # logging.info(f'WRITING TAGBOX FOR ITEM {item.id}') - container.set_title(self.lib.get_field_attr(field, "name")) - # container.set_editable(False) + + container.set_copy_callback(None) + container.set_edit_callback(None) + container.set_remove_callback(None) + + if isinstance(field, TagBoxField): + container.set_title(field.type.name) container.set_inline(False) - title = f"{self.lib.get_field_attr(field, 'name')} (Tag Box)" - if not mixed: - item = self.lib.get_entry( - self.selected[0][1] - ) # TODO TODO TODO: TEMPORARY - if type(container.get_inner_widget()) == TagBoxWidget: - inner_container: TagBoxWidget = container.get_inner_widget() - inner_container.set_item(item) - inner_container.set_tags(self.lib.get_field_attr(field, "content")) + title = f"{field.type.name} (Tag Box)" + + if not is_mixed: + inner_container = container.get_inner_widget() + if isinstance(inner_container, TagBoxWidget): + inner_container.set_field(field) + inner_container.set_tags(list(field.tags)) + try: inner_container.updated.disconnect() except RuntimeError: - pass - # inner_container.updated.connect(lambda f=self.filepath, i=item: self.write_container(item, index, field)) + logger.error("Failed to disconnect inner_container.updated") + else: + logger.info( + "inner_container is not instance of TagBoxWidget", + container=inner_container, + ) inner_container = TagBoxWidget( - item, + field, title, - index, - self.lib, - self.lib.get_field_attr(field, "content"), self.driver, ) container.set_inner_widget(inner_container) - inner_container.field = field + + # inner_container.field = field inner_container.updated.connect( lambda: ( self.write_container(index, field), - self.tags_updated.emit(), + self.update_widgets(), ) ) - # if type(item) == Entry: # NOTE: Tag Boxes have no Edit Button (But will when you can convert field types) - # f'Are you sure you want to remove this \"{self.lib.get_field_attr(field, "name")}\" field?' # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) - prompt = f'Are you sure you want to remove this "{self.lib.get_field_attr(field, "name")}" field?' - callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback( - lambda: self.remove_message_box(prompt=prompt, callback=callback) + lambda: self.remove_message_box( + prompt=self.remove_field_prompt(field.type.name), + callback=lambda: ( + self.remove_field(field), + update_selected_entry(self.driver), + # reload entry and its fields + self.update_widgets(), + ), + ) ) - container.set_copy_callback(None) - container.set_edit_callback(None) else: text = "Mixed Data" - title = f"{self.lib.get_field_attr(field, 'name')} (Wacky Tag Box)" + title = f"{field.type.name} (Wacky Tag Box)" inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) - container.set_copy_callback(None) - container.set_edit_callback(None) - container.set_remove_callback(None) self.tags_updated.emit() # self.dynamic_widgets.append(inner_container) - elif self.lib.get_field_attr(field, "type") in "text_line": - # logging.info(f'WRITING TEXTLINE FOR ITEM {item.id}') - container.set_title(self.lib.get_field_attr(field, "name")) - # container.set_editable(True) + elif field.type.type == FieldTypeEnum.TEXT_LINE: + container.set_title(field.type.name) container.set_inline(False) + # Normalize line endings in any text content. - if not mixed: - text = self.lib.get_field_attr(field, "content").replace("\r", "\n") + if not is_mixed: + assert isinstance(field.value, (str, type(None))) + text = field.value or "" else: text = "Mixed Data" - title = f"{self.lib.get_field_attr(field, 'name')} (Text Line)" + + title = f"{field.type.name} ({field.type.type})" inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) - # if type(item) == Entry: - if not mixed: + if not is_mixed: modal = PanelModal( - EditTextLine(self.lib.get_field_attr(field, "content")), + EditTextLine(field.value), title=title, - window_title=f'Edit {self.lib.get_field_attr(field, "name")}', + window_title=f"Edit {field.type.name}", save_callback=( lambda content: ( self.update_field(field, content), @@ -878,39 +815,39 @@ def write_container(self, index, field, mixed=False): ) ), ) + if "pytest" in sys.modules: + # for better testability + container.modal = modal # type: ignore + container.set_edit_callback(modal.show) - prompt = f'Are you sure you want to remove this "{self.lib.get_field_attr(field, "name")}" field?' - callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback( - lambda: self.remove_message_box(prompt=prompt, callback=callback) + lambda: self.remove_message_box( + prompt=self.remove_field_prompt(field.type.name), + callback=lambda: ( + self.remove_field(field), + self.update_widgets(), + ), + ) ) - container.set_copy_callback(None) - else: - container.set_edit_callback(None) - container.set_copy_callback(None) - container.set_remove_callback(None) - # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) - elif self.lib.get_field_attr(field, "type") in "text_box": - # logging.info(f'WRITING TEXTBOX FOR ITEM {item.id}') - container.set_title(self.lib.get_field_attr(field, "name")) + elif field.type.type == FieldTypeEnum.TEXT_BOX: + container.set_title(field.type.name) # container.set_editable(True) container.set_inline(False) # Normalize line endings in any text content. - if not mixed: - text = self.lib.get_field_attr(field, "content").replace("\r", "\n") + if not is_mixed: + assert isinstance(field.value, (str, type(None))) + text = (field.value or "").replace("\r", "\n") else: text = "Mixed Data" - title = f"{self.lib.get_field_attr(field, 'name')} (Text Box)" + title = f"{field.type.name} (Text Box)" inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) - # if type(item) == Entry: - if not mixed: - container.set_copy_callback(None) + if not is_mixed: modal = PanelModal( - EditTextBox(self.lib.get_field_attr(field, "content")), + EditTextBox(field.value), title=title, - window_title=f'Edit {self.lib.get_field_attr(field, "name")}', + window_title=f"Edit {field.type.name}", save_callback=( lambda content: ( self.update_field(field, content), @@ -919,140 +856,107 @@ def write_container(self, index, field, mixed=False): ), ) container.set_edit_callback(modal.show) - prompt = f'Are you sure you want to remove this "{self.lib.get_field_attr(field, "name")}" field?' - callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback( - lambda: self.remove_message_box(prompt=prompt, callback=callback) + lambda: self.remove_message_box( + prompt=self.remove_field_prompt(field.type.name), + callback=lambda: ( + self.remove_field(field), + self.update_widgets(), + ), + ) ) - else: - container.set_edit_callback(None) - container.set_copy_callback(None) - container.set_remove_callback(None) - elif self.lib.get_field_attr(field, "type") == "collation": - # logging.info(f'WRITING COLLATION FOR ITEM {item.id}') - container.set_title(self.lib.get_field_attr(field, "name")) - # container.set_editable(True) - container.set_inline(False) - collation = self.lib.get_collation( - self.lib.get_field_attr(field, "content") - ) - title = f"{self.lib.get_field_attr(field, 'name')} (Collation)" - text = f"{collation.title} ({len(collation.e_ids_and_pages)} Items)" - if len(self.selected) == 1: - text += f" - Page {collation.e_ids_and_pages[[x[0] for x in collation.e_ids_and_pages].index(self.selected[0][1])][1]}" - inner_container = TextWidget(title, text) - container.set_inner_widget(inner_container) - # if type(item) == Entry: - container.set_copy_callback(None) - # container.set_edit_callback(None) - # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) - prompt = f'Are you sure you want to remove this "{self.lib.get_field_attr(field, "name")}" field?' - callback = lambda: (self.remove_field(field), self.update_widgets()) - container.set_remove_callback( - lambda: self.remove_message_box(prompt=prompt, callback=callback) - ) - elif self.lib.get_field_attr(field, "type") == "datetime": + + elif field.type == DatetimeField: # logging.info(f'WRITING DATETIME FOR ITEM {item.id}') - if not mixed: + if not is_mixed: try: - container.set_title(self.lib.get_field_attr(field, "name")) + container.set_title(field.type.name) # container.set_editable(False) container.set_inline(False) # TODO: Localize this and/or add preferences. - date = dt.strptime( - self.lib.get_field_attr(field, "content"), "%Y-%m-%d %H:%M:%S" - ) - title = f"{self.lib.get_field_attr(field, 'name')} (Date)" + date = dt.strptime(field.value, "%Y-%m-%d %H:%M:%S") + title = f"{field.type.name} (Date)" inner_container = TextWidget(title, date.strftime("%D - %r")) container.set_inner_widget(inner_container) - except: - container.set_title(self.lib.get_field_attr(field, "name")) + except Exception: + container.set_title(field.type.name) # container.set_editable(False) container.set_inline(False) - title = f"{self.lib.get_field_attr(field, 'name')} (Date) (Unknown Format)" - inner_container = TextWidget( - title, str(self.lib.get_field_attr(field, "content")) - ) + title = f"{field.type.name} (Date) (Unknown Format)" + inner_container = TextWidget(title, str(field.value)) + container.set_inner_widget(inner_container) + # if type(item) == Entry: - container.set_copy_callback(None) - container.set_edit_callback(None) # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) - prompt = f'Are you sure you want to remove this "{self.lib.get_field_attr(field, "name")}" field?' - callback = lambda: (self.remove_field(field), self.update_widgets()) container.set_remove_callback( - lambda: self.remove_message_box(prompt=prompt, callback=callback) + lambda: self.remove_message_box( + prompt=self.remove_field_prompt(field.type.name), + callback=lambda: ( + self.remove_field(field), + self.update_widgets(), + ), + ) ) else: text = "Mixed Data" - title = f"{self.lib.get_field_attr(field, 'name')} (Wacky Date)" + title = f"{field.type.name} (Wacky Date)" inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) - container.set_copy_callback(None) - container.set_edit_callback(None) - container.set_remove_callback(None) else: - # logging.info(f'[ENTRY PANEL] Unknown Type: {self.lib.get_field_attr(field, "type")}') - container.set_title(self.lib.get_field_attr(field, "name")) + container.set_title(field.type.name) # container.set_editable(False) container.set_inline(False) - title = f"{self.lib.get_field_attr(field, 'name')} (Unknown Field Type)" - inner_container = TextWidget( - title, str(self.lib.get_field_attr(field, "content")) - ) + title = f"{field.type.name} (Unknown Field Type)" + inner_container = TextWidget(title, field.type.name) container.set_inner_widget(inner_container) # if type(item) == Entry: - container.set_copy_callback(None) - container.set_edit_callback(None) # container.set_remove_callback(lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets(item))) - prompt = f'Are you sure you want to remove this "{self.lib.get_field_attr(field, "name")}" field?' - callback = lambda: (self.remove_field(field), self.update_widgets()) - # callback = lambda: (self.lib.get_entry(item.id).fields.pop(index), self.update_widgets()) container.set_remove_callback( - lambda: self.remove_message_box(prompt=prompt, callback=callback) + lambda: self.remove_message_box( + prompt=self.remove_field_prompt(field.type.name), + callback=lambda: ( + self.remove_field(field), + self.update_widgets(), + ), + ) ) + container.edit_button.setHidden(True) container.setHidden(False) self.place_add_field_button() - def remove_field(self, field: dict): - """Removes a field from all selected Entries, given a field object.""" - for item_pair in self.selected: - if item_pair[0] == ItemType.ENTRY: - entry = self.lib.get_entry(item_pair[1]) - try: - index = entry.fields.index(field) - updated_badges = False - if 8 in entry.fields[index].keys() and ( - 1 in entry.fields[index][8] or 0 in entry.fields[index][8] - ): - updated_badges = True - # TODO: Create a proper Library/Entry method to manage fields. - entry.fields.pop(index) - if updated_badges: - self.driver.update_badges() - except ValueError: - logging.info( - f"[PREVIEW PANEL][ERROR?] Tried to remove field from Entry ({entry.id}) that never had it" - ) - pass - - def update_field(self, field: dict, content): - """Removes a field from all selected Entries, given a field object.""" - field = dict(field) - for item_pair in self.selected: - if item_pair[0] == ItemType.ENTRY: - entry = self.lib.get_entry(item_pair[1]) - try: - logging.info(field) - index = entry.fields.index(field) - self.lib.update_entry_field(entry.id, index, content, "replace") - except ValueError: - logging.info( - f"[PREVIEW PANEL][ERROR] Tried to update field from Entry ({entry.id}) that never had it" - ) - pass + def remove_field(self, field: Field): + """Remove a field from all selected Entries.""" + logger.info("removing field", field=field, selected=self.selected) + entry_ids = [] + for grid_idx in self.selected: + entry = self.driver.frame_content[grid_idx] + entry_ids.append(entry.id) + + self.lib.remove_entry_field(field, entry_ids) + + # if the field is meta tags, update the badges + if field.type_key == _FieldID.TAGS_META.value: + self.driver.update_badges(self.selected) + + def update_field(self, field: Field, content: str) -> None: + """Remove a field from all selected Entries, given a field object.""" + assert isinstance( + field, (TextField, DatetimeField, TagBoxField) + ), f"instance: {type(field)}" + entry_ids = [] + for grid_idx in self.selected: + entry = self.driver.frame_content[grid_idx] + entry_ids.append(entry.id) + + assert entry_ids, "No entries selected" + self.lib.update_entry_field( + entry_ids, + field, + content, + ) - def remove_message_box(self, prompt: str, callback: typing.Callable) -> None: + def remove_message_box(self, prompt: str, callback: Callable) -> None: remove_mb = QMessageBox() remove_mb.setText(prompt) remove_mb.setWindowTitle("Remove Field") @@ -1060,13 +964,11 @@ def remove_message_box(self, prompt: str, callback: typing.Callable) -> None: cancel_button = remove_mb.addButton( "&Cancel", QMessageBox.ButtonRole.DestructiveRole ) - remove_button = remove_mb.addButton( - "&Remove", QMessageBox.ButtonRole.RejectRole - ) + remove_mb.addButton("&Remove", QMessageBox.ButtonRole.RejectRole) # remove_mb.setStandardButtons(QMessageBox.StandardButton.Cancel) remove_mb.setDefaultButton(cancel_button) remove_mb.setEscapeButton(cancel_button) result = remove_mb.exec_() # logging.info(result) - if result == 3: + if result == 3: # TODO - what is this magic number? callback() diff --git a/tagstudio/src/qt/widgets/tag.py b/tagstudio/src/qt/widgets/tag.py index 739369dcf..3cbb21f78 100644 --- a/tagstudio/src/qt/widgets/tag.py +++ b/tagstudio/src/qt/widgets/tag.py @@ -4,7 +4,6 @@ import math -import os from types import FunctionType from pathlib import Path @@ -13,15 +12,10 @@ from PySide6.QtGui import QEnterEvent, QAction from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QPushButton -from src.core.library import Library, Tag +from src.core.library import Tag from src.core.palette import ColorType, get_tag_color -ERROR = f"[ERROR]" -WARNING = f"[WARNING]" -INFO = f"[INFO]" - - class TagWidget(QWidget): edit_icon_128: Image.Image = Image.open( str(Path(__file__).parents[3] / "resources/qt/images/edit_icon_128.png") @@ -33,7 +27,6 @@ class TagWidget(QWidget): def __init__( self, - library: Library, tag: Tag, has_edit: bool, has_remove: bool, @@ -42,10 +35,9 @@ def __init__( on_edit_callback: FunctionType = None, ) -> None: super().__init__() - self.lib = library self.tag = tag - self.has_edit: bool = has_edit - self.has_remove: bool = has_remove + self.has_edit = has_edit + self.has_remove = has_remove # self.bg_label = QLabel() # self.setStyleSheet('background-color:blue;') @@ -57,7 +49,7 @@ def __init__( self.bg_button = QPushButton(self) self.bg_button.setFlat(True) - self.bg_button.setText(tag.display_name(self.lib).replace("&", "&&")) + self.bg_button.setText(tag.name) if has_edit: edit_action = QAction("Edit", self) edit_action.triggered.connect(on_edit_callback) @@ -65,13 +57,8 @@ def __init__( self.bg_button.addAction(edit_action) # if on_click_callback: self.bg_button.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu) - # if has_remove: - # remove_action = QAction('Remove', self) - # # remove_action.triggered.connect(on_remove_callback) - # remove_action.triggered.connect(self.on_remove.emit()) - # self.bg_button.addAction(remove_action) + search_for_tag_action = QAction("Search for Tag", self) - # search_for_tag_action.triggered.connect(on_click_callback) search_for_tag_action.triggered.connect(self.on_click.emit) self.bg_button.addAction(search_for_tag_action) add_to_search_action = QAction("Add to Search", self) @@ -106,7 +93,7 @@ def __init__( f"border-color:{get_tag_color(ColorType.BORDER, tag.color)};" f"border-radius: 6px;" f"border-style:solid;" - f"border-width: {math.ceil(1*self.devicePixelRatio())}px;" + f"border-width: {math.ceil(self.devicePixelRatio())}px;" # f'border-top:2px solid {get_tag_color(ColorType.LIGHT_ACCENT, tag.color)};' # f'border-bottom:2px solid {get_tag_color(ColorType.BORDER, tag.color)};' # f'border-left:2px solid {get_tag_color(ColorType.BORDER, tag.color)};' @@ -167,35 +154,6 @@ def __init__( # self.remove_button.clicked.connect(on_remove_callback) self.remove_button.clicked.connect(self.on_remove.emit) - # NOTE: No more edit button! Just make it a right-click option. - # self.edit_button = QPushButton(self) - # self.edit_button.setFlat(True) - # self.edit_button.setText('Edit') - # self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128))) - # self.edit_button.setIconSize(QSize(14,14)) - # self.edit_button.setHidden(True) - # self.edit_button.setStyleSheet(f'color: {color};' - # f"background: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};" - # # f"color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};" - # f"border-color: {'black' if color not in ['black', 'gray', 'dark gray', 'cool gray', 'warm gray', 'blue', 'purple', 'violet'] else 'white'};" - # f'font-weight: 600;' - # # f"border-color:{'black' if color not in [ - # # 'black', 'gray', 'dark gray', - # # 'cool gray', 'warm gray', 'blue', - # # 'purple', 'violet'] else 'white'};" - # # f'QPushButton{{border-image: url(:/images/edit_icon_128.png);}}' - # # f'QPushButton{{border-image: url(:/images/edit_icon_128.png);}}' - # f'border-radius: 4px;' - # # f'border-style:solid;' - # # f'border-width:1px;' - # f'padding-top: 1.5px;' - # f'padding-right: 4px;' - # f'padding-bottom: 3px;' - # f'padding-left: 4px;' - # f'font-size: 14px') - # self.edit_button.setMinimumSize(18,18) - # # self.edit_button.setMaximumSize(18,18) - # self.inner_layout.addWidget(self.edit_button) if has_remove: self.inner_layout.addWidget(self.remove_button) @@ -209,32 +167,6 @@ def __init__( # self.setMinimumSize(50,20) - # def set_name(self, name:str): - # self.bg_label.setText(str) - - # def on_remove(self): - # if self.item and self.item[0] == ItemType.ENTRY: - # if self.field_index >= 0: - # self.lib.get_entry(self.item[1]).remove_tag(self.tag.id, self.field_index) - # else: - # self.lib.get_entry(self.item[1]).remove_tag(self.tag.id) - - # def set_click(self, callback): - # try: - # self.bg_button.clicked.disconnect() - # except RuntimeError: - # pass - # if callback: - # self.bg_button.clicked.connect(callback) - - # def set_click(self, function): - # try: - # self.bg.clicked.disconnect() - # except RuntimeError: - # pass - # # self.bg.clicked.connect(lambda checked=False, filepath=filepath: open_file(filepath)) - # # self.bg.clicked.connect(function) - def enterEvent(self, event: QEnterEvent) -> None: if self.has_remove: self.remove_button.setHidden(False) diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index 06b8b1fe5..24ef9bf3a 100644 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -3,15 +3,17 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging import math import typing +import structlog from PySide6.QtCore import Signal, Qt from PySide6.QtWidgets import QPushButton from src.core.constants import TAG_FAVORITE, TAG_ARCHIVED -from src.core.library import Library, Tag +from src.core.library import Entry, Tag +from src.core.library.alchemy.enums import FilterState +from src.core.library.alchemy.fields import TagBoxField from src.qt.flowlayout import FlowLayout from src.qt.widgets.fields import FieldWidget from src.qt.widgets.tag import TagWidget @@ -19,30 +21,28 @@ from src.qt.modals.build_tag import BuildTagPanel from src.qt.modals.tag_search import TagSearchPanel -# Only import for type checking/autocompletion, will not be imported at runtime. if typing.TYPE_CHECKING: from src.qt.ts_qt import QtDriver +logger = structlog.get_logger(__name__) + class TagBoxWidget(FieldWidget): updated = Signal() + error_occurred = Signal(Exception) def __init__( self, - item, - title, - field_index, - library: Library, - tags: list[int], + field: TagBoxField, + title: str, driver: "QtDriver", ) -> None: super().__init__(title) - # QObject.__init__(self) - self.item = item - self.lib = library + + assert isinstance(field, TagBoxField), f"field is {type(field)}" + + self.field = field self.driver = driver # Used for creating tag click callbacks that search entries for that tag. - self.field_index = field_index - self.tags: list[int] = tags self.setObjectName("tagBox") self.base_layout = FlowLayout() self.base_layout.setGridEfficiency(False) @@ -62,11 +62,8 @@ def __init__( f"border-color: #333333;" f"border-radius: 6px;" f"border-style:solid;" - f"border-width:{math.ceil(1*self.devicePixelRatio())}px;" - # f'padding-top: 1.5px;' - # f'padding-right: 4px;' + f"border-width:{math.ceil(self.devicePixelRatio())}px;" f"padding-bottom: 5px;" - # f'padding-left: 4px;' f"font-size: 20px;" f"}}" f"QPushButton::hover" @@ -75,46 +72,44 @@ def __init__( f"background: #555555;" f"}}" ) - tsp = TagSearchPanel(self.lib) + tsp = TagSearchPanel(self.driver.lib) tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x)) self.add_modal = PanelModal(tsp, title, "Add Tags") self.add_button.clicked.connect( - lambda: (tsp.update_tags(), self.add_modal.show()) # type: ignore + lambda: ( + tsp.update_tags(), + self.add_modal.show(), + ) ) - self.set_tags(tags) - # self.add_button.setHidden(True) + self.set_tags(field.tags) - def set_item(self, item): - self.item = item + def set_field(self, field: TagBoxField): + self.field = field - def set_tags(self, tags: list[int]): - logging.info(f"[TAG BOX WIDGET] SET TAGS: T:{tags} for E:{self.item.id}") + def set_tags(self, tags: typing.Iterable[Tag]): is_recycled = False - if self.base_layout.itemAt(0): - # logging.info(type(self.base_layout.itemAt(0).widget())) - while self.base_layout.itemAt(0) and self.base_layout.itemAt(1): - # logging.info(f"I'm deleting { self.base_layout.itemAt(0).widget()}") - self.base_layout.takeAt(0).widget().deleteLater() + while self.base_layout.itemAt(0) and self.base_layout.itemAt(1): + self.base_layout.takeAt(0).widget().deleteLater() is_recycled = True + for tag in tags: - # TODO: Remove space from the special search here (tag_id:x) once that system is finalized. - # tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True, - # on_remove_callback=lambda checked=False, t=tag: (self.lib.get_entry(self.item.id).remove_tag(self.lib, t, self.field_index), self.updated.emit()), - # on_click_callback=lambda checked=False, q=f'tag_id: {tag}': (self.driver.main_window.searchField.setText(q), self.driver.filter_items(q)), - # on_edit_callback=lambda checked=False, t=tag: (self.edit_tag(t)) - # ) - tw = TagWidget(self.lib, self.lib.get_tag(tag), True, True) - tw.on_click.connect( - lambda checked=False, q=f"tag_id: {tag}": ( - self.driver.main_window.searchField.setText(q), - self.driver.filter_items(q), + tag_widget = TagWidget(tag, True, True) + tag_widget.on_click.connect( + lambda tag_id=tag.id: ( + self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"), + self.driver.filter_items(FilterState(id=tag_id)), + ) + ) + + tag_widget.on_remove.connect( + lambda tag_id=tag.id: ( + self.remove_tag(tag_id), + self.driver.preview_panel.update_widgets(), ) ) - tw.on_remove.connect(lambda checked=False, t=tag: (self.remove_tag(t))) - tw.on_edit.connect(lambda checked=False, t=tag: (self.edit_tag(t))) - self.base_layout.addWidget(tw) - self.tags = tags + tag_widget.on_edit.connect(lambda t=tag: self.edit_tag(t)) + self.base_layout.addWidget(tag_widget) # Move or add the '+' button. if is_recycled: @@ -127,62 +122,60 @@ def set_tags(self, tags: list[int]): if self.base_layout.itemAt(0) and not self.base_layout.itemAt(1): self.base_layout.update() - def edit_tag(self, tag_id: int): - btp = BuildTagPanel(self.lib, tag_id) - # btp.on_edit.connect(lambda x: self.edit_tag_callback(x)) + def edit_tag(self, tag: Tag): + assert isinstance(tag, Tag), f"tag is {type(tag)}" + build_tag_panel = BuildTagPanel(self.driver.lib, tag=tag) + self.edit_modal = PanelModal( - btp, - self.lib.get_tag(tag_id).display_name(self.lib), + build_tag_panel, + tag.name, # TODO - display name including subtags "Edit Tag", - done_callback=(self.driver.preview_panel.update_widgets), + done_callback=self.driver.preview_panel.update_widgets, has_save=True, ) # self.edit_modal.widget.update_display_name.connect(lambda t: self.edit_modal.title_widget.setText(t)) - self.edit_modal.saved.connect(lambda: self.lib.update_tag(btp.build_tag())) + # TODO - this was update_tag() + self.edit_modal.saved.connect( + lambda: self.driver.lib.update_tag( + build_tag_panel.build_tag(), + subtag_ids=build_tag_panel.subtags, + ) + ) # panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag)) self.edit_modal.show() def add_tag_callback(self, tag_id: int): - # self.base_layout.addWidget(TagWidget(self.lib, self.lib.get_tag(tag), True)) - # self.tags.append(tag) - logging.info( - f"[TAG BOX WIDGET] ADD TAG CALLBACK: T:{tag_id} to E:{self.item.id}" - ) - logging.info(f"[TAG BOX WIDGET] SELECTED T:{self.driver.selected}") - id: int = list(self.field.keys())[0] # type: ignore - for x in self.driver.selected: - self.driver.lib.get_entry(x[1]).add_tag( - self.driver.lib, tag_id, field_id=id, field_index=-1 - ) - self.updated.emit() + logger.info("add_tag_callback", tag_id=tag_id, selected=self.driver.selected) + + tag = self.driver.lib.get_tag(tag_id=tag_id) + + for idx in self.driver.selected: + entry: Entry = self.driver.frame_content[idx] + + if not self.driver.lib.add_field_tag(entry, tag, self.field.type_key): + # TODO - add some visible error + self.error_occurred.emit(Exception("Failed to add tag")) + + self.updated.emit() + if tag_id in (TAG_FAVORITE, TAG_ARCHIVED): self.driver.update_badges() - # if type((x[0]) == ThumbButton): - # # TODO: Remove space from the special search here (tag_id:x) once that system is finalized. - # logging.info(f'I want to add tag ID {tag_id} to entry {self.item.filename}') - # self.updated.emit() - # if tag_id not in self.tags: - # self.tags.append(tag_id) - # self.set_tags(self.tags) - # elif type((x[0]) == ThumbButton): - def edit_tag_callback(self, tag: Tag): - self.lib.update_tag(tag) + self.driver.lib.update_tag(tag) def remove_tag(self, tag_id: int): - logging.info(f"[TAG BOX WIDGET] SELECTED T:{self.driver.selected}") - id: int = list(self.field.keys())[0] # type: ignore - for x in self.driver.selected: - index = self.driver.lib.get_field_index_in_entry( - self.driver.lib.get_entry(x[1]), id - ) - self.driver.lib.get_entry(x[1]).remove_tag( - self.driver.lib, tag_id, field_index=index[0] - ) + logger.info( + "remove_tag", + selected=self.driver.selected, + field_type=self.field.type, + ) + + for grid_idx in self.driver.selected: + entry = self.driver.frame_content[grid_idx] + self.driver.lib.remove_field_tag(entry, tag_id, self.field.type_key) + self.updated.emit() + if tag_id in (TAG_FAVORITE, TAG_ARCHIVED): self.driver.update_badges() - - # def show_add_button(self, value:bool): - # self.add_button.setHidden(not value) diff --git a/tagstudio/src/qt/widgets/thumb_renderer.py b/tagstudio/src/qt/widgets/thumb_renderer.py index 1e6a3ad17..e0045902b 100644 --- a/tagstudio/src/qt/widgets/thumb_renderer.py +++ b/tagstudio/src/qt/widgets/thumb_renderer.py @@ -3,7 +3,6 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -import logging import math from pathlib import Path @@ -22,6 +21,7 @@ from PIL.Image import DecompressionBombError from PySide6.QtCore import QObject, Signal, QSize from PySide6.QtGui import QPixmap + from src.qt.helpers.gradient import four_corner_gradient_background from src.core.constants import ( PLAINTEXT_TYPES, @@ -29,15 +29,15 @@ IMAGE_TYPES, RAW_IMAGE_TYPES, ) +import structlog + from src.core.utils.encoding import detect_char_encoding ImageFile.LOAD_TRUNCATED_IMAGES = True -ERROR = "[ERROR]" -WARNING = "[WARNING]" -INFO = "[INFO]" -logging.basicConfig(format="%(message)s", level=logging.INFO) +logger = structlog.get_logger(__name__) + register_heif_opener() register_avif_opener() @@ -95,7 +95,10 @@ def render( gradient=False, update_on_ratio_change=False, ): - """Internal renderer. Renders an entry/element thumbnail for the GUI.""" + """Internal renderer. Render an entry/element thumbnail for the GUI.""" + + logger.debug("rendering thumbnail", path=filepath) + image: Image.Image = None pixmap: QPixmap = None final: Image.Image = None @@ -133,8 +136,8 @@ def render( image = ImageOps.exif_transpose(image) except DecompressionBombError as e: - logging.info( - f"[ThumbRenderer]{WARNING} Couldn't Render thumbnail for {_filepath.name} ({type(e).__name__})" + logger.error( + "Couldn't Render thumbnail", filepath=filepath, error=e ) elif _filepath.suffix.lower() in RAW_IMAGE_TYPES: @@ -148,15 +151,16 @@ def render( decoder_name="raw", ) except DecompressionBombError as e: - logging.info( - f"[ThumbRenderer]{WARNING} Couldn't Render thumbnail for {_filepath.name} ({type(e).__name__})" + logger.error( + "Couldn't Render thumbnail", filepath=filepath, error=e ) + except ( rawpy._rawpy.LibRawIOError, rawpy._rawpy.LibRawFileUnsupportedError, ) as e: - logging.info( - f"[ThumbRenderer]{ERROR} Couldn't Render thumbnail for raw image {_filepath.name} ({type(e).__name__})" + logger.error( + "Couldn't Render thumbnail", filepath=filepath, error=e ) # Videos ======================================================= @@ -179,7 +183,7 @@ def render( # Plain Text =================================================== elif _filepath.suffix.lower() in PLAINTEXT_TYPES: encoding = detect_char_encoding(_filepath) - with open(_filepath, "r", encoding=encoding) as text_file: + with open(_filepath, encoding=encoding) as text_file: text = text_file.read(256) bg = Image.new("RGB", (256, 256), color="#1e1e1e") draw = ImageDraw.Draw(bg) @@ -268,9 +272,10 @@ def render( UnicodeDecodeError, ) as e: if e is not UnicodeDecodeError: - logging.info( - f"[ThumbRenderer]{ERROR}: Couldn't render thumbnail for {_filepath.name} ({type(e).__name__})" + logger.error( + "Couldn't Render thumbnail", filepath=filepath, error=e ) + if update_on_ratio_change: self.updated_ratio.emit(1) final = ThumbRenderer.thumb_broken_512.resize( diff --git a/tagstudio/src/qt/widgets/video_player.py b/tagstudio/src/qt/widgets/video_player.py index 6bb860993..9fc65604c 100644 --- a/tagstudio/src/qt/widgets/video_player.py +++ b/tagstudio/src/qt/widgets/video_player.py @@ -3,7 +3,6 @@ import logging -from pathlib import Path import typing from PySide6.QtCore import ( @@ -122,7 +121,7 @@ def __init__(self, driver: "QtDriver") -> None: autoplay_action.setCheckable(True) self.addAction(autoplay_action) autoplay_action.setChecked( - self.driver.settings.value(SettingItems.AUTOPLAY, True, bool) # type: ignore + bool(self.driver.settings.value(SettingItems.AUTOPLAY, True, type=bool)) ) autoplay_action.triggered.connect(lambda: self.toggleAutoplay()) self.autoplay = autoplay_action @@ -176,37 +175,30 @@ def wheelEvent(self, event: QWheelEvent) -> None: def eventFilter(self, obj: QObject, event: QEvent) -> bool: # This chunk of code is for the video controls. if ( - obj == self.play_pause - and event.type() == QEvent.Type.MouseButtonPress + event.type() == QEvent.Type.MouseButtonPress and event.button() == Qt.MouseButton.LeftButton # type: ignore ): - if self.player.hasVideo(): + if obj == self.play_pause and self.player.hasVideo(): self.pauseToggle() - - if ( - obj == self.mute_button - and event.type() == QEvent.Type.MouseButtonPress - and event.button() == Qt.MouseButton.LeftButton # type: ignore - ): - if self.player.hasAudio(): + elif obj == self.mute_button and self.player.hasAudio(): self.muteToggle() - if ( - obj == self.video_preview - and event.type() == QEvent.Type.GraphicsSceneHoverEnter - or event.type() == QEvent.Type.HoverEnter - ): - if self.video_preview.isUnderMouse(): - self.underMouse() - self.hover_fix_timer.start(10) - elif ( - obj == self.video_preview - and event.type() == QEvent.Type.GraphicsSceneHoverLeave - or event.type() == QEvent.Type.HoverLeave - ): - if not self.video_preview.isUnderMouse(): + elif obj == self.video_preview: + if event.type() in ( + QEvent.Type.GraphicsSceneHoverEnter, + QEvent.Type.HoverEnter, + ): + if self.video_preview.isUnderMouse(): + self.underMouse() + self.hover_fix_timer.start(10) + elif ( + event.type() + in (QEvent.Type.GraphicsSceneHoverLeave, QEvent.Type.HoverLeave) + and not self.video_preview.isUnderMouse() + ): self.hover_fix_timer.stop() self.releaseMouse() + return super().eventFilter(obj, event) def checkIfStillHovered(self) -> None: @@ -334,14 +326,13 @@ def resizeEvent(self, event: QResizeEvent) -> None: int(self.video_preview.size().height()), ) ) - return class VideoPreview(QGraphicsVideoItem): def boundingRect(self): return QRectF(0, 0, self.size().width(), self.size().height()) - def paint(self, painter, option, widget): + def paint(self, painter, option, widget=None) -> None: # painter.brush().setColor(QColor(0, 0, 0, 255)) # You can set any shape you want here. # RoundedRect is the standard rectangle with rounded corners. diff --git a/tagstudio/tag_studio.py b/tagstudio/tag_studio.py old mode 100644 new mode 100755 index 1861474f3..a6b47b6bc --- a/tagstudio/tag_studio.py +++ b/tagstudio/tag_studio.py @@ -1,16 +1,23 @@ +#!/usr/bin/env python # Copyright (C) 2024 Travis Abendshien (CyanVoxel). # Licensed under the GPL-3.0 License. # Created for TagStudio: https://github.com/CyanVoxel/TagStudio """TagStudio launcher.""" -from src.core.ts_core import TagStudioCore -from src.cli.ts_cli import CliDriver # type: ignore +import structlog +import logging + from src.qt.ts_qt import QtDriver import argparse import traceback +structlog.configure( + wrapper_class=structlog.make_filtering_bound_logger(logging.INFO), +) + + def main(): # appid = "cyanvoxel.tagstudio.9" # ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) @@ -48,27 +55,11 @@ def main(): type=str, help="User interface option for TagStudio. Options: qt, cli (Default: qt)", ) - parser.add_argument( - "--ci", - action=argparse.BooleanOptionalAction, - help="Exit the application after checking it starts without any problem. Meant for CI check.", - ) args = parser.parse_args() + from src.core.library import alchemy as backend - core = TagStudioCore() # The TagStudio Core instance. UI agnostic. - driver = None # The UI driver instance. - ui_name: str = "unknown" # Display name for the UI, used in logs. - - # Driver selection based on parameters. - if args.ui and args.ui == "qt": - driver = QtDriver(core, args) - ui_name = "Qt" - elif args.ui and args.ui == "cli": - driver = CliDriver(core, args) - ui_name = "CLI" - else: - driver = QtDriver(core, args) - ui_name = "Qt" + driver = QtDriver(backend, args) + ui_name = "Qt" # Run the chosen frontend driver. try: diff --git a/tagstudio/tests/conftest.py b/tagstudio/tests/conftest.py index 2c5cd225d..dd34d205f 100644 --- a/tagstudio/tests/conftest.py +++ b/tagstudio/tests/conftest.py @@ -1,42 +1,110 @@ import sys import pathlib +from tempfile import TemporaryDirectory +from unittest.mock import patch, Mock import pytest -from syrupy.extensions.json import JSONSnapshotExtension CWD = pathlib.Path(__file__).parent - +# this needs to be above `src` imports sys.path.insert(0, str(CWD.parent)) -from src.core.library import Tag, Library +from src.core.library import Library, Tag +from src.core.library.alchemy.enums import TagColor +from src.core.library.alchemy.fields import TagBoxField, _FieldID +from tests.test_library import generate_entry +from src.core.library import alchemy as backend +from src.qt.ts_qt import QtDriver @pytest.fixture -def test_tag(): - yield Tag( - id=1, - name="Tag Name", - shorthand="TN", - aliases=["First A", "Second A"], - subtags_ids=[2, 3, 4], - color="", - ) +def cwd(): + return CWD @pytest.fixture -def test_library(): - lib_dir = CWD / "fixtures" / "library" +def library(request): + # when no param is passed, use the default + library_path = "/tmp/" + if hasattr(request, "param"): + if isinstance(request.param, TemporaryDirectory): + library_path = request.param.name + else: + library_path = request.param lib = Library() - ret_code = lib.open_library(lib_dir) - assert ret_code == 1 - # create files for the entries - for entry in lib.entries: - (lib_dir / entry.filename).touch() + lib.open_library(library_path, ":memory:") + + tag = Tag( + name="foo", + color=TagColor.RED, + ) + + tag2 = Tag( + name="bar", + color=TagColor.BLUE, + ) + + assert lib.add_tag(tag) + + # default item with deterministic name + entry = generate_entry(path=pathlib.Path("foo.txt")) + + entry.tag_box_fields = [ + TagBoxField( + type_key=_FieldID.TAGS.name, + tags={tag}, + ), + TagBoxField( + type_key=_FieldID.TAGS_META.name, + # tags={tag2} + ), + ] + + entry2 = generate_entry(path=pathlib.Path("one/two/bar.md")) + entry2.tag_box_fields = [ + TagBoxField( + tags={tag2}, + type_key=_FieldID.TAGS_META.name, + ), + ] + + assert lib.add_entries([entry, entry2]) + assert len(lib.tags) == 4 yield lib @pytest.fixture -def snapshot_json(snapshot): - return snapshot.with_defaults(extension_class=JSONSnapshotExtension) +def qt_driver(qtbot, library): + with TemporaryDirectory() as tmp_dir: + + class Args: + config_file = pathlib.Path(tmp_dir) / "tagstudio.ini" + open = pathlib.Path(tmp_dir) + ci = True + + # patch CustomRunnable + + with patch("src.qt.ts_qt.Consumer"), patch("src.qt.ts_qt.CustomRunnable"): + driver = QtDriver(backend, Args()) + + driver.main_window = Mock() + driver.preview_panel = Mock() + driver.flow_container = Mock() + driver.item_thumbs = [] + + driver.lib = library + # TODO - downsize this method and use it + # driver.start() + driver.frame_content = list(library._entries) + yield driver + + +@pytest.fixture +def generate_tag(): + def inner(name, **kwargs): + params = dict(name=name, color=TagColor.RED) | kwargs + return Tag(**params) + + yield inner diff --git a/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[--nomatch--].json b/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[--nomatch--].json deleted file mode 100644 index fe51488c7..000000000 --- a/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[--nomatch--].json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[First].json b/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[First].json deleted file mode 100644 index e4e6902ca..000000000 --- a/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[First].json +++ /dev/null @@ -1,6 +0,0 @@ -[ - [ - "", - 2 - ] -] diff --git a/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[Second].json b/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[Second].json deleted file mode 100644 index 920ff4959..000000000 --- a/tagstudio/tests/core/__snapshots__/test_lib/test_library_search[Second].json +++ /dev/null @@ -1,6 +0,0 @@ -[ - [ - "", - 1 - ] -] diff --git a/tagstudio/tests/core/__snapshots__/test_lib/test_open_library.json b/tagstudio/tests/core/__snapshots__/test_lib/test_open_library.json deleted file mode 100644 index a576676f5..000000000 --- a/tagstudio/tests/core/__snapshots__/test_lib/test_open_library.json +++ /dev/null @@ -1,4 +0,0 @@ -[ - "{'id': 1, 'filename': 'foo.txt', 'path': '.', 'fields': [{6: [1001]}]}", - "{'id': 2, 'filename': 'bar.txt', 'path': '.', 'fields': [{6: [1000]}]}" -] diff --git a/tagstudio/tests/core/test_lib.py b/tagstudio/tests/core/test_lib.py deleted file mode 100644 index 997598f22..000000000 --- a/tagstudio/tests/core/test_lib.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - - -def test_open_library(test_library, snapshot_json): - assert test_library.entries == snapshot_json - - -@pytest.mark.parametrize( - ["query"], - [ - ("First",), - ("Second",), - ("--nomatch--",), - ], -) -def test_library_search(test_library, query, snapshot_json): - res = test_library.search_library(query) - assert res == snapshot_json diff --git a/tagstudio/tests/core/test_tags.py b/tagstudio/tests/core/test_tags.py deleted file mode 100644 index 43cde4270..000000000 --- a/tagstudio/tests/core/test_tags.py +++ /dev/null @@ -1,8 +0,0 @@ -def test_subtag(test_tag): - test_tag.remove_subtag(2) - test_tag.remove_subtag(2) - - test_tag.add_subtag(5) - # repeated add should not add the subtag - test_tag.add_subtag(5) - assert test_tag.subtag_ids == [3, 4, 5] diff --git a/tagstudio/tests/fixtures/library/.TagStudio/ts_library.json b/tagstudio/tests/fixtures/library/.TagStudio/ts_library.json deleted file mode 100644 index eeab9fd64..000000000 --- a/tagstudio/tests/fixtures/library/.TagStudio/ts_library.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "ts-version": "9.3.1", - "ext_list": [ - ".json", - ".xmp", - ".aae" - ], - "is_exclude_list": true, - "tags": [ - { - "id": 0, - "name": "Archived", - "aliases": [ - "Archive" - ], - "color": "Red" - }, - { - "id": 1, - "name": "Favorite", - "aliases": [ - "Favorited", - "Favorites" - ], - "color": "Yellow" - }, - { - "id": 1000, - "name": "first", - "shorthand": "first", - "color": "magenta" - }, - { - "id": 1001, - "name": "second", - "shorthand": "second", - "color": "blue" - } - ], - "collations": [], - "fields": [], - "macros": [], - "entries": [ - { - "id": 1, - "filename": "foo.txt", - "path": ".", - "fields": [ - { - "6": [ - 1001 - ] - } - ] - }, - { - "id": 2, - "filename": "bar.txt", - "path": ".", - "fields": [ - { - "6": [ - 1000 - ] - } - ] - } - ] -} diff --git a/tagstudio/tests/fixtures/result.dupeguru b/tagstudio/tests/fixtures/result.dupeguru new file mode 100644 index 000000000..31341bb8b --- /dev/null +++ b/tagstudio/tests/fixtures/result.dupeguru @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tagstudio/tests/fixtures/sidecar_newgrounds.json b/tagstudio/tests/fixtures/sidecar_newgrounds.json new file mode 100644 index 000000000..d72717324 --- /dev/null +++ b/tagstudio/tests/fixtures/sidecar_newgrounds.json @@ -0,0 +1,10 @@ +{ + "tags": [ + "ng_tag", + "ng_tag2" + ], + "date": "2024-01-02", + "description": "NG description", + "user": "NG artist", + "post_url": "https://ng.com" +} diff --git a/tagstudio/tests/macros/test_dupe_entries.py b/tagstudio/tests/macros/test_dupe_entries.py new file mode 100644 index 000000000..06fa0e947 --- /dev/null +++ b/tagstudio/tests/macros/test_dupe_entries.py @@ -0,0 +1,26 @@ +import pathlib + +from src.core.utils.dupe_files import DupeRegistry +from tests.test_library import generate_entry + +CWD = pathlib.Path(__file__).parent + + +def test_refresh_dupe_files(library): + entry = generate_entry(path=pathlib.Path("bar/foo.txt")) + entry2 = generate_entry(path=pathlib.Path("foo/foo.txt")) + + library.add_entries([entry, entry2]) + + registry = DupeRegistry(library=library) + + dupe_file_path = CWD.parent / "fixtures" / "result.dupeguru" + registry.refresh_dupe_files(dupe_file_path) + + assert len(registry.groups) == 1 + paths = [entry.path for entry in registry.groups[0]] + assert paths == [ + pathlib.Path("bar/foo.txt"), + pathlib.Path("foo.txt"), + pathlib.Path("foo/foo.txt"), + ] diff --git a/tagstudio/tests/macros/test_missing_files.py b/tagstudio/tests/macros/test_missing_files.py new file mode 100644 index 000000000..68e9c2572 --- /dev/null +++ b/tagstudio/tests/macros/test_missing_files.py @@ -0,0 +1,31 @@ +import pathlib +from tempfile import TemporaryDirectory + +import pytest + +from src.core.library import Library +from src.core.library.alchemy.enums import FilterState +from src.core.utils.missing_files import MissingRegistry + +CWD = pathlib.Path(__file__).parent + + +@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True) +def test_refresh_missing_files(library: Library): + registry = MissingRegistry(library=library) + + # touch the file `one/two/bar.md` but in wrong location to simulate a moved file + (library.library_dir / "bar.md").touch() + + # no files actually exist, so it should return all entries + assert list(registry.refresh_missing_files()) == [0, 1] + + # neither of the library entries exist + assert len(registry.missing_files) == 2 + + # iterate through two files + assert list(registry.fix_missing_files()) == [1, 2] + + # `bar.md` should be relinked to new correct path + _, entries = library.search_library(FilterState(path="bar.md")) + assert entries[0].path == pathlib.Path("bar.md") diff --git a/tagstudio/tests/macros/test_refresh_dir.py b/tagstudio/tests/macros/test_refresh_dir.py new file mode 100644 index 000000000..f72572de1 --- /dev/null +++ b/tagstudio/tests/macros/test_refresh_dir.py @@ -0,0 +1,19 @@ +import pathlib +from tempfile import TemporaryDirectory + +import pytest +from src.core.utils.refresh_dir import RefreshDirTracker + +CWD = pathlib.Path(__file__).parent + + +@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True) +def test_refresh_new_files(library): + registry = RefreshDirTracker(library=library) + + # touch new files to simulate new files + (library.library_dir / "foo.md").touch() + + assert not list(registry.refresh_dir()) + + assert registry.files_not_in_library == [pathlib.Path("foo.md")] diff --git a/tagstudio/tests/macros/test_sidecar.py b/tagstudio/tests/macros/test_sidecar.py new file mode 100644 index 000000000..aa782fa02 --- /dev/null +++ b/tagstudio/tests/macros/test_sidecar.py @@ -0,0 +1,38 @@ +import shutil +from pathlib import Path +from tempfile import TemporaryDirectory + +import pytest + +from src.core.enums import MacroID +from src.core.library.alchemy.fields import _FieldID + + +@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True) +def test_sidecar_macro(qt_driver, library, cwd): + entry = next(library._entries_full) + entry.path = Path("newgrounds/foo.txt") + + fixture = cwd / "fixtures/sidecar_newgrounds.json" + dst = library.library_dir / "newgrounds" / (entry.path.stem + ".json") + dst.parent.mkdir() + shutil.copy(fixture, dst) + + qt_driver.frame_content = [entry] + qt_driver.run_macro(MacroID.SIDECAR, 0) + + entry = next(library._entries_full) + new_fields = ( + (_FieldID.DESCRIPTION.name, "NG description"), + (_FieldID.ARTIST.name, "NG artist"), + (_FieldID.SOURCE.name, "https://ng.com"), + (_FieldID.TAGS.name, None), + ) + found = [(field.type.key, field.value) for field in entry.fields] + + # `new_fields` should be subset of `found` + for field in new_fields: + assert field in found, f"Field not found: {field} / {found}" + + expected_tags = {"ng_tag", "ng_tag2"} + assert {x.name in expected_tags for x in entry.tags} diff --git a/tagstudio/tests/qt/__snapshots__/test_folders_to_tags.ambr b/tagstudio/tests/qt/__snapshots__/test_folders_to_tags.ambr new file mode 100644 index 000000000..b54f3fe97 --- /dev/null +++ b/tagstudio/tests/qt/__snapshots__/test_folders_to_tags.ambr @@ -0,0 +1,4 @@ +# serializer version: 1 +# name: test_generate_preview_data + BranchData(dirs={'two': BranchData(dirs={}, files=['bar.md'], tag=)}, files=[], tag=None) +# --- diff --git a/tagstudio/tests/qt/test_driver.py b/tagstudio/tests/qt/test_driver.py new file mode 100644 index 000000000..104b0fffb --- /dev/null +++ b/tagstudio/tests/qt/test_driver.py @@ -0,0 +1,62 @@ +from pathlib import Path +from unittest.mock import Mock + +from src.core.library import Entry +from src.core.library.json.library import ItemType +from src.qt.widgets.item_thumb import ItemThumb + + +def test_update_thumbs(qt_driver): + qt_driver.frame_content = [Entry(path=Path("/tmp/foo"))] + + qt_driver.item_thumbs = [] + for i in range(3): + qt_driver.item_thumbs.append( + ItemThumb( + mode=ItemType.ENTRY, + library=qt_driver.lib, + driver=qt_driver, + thumb_size=(100, 100), + grid_idx=i, + ) + ) + + qt_driver.update_thumbs() + + for idx, thumb in enumerate(qt_driver.item_thumbs): + # only first item is visible + assert thumb.isVisible() == (idx == 0) + + +def test_select_item_bridge(qt_driver): + # mock some props since we're not running `start()` + qt_driver.autofill_action = Mock() + qt_driver.sort_fields_action = Mock() + + entry = next(qt_driver.lib._entries) + + # set the content manually + qt_driver.frame_content = [entry] * 3 + + qt_driver.filter.page_size = 3 + qt_driver._init_thumb_grid() + assert len(qt_driver.item_thumbs) == 3 + + # select first item + qt_driver.select_item(0, False, False) + assert qt_driver.selected == [0] + + # add second item to selection + qt_driver.select_item(1, False, bridge=True) + assert qt_driver.selected == [0, 1] + + # add third item to selection + qt_driver.select_item(2, False, bridge=True) + assert qt_driver.selected == [0, 1, 2] + + # select third item only + qt_driver.select_item(2, False, bridge=False) + assert qt_driver.selected == [2] + + qt_driver.select_item(0, False, bridge=True) + assert qt_driver.selected == [0, 1, 2] diff --git a/tagstudio/tests/qt/test_flow_widget.py b/tagstudio/tests/qt/test_flow_widget.py new file mode 100644 index 000000000..ccfac874a --- /dev/null +++ b/tagstudio/tests/qt/test_flow_widget.py @@ -0,0 +1,18 @@ +from PySide6.QtCore import QRect +from PySide6.QtWidgets import QWidget, QPushButton + +from src.qt.flowlayout import FlowLayout + + +def test_flow_layout_happy_path(qtbot): + class Window(QWidget): + def __init__(self): + super().__init__() + + self.flow_layout = FlowLayout(self) + self.flow_layout.setGridEfficiency(True) + self.flow_layout.addWidget(QPushButton("Short")) + + window = Window() + assert window.flow_layout.count() + assert window.flow_layout._do_layout(QRect(0, 0, 0, 0), False) diff --git a/tagstudio/tests/qt/test_folders_to_tags.py b/tagstudio/tests/qt/test_folders_to_tags.py new file mode 100644 index 000000000..b7f079f18 --- /dev/null +++ b/tagstudio/tests/qt/test_folders_to_tags.py @@ -0,0 +1,7 @@ +from src.qt.modals.folders_to_tags import generate_preview_data + + +def test_generate_preview_data(library, snapshot): + preview = generate_preview_data(library) + + assert preview == snapshot diff --git a/tagstudio/tests/qt/test_item_thumb.py b/tagstudio/tests/qt/test_item_thumb.py new file mode 100644 index 000000000..9d6ad7225 --- /dev/null +++ b/tagstudio/tests/qt/test_item_thumb.py @@ -0,0 +1,21 @@ +import pytest + +from src.core.library import ItemType +from src.qt.widgets.item_thumb import ItemThumb, BadgeType + + +@pytest.mark.parametrize("new_value", (True, False)) +def test_badge_visual_state(library, qt_driver, new_value): + entry = next(qt_driver.lib._entries) + + thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100), 0) + + qt_driver.frame_content = [entry] + qt_driver.selected = [0] + qt_driver.item_thumbs = [thumb] + + thumb.badges[BadgeType.FAVORITE].setChecked(new_value) + assert thumb.badges[BadgeType.FAVORITE].isChecked() == new_value + # TODO + # assert thumb.favorite_badge.isHidden() == initial_state + assert thumb.is_favorite == new_value diff --git a/tagstudio/tests/qt/test_preview_panel.py b/tagstudio/tests/qt/test_preview_panel.py new file mode 100644 index 000000000..ec1ef60c9 --- /dev/null +++ b/tagstudio/tests/qt/test_preview_panel.py @@ -0,0 +1,119 @@ +from src.core.library.alchemy.enums import FieldTypeEnum +from src.core.library.alchemy.fields import _FieldID, TextField +from src.qt.widgets.preview_panel import PreviewPanel +from tests.test_library import generate_entry + + +def test_update_widgets_not_selected(qt_driver, library): + qt_driver.frame_content = list(library._entries) + qt_driver.selected = [] + + panel = PreviewPanel(library, qt_driver) + panel.update_widgets() + + assert panel.preview_img.isVisible() + assert panel.file_label.text() == "No Items Selected" + + +def test_update_widgets_single_selected(qt_driver, library): + qt_driver.frame_content = list(library._entries) + qt_driver.selected = [0] + + panel = PreviewPanel(library, qt_driver) + panel.update_widgets() + + assert panel.preview_img.isVisible() + + +def test_update_widgets_multiple_selected(qt_driver, library): + # entry with no tag fields + entry = generate_entry(fields=[TextField(type_key=_FieldID.TITLE.name)]) + + assert not entry.tag_box_fields + + library.add_entries([entry]) + assert library.entries_count == 3 + + qt_driver.frame_content = list(library._entries) + qt_driver.selected = [0, 1, 2] + + panel = PreviewPanel(library, qt_driver) + panel.update_widgets() + + assert {f.type_key for f in panel.common_fields} == { + _FieldID.TITLE.name, + } + + assert {f.type_key for f in panel.mixed_fields} == { + _FieldID.TAGS.name, + _FieldID.TAGS_META.name, + } + + +def test_write_container_text_line(qt_driver, library): + # Given + panel = PreviewPanel(library, qt_driver) + entry = next(library._entries_full) + + field = entry.text_fields[0] + assert len(entry.text_fields) == 1 + assert field.type.type == FieldTypeEnum.TEXT_LINE + assert field.type.name == "Title" + + # set any value + field.value = "foo" + panel.write_container(0, field) + panel.selected = [0] + + assert len(panel.containers) == 1 + container = panel.containers[0] + widget = container.get_inner_widget() + # test it's not "mixed data" + assert widget.text_label.text() == "foo" + + # When update and submit modal + modal = panel.containers[0].modal + modal.widget.text_edit.setText("bar") + modal.save_button.click() + + # Then reload entry + entry = next(library._entries_full) + # the value was updated + assert entry.text_fields[0].value == "bar" + + +def test_remove_field(qt_driver, library): + # Given + panel = PreviewPanel(library, qt_driver) + entries = list(library._entries_full) + qt_driver.frame_content = entries + panel.selected = [1] + + field = entries[1].text_fields[0] + panel.write_container(0, field) + panel.remove_field(field) + + entries = list(library._entries_full) + assert not entries[1].text_fields + + +def test_update_field(qt_driver, library): + panel = PreviewPanel(library, qt_driver) + + qt_driver.frame_content = list(library._entries)[:2] + qt_driver.selected = [0, 1] + panel.selected = [0, 1] + + field = [ + x + for x in next(library._entries_full).text_fields + if x.type.type == FieldTypeEnum.TEXT_LINE + ][0] + + panel.update_field(field, "meow") + + for entry in list(library._entries_full)[:2]: + field = [ + x for x in entry.text_fields if x.type.type == FieldTypeEnum.TEXT_LINE + ][0] + assert field.value == "meow" diff --git a/tagstudio/tests/qt/test_tag_panel.py b/tagstudio/tests/qt/test_tag_panel.py new file mode 100644 index 000000000..c22298882 --- /dev/null +++ b/tagstudio/tests/qt/test_tag_panel.py @@ -0,0 +1,24 @@ +from src.core.library import Tag +from src.qt.modals.build_tag import BuildTagPanel + + +def test_tag_panel(qtbot, library): + panel = BuildTagPanel(library) + + qtbot.addWidget(panel) + + +def test_add_tag_callback(qt_driver): + # Given + assert len(qt_driver.lib.tags) == 4 + qt_driver.add_tag_action_callback() + + # When + qt_driver.modal.widget.name_field.setText("xxx") + qt_driver.modal.widget.color_field.setCurrentIndex(1) + qt_driver.modal.saved.emit() + + # Then + tags: set[Tag] = qt_driver.lib.tags + assert len(tags) == 5 + assert "xxx" in {tag.name for tag in tags} diff --git a/tagstudio/tests/qt/test_tag_search_panel.py b/tagstudio/tests/qt/test_tag_search_panel.py new file mode 100644 index 000000000..6ea6590d3 --- /dev/null +++ b/tagstudio/tests/qt/test_tag_search_panel.py @@ -0,0 +1,11 @@ +from src.qt.modals.tag_search import TagSearchPanel + + +def test_update_tags(qtbot, library): + # Given + panel = TagSearchPanel(library) + + qtbot.addWidget(panel) + + # When + panel.update_tags() diff --git a/tagstudio/tests/qt/test_tag_widget.py b/tagstudio/tests/qt/test_tag_widget.py new file mode 100644 index 000000000..60e9cff24 --- /dev/null +++ b/tagstudio/tests/qt/test_tag_widget.py @@ -0,0 +1,118 @@ +from unittest.mock import patch + +from src.core.library import Entry +from src.core.library.alchemy.fields import _FieldID +from src.qt.widgets.tag import TagWidget +from src.qt.widgets.tag_box import TagBoxWidget +from src.qt.modals.build_tag import BuildTagPanel + + +def test_tag_widget(qtbot, library, qt_driver): + # given + entry = next(library._entries_full) + field = entry.tag_box_fields[0] + + tag_widget = TagBoxWidget(field, "title", qt_driver) + + qtbot.add_widget(tag_widget) + + assert not tag_widget.add_modal.isVisible() + + # when/then check no exception is raised + tag_widget.add_button.clicked.emit() + # check `tag_widget.add_modal` is visible + assert tag_widget.add_modal.isVisible() + + +def test_tag_widget_add_existing_raises(qtbot, library, qt_driver): + # Given + entry = next(library._entries_full) + tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] + + assert len(entry.tags) == 1 + tag = next(iter(entry.tags)) + tag_widget = TagBoxWidget(tag_field, "title", qt_driver) + + # When + qtbot.add_widget(tag_widget) + tag_widget.driver.frame_content = [entry] + tag_widget.driver.selected = [0] + + # Then + with patch.object(tag_widget, "error_occurred") as mocked: + tag_widget.add_modal.widget.tag_chosen.emit(tag.id) + assert mocked.emit.called + + +def test_tag_widget_add_new_pass(qtbot, library, qt_driver, generate_tag): + # Given + entry = next(library._entries_full) + field = entry.tag_box_fields[0] + + tag = generate_tag(name="new_tag") + library.add_tag(tag) + + tag_widget = TagBoxWidget(field, "title", qt_driver) + + qtbot.add_widget(tag_widget) + + tag_widget.driver.selected = [0] + with patch.object(tag_widget, "error_occurred") as mocked: + # When + tag_widget.add_modal.widget.tag_chosen.emit(tag.id) + + # Then + assert not mocked.emit.called + + +def test_tag_widget_remove(qtbot, qt_driver): + entry: Entry = next(qt_driver.lib._entries_full) + + tag = list(entry.tags)[0] + assert tag + + assert entry.tag_box_fields + tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] + + tag_widget = TagBoxWidget(tag_field, "title", qt_driver) + tag_widget.driver.selected = [0] + + qtbot.add_widget(tag_widget) + + tag_widget = tag_widget.base_layout.itemAt(0).widget() + assert isinstance(tag_widget, TagWidget) + + tag_widget.remove_button.clicked.emit() + + entry: Entry = next(qt_driver.lib._entries_full) + assert not entry.tag_box_fields[0].tags + + +def test_tag_widget_edit(qtbot, qt_driver): + # Given + entry: Entry = next(qt_driver.lib._entries_full) + + tag = list(entry.tags)[0] + assert tag + + assert entry.tag_box_fields + tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] + + tag_box_widget = TagBoxWidget(tag_field, "title", qt_driver) + tag_box_widget.driver.selected = [0] + + qtbot.add_widget(tag_box_widget) + + tag_widget = tag_box_widget.base_layout.itemAt(0).widget() + assert isinstance(tag_widget, TagWidget) + + # When + actions = tag_widget.bg_button.actions() + edit_action = [a for a in actions if a.text() == "Edit"][0] + edit_action.triggered.emit() + + # Then + panel = tag_box_widget.edit_modal.widget + assert isinstance(panel, BuildTagPanel) + assert panel.tag.name == tag.name + assert panel.name_field.text() == tag.name diff --git a/tagstudio/tests/test_folders_tags.py b/tagstudio/tests/test_folders_tags.py new file mode 100644 index 000000000..b65599a89 --- /dev/null +++ b/tagstudio/tests/test_folders_tags.py @@ -0,0 +1,7 @@ +from src.qt.modals.folders_to_tags import folders_to_tags + + +def test_folders_to_tags(library): + folders_to_tags(library) + entry = [x for x in library._entries_full if "bar.md" in str(x.path)][0] + assert {x.name for x in entry.tags} == {"two", "bar"} diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py new file mode 100644 index 000000000..0ec7528a6 --- /dev/null +++ b/tagstudio/tests/test_library.py @@ -0,0 +1,326 @@ +import random +import string +from pathlib import Path, PureWindowsPath +from tempfile import TemporaryDirectory + +import pytest + +from src.core.constants import LibraryPrefs +from src.core.library.alchemy import Entry +from src.core.library.alchemy import Library +from src.core.library.alchemy.enums import FilterState +from src.core.library.alchemy.fields import _FieldID, TextField + + +def generate_entry(*, path: Path = None, **kwargs) -> Entry: + if not path: + # TODO - be sure no collision happens + name = "".join(random.choices(string.ascii_lowercase, k=10)) + path = Path(name) + + return Entry( + path=path, + **kwargs, + ) + + +def test_library_bootstrap(): + with TemporaryDirectory() as tmp_dir: + lib = Library() + lib.open_library(tmp_dir) + assert lib.engine + + +def test_library_add_file(): + """Check Entry.path handling for insert vs lookup""" + with TemporaryDirectory() as tmp_dir: + # create file in tmp_dir + file_path = Path(tmp_dir) / "bar.txt" + file_path.write_text("bar") + + entry = Entry(path=file_path) + + lib = Library() + lib.open_library(tmp_dir) + assert not lib.has_path_entry(entry.path) + + assert lib.add_entries([entry]) + + assert lib.has_path_entry(entry.path) is True + + +def test_create_tag(library, generate_tag): + # tag already exists + assert not library.add_tag(generate_tag("foo")) + + # new tag name + tag = library.add_tag(generate_tag("xxx", id=123)) + assert tag + assert tag.id == 123 + + tag_inc = library.add_tag(generate_tag("yyy")) + assert tag_inc.id == 1002 + + +def test_library_search(library, generate_tag): + assert library.entries_count == 2 + entry = next(library._entries_full) + tag = list(entry.tags)[0] + + query_count, items = library.search_library( + FilterState( + tag=tag.name, + ), + ) + + assert query_count == 1 + assert len(items) == 1 + + entry = items[0] + assert {x.name for x in entry.tags} == { + "foo", + } + + assert entry.tag_box_fields + + +def test_tag_search(library): + tag = library.tags[0] + + assert library.search_tags( + FilterState(tag=tag.name.lower()), + ) + + assert library.search_tags( + FilterState(tag=tag.name.upper()), + ) + + assert not library.search_tags( + FilterState(tag=tag.name * 2), + ) + + +def test_get_entry(library): + entry = next(library._entries) + assert entry.id + + _, entries = library.search_library(FilterState(id=entry.id)) + assert len(entries) == 1 + entry = entries[0] + assert entry.path + assert entry.tags + + +def test_entries_count(library): + entries = [generate_entry() for _ in range(10)] + library.add_entries(entries) + matches, page = library.search_library( + FilterState( + page_size=5, + ) + ) + + assert matches == 12 + assert len(page) == 5 + + +def test_add_field_to_entry(library): + # Given + item_path = Path("xxx") + entry = generate_entry(path=item_path) + # meta tags + content tags + assert len(entry.tag_box_fields) == 2 + + library.add_entries([entry]) + + # When + library.add_entry_field_type(entry.id, field_id=_FieldID.TAGS) + + # Then + entry = [x for x in library._entries_full if x.path == item_path][0] + # meta tags and tags field present + assert len(entry.tag_box_fields) == 3 + + +def test_add_field_tag(library, generate_tag): + # Given + tag_name = "xxx" + tag = generate_tag(tag_name) + entry = next(library._entries_full) + tag_field = entry.tag_box_fields[0] + + # When + library.add_field_tag(entry, tag, tag_field.type_key) + + # Then + _, entries = library.search_library(FilterState(id=entry.id)) + tag_field = entries[0].tag_box_fields[0] + assert [x.name for x in tag_field.tags if x.name == tag_name] + + +def test_subtags_add(library, generate_tag): + # Given + tag = library.tags[0] + assert tag.id is not None + + subtag = generate_tag("subtag1") + subtag = library.add_tag(subtag) + assert subtag.id is not None + + # When + assert library.add_subtag(tag.id, subtag.id) + + # Then + assert tag.id is not None + tag = library.get_tag(tag.id) + assert tag.subtag_ids + + +@pytest.mark.parametrize("is_exclude", [True, False]) +def test_search_filter_extensions(library, is_exclude): + # Given + entries = list(library._entries) + assert len(entries) == 2, entries + + library.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, is_exclude) + library.set_prefs(LibraryPrefs.EXTENSION_LIST, ["md"]) + + # When + query_count, items = library.search_library( + FilterState(), + ) + + # Then + assert query_count == 1 + assert len(items) == 1 + + entry = items[0] + assert (entry.path.suffix == ".txt") == is_exclude + + +def test_search_library_case_insensitive(library): + # Given + entries = list(library._entries_full) + assert len(entries) == 2, entries + + entry = entries[0] + tag = list(entry.tags)[0] + + # When + query_count, items = library.search_library( + FilterState(tag=tag.name.upper()), + ) + + # Then + assert query_count == 1 + assert len(items) == 1 + + assert items[0].id == entry.id + + +def test_preferences(library): + for pref in LibraryPrefs: + assert library.prefs(pref) == pref.value + + +def test_save_windows_path(library, generate_tag): + # pretend we are on windows and create `Path` + entry = Entry(path=PureWindowsPath("foo\\bar.txt")) + tag = generate_tag("win_path") + tag_name = tag.name + + library.add_entries([entry]) + # library.add_tag(tag) + library.add_field_tag(entry, tag, create_field=True) + + _, found = library.search_library(FilterState(tag=tag_name)) + assert found + + # path should be saved in posix format + assert str(found[0].path) == "foo/bar.txt" + + +def test_remove_entry_field(library): + entry = next(library._entries_full) + + title_field = entry.text_fields[0] + + library.remove_entry_field(title_field, [entry.id]) + + entry = next(library._entries_full) + assert not entry.text_fields + + +def test_update_entry_field(library): + entry = next(library._entries_full) + + title_field = entry.text_fields[0] + + library.update_entry_field( + entry.id, + title_field, + "new value", + ) + + entry = next(library._entries_full) + assert entry.text_fields[0].value == "new value" + + +def test_mirror_entry_fields(library, snapshot): + entry = next(library._entries_full) + + target_entry = generate_entry( + path=Path("xxx"), + fields=[ + TextField( + type_key=_FieldID.NOTES.name, + value="notes", + ) + ], + ) + + entry_id = library.add_entries([target_entry])[0] + + _, entries = library.search_library(FilterState(id=entry_id)) + new_entry = entries[0] + + library.mirror_entry_fields(new_entry, entry) + + _, entries = library.search_library(FilterState(id=entry_id)) + entry = entries[0] + + assert len(entry.fields) == 4 + assert {x.type_key for x in entry.fields} == { + _FieldID.TITLE.name, + _FieldID.NOTES.name, + _FieldID.TAGS_META.name, + _FieldID.TAGS.name, + } + + +@pytest.mark.parametrize( + ["item_path", "found"], + [ + ("one/two/bar.md", True), + ("two/bar.md", False), # partial match should not return + ], +) +def test_path_search(library, item_path, found): + # When + cnt, entries = library.search_library(FilterState(path=item_path)) + # Then + assert bool(cnt) == found + + +def test_remove_tag_from_field(library): + entry = next(library._entries_full) + + for field in entry.tag_box_fields: + for tag in field.tags: + removed_tag = tag.name + library.remove_tag_from_field(tag, field) + break + + entry = next(library._entries_full) + for field in entry.tag_box_fields: + assert removed_tag not in [tag.name for tag in field.tags] From b6d75cc1060474e9f21ebc202f10d4b0430a3a98 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Thu, 5 Sep 2024 17:10:08 +0700 Subject: [PATCH 10/25] change entries getter --- tagstudio/src/core/library/alchemy/library.py | 41 ++++++------ tagstudio/src/core/utils/missing_files.py | 2 +- tagstudio/src/qt/modals/folders_to_tags.py | 4 +- tagstudio/tests/conftest.py | 12 +++- tagstudio/tests/macros/test_sidecar.py | 11 ++-- tagstudio/tests/qt/test_driver.py | 4 +- tagstudio/tests/qt/test_item_thumb.py | 4 +- tagstudio/tests/qt/test_preview_panel.py | 27 ++++---- tagstudio/tests/qt/test_tag_widget.py | 33 +++++----- tagstudio/tests/test_folders_tags.py | 4 +- tagstudio/tests/test_library.py | 63 ++++++++----------- 11 files changed, 96 insertions(+), 109 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 933d1168b..03be9c9a1 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -217,33 +217,28 @@ def entries_count(self) -> int: with Session(self.engine) as session: return session.scalar(select(func.count(Entry.id))) - @property - def _entries(self) -> Iterator[Entry]: + def get_entries(self, with_joins: bool = False) -> Iterator[Entry]: """Load entries without joins.""" with Session(self.engine) as session: - entries = session.execute(select(Entry).distinct()).scalars() # .unique() - for entry in entries: - yield entry - session.expunge(entry) - - @property - def _entries_full(self) -> Iterator[Entry]: - """Load entries with joins.""" - with Session(self.engine) as session: - stmt = ( - select(Entry) - .outerjoin(Entry.text_fields) - .outerjoin(Entry.datetime_fields) - .outerjoin(Entry.tag_box_fields) - .options( - contains_eager(Entry.text_fields), - contains_eager(Entry.datetime_fields), - contains_eager(Entry.tag_box_fields).selectinload(TagBoxField.tags), + stmt = select(Entry) + if with_joins: + stmt = ( + stmt.outerjoin(Entry.text_fields) + .outerjoin(Entry.datetime_fields) + .outerjoin(Entry.tag_box_fields) + .options( + contains_eager(Entry.text_fields), + contains_eager(Entry.datetime_fields), + contains_eager(Entry.tag_box_fields).selectinload( + TagBoxField.tags + ), + ) ) - .distinct() - ) + stmt = stmt.distinct() - entries = session.execute(stmt).scalars().unique() # .all() + entries = session.execute(stmt).scalars() + if with_joins: + entries = entries.unique() for entry in entries: yield entry diff --git a/tagstudio/src/core/utils/missing_files.py b/tagstudio/src/core/utils/missing_files.py index 86f3714e1..8ecf9a4c5 100644 --- a/tagstudio/src/core/utils/missing_files.py +++ b/tagstudio/src/core/utils/missing_files.py @@ -29,7 +29,7 @@ def refresh_missing_files(self) -> Iterator[int]: """Track the number of Entries that point to an invalid file path.""" logger.info("refresh_missing_files running") self.missing_files = [] - for i, entry in enumerate(self.library._entries): + for i, entry in enumerate(self.library.get_entries()): full_path = self.library.library_dir / entry.path if not full_path.exists() or not full_path.is_file(): self.missing_files.append(entry) diff --git a/tagstudio/src/qt/modals/folders_to_tags.py b/tagstudio/src/qt/modals/folders_to_tags.py index d779899b5..ae6dacb3f 100644 --- a/tagstudio/src/qt/modals/folders_to_tags.py +++ b/tagstudio/src/qt/modals/folders_to_tags.py @@ -68,7 +68,7 @@ def add_tag_to_tree(items: list[Tag]): reversed_tag = reverse_tag(library, tag, None) add_tag_to_tree(reversed_tag) - for entry in library._entries: + for entry in library.get_entries(): folders = entry.path.parts[1:-1] if not folders: continue @@ -121,7 +121,7 @@ def _add_folders_to_tree(items: typing.Sequence[str]) -> BranchData: reversed_tag = reverse_tag(library, tag, None) add_tag_to_tree(reversed_tag) - for entry in library._entries: + for entry in library.get_entries(): folders = entry.path.parts[1:-1] if not folders: continue diff --git a/tagstudio/tests/conftest.py b/tagstudio/tests/conftest.py index dd34d205f..eb386c82d 100644 --- a/tagstudio/tests/conftest.py +++ b/tagstudio/tests/conftest.py @@ -75,6 +75,16 @@ def library(request): yield lib +@pytest.fixture +def entry(library): + yield next(library.get_entries()) + + +@pytest.fixture +def entry_full(library): + yield next(library.get_entries(with_joins=True)) + + @pytest.fixture def qt_driver(qtbot, library): with TemporaryDirectory() as tmp_dir: @@ -97,7 +107,7 @@ class Args: driver.lib = library # TODO - downsize this method and use it # driver.start() - driver.frame_content = list(library._entries) + driver.frame_content = list(library.get_entries()) yield driver diff --git a/tagstudio/tests/macros/test_sidecar.py b/tagstudio/tests/macros/test_sidecar.py index aa782fa02..a1f1be35b 100644 --- a/tagstudio/tests/macros/test_sidecar.py +++ b/tagstudio/tests/macros/test_sidecar.py @@ -9,19 +9,18 @@ @pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True) -def test_sidecar_macro(qt_driver, library, cwd): - entry = next(library._entries_full) - entry.path = Path("newgrounds/foo.txt") +def test_sidecar_macro(qt_driver, library, cwd, entry_full): + entry_full.path = Path("newgrounds/foo.txt") fixture = cwd / "fixtures/sidecar_newgrounds.json" - dst = library.library_dir / "newgrounds" / (entry.path.stem + ".json") + dst = library.library_dir / "newgrounds" / (entry_full.path.stem + ".json") dst.parent.mkdir() shutil.copy(fixture, dst) - qt_driver.frame_content = [entry] + qt_driver.frame_content = [entry_full] qt_driver.run_macro(MacroID.SIDECAR, 0) - entry = next(library._entries_full) + entry = next(library.get_entries(with_joins=True)) new_fields = ( (_FieldID.DESCRIPTION.name, "NG description"), (_FieldID.ARTIST.name, "NG artist"), diff --git a/tagstudio/tests/qt/test_driver.py b/tagstudio/tests/qt/test_driver.py index 104b0fffb..b1b2d8d74 100644 --- a/tagstudio/tests/qt/test_driver.py +++ b/tagstudio/tests/qt/test_driver.py @@ -28,13 +28,11 @@ def test_update_thumbs(qt_driver): assert thumb.isVisible() == (idx == 0) -def test_select_item_bridge(qt_driver): +def test_select_item_bridge(qt_driver, entry): # mock some props since we're not running `start()` qt_driver.autofill_action = Mock() qt_driver.sort_fields_action = Mock() - entry = next(qt_driver.lib._entries) - # set the content manually qt_driver.frame_content = [entry] * 3 diff --git a/tagstudio/tests/qt/test_item_thumb.py b/tagstudio/tests/qt/test_item_thumb.py index 9d6ad7225..9f0e79829 100644 --- a/tagstudio/tests/qt/test_item_thumb.py +++ b/tagstudio/tests/qt/test_item_thumb.py @@ -5,9 +5,7 @@ @pytest.mark.parametrize("new_value", (True, False)) -def test_badge_visual_state(library, qt_driver, new_value): - entry = next(qt_driver.lib._entries) - +def test_badge_visual_state(library, qt_driver, entry, new_value): thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100), 0) qt_driver.frame_content = [entry] diff --git a/tagstudio/tests/qt/test_preview_panel.py b/tagstudio/tests/qt/test_preview_panel.py index ec1ef60c9..851899506 100644 --- a/tagstudio/tests/qt/test_preview_panel.py +++ b/tagstudio/tests/qt/test_preview_panel.py @@ -5,7 +5,7 @@ def test_update_widgets_not_selected(qt_driver, library): - qt_driver.frame_content = list(library._entries) + qt_driver.frame_content = list(library.get_entries()) qt_driver.selected = [] panel = PreviewPanel(library, qt_driver) @@ -16,7 +16,7 @@ def test_update_widgets_not_selected(qt_driver, library): def test_update_widgets_single_selected(qt_driver, library): - qt_driver.frame_content = list(library._entries) + qt_driver.frame_content = list(library.get_entries()) qt_driver.selected = [0] panel = PreviewPanel(library, qt_driver) @@ -34,7 +34,7 @@ def test_update_widgets_multiple_selected(qt_driver, library): library.add_entries([entry]) assert library.entries_count == 3 - qt_driver.frame_content = list(library._entries) + qt_driver.frame_content = list(library.get_entries()) qt_driver.selected = [0, 1, 2] panel = PreviewPanel(library, qt_driver) @@ -50,13 +50,12 @@ def test_update_widgets_multiple_selected(qt_driver, library): } -def test_write_container_text_line(qt_driver, library): +def test_write_container_text_line(qt_driver, entry_full, library): # Given panel = PreviewPanel(library, qt_driver) - entry = next(library._entries_full) - field = entry.text_fields[0] - assert len(entry.text_fields) == 1 + field = entry_full.text_fields[0] + assert len(entry_full.text_fields) == 1 assert field.type.type == FieldTypeEnum.TEXT_LINE assert field.type.name == "Title" @@ -77,15 +76,15 @@ def test_write_container_text_line(qt_driver, library): modal.save_button.click() # Then reload entry - entry = next(library._entries_full) + entry_full = next(library.get_entries(with_joins=True)) # the value was updated - assert entry.text_fields[0].value == "bar" + assert entry_full.text_fields[0].value == "bar" def test_remove_field(qt_driver, library): # Given panel = PreviewPanel(library, qt_driver) - entries = list(library._entries_full) + entries = list(library.get_entries(with_joins=True)) qt_driver.frame_content = entries panel.selected = [1] @@ -93,26 +92,26 @@ def test_remove_field(qt_driver, library): panel.write_container(0, field) panel.remove_field(field) - entries = list(library._entries_full) + entries = list(library.get_entries(with_joins=True)) assert not entries[1].text_fields def test_update_field(qt_driver, library): panel = PreviewPanel(library, qt_driver) - qt_driver.frame_content = list(library._entries)[:2] + qt_driver.frame_content = list(library.get_entries())[:2] qt_driver.selected = [0, 1] panel.selected = [0, 1] field = [ x - for x in next(library._entries_full).text_fields + for x in next(library.get_entries(with_joins=True)).text_fields if x.type.type == FieldTypeEnum.TEXT_LINE ][0] panel.update_field(field, "meow") - for entry in list(library._entries_full)[:2]: + for entry in library.get_entries(with_joins=True): field = [ x for x in entry.text_fields if x.type.type == FieldTypeEnum.TEXT_LINE ][0] diff --git a/tagstudio/tests/qt/test_tag_widget.py b/tagstudio/tests/qt/test_tag_widget.py index 60e9cff24..f19c50610 100644 --- a/tagstudio/tests/qt/test_tag_widget.py +++ b/tagstudio/tests/qt/test_tag_widget.py @@ -1,6 +1,5 @@ from unittest.mock import patch -from src.core.library import Entry from src.core.library.alchemy.fields import _FieldID from src.qt.widgets.tag import TagWidget from src.qt.widgets.tag_box import TagBoxWidget @@ -9,7 +8,7 @@ def test_tag_widget(qtbot, library, qt_driver): # given - entry = next(library._entries_full) + entry = next(library.get_entries(with_joins=True)) field = entry.tag_box_fields[0] tag_widget = TagBoxWidget(field, "title", qt_driver) @@ -26,7 +25,7 @@ def test_tag_widget(qtbot, library, qt_driver): def test_tag_widget_add_existing_raises(qtbot, library, qt_driver): # Given - entry = next(library._entries_full) + entry = next(library.get_entries(with_joins=True)) tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] assert len(entry.tags) == 1 @@ -46,7 +45,7 @@ def test_tag_widget_add_existing_raises(qtbot, library, qt_driver): def test_tag_widget_add_new_pass(qtbot, library, qt_driver, generate_tag): # Given - entry = next(library._entries_full) + entry = next(library.get_entries(with_joins=True)) field = entry.tag_box_fields[0] tag = generate_tag(name="new_tag") @@ -65,14 +64,14 @@ def test_tag_widget_add_new_pass(qtbot, library, qt_driver, generate_tag): assert not mocked.emit.called -def test_tag_widget_remove(qtbot, qt_driver): - entry: Entry = next(qt_driver.lib._entries_full) - - tag = list(entry.tags)[0] +def test_tag_widget_remove(qtbot, qt_driver, library, entry_full): + tag = list(entry_full.tags)[0] assert tag - assert entry.tag_box_fields - tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] + assert entry_full.tag_box_fields + tag_field = [ + f for f in entry_full.tag_box_fields if f.type_key == _FieldID.TAGS.name + ][0] tag_widget = TagBoxWidget(tag_field, "title", qt_driver) tag_widget.driver.selected = [0] @@ -84,19 +83,19 @@ def test_tag_widget_remove(qtbot, qt_driver): tag_widget.remove_button.clicked.emit() - entry: Entry = next(qt_driver.lib._entries_full) + entry = next(qt_driver.lib.get_entries(with_joins=True)) assert not entry.tag_box_fields[0].tags -def test_tag_widget_edit(qtbot, qt_driver): +def test_tag_widget_edit(qtbot, qt_driver, library, entry_full): # Given - entry: Entry = next(qt_driver.lib._entries_full) - - tag = list(entry.tags)[0] + tag = list(entry_full.tags)[0] assert tag - assert entry.tag_box_fields - tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] + assert entry_full.tag_box_fields + tag_field = [ + f for f in entry_full.tag_box_fields if f.type_key == _FieldID.TAGS.name + ][0] tag_box_widget = TagBoxWidget(tag_field, "title", qt_driver) tag_box_widget.driver.selected = [0] diff --git a/tagstudio/tests/test_folders_tags.py b/tagstudio/tests/test_folders_tags.py index b65599a89..a5263c7a7 100644 --- a/tagstudio/tests/test_folders_tags.py +++ b/tagstudio/tests/test_folders_tags.py @@ -3,5 +3,7 @@ def test_folders_to_tags(library): folders_to_tags(library) - entry = [x for x in library._entries_full if "bar.md" in str(x.path)][0] + entry = [ + x for x in library.get_entries(with_joins=True) if "bar.md" in str(x.path) + ][0] assert {x.name for x in entry.tags} == {"two", "bar"} diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 0ec7528a6..413bd05f7 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -62,10 +62,9 @@ def test_create_tag(library, generate_tag): assert tag_inc.id == 1002 -def test_library_search(library, generate_tag): +def test_library_search(library, generate_tag, entry_full): assert library.entries_count == 2 - entry = next(library._entries_full) - tag = list(entry.tags)[0] + tag = list(entry_full.tags)[0] query_count, items = library.search_library( FilterState( @@ -100,14 +99,11 @@ def test_tag_search(library): ) -def test_get_entry(library): - entry = next(library._entries) +def test_get_entry(library, entry): assert entry.id - - _, entries = library.search_library(FilterState(id=entry.id)) - assert len(entries) == 1 + cnt, entries = library.search_library(FilterState(id=entry.id)) + assert len(entries) == cnt == 1 entry = entries[0] - assert entry.path assert entry.tags @@ -137,23 +133,22 @@ def test_add_field_to_entry(library): library.add_entry_field_type(entry.id, field_id=_FieldID.TAGS) # Then - entry = [x for x in library._entries_full if x.path == item_path][0] + entry = [x for x in library.get_entries(with_joins=True) if x.path == item_path][0] # meta tags and tags field present assert len(entry.tag_box_fields) == 3 -def test_add_field_tag(library, generate_tag): +def test_add_field_tag(library, entry_full, generate_tag): # Given tag_name = "xxx" tag = generate_tag(tag_name) - entry = next(library._entries_full) - tag_field = entry.tag_box_fields[0] + tag_field = entry_full.tag_box_fields[0] # When - library.add_field_tag(entry, tag, tag_field.type_key) + library.add_field_tag(entry_full, tag, tag_field.type_key) # Then - _, entries = library.search_library(FilterState(id=entry.id)) + _, entries = library.search_library(FilterState(id=entry_full.id)) tag_field = entries[0].tag_box_fields[0] assert [x.name for x in tag_field.tags if x.name == tag_name] @@ -179,7 +174,7 @@ def test_subtags_add(library, generate_tag): @pytest.mark.parametrize("is_exclude", [True, False]) def test_search_filter_extensions(library, is_exclude): # Given - entries = list(library._entries) + entries = list(library.get_entries()) assert len(entries) == 2, entries library.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, is_exclude) @@ -200,7 +195,7 @@ def test_search_filter_extensions(library, is_exclude): def test_search_library_case_insensitive(library): # Given - entries = list(library._entries_full) + entries = list(library.get_entries(with_joins=True)) assert len(entries) == 2, entries entry = entries[0] @@ -240,35 +235,29 @@ def test_save_windows_path(library, generate_tag): assert str(found[0].path) == "foo/bar.txt" -def test_remove_entry_field(library): - entry = next(library._entries_full) +def test_remove_entry_field(library, entry_full): + title_field = entry_full.text_fields[0] - title_field = entry.text_fields[0] + library.remove_entry_field(title_field, [entry_full.id]) - library.remove_entry_field(title_field, [entry.id]) - - entry = next(library._entries_full) + entry = next(library.get_entries(with_joins=True)) assert not entry.text_fields -def test_update_entry_field(library): - entry = next(library._entries_full) - - title_field = entry.text_fields[0] +def test_update_entry_field(library, entry_full): + title_field = entry_full.text_fields[0] library.update_entry_field( - entry.id, + entry_full.id, title_field, "new value", ) - entry = next(library._entries_full) + entry = next(library.get_entries(with_joins=True)) assert entry.text_fields[0].value == "new value" -def test_mirror_entry_fields(library, snapshot): - entry = next(library._entries_full) - +def test_mirror_entry_fields(library, entry_full): target_entry = generate_entry( path=Path("xxx"), fields=[ @@ -284,7 +273,7 @@ def test_mirror_entry_fields(library, snapshot): _, entries = library.search_library(FilterState(id=entry_id)) new_entry = entries[0] - library.mirror_entry_fields(new_entry, entry) + library.mirror_entry_fields(new_entry, entry_full) _, entries = library.search_library(FilterState(id=entry_id)) entry = entries[0] @@ -312,15 +301,13 @@ def test_path_search(library, item_path, found): assert bool(cnt) == found -def test_remove_tag_from_field(library): - entry = next(library._entries_full) - - for field in entry.tag_box_fields: +def test_remove_tag_from_field(library, entry_full): + for field in entry_full.tag_box_fields: for tag in field.tags: removed_tag = tag.name library.remove_tag_from_field(tag, field) break - entry = next(library._entries_full) + entry = next(library.get_entries(with_joins=True)) for field in entry.tag_box_fields: assert removed_tag not in [tag.name for tag in field.tags] From bd30438c024042216f93c8f5bb48e00a987851b9 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Fri, 6 Sep 2024 07:30:42 +0700 Subject: [PATCH 11/25] page filterstate.page_size persistent --- tagstudio/src/core/library/alchemy/enums.py | 17 ++++++-- tagstudio/src/core/library/alchemy/library.py | 2 +- tagstudio/src/qt/widgets/landing.py | 1 - tagstudio/tests/conftest.py | 2 +- tagstudio/tests/qt/test_driver.py | 39 ++++++++++++++++++- tagstudio/tests/qt/test_item_thumb.py | 4 +- tagstudio/tests/test_library.py | 23 ++--------- 7 files changed, 59 insertions(+), 29 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/enums.py b/tagstudio/src/core/library/alchemy/enums.py index d1eea8aee..00bb83183 100644 --- a/tagstudio/src/core/library/alchemy/enums.py +++ b/tagstudio/src/core/library/alchemy/enums.py @@ -60,14 +60,17 @@ class ItemType(enum.Enum): class FilterState: """Represent a state of the Library grid view.""" - page_index: int = 0 - page_size: int = 500 + # these should remain + page_index: int | None = None + page_size: int | None = None + search_mode: SearchMode = SearchMode.AND # TODO - actually implement this + + # these should be erased on update tag: str | None = None id: int | None = None tag_id: int | None = None path: str | Path | None = None query: str | None = None - search_mode: SearchMode = SearchMode.AND # TODO - actually implement this def __post_init__(self): # strip values automatically @@ -79,6 +82,9 @@ def __post_init__(self): # default to tag search kind, value = "tag", query + # reset the query + self.query = None + if kind == "id": self.id = int(value) elif kind == "tag_id": @@ -94,6 +100,11 @@ def __post_init__(self): self.tag_id = int(self.tag_id) if self.tag_id is not None else None self.path = self.path and str(self.path) + if self.page_index is None: + self.page_index = 0 + if self.page_size is None: + self.page_size = 500 + @property def summary(self): """Show query summary""" diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 03be9c9a1..eecad312a 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -331,7 +331,7 @@ def search_library( elif search.tag_id: statement = statement.where(Tag.id == search.tag_id) elif search.path: - statement = statement.where(Entry.path.ilike(search.path)) + statement = statement.where(Entry.path.ilike(f"%{search.path}%")) extensions = self.prefs(LibraryPrefs.EXTENSION_LIST) is_exclude_list = self.prefs(LibraryPrefs.IS_EXCLUDE_LIST) diff --git a/tagstudio/src/qt/widgets/landing.py b/tagstudio/src/qt/widgets/landing.py index a44a49c6d..e5df97527 100644 --- a/tagstudio/src/qt/widgets/landing.py +++ b/tagstudio/src/qt/widgets/landing.py @@ -56,7 +56,6 @@ def __init__(self, driver: "QtDriver", pixel_ratio: float): self.logo_special_anim.setDuration(500) # Create "Open/Create Library" button ---------------------------------- - open_shortcut_text: str = "" if sys.platform == "darwin": open_shortcut_text = "(⌘+O)" else: diff --git a/tagstudio/tests/conftest.py b/tagstudio/tests/conftest.py index eb386c82d..c5af09fff 100644 --- a/tagstudio/tests/conftest.py +++ b/tagstudio/tests/conftest.py @@ -76,7 +76,7 @@ def library(request): @pytest.fixture -def entry(library): +def entry_min(library): yield next(library.get_entries()) diff --git a/tagstudio/tests/qt/test_driver.py b/tagstudio/tests/qt/test_driver.py index b1b2d8d74..c52874c0e 100644 --- a/tagstudio/tests/qt/test_driver.py +++ b/tagstudio/tests/qt/test_driver.py @@ -2,6 +2,7 @@ from unittest.mock import Mock from src.core.library import Entry +from src.core.library.alchemy.enums import FilterState from src.core.library.json.library import ItemType from src.qt.widgets.item_thumb import ItemThumb @@ -28,13 +29,13 @@ def test_update_thumbs(qt_driver): assert thumb.isVisible() == (idx == 0) -def test_select_item_bridge(qt_driver, entry): +def test_select_item_bridge(qt_driver, entry_min): # mock some props since we're not running `start()` qt_driver.autofill_action = Mock() qt_driver.sort_fields_action = Mock() # set the content manually - qt_driver.frame_content = [entry] * 3 + qt_driver.frame_content = [entry_min] * 3 qt_driver.filter.page_size = 3 qt_driver._init_thumb_grid() @@ -58,3 +59,37 @@ def test_select_item_bridge(qt_driver, entry): qt_driver.select_item(0, False, bridge=True) assert qt_driver.selected == [0, 1, 2] + + +def test_library_state_update(qt_driver): + # Given + for idx, entry in enumerate(qt_driver.lib.get_entries(with_joins=True)): + thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100), idx) + qt_driver.item_thumbs.append(thumb) + qt_driver.frame_content.append(entry) + + # no filter, both items are returned + qt_driver.filter_items() + assert len(qt_driver.frame_content) == 2 + + # filter by tag + state = FilterState(tag="foo", page_size=10) + qt_driver.filter_items(state) + assert qt_driver.filter.page_size == 10 + assert len(qt_driver.frame_content) == 1 + entry = qt_driver.frame_content[0] + assert list(entry.tags)[0].name == "foo" + + # When state is not changed, previous one is still applied + qt_driver.filter_items() + assert qt_driver.filter.page_size == 10 + assert len(qt_driver.frame_content) == 1 + entry = qt_driver.frame_content[0] + assert list(entry.tags)[0].name == "foo" + + # When state property is changed, previous one is overriden + state = FilterState(path="bar.md") + qt_driver.filter_items(state) + assert len(qt_driver.frame_content) == 1 + entry = qt_driver.frame_content[0] + assert list(entry.tags)[0].name == "bar" diff --git a/tagstudio/tests/qt/test_item_thumb.py b/tagstudio/tests/qt/test_item_thumb.py index 9f0e79829..7ed0c5dae 100644 --- a/tagstudio/tests/qt/test_item_thumb.py +++ b/tagstudio/tests/qt/test_item_thumb.py @@ -5,10 +5,10 @@ @pytest.mark.parametrize("new_value", (True, False)) -def test_badge_visual_state(library, qt_driver, entry, new_value): +def test_badge_visual_state(library, qt_driver, entry_min, new_value): thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100), 0) - qt_driver.frame_content = [entry] + qt_driver.frame_content = [entry_min] qt_driver.selected = [0] qt_driver.item_thumbs = [thumb] diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 413bd05f7..92464aa1e 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -99,12 +99,11 @@ def test_tag_search(library): ) -def test_get_entry(library, entry): - assert entry.id - cnt, entries = library.search_library(FilterState(id=entry.id)) +def test_get_entry(library, entry_min): + assert entry_min.id + cnt, entries = library.search_library(FilterState(id=entry_min.id)) assert len(entries) == cnt == 1 - entry = entries[0] - assert entry.tags + assert entries[0].tags def test_entries_count(library): @@ -287,20 +286,6 @@ def test_mirror_entry_fields(library, entry_full): } -@pytest.mark.parametrize( - ["item_path", "found"], - [ - ("one/two/bar.md", True), - ("two/bar.md", False), # partial match should not return - ], -) -def test_path_search(library, item_path, found): - # When - cnt, entries = library.search_library(FilterState(path=item_path)) - # Then - assert bool(cnt) == found - - def test_remove_tag_from_field(library, entry_full): for field in entry_full.tag_box_fields: for tag in field.tags: From c149d048ac741fd7ff15750ad6ab957d5b1d06ce Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Fri, 6 Sep 2024 22:00:49 +0700 Subject: [PATCH 12/25] add test for entry.id filter --- tagstudio/src/core/library/alchemy/enums.py | 39 ++++++++++++------- tagstudio/src/core/library/alchemy/library.py | 27 +++++++++---- tagstudio/tests/test_library.py | 37 ++++++++++++++++++ 3 files changed, 81 insertions(+), 22 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/enums.py b/tagstudio/src/core/library/alchemy/enums.py index 00bb83183..bd06b9ad9 100644 --- a/tagstudio/src/core/library/alchemy/enums.py +++ b/tagstudio/src/core/library/alchemy/enums.py @@ -66,10 +66,19 @@ class FilterState: search_mode: SearchMode = SearchMode.AND # TODO - actually implement this # these should be erased on update + # tag name tag: str | None = None - id: int | None = None + # tag ID tag_id: int | None = None - path: str | Path | None = None + + # entry id + id: int | None = None + # whole path + path: Path | str | None = None + # file name + name: str | None = None + + # a generic query to be parsed query: str | None = None def __post_init__(self): @@ -82,23 +91,23 @@ def __post_init__(self): # default to tag search kind, value = "tag", query - # reset the query - self.query = None - - if kind == "id": - self.id = int(value) - elif kind == "tag_id": + if kind == "tag_id": self.tag_id = int(value) - elif kind == "path": - self.path = value elif kind == "tag": self.tag = value + elif kind == "path": + self.path = value + elif kind == "name": + self.name = value + elif kind == "id": + self.id = int(self.id) if str(self.id).isnumeric() else self.id else: self.tag = self.tag and self.tag.strip() - self.id = int(self.id) if self.id is not None else None - self.tag_id = int(self.tag_id) if self.tag_id is not None else None - self.path = self.path and str(self.path) + self.tag_id = int(self.tag_id) if str(self.tag_id).isnumeric() else self.id + self.path = self.path and str(self.path).strip() + self.name = self.name and self.name.strip() + self.id = int(self.id) if str(self.id).isnumeric() else self.id if self.page_index is None: self.page_index = 0 @@ -108,7 +117,9 @@ def __post_init__(self): @property def summary(self): """Show query summary""" - return self.query or self.tag or self.id or self.tag_id or self.path + return ( + self.query or self.tag or self.name or self.tag_id or self.path or self.id + ) @property def limit(self): diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index eecad312a..451a58efd 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -328,6 +328,15 @@ def search_library( elif search.id: statement = statement.where(Entry.id == search.id) + elif search.name: + statement = select(Entry).where( + and_( + Entry.path.ilike(f"%{search.name}%"), + # dont match directory name (ie. has following slash) + ~Entry.path.ilike(f"%{search.name}%/%"), + ) + ) + elif search.tag_id: statement = statement.where(Tag.id == search.tag_id) elif search.path: @@ -335,14 +344,16 @@ def search_library( extensions = self.prefs(LibraryPrefs.EXTENSION_LIST) is_exclude_list = self.prefs(LibraryPrefs.IS_EXCLUDE_LIST) - if extensions and is_exclude_list: - statement = statement.where( - Entry.path.notilike(f"%.{','.join(extensions)}") - ) - elif extensions: - statement = statement.where( - Entry.path.ilike(f"%.{','.join(extensions)}") - ) + + if not search.id: # if `id` is set, we don't need to filter by extensions + if extensions and is_exclude_list: + statement = statement.where( + Entry.path.notilike(f"%.{','.join(extensions)}") + ) + elif extensions: + statement = statement.where( + Entry.path.ilike(f"%.{','.join(extensions)}") + ) statement = statement.options( selectinload(Entry.text_fields), diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 92464aa1e..321ff1c27 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -296,3 +296,40 @@ def test_remove_tag_from_field(library, entry_full): entry = next(library.get_entries(with_joins=True)) for field in entry.tag_box_fields: assert removed_tag not in [tag.name for tag in field.tags] + + +@pytest.mark.parametrize( + ["query_name", "has_result"], + [ + ("foo", 1), # filename substring + ("bar", 1), # filename substring + ("one", 0), # path, should not match + ], +) +def test_search_file_name(library, query_name, has_result): + res_count, items = library.search_library( + FilterState(name=query_name), + ) + + assert ( + res_count == has_result + ), f"mismatch with query: {query_name}, result: {res_count}" + + +@pytest.mark.parametrize( + ["query_name", "has_result"], + [ + (1, 1), + ("1", 1), + ("xxx", 0), + (222, 0), + ], +) +def test_search_entry_id(library, query_name, has_result): + res_count, items = library.search_library( + FilterState(id=query_name), + ) + + assert ( + res_count == has_result + ), f"mismatch with query: {query_name}, result: {res_count}" From b586a4065b32f3729dc0e73c9efe73b25b993d71 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sat, 7 Sep 2024 07:00:35 +0700 Subject: [PATCH 13/25] fix closing library --- tagstudio/src/qt/ts_qt.py | 10 +++++++--- tagstudio/tests/qt/test_driver.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index a4bbec311..f244b6dc8 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -460,6 +460,7 @@ def create_folders_tags_modal(): self.thumb_size = 128 self.item_thumbs: list[ItemThumb] = [] self.thumb_renderers: list[ThumbRenderer] = [] + self.filter = FilterState() self.init_library_window() @@ -568,19 +569,22 @@ def shutdown(self): def close_library(self): if not self.lib.library_dir: + logger.info("No Library to Close") return logger.info("Closing Library...") - self.main_window.statusbar.showMessage("Closing & Saving Library...") + self.main_window.statusbar.showMessage("Closing Library...") start_time = time.time() self.settings.setValue(SettingItems.LAST_LIBRARY, self.lib.library_dir) self.settings.sync() - self.lib.clear_internal_vars() title_text = f"{self.base_title}" self.main_window.setWindowTitle(title_text) - self.selected.clear() + self.selected = [] + self.frame_content = [] + self.item_thumbs = [] + self.preview_panel.update_widgets() self.main_window.toggle_landing_page(True) diff --git a/tagstudio/tests/qt/test_driver.py b/tagstudio/tests/qt/test_driver.py index c52874c0e..b01ac355f 100644 --- a/tagstudio/tests/qt/test_driver.py +++ b/tagstudio/tests/qt/test_driver.py @@ -93,3 +93,13 @@ def test_library_state_update(qt_driver): assert len(qt_driver.frame_content) == 1 entry = qt_driver.frame_content[0] assert list(entry.tags)[0].name == "bar" + + +def test_close_library(qt_driver): + # Given + qt_driver.close_library() + + # Then + assert len(qt_driver.frame_content) == 0 + assert len(qt_driver.item_thumbs) == 0 + assert qt_driver.selected == [] From 6c804068a4a1194fde74852cb7014d18f94f6cf5 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sat, 7 Sep 2024 22:16:34 +0700 Subject: [PATCH 14/25] fix tag search, adding field --- tagstudio/src/core/library/alchemy/enums.py | 4 +- tagstudio/src/core/library/alchemy/library.py | 42 ++++++++++++++----- tagstudio/src/qt/modals/add_field.py | 2 +- tagstudio/src/qt/ts_qt.py | 2 +- tagstudio/src/qt/widgets/preview_panel.py | 10 +++-- tagstudio/src/qt/widgets/tag_box.py | 2 +- .../tests/{ => macros}/test_folders_tags.py | 0 tagstudio/tests/test_filter_state.py | 37 ++++++++++++++++ tagstudio/tests/test_library.py | 37 ++++++++++++++++ 9 files changed, 118 insertions(+), 18 deletions(-) rename tagstudio/tests/{ => macros}/test_folders_tags.py (100%) create mode 100644 tagstudio/tests/test_filter_state.py diff --git a/tagstudio/src/core/library/alchemy/enums.py b/tagstudio/src/core/library/alchemy/enums.py index bd06b9ad9..f7595e624 100644 --- a/tagstudio/src/core/library/alchemy/enums.py +++ b/tagstudio/src/core/library/alchemy/enums.py @@ -104,7 +104,9 @@ def __post_init__(self): else: self.tag = self.tag and self.tag.strip() - self.tag_id = int(self.tag_id) if str(self.tag_id).isnumeric() else self.id + self.tag_id = ( + int(self.tag_id) if str(self.tag_id).isnumeric() else self.tag_id + ) self.path = self.path and str(self.path).strip() self.name = self.name and self.name.strip() self.id = int(self.id) if str(self.id).isnumeric() else self.id diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 451a58efd..29ec921aa 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -325,6 +325,12 @@ def search_library( ) ) ) + elif search.tag_id: + statement = ( + statement.join(Entry.tag_box_fields) + .join(TagBoxField.tags) + .where(Tag.id == search.tag_id) + ) elif search.id: statement = statement.where(Entry.id == search.id) @@ -336,9 +342,6 @@ def search_library( ~Entry.path.ilike(f"%{search.name}%/%"), ) ) - - elif search.tag_id: - statement = statement.where(Tag.id == search.tag_id) elif search.path: statement = statement.where(Entry.path.ilike(f"%{search.path}%")) @@ -474,12 +477,22 @@ def remove_entry_field( field_class = type(field) with Session(self.engine) as session: - session.query(field_class).where( - and_( - field_class.entry_id.in_(entry_ids), - field_class.type == field.type, + # Subquery to select one matching field per entry + subquery = ( + session.query(field_class.id) + .filter( + and_( + field_class.entry_id.in_(entry_ids), + field_class.type == field.type, + ) ) - ).delete() + .limit(1) + .subquery() + ) + + # Delete one matching field per entry + session.query(field_class).filter(field_class.id.in_(subquery)).delete() + session.commit() def update_entry_field( @@ -494,14 +507,23 @@ def update_entry_field( field_class = type(field) with Session(self.engine) as session: - update_stmt = ( - update(field_class) + # Subquery to select one matching field's id per entry + subquery = ( + select(field_class.id) .where( and_( field_class.type == field.type, field_class.entry_id.in_(entry_ids), ) ) + .limit(1) + .subquery() + ) + + # Update one matching field per entry + update_stmt = ( + update(field_class) + .where(field_class.id.in_(subquery)) .values(value=content) ) diff --git a/tagstudio/src/qt/modals/add_field.py b/tagstudio/src/qt/modals/add_field.py index 5f6ceacb9..9e1ef5c7f 100644 --- a/tagstudio/src/qt/modals/add_field.py +++ b/tagstudio/src/qt/modals/add_field.py @@ -83,7 +83,7 @@ def __init__(self, library: Library): def show(self): self.list_widget.clear() for df in self.lib.field_types.values(): - item = QListWidgetItem(f"{df.name} ({df.type})") + item = QListWidgetItem(f"{df.name} ({df.type.value})") item.setData(Qt.ItemDataRole.UserRole, df.key) self.list_widget.addItem(item) diff --git a/tagstudio/src/qt/ts_qt.py b/tagstudio/src/qt/ts_qt.py index f244b6dc8..eb041dd45 100644 --- a/tagstudio/src/qt/ts_qt.py +++ b/tagstudio/src/qt/ts_qt.py @@ -553,7 +553,7 @@ def handleSIGTERM(self): def shutdown(self): """Save Library on Application Exit""" - if self.lib.library_dir: + if self.lib and self.lib.library_dir: self.settings.setValue(SettingItems.LAST_LIBRARY, self.lib.library_dir) self.settings.sync() logger.info("[SHUTDOWN] Ending Thumbnail Threads...") diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index b8311cf28..630738e1b 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -441,7 +441,8 @@ def add_field_to_selected(self, field_list: list): entry = self.driver.frame_content[grid_idx] for field_item in field_list: self.lib.add_entry_field_type( - entry.id, field_id=field_item.currentData() + entry.id, + field_id=field_item.data(Qt.ItemDataRole.UserRole), ) def update_widgets(self) -> bool: @@ -495,6 +496,7 @@ def update_widgets(self) -> bool: # reload entry and fill it into the grid again # TODO - do this more granular + # TODO - Entry reload is maybe not necessary for grid_idx in self.driver.selected: entry = self.driver.frame_content[grid_idx] _, entries = self.lib.search_library(FilterState(id=entry.id)) @@ -800,14 +802,14 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): else: text = "Mixed Data" - title = f"{field.type.name} ({field.type.type})" + title = f"{field.type.name} ({field.type.type.value})" inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) if not is_mixed: modal = PanelModal( EditTextLine(field.value), title=title, - window_title=f"Edit {field.type.name}", + window_title=f"Edit {field.type.type.value}", save_callback=( lambda content: ( self.update_field(field, content), @@ -822,7 +824,7 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): container.set_edit_callback(modal.show) container.set_remove_callback( lambda: self.remove_message_box( - prompt=self.remove_field_prompt(field.type.name), + prompt=self.remove_field_prompt(field.type.type.value), callback=lambda: ( self.remove_field(field), self.update_widgets(), diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index 24ef9bf3a..49e3f59c0 100644 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -98,7 +98,7 @@ def set_tags(self, tags: typing.Iterable[Tag]): tag_widget.on_click.connect( lambda tag_id=tag.id: ( self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"), - self.driver.filter_items(FilterState(id=tag_id)), + self.driver.filter_items(FilterState(tag_id=tag_id)), ) ) diff --git a/tagstudio/tests/test_folders_tags.py b/tagstudio/tests/macros/test_folders_tags.py similarity index 100% rename from tagstudio/tests/test_folders_tags.py rename to tagstudio/tests/macros/test_folders_tags.py diff --git a/tagstudio/tests/test_filter_state.py b/tagstudio/tests/test_filter_state.py new file mode 100644 index 000000000..4add369bb --- /dev/null +++ b/tagstudio/tests/test_filter_state.py @@ -0,0 +1,37 @@ +import pytest + +from src.core.library.alchemy.enums import FilterState + + +def test_filter_state_query(): + # Given + query = "tag:foo" + state = FilterState(query=query) + + # When + assert state.tag == "foo" + + +@pytest.mark.parametrize( + ["attribute", "comparator"], + [ + ("tag", str), + ("tag_id", int), + ("path", str), + ("name", str), + ("id", int), + ], +) +def test_filter_state_attrs_compare(attribute, comparator): + # When + state = FilterState(**{attribute: "2"}) + + # Then + # compare the attribute value + assert getattr(state, attribute) == comparator("2") + + # Then + for prop in ("tag", "tag_id", "path", "name", "id"): + if prop == attribute: + continue + assert not getattr(state, prop) diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 321ff1c27..a1132db14 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -243,6 +243,22 @@ def test_remove_entry_field(library, entry_full): assert not entry.text_fields +def test_remove_field_entry_with_multiple_field(library, entry_full): + # Given + title_field = entry_full.text_fields[0] + + # When + # add identical field + library.add_entry_field_type(entry_full.id, field_id=title_field.type_key) + + # remove entyr field + library.remove_entry_field(title_field, [entry_full.id]) + + # Then one field should remain + entry = next(library.get_entries(with_joins=True)) + assert len(entry.text_fields) == 1 + + def test_update_entry_field(library, entry_full): title_field = entry_full.text_fields[0] @@ -256,6 +272,27 @@ def test_update_entry_field(library, entry_full): assert entry.text_fields[0].value == "new value" +def test_update_entry_with_multiple_identical_fields(library, entry_full): + # Given + title_field = entry_full.text_fields[0] + + # When + # add identical field + library.add_entry_field_type(entry_full.id, field_id=title_field.type_key) + + # update one of the fields + library.update_entry_field( + entry_full.id, + title_field, + "new value", + ) + + # Then only one should be updated + entry = next(library.get_entries(with_joins=True)) + assert entry.text_fields[0].value == "" + assert entry.text_fields[1].value == "new value" + + def test_mirror_entry_fields(library, entry_full): target_entry = generate_entry( path=Path("xxx"), From 3168d51fa1a0f2608e4c16a5ccaf3e306e86f176 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sat, 7 Sep 2024 22:52:06 +0700 Subject: [PATCH 15/25] add field position --- tagstudio/src/core/library/alchemy/fields.py | 4 ++ tagstudio/src/core/library/alchemy/library.py | 38 ++++++++++++++----- tagstudio/src/qt/widgets/preview_panel.py | 1 + tagstudio/tests/qt/test_preview_panel.py | 17 +++------ tagstudio/tests/test_library.py | 16 ++++++++ 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/fields.py b/tagstudio/src/core/library/alchemy/fields.py index 66d7ff892..e683a4ab3 100644 --- a/tagstudio/src/core/library/alchemy/fields.py +++ b/tagstudio/src/core/library/alchemy/fields.py @@ -27,6 +27,7 @@ class BooleanField(Base): entry: Mapped[Entry] = relationship() value: Mapped[bool] + position: Mapped[int] = mapped_column(default=0) def __key(self): return (self.type, self.value) @@ -51,6 +52,7 @@ class TextField(Base): entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) value: Mapped[str | None] + position: Mapped[int] = mapped_column(default=0) def __key(self): return (self.type, self.value) @@ -77,6 +79,7 @@ class TagBoxField(Base): entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) tags: Mapped[set[Tag]] = relationship(secondary="tag_fields") + position: Mapped[int] = mapped_column(default=0) def __key(self): return ( @@ -109,6 +112,7 @@ class DatetimeField(Base): entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) value: Mapped[str | None] + position: Mapped[int] = mapped_column(default=0) def __key(self): return (self.type, self.value) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 29ec921aa..805ae4263 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -469,6 +469,27 @@ def remove_tag_from_field(self, tag: Tag, field: TagBoxField) -> None: session.add(field_) session.commit() + def update_field_position(self, field: Field, entry_ids: list[int]): + if isinstance(entry_ids, int): + entry_ids = [entry_ids] + + FieldClass = type(field) + + with Session(self.engine) as session: + for entry_id in entry_ids: + rows = session.scalars( + select(FieldClass) + .where(FieldClass.entry_id == entry_id) + .order_by(FieldClass.position, FieldClass.id) + ) + + # Reassign `order` starting from 0 + for index, row in enumerate(rows): + row.position = index # type: ignore + + session.add(row) + session.commit() + def remove_entry_field( self, field: Field, @@ -491,8 +512,7 @@ def remove_entry_field( ) # Delete one matching field per entry - session.query(field_class).filter(field_class.id.in_(subquery)).delete() - + session.query(field_class).filter(field_class.id.in_(subquery)).delete() # type: ignore session.commit() def update_entry_field( @@ -504,26 +524,26 @@ def update_entry_field( if isinstance(entry_ids, int): entry_ids = [entry_ids] - field_class = type(field) + FieldClass = type(field) with Session(self.engine) as session: # Subquery to select one matching field's id per entry subquery = ( - select(field_class.id) + select(FieldClass.id) .where( and_( - field_class.type == field.type, - field_class.entry_id.in_(entry_ids), + FieldClass.type == field.type, + FieldClass.entry_id.in_(entry_ids), ) ) - .limit(1) + # .limit(1) # TODO - uncomment this when .position will be implemented .subquery() ) # Update one matching field per entry update_stmt = ( - update(field_class) - .where(field_class.id.in_(subquery)) + update(FieldClass) + .where(FieldClass.id.in_(subquery)) # type: ignore .values(value=content) ) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 630738e1b..3ab8594b1 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -946,6 +946,7 @@ def update_field(self, field: Field, content: str) -> None: assert isinstance( field, (TextField, DatetimeField, TagBoxField) ), f"instance: {type(field)}" + entry_ids = [] for grid_idx in self.selected: entry = self.driver.frame_content[grid_idx] diff --git a/tagstudio/tests/qt/test_preview_panel.py b/tagstudio/tests/qt/test_preview_panel.py index 851899506..986ddbae9 100644 --- a/tagstudio/tests/qt/test_preview_panel.py +++ b/tagstudio/tests/qt/test_preview_panel.py @@ -96,23 +96,18 @@ def test_remove_field(qt_driver, library): assert not entries[1].text_fields -def test_update_field(qt_driver, library): +def test_update_field(qt_driver, library, entry_full): panel = PreviewPanel(library, qt_driver) + # select both entries qt_driver.frame_content = list(library.get_entries())[:2] qt_driver.selected = [0, 1] panel.selected = [0, 1] - field = [ - x - for x in next(library.get_entries(with_joins=True)).text_fields - if x.type.type == FieldTypeEnum.TEXT_LINE - ][0] - - panel.update_field(field, "meow") + # update field + title_field = entry_full.text_fields[0] + panel.update_field(title_field, "meow") for entry in library.get_entries(with_joins=True): - field = [ - x for x in entry.text_fields if x.type.type == FieldTypeEnum.TEXT_LINE - ][0] + field = [x for x in entry.text_fields if x.type_key == title_field.type_key][0] assert field.value == "meow" diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index a1132db14..276324c38 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -272,6 +272,8 @@ def test_update_entry_field(library, entry_full): assert entry.text_fields[0].value == "new value" +# TODO - uncomment this when .position will be implemented +@pytest.mark.skip def test_update_entry_with_multiple_identical_fields(library, entry_full): # Given title_field = entry_full.text_fields[0] @@ -370,3 +372,17 @@ def test_search_entry_id(library, query_name, has_result): assert ( res_count == has_result ), f"mismatch with query: {query_name}, result: {res_count}" + + +def test_update_field_order(library, entry_full): + # Given + title_field = entry_full.text_fields[0] + + # When + library.add_entry_field_type(entry_full.id, field_id=title_field.type_key) + library.update_field_position(title_field, entry_full.id) + + # Then + entry = next(library.get_entries(with_joins=True)) + assert entry.text_fields[0].position == 0 + assert entry.text_fields[1].position == 1 From 2cb21b9a1bb24a7160a5de156f974fc6bd0992d5 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 10:43:46 +0700 Subject: [PATCH 16/25] add fields reordering --- tagstudio/src/core/library/alchemy/fields.py | 9 +++- tagstudio/src/core/library/alchemy/library.py | 52 ++++++++----------- tagstudio/tests/test_library.py | 21 ++++++-- 3 files changed, 47 insertions(+), 35 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/fields.py b/tagstudio/src/core/library/alchemy/fields.py index e683a4ab3..095966a3c 100644 --- a/tagstudio/src/core/library/alchemy/fields.py +++ b/tagstudio/src/core/library/alchemy/fields.py @@ -4,7 +4,7 @@ from enum import Enum from typing import Union, Any, TYPE_CHECKING -from sqlalchemy import ForeignKey +from sqlalchemy import ForeignKey, ForeignKeyConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from .db import Base @@ -43,6 +43,13 @@ def __eq__(self, value) -> bool: class TextField(Base): __tablename__ = "text_fields" + # constrain for combination of: entry_id, type_key and position + __table_args__ = ( + ForeignKeyConstraint( + ["entry_id", "type_key", "position"], + ["text_fields.entry_id", "text_fields.type_key", "text_fields.position"], + ), + ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 805ae4263..4ba7f46bc 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -2,6 +2,7 @@ import shutil from os import makedirs from pathlib import Path +from random import randint from typing import Iterator, Any import structlog @@ -480,7 +481,7 @@ def update_field_position(self, field: Field, entry_ids: list[int]): rows = session.scalars( select(FieldClass) .where(FieldClass.entry_id == entry_id) - .order_by(FieldClass.position, FieldClass.id) + .order_by(FieldClass.id) ) # Reassign `order` starting from 0 @@ -488,33 +489,28 @@ def update_field_position(self, field: Field, entry_ids: list[int]): row.position = index # type: ignore session.add(row) - session.commit() + session.commit() def remove_entry_field( self, field: Field, entry_ids: list[int], ) -> None: - field_class = type(field) + FieldClass = type(field) with Session(self.engine) as session: - # Subquery to select one matching field per entry - subquery = ( - session.query(field_class.id) - .filter( - and_( - field_class.entry_id.in_(entry_ids), - field_class.type == field.type, - ) + session.query(FieldClass).where( + and_( + FieldClass.entry_id.in_(entry_ids), + FieldClass.type == field.type, + FieldClass.position == field.position, ) - .limit(1) - .subquery() - ) - - # Delete one matching field per entry - session.query(field_class).filter(field_class.id.in_(subquery)).delete() # type: ignore + ).delete() session.commit() + # recalculate the remaining positions + self.update_field_position(field, entry_ids) + def update_entry_field( self, entry_ids: list[int] | int, @@ -527,23 +523,15 @@ def update_entry_field( FieldClass = type(field) with Session(self.engine) as session: - # Subquery to select one matching field's id per entry - subquery = ( - select(FieldClass.id) + update_stmt = ( + update(FieldClass) .where( and_( + FieldClass.position == field.position, FieldClass.type == field.type, FieldClass.entry_id.in_(entry_ids), ) ) - # .limit(1) # TODO - uncomment this when .position will be implemented - .subquery() - ) - - # Update one matching field per entry - update_stmt = ( - update(FieldClass) - .where(FieldClass.id.in_(subquery)) # type: ignore .values(value=content) ) @@ -615,17 +603,23 @@ def add_entry_field_type( try: for entry_id in entry_ids: field_model.entry_id = entry_id + # create random value position to avoid IntegrityError, reordering is below + field_model.position = -randint(0, 100_000) + session.add(field_model) session.flush() session.commit() - return True except IntegrityError as e: logger.exception(e) session.rollback() return False # TODO - trigger error signal + # recalculate the positions of fields + self.update_field_position(field_model, entry_ids) + return True + def add_tag(self, tag: Tag, subtag_ids: list[int] | None = None) -> Tag | None: with Session(self.engine, expire_on_commit=False) as session: try: diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 276324c38..5d869e502 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -251,7 +251,7 @@ def test_remove_field_entry_with_multiple_field(library, entry_full): # add identical field library.add_entry_field_type(entry_full.id, field_id=title_field.type_key) - # remove entyr field + # remove entry field library.remove_entry_field(title_field, [entry_full.id]) # Then one field should remain @@ -272,8 +272,6 @@ def test_update_entry_field(library, entry_full): assert entry.text_fields[0].value == "new value" -# TODO - uncomment this when .position will be implemented -@pytest.mark.skip def test_update_entry_with_multiple_identical_fields(library, entry_full): # Given title_field = entry_full.text_fields[0] @@ -378,11 +376,24 @@ def test_update_field_order(library, entry_full): # Given title_field = entry_full.text_fields[0] - # When - library.add_entry_field_type(entry_full.id, field_id=title_field.type_key) + # When add two more fields + library.add_entry_field_type( + entry_full.id, field_id=title_field.type_key, value="first" + ) + library.add_entry_field_type( + entry_full.id, field_id=title_field.type_key, value="second" + ) + + # remove the one on first position + assert title_field.position == 0 + library.remove_entry_field(title_field, [entry_full.id]) + + # recalculate the positions library.update_field_position(title_field, entry_full.id) # Then entry = next(library.get_entries(with_joins=True)) assert entry.text_fields[0].position == 0 + assert entry.text_fields[0].value == "first" assert entry.text_fields[1].position == 1 + assert entry.text_fields[1].value == "second" From 2689bb4f9df3da947e68e5f2a7ab4783d2ad7942 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 12:03:37 +0700 Subject: [PATCH 17/25] use folder --- tagstudio/src/core/library/alchemy/library.py | 84 ++++++++++++++----- tagstudio/src/core/library/alchemy/models.py | 14 ++++ tagstudio/src/core/utils/refresh_dir.py | 19 +++-- tagstudio/src/qt/widgets/preview_panel.py | 8 +- tagstudio/tests/conftest.py | 11 ++- tagstudio/tests/macros/test_dupe_entries.py | 10 ++- tagstudio/tests/qt/test_driver.py | 4 +- tagstudio/tests/qt/test_preview_panel.py | 18 +++- tagstudio/tests/test_library.py | 13 ++- 9 files changed, 145 insertions(+), 36 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 4ba7f46bc..580c6dd4f 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -4,6 +4,7 @@ from pathlib import Path from random import randint from typing import Iterator, Any +from uuid import uuid4 import structlog from sqlalchemy import ( @@ -16,6 +17,7 @@ update, URL, exists, + delete, ) from sqlalchemy.exc import IntegrityError, InvalidRequestError from sqlalchemy.orm import ( @@ -36,7 +38,7 @@ Field, ) from .joins import TagSubtag, TagField -from .models import Entry, Preferences, Tag, TagAlias, LibraryField +from .models import Entry, Preferences, Tag, TagAlias, LibraryField, Folder from ...constants import ( LibraryPrefs, TS_FOLDER_NAME, @@ -97,6 +99,7 @@ class Library: library_dir: Path storage_path: Path | str engine: Engine | None + folder: Folder | None ignored_extensions: list[str] @@ -157,6 +160,23 @@ def open_library( logger.debug("preference already exists", pref=pref) session.rollback() + # check if folder matching current path exists already + self.folder = session.scalar( + select(Folder).where(Folder.path == self.library_dir) + ) + if not self.folder: + folder = Folder( + path=self.library_dir, + uuid=str(uuid4()), + ) + session.add(folder) + session.expunge(folder) + + session.commit() + self.folder = folder + + print("folder", self.folder) + # load ignored extensions self.ignored_extensions = self.prefs(LibraryPrefs.EXTENSION_LIST) @@ -223,19 +243,20 @@ def get_entries(self, with_joins: bool = False) -> Iterator[Entry]: with Session(self.engine) as session: stmt = select(Entry) if with_joins: + # load Entry with all joins and all tags stmt = ( stmt.outerjoin(Entry.text_fields) .outerjoin(Entry.datetime_fields) .outerjoin(Entry.tag_box_fields) - .options( - contains_eager(Entry.text_fields), - contains_eager(Entry.datetime_fields), - contains_eager(Entry.tag_box_fields).selectinload( - TagBoxField.tags - ), - ) ) - stmt = stmt.distinct() + stmt = stmt.options( + contains_eager(Entry.text_fields), + contains_eager(Entry.datetime_fields), + contains_eager(Entry.tag_box_fields).selectinload(TagBoxField.tags), + ) + + stmt = stmt # .distinct() + print(stmt.compile(compile_kwargs={"literal_binds": True})) entries = session.execute(stmt).scalars() if with_joins: @@ -477,19 +498,30 @@ def update_field_position(self, field: Field, entry_ids: list[int]): FieldClass = type(field) with Session(self.engine) as session: + field_from_session = session.merge(field) + field_position = field_from_session.position + for entry_id in entry_ids: - rows = session.scalars( - select(FieldClass) - .where(FieldClass.entry_id == entry_id) - .order_by(FieldClass.id) + rows = list( + session.scalars( + select(FieldClass) + .where( + and_( + FieldClass.entry_id == entry_id, + FieldClass.position == field_position, + ) + ) + .order_by(FieldClass.id) + ) ) # Reassign `order` starting from 0 for index, row in enumerate(rows): row.position = index # type: ignore + session.add(row) - session.add(row) - session.commit() + if rows: + session.commit() def remove_entry_field( self, @@ -498,14 +530,28 @@ def remove_entry_field( ) -> None: FieldClass = type(field) + logger.info( + "remove_entry_field", + field=field, + entry_ids=entry_ids, + field_type=field.type, + cls=FieldClass, + pos=field.position, + ) + with Session(self.engine) as session: - session.query(FieldClass).where( + # remove all fields matching entry and field_type + delete_stmt = delete(FieldClass).where( and_( - FieldClass.entry_id.in_(entry_ids), - FieldClass.type == field.type, FieldClass.position == field.position, + FieldClass.type_key == field.type_key, + FieldClass.entry_id.in_(entry_ids), ) - ).delete() + ) + + print(delete_stmt.compile(compile_kwargs={"literal_binds": True})) + + session.execute(delete_stmt) session.commit() # recalculate the remaining positions diff --git a/tagstudio/src/core/library/alchemy/models.py b/tagstudio/src/core/library/alchemy/models.py index ce43ce69e..61d6fdcca 100644 --- a/tagstudio/src/core/library/alchemy/models.py +++ b/tagstudio/src/core/library/alchemy/models.py @@ -101,11 +101,23 @@ def __repr__(self) -> str: return self.__str__() +class Folder(Base): + __tablename__ = "folders" + + # TODO - implement this + id: Mapped[int] = mapped_column(primary_key=True) + path: Mapped[Path] = mapped_column(PathType, unique=True) + uuid: Mapped[str] = mapped_column(unique=True) + + class Entry(Base): __tablename__ = "entries" id: Mapped[int] = mapped_column(primary_key=True) + folder_id: Mapped[int] = mapped_column(ForeignKey("folders.id")) + folder: Mapped[Folder] = relationship("Folder") + path: Mapped[Path] = mapped_column(PathType, unique=True) text_fields: Mapped[list[TextField]] = relationship( @@ -158,9 +170,11 @@ def is_archived(self) -> bool: def __init__( self, path: Path, + folder: Folder, fields: list[Field] | None = None, ) -> None: self.path = path + self.folder = folder if fields is None: fields = [ diff --git a/tagstudio/src/core/utils/refresh_dir.py b/tagstudio/src/core/utils/refresh_dir.py index 1c0a3c191..5d56dfb41 100644 --- a/tagstudio/src/core/utils/refresh_dir.py +++ b/tagstudio/src/core/utils/refresh_dir.py @@ -23,21 +23,30 @@ def save_new_files(self) -> Iterator[int]: yield 0 for idx, entry_path in enumerate(self.files_not_in_library): - self.library.add_entries([Entry(path=entry_path)]) + self.library.add_entries( + [ + Entry( + path=entry_path, + folder=self.library.folder, + ) + ] + ) yield idx self.files_not_in_library = [] def refresh_dir(self) -> Iterator[int]: """Scan a directory for files, and add those relative filenames to internal variables.""" - if self.library.library_dir is None: - raise ValueError("No library path set.") + if self.library.folder is None: + raise ValueError("No folder set.") start_time = time.time() self.files_not_in_library = [] self.dir_file_count = 0 - for path in self.library.library_dir.glob("**/*"): + lib_path = self.library.folder.path + + for path in lib_path.glob("**/*"): str_path = str(path) if ( path.is_dir() @@ -52,7 +61,7 @@ def refresh_dir(self) -> Iterator[int]: continue self.dir_file_count += 1 - relative_path = path.relative_to(self.library.library_dir) + relative_path = path.relative_to(lib_path) # TODO - load these in batch somehow if not self.library.has_path_entry(relative_path): self.files_not_in_library.append(relative_path) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 3ab8594b1..b2e8b5152 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -792,6 +792,7 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): self.tags_updated.emit() # self.dynamic_widgets.append(inner_container) elif field.type.type == FieldTypeEnum.TEXT_LINE: + print("is text line") container.set_title(field.type.name) container.set_inline(False) @@ -833,6 +834,8 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): ) elif field.type.type == FieldTypeEnum.TEXT_BOX: + print("is text box") + container.set_title(field.type.name) # container.set_editable(True) container.set_inline(False) @@ -868,8 +871,7 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): ) ) - elif field.type == DatetimeField: - # logging.info(f'WRITING DATETIME FOR ITEM {item.id}') + elif field.type.type == FieldTypeEnum.DATETIME: if not is_mixed: try: container.set_title(field.type.name) @@ -905,6 +907,7 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): inner_container = TextWidget(title, text) container.set_inner_widget(inner_container) else: + logger.warning("write_container - unknown field", field=field) container.set_title(field.type.name) # container.set_editable(False) container.set_inline(False) @@ -931,6 +934,7 @@ def remove_field(self, field: Field): """Remove a field from all selected Entries.""" logger.info("removing field", field=field, selected=self.selected) entry_ids = [] + for grid_idx in self.selected: entry = self.driver.frame_content[grid_idx] entry_ids.append(entry.id) diff --git a/tagstudio/tests/conftest.py b/tagstudio/tests/conftest.py index c5af09fff..c4dc81a88 100644 --- a/tagstudio/tests/conftest.py +++ b/tagstudio/tests/conftest.py @@ -34,6 +34,7 @@ def library(request): lib = Library() lib.open_library(library_path, ":memory:") + assert lib.folder tag = Tag( name="foo", @@ -48,7 +49,10 @@ def library(request): assert lib.add_tag(tag) # default item with deterministic name - entry = generate_entry(path=pathlib.Path("foo.txt")) + entry = generate_entry( + folder=lib.folder, + path=pathlib.Path("foo.txt"), + ) entry.tag_box_fields = [ TagBoxField( @@ -61,7 +65,10 @@ def library(request): ), ] - entry2 = generate_entry(path=pathlib.Path("one/two/bar.md")) + entry2 = generate_entry( + folder=lib.folder, + path=pathlib.Path("one/two/bar.md"), + ) entry2.tag_box_fields = [ TagBoxField( tags={tag2}, diff --git a/tagstudio/tests/macros/test_dupe_entries.py b/tagstudio/tests/macros/test_dupe_entries.py index 06fa0e947..22dfc84a9 100644 --- a/tagstudio/tests/macros/test_dupe_entries.py +++ b/tagstudio/tests/macros/test_dupe_entries.py @@ -7,8 +7,14 @@ def test_refresh_dupe_files(library): - entry = generate_entry(path=pathlib.Path("bar/foo.txt")) - entry2 = generate_entry(path=pathlib.Path("foo/foo.txt")) + entry = generate_entry( + folder=library.folder, + path=pathlib.Path("bar/foo.txt"), + ) + entry2 = generate_entry( + folder=library.folder, + path=pathlib.Path("foo/foo.txt"), + ) library.add_entries([entry, entry2]) diff --git a/tagstudio/tests/qt/test_driver.py b/tagstudio/tests/qt/test_driver.py index b01ac355f..715ad2245 100644 --- a/tagstudio/tests/qt/test_driver.py +++ b/tagstudio/tests/qt/test_driver.py @@ -8,7 +8,9 @@ def test_update_thumbs(qt_driver): - qt_driver.frame_content = [Entry(path=Path("/tmp/foo"))] + qt_driver.frame_content = [ + Entry(folder=qt_driver.lib.folder, path=Path("/tmp/foo")) + ] qt_driver.item_thumbs = [] for i in range(3): diff --git a/tagstudio/tests/qt/test_preview_panel.py b/tagstudio/tests/qt/test_preview_panel.py index 986ddbae9..9b121a238 100644 --- a/tagstudio/tests/qt/test_preview_panel.py +++ b/tagstudio/tests/qt/test_preview_panel.py @@ -1,3 +1,5 @@ +from sqlalchemy.orm import Session + from src.core.library.alchemy.enums import FieldTypeEnum from src.core.library.alchemy.fields import _FieldID, TextField from src.qt.widgets.preview_panel import PreviewPanel @@ -27,7 +29,10 @@ def test_update_widgets_single_selected(qt_driver, library): def test_update_widgets_multiple_selected(qt_driver, library): # entry with no tag fields - entry = generate_entry(fields=[TextField(type_key=_FieldID.TITLE.name)]) + entry = generate_entry( + folder=library.folder, + fields=[TextField(type_key=_FieldID.TITLE.name)], + ) assert not entry.tag_box_fields @@ -86,6 +91,8 @@ def test_remove_field(qt_driver, library): panel = PreviewPanel(library, qt_driver) entries = list(library.get_entries(with_joins=True)) qt_driver.frame_content = entries + + # When second entry is selected panel.selected = [1] field = entries[1].text_fields[0] @@ -93,7 +100,14 @@ def test_remove_field(qt_driver, library): panel.remove_field(field) entries = list(library.get_entries(with_joins=True)) - assert not entries[1].text_fields + print("entry fields", entries[1].fields) + + with Session(library.engine) as session: + entries = session.query(TextField).all() + for entry in entries: + print("entry", entry.entry_id) + + # assert not entries[1].text_fields def test_update_field(qt_driver, library, entry_full): diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 5d869e502..3e1ae34b4 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -10,9 +10,11 @@ from src.core.library.alchemy import Library from src.core.library.alchemy.enums import FilterState from src.core.library.alchemy.fields import _FieldID, TextField +from src.core.library.alchemy.models import Folder -def generate_entry(*, path: Path = None, **kwargs) -> Entry: +def generate_entry(*, folder: Folder, path: Path = None, **kwargs) -> Entry: + assert folder if not path: # TODO - be sure no collision happens name = "".join(random.choices(string.ascii_lowercase, k=10)) @@ -20,6 +22,7 @@ def generate_entry(*, path: Path = None, **kwargs) -> Entry: return Entry( path=path, + folder=folder, **kwargs, ) @@ -107,7 +110,7 @@ def test_get_entry(library, entry_min): def test_entries_count(library): - entries = [generate_entry() for _ in range(10)] + entries = [generate_entry(folder=library.folder) for _ in range(10)] library.add_entries(entries) matches, page = library.search_library( FilterState( @@ -122,7 +125,10 @@ def test_entries_count(library): def test_add_field_to_entry(library): # Given item_path = Path("xxx") - entry = generate_entry(path=item_path) + entry = generate_entry( + folder=library.folder, + path=item_path, + ) # meta tags + content tags assert len(entry.tag_box_fields) == 2 @@ -295,6 +301,7 @@ def test_update_entry_with_multiple_identical_fields(library, entry_full): def test_mirror_entry_fields(library, entry_full): target_entry = generate_entry( + folder=library.folder, path=Path("xxx"), fields=[ TextField( From a4acf0a90f4a3f6d42b0190f4a1aed7db4a8997f Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 16:04:01 +0700 Subject: [PATCH 18/25] take field position into consideration --- tagstudio/src/core/library/alchemy/fields.py | 8 +-- tagstudio/src/core/library/alchemy/library.py | 58 ++++++++++--------- tagstudio/src/core/library/alchemy/models.py | 8 +-- tagstudio/src/qt/modals/build_tag.py | 18 +++--- tagstudio/src/qt/widgets/item_thumb.py | 7 ++- tagstudio/src/qt/widgets/preview_panel.py | 10 ++-- tagstudio/tests/conftest.py | 14 ++--- tagstudio/tests/macros/test_dupe_entries.py | 6 +- tagstudio/tests/qt/test_preview_panel.py | 19 +++--- tagstudio/tests/test_library.py | 44 +++++--------- 10 files changed, 88 insertions(+), 104 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/fields.py b/tagstudio/src/core/library/alchemy/fields.py index 095966a3c..9707da172 100644 --- a/tagstudio/src/core/library/alchemy/fields.py +++ b/tagstudio/src/core/library/alchemy/fields.py @@ -27,7 +27,7 @@ class BooleanField(Base): entry: Mapped[Entry] = relationship() value: Mapped[bool] - position: Mapped[int] = mapped_column(default=0) + position: Mapped[int] def __key(self): return (self.type, self.value) @@ -59,7 +59,7 @@ class TextField(Base): entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) value: Mapped[str | None] - position: Mapped[int] = mapped_column(default=0) + position: Mapped[int] def __key(self): return (self.type, self.value) @@ -86,7 +86,7 @@ class TagBoxField(Base): entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) tags: Mapped[set[Tag]] = relationship(secondary="tag_fields") - position: Mapped[int] = mapped_column(default=0) + position: Mapped[int] def __key(self): return ( @@ -119,7 +119,7 @@ class DatetimeField(Base): entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) value: Mapped[str | None] - position: Mapped[int] = mapped_column(default=0) + position: Mapped[int] def __key(self): return (self.type, self.value) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 580c6dd4f..eae1a2986 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -3,7 +3,7 @@ from os import makedirs from pathlib import Path from random import randint -from typing import Iterator, Any +from typing import Iterator, Any, Type from uuid import uuid4 import structlog @@ -175,8 +175,6 @@ def open_library( session.commit() self.folder = folder - print("folder", self.folder) - # load ignored extensions self.ignored_extensions = self.prefs(LibraryPrefs.EXTENSION_LIST) @@ -255,8 +253,7 @@ def get_entries(self, with_joins: bool = False) -> Iterator[Entry]: contains_eager(Entry.tag_box_fields).selectinload(TagBoxField.tags), ) - stmt = stmt # .distinct() - print(stmt.compile(compile_kwargs={"literal_binds": True})) + stmt = stmt.distinct() entries = session.execute(stmt).scalars() if with_joins: @@ -491,27 +488,27 @@ def remove_tag_from_field(self, tag: Tag, field: TagBoxField) -> None: session.add(field_) session.commit() - def update_field_position(self, field: Field, entry_ids: list[int]): + def update_field_position( + self, + field_class: Type[Field], + field_type: str, + entry_ids: list[int] | int, + ): if isinstance(entry_ids, int): entry_ids = [entry_ids] - FieldClass = type(field) - with Session(self.engine) as session: - field_from_session = session.merge(field) - field_position = field_from_session.position - for entry_id in entry_ids: rows = list( session.scalars( - select(FieldClass) + select(field_class) .where( and_( - FieldClass.entry_id == entry_id, - FieldClass.position == field_position, + field_class.entry_id == entry_id, + field_class.type_key == field_type, ) ) - .order_by(FieldClass.id) + .order_by(field_class.id) ) ) @@ -519,9 +516,9 @@ def update_field_position(self, field: Field, entry_ids: list[int]): for index, row in enumerate(rows): row.position = index # type: ignore session.add(row) - - if rows: - session.commit() + session.flush() + if rows: + session.commit() def remove_entry_field( self, @@ -549,13 +546,12 @@ def remove_entry_field( ) ) - print(delete_stmt.compile(compile_kwargs={"literal_binds": True})) - session.execute(delete_stmt) + session.commit() # recalculate the remaining positions - self.update_field_position(field, entry_ids) + # self.update_field_position(type(field), field.type, entry_ids) def update_entry_field( self, @@ -591,9 +587,11 @@ def field_types(self) -> dict[str, LibraryField]: def get_library_field(self, field_key: str) -> LibraryField: with Session(self.engine) as session: - return session.scalar( + field = session.scalar( select(LibraryField).where(LibraryField.key == field_key) ) + session.expunge(field) + return field def add_entry_field_type( self, @@ -624,12 +622,13 @@ def add_entry_field_type( field_model: TextField | DatetimeField | TagBoxField if field.type in (FieldTypeEnum.TEXT_LINE, FieldTypeEnum.TEXT_BOX): field_model = TextField( - type=field, + type_key=field.key, value=value or "", + position=randint(100, 100_000), ) elif field.type == FieldTypeEnum.TAGS: field_model = TagBoxField( - type=field, + type_key=field.key, ) if value: @@ -639,7 +638,7 @@ def add_entry_field_type( elif field.type == FieldTypeEnum.DATETIME: field_model = DatetimeField( - type=field, + type_key=field.key, value=value, ) else: @@ -650,7 +649,7 @@ def add_entry_field_type( for entry_id in entry_ids: field_model.entry_id = entry_id # create random value position to avoid IntegrityError, reordering is below - field_model.position = -randint(0, 100_000) + field_model.position = randint(100, 100_000) session.add(field_model) session.flush() @@ -663,7 +662,11 @@ def add_entry_field_type( # TODO - trigger error signal # recalculate the positions of fields - self.update_field_position(field_model, entry_ids) + self.update_field_position( + field_class=type(field_model), + field_type=field.key, + entry_ids=entry_ids, + ) return True def add_tag(self, tag: Tag, subtag_ids: list[int] | None = None) -> Tag | None: @@ -718,6 +721,7 @@ def add_field_tag( field = TagBoxField( type_key=field_key, entry_id=entry.id, + position=0, ) field.tags = field.tags | {tag} diff --git a/tagstudio/src/core/library/alchemy/models.py b/tagstudio/src/core/library/alchemy/models.py index 61d6fdcca..da036ad8f 100644 --- a/tagstudio/src/core/library/alchemy/models.py +++ b/tagstudio/src/core/library/alchemy/models.py @@ -178,9 +178,9 @@ def __init__( if fields is None: fields = [ - TagBoxField(type_key=_FieldID.TAGS_META.name), - TagBoxField(type_key=_FieldID.TAGS_CONTENT.name), - TextField(type_key=_FieldID.TITLE.name), + TagBoxField(type_key=_FieldID.TAGS_META.name, position=0), + TagBoxField(type_key=_FieldID.TAGS_CONTENT.name, position=0), + TextField(type_key=_FieldID.TITLE.name, position=0), ] for field in fields: @@ -225,7 +225,7 @@ class LibraryField(Base): key: Mapped[str] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(nullable=False) type: Mapped[FieldTypeEnum] = mapped_column(default=FieldTypeEnum.TEXT_LINE) - order: Mapped[int] = mapped_column(default=0) + order: Mapped[int] # add relations to other tables text_fields: Mapped[list[TextField]] = relationship( diff --git a/tagstudio/src/qt/modals/build_tag.py b/tagstudio/src/qt/modals/build_tag.py index 78769f51b..5507fb09a 100644 --- a/tagstudio/src/qt/modals/build_tag.py +++ b/tagstudio/src/qt/modals/build_tag.py @@ -31,9 +31,7 @@ class BuildTagPanel(PanelWidget): on_edit = Signal(Tag) - def __init__( - self, library: Library, tag_id: int | None = None, tag: Tag | None = None - ): + def __init__(self, library: Library, tag: Tag | None = None): super().__init__() self.lib = library # self.callback = callback @@ -161,14 +159,9 @@ def __init__( self.root_layout.addWidget(self.color_widget) # self.parent().done.connect(self.update_tag) - if tag_id is not None: - tag = self.lib.get_tag(tag_id) - elif not tag: - tag = Tag(name="New Tag") - # TODO - fill subtags self.subtags: set[int] = set() - self.set_tag(tag) + self.set_tag(tag or Tag(name="New Tag")) def add_subtag_callback(self, tag_id: int): logger.info("add_subtag_callback", tag_id=tag_id) @@ -203,7 +196,12 @@ def set_tag(self, tag: Tag): # TODO: Implement aliases # self.aliases_field.setText("\n".join(tag.aliases)) self.set_subtags() - self.color_field.setCurrentIndex(tag.color.value) + # select item in self.color_field where the userData value matched tag.color + for i in range(self.color_field.count()): + if self.color_field.itemData(i) == tag.color: + self.color_field.setCurrentIndex(i) + break + self.tag = tag def build_tag(self) -> Tag: diff --git a/tagstudio/src/qt/widgets/item_thumb.py b/tagstudio/src/qt/widgets/item_thumb.py index aae012f34..05a02d060 100644 --- a/tagstudio/src/qt/widgets/item_thumb.py +++ b/tagstudio/src/qt/widgets/item_thumb.py @@ -438,15 +438,16 @@ def set_item_id(self, entry: Entry): self.item_id = entry.id def assign_badge(self, badge_type: BadgeType, value: bool) -> None: - # mode = self.mode - # self.mode = None + mode = self.mode + # blank mode to avoid recursive badge updates + self.mode = None badge = self.badges[badge_type] self.badge_active[badge_type] = value if badge.isChecked() != value: badge.setChecked(value) badge.setHidden(not value) - # self.mode = mode + self.mode = mode def show_check_badges(self, show: bool): if self.mode != ItemType.TAG_GROUP: diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index b2e8b5152..63d11697f 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -616,9 +616,12 @@ def update_widgets(self) -> bool: self.preview_img.is_connected = True self.selected = self.driver.selected - logger.info("rendering item fields", item=item, fields=item.tag_box_fields) + logger.info( + "rendering item fields", + item=item.id, + fields=[x.type_key for x in item.fields], + ) for idx, field in enumerate(item.fields): - logger.info("write container in update_widgets", idx=idx, field=field) self.write_container(idx, field) # Hide leftover containers @@ -792,7 +795,6 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): self.tags_updated.emit() # self.dynamic_widgets.append(inner_container) elif field.type.type == FieldTypeEnum.TEXT_LINE: - print("is text line") container.set_title(field.type.name) container.set_inline(False) @@ -834,8 +836,6 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): ) elif field.type.type == FieldTypeEnum.TEXT_BOX: - print("is text box") - container.set_title(field.type.name) # container.set_editable(True) container.set_inline(False) diff --git a/tagstudio/tests/conftest.py b/tagstudio/tests/conftest.py index c4dc81a88..2535be7b2 100644 --- a/tagstudio/tests/conftest.py +++ b/tagstudio/tests/conftest.py @@ -9,10 +9,9 @@ # this needs to be above `src` imports sys.path.insert(0, str(CWD.parent)) -from src.core.library import Library, Tag +from src.core.library import Library, Tag, Entry from src.core.library.alchemy.enums import TagColor from src.core.library.alchemy.fields import TagBoxField, _FieldID -from tests.test_library import generate_entry from src.core.library import alchemy as backend from src.qt.ts_qt import QtDriver @@ -49,23 +48,21 @@ def library(request): assert lib.add_tag(tag) # default item with deterministic name - entry = generate_entry( + entry = Entry( folder=lib.folder, path=pathlib.Path("foo.txt"), ) entry.tag_box_fields = [ - TagBoxField( - type_key=_FieldID.TAGS.name, - tags={tag}, - ), + TagBoxField(type_key=_FieldID.TAGS.name, tags={tag}, position=0), TagBoxField( type_key=_FieldID.TAGS_META.name, + position=0, # tags={tag2} ), ] - entry2 = generate_entry( + entry2 = Entry( folder=lib.folder, path=pathlib.Path("one/two/bar.md"), ) @@ -73,6 +70,7 @@ def library(request): TagBoxField( tags={tag2}, type_key=_FieldID.TAGS_META.name, + position=0, ), ] diff --git a/tagstudio/tests/macros/test_dupe_entries.py b/tagstudio/tests/macros/test_dupe_entries.py index 22dfc84a9..9b10a5811 100644 --- a/tagstudio/tests/macros/test_dupe_entries.py +++ b/tagstudio/tests/macros/test_dupe_entries.py @@ -1,17 +1,17 @@ import pathlib +from src.core.library import Entry from src.core.utils.dupe_files import DupeRegistry -from tests.test_library import generate_entry CWD = pathlib.Path(__file__).parent def test_refresh_dupe_files(library): - entry = generate_entry( + entry = Entry( folder=library.folder, path=pathlib.Path("bar/foo.txt"), ) - entry2 = generate_entry( + entry2 = Entry( folder=library.folder, path=pathlib.Path("foo/foo.txt"), ) diff --git a/tagstudio/tests/qt/test_preview_panel.py b/tagstudio/tests/qt/test_preview_panel.py index 9b121a238..7d66aa8d7 100644 --- a/tagstudio/tests/qt/test_preview_panel.py +++ b/tagstudio/tests/qt/test_preview_panel.py @@ -1,9 +1,10 @@ -from sqlalchemy.orm import Session +from pathlib import Path + +from src.core.library import Entry from src.core.library.alchemy.enums import FieldTypeEnum from src.core.library.alchemy.fields import _FieldID, TextField from src.qt.widgets.preview_panel import PreviewPanel -from tests.test_library import generate_entry def test_update_widgets_not_selected(qt_driver, library): @@ -29,9 +30,10 @@ def test_update_widgets_single_selected(qt_driver, library): def test_update_widgets_multiple_selected(qt_driver, library): # entry with no tag fields - entry = generate_entry( + entry = Entry( + path=Path("test.txt"), folder=library.folder, - fields=[TextField(type_key=_FieldID.TITLE.name)], + fields=[TextField(type_key=_FieldID.TITLE.name, position=0)], ) assert not entry.tag_box_fields @@ -100,14 +102,7 @@ def test_remove_field(qt_driver, library): panel.remove_field(field) entries = list(library.get_entries(with_joins=True)) - print("entry fields", entries[1].fields) - - with Session(library.engine) as session: - entries = session.query(TextField).all() - for entry in entries: - print("entry", entry.entry_id) - - # assert not entries[1].text_fields + assert not entries[1].text_fields def test_update_field(qt_driver, library, entry_full): diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 3e1ae34b4..7dec2ef41 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -1,5 +1,3 @@ -import random -import string from pathlib import Path, PureWindowsPath from tempfile import TemporaryDirectory @@ -10,21 +8,6 @@ from src.core.library.alchemy import Library from src.core.library.alchemy.enums import FilterState from src.core.library.alchemy.fields import _FieldID, TextField -from src.core.library.alchemy.models import Folder - - -def generate_entry(*, folder: Folder, path: Path = None, **kwargs) -> Entry: - assert folder - if not path: - # TODO - be sure no collision happens - name = "".join(random.choices(string.ascii_lowercase, k=10)) - path = Path(name) - - return Entry( - path=path, - folder=folder, - **kwargs, - ) def test_library_bootstrap(): @@ -41,10 +24,11 @@ def test_library_add_file(): file_path = Path(tmp_dir) / "bar.txt" file_path.write_text("bar") - entry = Entry(path=file_path) - lib = Library() lib.open_library(tmp_dir) + + entry = Entry(path=file_path, folder=lib.folder) + assert not lib.has_path_entry(entry.path) assert lib.add_entries([entry]) @@ -110,7 +94,7 @@ def test_get_entry(library, entry_min): def test_entries_count(library): - entries = [generate_entry(folder=library.folder) for _ in range(10)] + entries = [Entry(path=Path(f"{x}.txt"), folder=library.folder) for x in range(10)] library.add_entries(entries) matches, page = library.search_library( FilterState( @@ -124,10 +108,9 @@ def test_entries_count(library): def test_add_field_to_entry(library): # Given - item_path = Path("xxx") - entry = generate_entry( + entry = Entry( folder=library.folder, - path=item_path, + path=Path("xxx"), ) # meta tags + content tags assert len(entry.tag_box_fields) == 2 @@ -138,7 +121,7 @@ def test_add_field_to_entry(library): library.add_entry_field_type(entry.id, field_id=_FieldID.TAGS) # Then - entry = [x for x in library.get_entries(with_joins=True) if x.path == item_path][0] + entry = [x for x in library.get_entries(with_joins=True) if x.path == entry.path][0] # meta tags and tags field present assert len(entry.tag_box_fields) == 3 @@ -225,7 +208,7 @@ def test_preferences(library): def test_save_windows_path(library, generate_tag): # pretend we are on windows and create `Path` - entry = Entry(path=PureWindowsPath("foo\\bar.txt")) + entry = Entry(path=PureWindowsPath("foo\\bar.txt"), folder=library.folder) tag = generate_tag("win_path") tag_name = tag.name @@ -255,7 +238,7 @@ def test_remove_field_entry_with_multiple_field(library, entry_full): # When # add identical field - library.add_entry_field_type(entry_full.id, field_id=title_field.type_key) + assert library.add_entry_field_type(entry_full.id, field_id=title_field.type_key) # remove entry field library.remove_entry_field(title_field, [entry_full.id]) @@ -300,13 +283,14 @@ def test_update_entry_with_multiple_identical_fields(library, entry_full): def test_mirror_entry_fields(library, entry_full): - target_entry = generate_entry( + target_entry = Entry( folder=library.folder, path=Path("xxx"), fields=[ TextField( type_key=_FieldID.NOTES.name, value="notes", + position=0, ) ], ) @@ -396,7 +380,11 @@ def test_update_field_order(library, entry_full): library.remove_entry_field(title_field, [entry_full.id]) # recalculate the positions - library.update_field_position(title_field, entry_full.id) + library.update_field_position( + type(title_field), + title_field.type_key, + entry_full.id, + ) # Then entry = next(library.get_entries(with_joins=True)) From e983a172a9e24173f4fb42fa5a4f9f9dff92418e Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 16:40:22 +0700 Subject: [PATCH 19/25] fix adding tag --- tagstudio/src/core/library/alchemy/library.py | 11 +++++++++-- tagstudio/src/qt/widgets/tag_box.py | 1 - 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index eae1a2986..8f394bfa0 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -723,9 +723,16 @@ def add_field_tag( entry_id=entry.id, position=0, ) + session.add(field) + session.flush() + + # create record for `TagField` table + tag_field = TagField( + tag_id=tag.id, + field_id=field.id, + ) + session.add(tag_field) - field.tags = field.tags | {tag} - session.add(field) session.commit() logger.info( "tag added to field", tag=tag, field=field, entry_id=entry.id diff --git a/tagstudio/src/qt/widgets/tag_box.py b/tagstudio/src/qt/widgets/tag_box.py index 49e3f59c0..567721fa1 100644 --- a/tagstudio/src/qt/widgets/tag_box.py +++ b/tagstudio/src/qt/widgets/tag_box.py @@ -148,7 +148,6 @@ def add_tag_callback(self, tag_id: int): logger.info("add_tag_callback", tag_id=tag_id, selected=self.driver.selected) tag = self.driver.lib.get_tag(tag_id=tag_id) - for idx in self.driver.selected: entry: Entry = self.driver.frame_content[idx] From a679d2cf90905bc9bebb431ec8cd109548adca35 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 16:52:43 +0700 Subject: [PATCH 20/25] fix test --- tagstudio/src/core/library/alchemy/library.py | 7 ++++++- tagstudio/tests/qt/test_tag_widget.py | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 8f394bfa0..7ea0045b9 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -727,12 +727,17 @@ def add_field_tag( session.flush() # create record for `TagField` table + if not tag.id: + session.add(tag) + session.flush() + + assert tag.id tag_field = TagField( tag_id=tag.id, field_id=field.id, ) - session.add(tag_field) + session.add(tag_field) session.commit() logger.info( "tag added to field", tag=tag, field=field, entry_id=entry.id diff --git a/tagstudio/tests/qt/test_tag_widget.py b/tagstudio/tests/qt/test_tag_widget.py index f19c50610..663a4ef18 100644 --- a/tagstudio/tests/qt/test_tag_widget.py +++ b/tagstudio/tests/qt/test_tag_widget.py @@ -1,5 +1,7 @@ from unittest.mock import patch +import pytest + from src.core.library.alchemy.fields import _FieldID from src.qt.widgets.tag import TagWidget from src.qt.widgets.tag_box import TagBoxWidget @@ -23,9 +25,12 @@ def test_tag_widget(qtbot, library, qt_driver): assert tag_widget.add_modal.isVisible() +@pytest.mark.skip def test_tag_widget_add_existing_raises(qtbot, library, qt_driver): # Given entry = next(library.get_entries(with_joins=True)) + print(entry.tag_box_fields) + tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] assert len(entry.tags) == 1 @@ -37,6 +42,9 @@ def test_tag_widget_add_existing_raises(qtbot, library, qt_driver): tag_widget.driver.frame_content = [entry] tag_widget.driver.selected = [0] + entry = next(library.get_entries(with_joins=True)) + print(entry.tag_box_fields) + # Then with patch.object(tag_widget, "error_occurred") as mocked: tag_widget.add_modal.widget.tag_chosen.emit(tag.id) From 51f0bb684aa776f05b5eaa8881f0c9dfc65af7dd Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 17:01:55 +0700 Subject: [PATCH 21/25] try to catch the correct exception, moron --- tagstudio/src/core/library/alchemy/library.py | 6 ++--- tagstudio/tests/qt/test_tag_widget.py | 24 +++++++------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 7ea0045b9..4b3948b0c 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -19,7 +19,7 @@ exists, delete, ) -from sqlalchemy.exc import IntegrityError, InvalidRequestError +from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import ( Session, contains_eager, @@ -731,7 +731,6 @@ def add_field_tag( session.add(tag) session.flush() - assert tag.id tag_field = TagField( tag_id=tag.id, field_id=field.id, @@ -744,9 +743,10 @@ def add_field_tag( ) return True - except InvalidRequestError as e: + except IntegrityError as e: logger.exception(e) session.rollback() + return False def save_library_backup_to_disk(self) -> Path: diff --git a/tagstudio/tests/qt/test_tag_widget.py b/tagstudio/tests/qt/test_tag_widget.py index 663a4ef18..e983841cf 100644 --- a/tagstudio/tests/qt/test_tag_widget.py +++ b/tagstudio/tests/qt/test_tag_widget.py @@ -1,6 +1,5 @@ from unittest.mock import patch -import pytest from src.core.library.alchemy.fields import _FieldID from src.qt.widgets.tag import TagWidget @@ -25,26 +24,19 @@ def test_tag_widget(qtbot, library, qt_driver): assert tag_widget.add_modal.isVisible() -@pytest.mark.skip -def test_tag_widget_add_existing_raises(qtbot, library, qt_driver): +def test_tag_widget_add_existing_raises(library, qt_driver, entry_full): # Given - entry = next(library.get_entries(with_joins=True)) - print(entry.tag_box_fields) - - tag_field = [f for f in entry.tag_box_fields if f.type_key == _FieldID.TAGS.name][0] - - assert len(entry.tags) == 1 - tag = next(iter(entry.tags)) - tag_widget = TagBoxWidget(tag_field, "title", qt_driver) + tag_field = [ + f for f in entry_full.tag_box_fields if f.type_key == _FieldID.TAGS.name + ][0] + assert len(entry_full.tags) == 1 + tag = next(iter(entry_full.tags)) # When - qtbot.add_widget(tag_widget) - tag_widget.driver.frame_content = [entry] + tag_widget = TagBoxWidget(tag_field, "title", qt_driver) + tag_widget.driver.frame_content = [entry_full] tag_widget.driver.selected = [0] - entry = next(library.get_entries(with_joins=True)) - print(entry.tag_box_fields) - # Then with patch.object(tag_widget, "error_occurred") as mocked: tag_widget.add_modal.widget.tag_chosen.emit(tag.id) From deeaef766012e913e6af8f205cdad4155da2e824 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 18:21:35 +0700 Subject: [PATCH 22/25] dont expunge subtags --- tagstudio/src/core/library/alchemy/library.py | 2 -- tagstudio/tests/conftest.py | 11 ++++++++--- tagstudio/tests/qt/test_tag_panel.py | 4 ++-- tagstudio/tests/test_library.py | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 4b3948b0c..4a688a9f7 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -273,8 +273,6 @@ def tags(self) -> list[Tag]: for tag in tags_list: session.expunge(tag) - for subtag in tag.subtags: - session.expunge(subtag) return list(tags_list) diff --git a/tagstudio/tests/conftest.py b/tagstudio/tests/conftest.py index 2535be7b2..2822fe9fd 100644 --- a/tagstudio/tests/conftest.py +++ b/tagstudio/tests/conftest.py @@ -39,14 +39,19 @@ def library(request): name="foo", color=TagColor.RED, ) + assert lib.add_tag(tag) + + subtag = Tag( + name="subbar", + color=TagColor.YELLOW, + ) tag2 = Tag( name="bar", color=TagColor.BLUE, + subtags={subtag}, ) - assert lib.add_tag(tag) - # default item with deterministic name entry = Entry( folder=lib.folder, @@ -75,7 +80,7 @@ def library(request): ] assert lib.add_entries([entry, entry2]) - assert len(lib.tags) == 4 + assert len(lib.tags) == 5 yield lib diff --git a/tagstudio/tests/qt/test_tag_panel.py b/tagstudio/tests/qt/test_tag_panel.py index c22298882..c09d5f777 100644 --- a/tagstudio/tests/qt/test_tag_panel.py +++ b/tagstudio/tests/qt/test_tag_panel.py @@ -10,7 +10,7 @@ def test_tag_panel(qtbot, library): def test_add_tag_callback(qt_driver): # Given - assert len(qt_driver.lib.tags) == 4 + assert len(qt_driver.lib.tags) == 5 qt_driver.add_tag_action_callback() # When @@ -20,5 +20,5 @@ def test_add_tag_callback(qt_driver): # Then tags: set[Tag] = qt_driver.lib.tags - assert len(tags) == 5 + assert len(tags) == 6 assert "xxx" in {tag.name for tag in tags} diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 7dec2ef41..00ef8f968 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -46,7 +46,7 @@ def test_create_tag(library, generate_tag): assert tag.id == 123 tag_inc = library.add_tag(generate_tag("yyy")) - assert tag_inc.id == 1002 + assert tag_inc.id > 1000 def test_library_search(library, generate_tag, entry_full): From 704237f7dc8746e2080c8d4235604c6723f19bbd Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Sun, 8 Sep 2024 20:32:38 +0700 Subject: [PATCH 23/25] DRY models --- tagstudio/src/core/library/alchemy/fields.py | 94 +++++++++---------- tagstudio/src/core/library/alchemy/library.py | 10 +- tagstudio/src/core/library/alchemy/models.py | 8 +- tagstudio/src/qt/widgets/preview_panel.py | 8 +- 4 files changed, 55 insertions(+), 65 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/fields.py b/tagstudio/src/core/library/alchemy/fields.py index 9707da172..168e5f7e8 100644 --- a/tagstudio/src/core/library/alchemy/fields.py +++ b/tagstudio/src/core/library/alchemy/fields.py @@ -2,10 +2,10 @@ from dataclasses import dataclass from enum import Enum -from typing import Union, Any, TYPE_CHECKING +from typing import Any, TYPE_CHECKING from sqlalchemy import ForeignKey, ForeignKeyConstraint -from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm import Mapped, mapped_column, relationship, declared_attr from .db import Base from .enums import FieldTypeEnum @@ -13,35 +13,58 @@ if TYPE_CHECKING: from .models import Entry, Tag, LibraryField -Field = Union["TextField", "TagBoxField", "DatetimeField"] +class BaseField(Base): + __abstract__ = True -class BooleanField(Base): - __tablename__ = "boolean_fields" + @declared_attr + def id(cls) -> Mapped[int]: + return mapped_column(primary_key=True, autoincrement=True) - id: Mapped[int] = mapped_column(primary_key=True) - type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) - type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) + @declared_attr + def type_key(cls) -> Mapped[str]: + return mapped_column(ForeignKey("library_fields.key")) - entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) - entry: Mapped[Entry] = relationship() + @declared_attr + def type(cls) -> Mapped[LibraryField]: + return relationship(foreign_keys=[cls.type_key], lazy=False) # type: ignore - value: Mapped[bool] - position: Mapped[int] + @declared_attr + def entry_id(cls) -> Mapped[int]: + return mapped_column(ForeignKey("entries.id")) - def __key(self): - return (self.type, self.value) + @declared_attr + def entry(cls) -> Mapped[Entry]: + return relationship(foreign_keys=[cls.entry_id]) # type: ignore + + @declared_attr + def position(cls) -> Mapped[int]: + return mapped_column() def __hash__(self): return hash(self.__key()) + def __key(self): + raise NotImplementedError + + value: Any + + +class BooleanField(BaseField): + __tablename__ = "boolean_fields" + + value: Mapped[bool] + + def __key(self): + return (self.type, self.value) + def __eq__(self, value) -> bool: if isinstance(value, BooleanField): return self.__key() == value.__key() raise NotImplementedError -class TextField(Base): +class TextField(BaseField): __tablename__ = "text_fields" # constrain for combination of: entry_id, type_key and position __table_args__ = ( @@ -51,21 +74,10 @@ class TextField(Base): ), ) - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) - type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) - - entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) - entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) - value: Mapped[str | None] - position: Mapped[int] - def __key(self): - return (self.type, self.value) - - def __hash__(self): - return hash(self.__key()) + def __key(self) -> tuple: + return self.type, self.value def __eq__(self, value) -> bool: if isinstance(value, TextField): @@ -75,18 +87,10 @@ def __eq__(self, value) -> bool: raise NotImplementedError -class TagBoxField(Base): +class TagBoxField(BaseField): __tablename__ = "tag_box_fields" - id: Mapped[int] = mapped_column(primary_key=True) - type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) - type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) - - entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) - entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) - tags: Mapped[set[Tag]] = relationship(secondary="tag_fields") - position: Mapped[int] def __key(self): return ( @@ -99,34 +103,20 @@ def value(self) -> None: """For interface compatibility with other field types.""" return None - def __hash__(self): - return hash(self.__key()) - def __eq__(self, value) -> bool: if isinstance(value, TagBoxField): return self.__key() == value.__key() raise NotImplementedError -class DatetimeField(Base): +class DatetimeField(BaseField): __tablename__ = "datetime_fields" - id: Mapped[int] = mapped_column(primary_key=True) - type_key: Mapped[str] = mapped_column(ForeignKey("library_fields.key")) - type: Mapped[LibraryField] = relationship(foreign_keys=[type_key], lazy=False) - - entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id")) - entry: Mapped[Entry] = relationship(foreign_keys=[entry_id]) - value: Mapped[str | None] - position: Mapped[int] def __key(self): return (self.type, self.value) - def __hash__(self): - return hash(self.__key()) - def __eq__(self, value) -> bool: if isinstance(value, DatetimeField): return self.__key() == value.__key() diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 4a688a9f7..f262c82b5 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -35,7 +35,7 @@ TagBoxField, TextField, _FieldID, - Field, + BaseField, ) from .joins import TagSubtag, TagField from .models import Entry, Preferences, Tag, TagAlias, LibraryField, Folder @@ -488,7 +488,7 @@ def remove_tag_from_field(self, tag: Tag, field: TagBoxField) -> None: def update_field_position( self, - field_class: Type[Field], + field_class: Type[BaseField], field_type: str, entry_ids: list[int] | int, ): @@ -512,7 +512,7 @@ def update_field_position( # Reassign `order` starting from 0 for index, row in enumerate(rows): - row.position = index # type: ignore + row.position = index session.add(row) session.flush() if rows: @@ -520,7 +520,7 @@ def update_field_position( def remove_entry_field( self, - field: Field, + field: BaseField, entry_ids: list[int], ) -> None: FieldClass = type(field) @@ -554,7 +554,7 @@ def remove_entry_field( def update_entry_field( self, entry_ids: list[int] | int, - field: Field, + field: BaseField, content: str | datetime | set[Tag], ): if isinstance(entry_ids, int): diff --git a/tagstudio/src/core/library/alchemy/models.py b/tagstudio/src/core/library/alchemy/models.py index da036ad8f..d9384df55 100644 --- a/tagstudio/src/core/library/alchemy/models.py +++ b/tagstudio/src/core/library/alchemy/models.py @@ -8,11 +8,11 @@ from .enums import TagColor from .fields import ( DatetimeField, - Field, TagBoxField, TextField, FieldTypeEnum, _FieldID, + BaseField, ) from .joins import TagSubtag from ...constants import TAG_FAVORITE, TAG_ARCHIVED @@ -134,8 +134,8 @@ class Entry(Base): ) @property - def fields(self) -> list[Field]: - fields: list[Field] = [] + def fields(self) -> list[BaseField]: + fields: list[BaseField] = [] fields.extend(self.tag_box_fields) fields.extend(self.text_fields) fields.extend(self.datetime_fields) @@ -171,7 +171,7 @@ def __init__( self, path: Path, folder: Folder, - fields: list[Field] | None = None, + fields: list[BaseField] | None = None, ) -> None: self.path = path self.folder = folder diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index 63d11697f..ac2573881 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -36,9 +36,9 @@ TagBoxField, DatetimeField, FieldTypeEnum, - Field, _FieldID, TextField, + BaseField, ) from src.qt.helpers.file_opener import FileOpenerLabel, FileOpenerHelper, open_file from src.qt.modals.add_field import AddFieldModal @@ -719,7 +719,7 @@ def set_tags_updated_slot(self, slot: object): self.tags_updated.connect(slot) self.is_connected = True - def write_container(self, index: int, field: Field, is_mixed: bool = False): + def write_container(self, index: int, field: BaseField, is_mixed: bool = False): """Update/Create data for a FieldContainer. :param is_mixed: Relevant when multiple items are selected. If True, field is not present in all selected items @@ -930,7 +930,7 @@ def write_container(self, index: int, field: Field, is_mixed: bool = False): container.setHidden(False) self.place_add_field_button() - def remove_field(self, field: Field): + def remove_field(self, field: BaseField): """Remove a field from all selected Entries.""" logger.info("removing field", field=field, selected=self.selected) entry_ids = [] @@ -945,7 +945,7 @@ def remove_field(self, field: Field): if field.type_key == _FieldID.TAGS_META.value: self.driver.update_badges(self.selected) - def update_field(self, field: Field, content: str) -> None: + def update_field(self, field: BaseField, content: str) -> None: """Remove a field from all selected Entries, given a field object.""" assert isinstance( field, (TextField, DatetimeField, TagBoxField) From 60bf0f2e46b0d55eece5627181c22a00c97d3c93 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Mon, 9 Sep 2024 07:54:01 +0700 Subject: [PATCH 24/25] rename LibraryField, add is_default property --- tagstudio/src/core/library/alchemy/fields.py | 23 ++++++---- tagstudio/src/core/library/alchemy/library.py | 36 ++++++++++------ tagstudio/src/core/library/alchemy/models.py | 43 +++++++++++++------ tagstudio/src/core/library/json/library.py | 5 ++- tagstudio/src/core/utils/refresh_dir.py | 1 + tagstudio/src/qt/widgets/preview_panel.py | 4 +- tagstudio/tests/conftest.py | 3 +- tagstudio/tests/macros/test_dupe_entries.py | 3 ++ tagstudio/tests/qt/test_driver.py | 6 ++- tagstudio/tests/test_library.py | 19 ++++++-- 10 files changed, 100 insertions(+), 43 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/fields.py b/tagstudio/src/core/library/alchemy/fields.py index 168e5f7e8..5207a8140 100644 --- a/tagstudio/src/core/library/alchemy/fields.py +++ b/tagstudio/src/core/library/alchemy/fields.py @@ -1,6 +1,6 @@ from __future__ import annotations -from dataclasses import dataclass +from dataclasses import dataclass, field from enum import Enum from typing import Any, TYPE_CHECKING @@ -11,7 +11,7 @@ from .enums import FieldTypeEnum if TYPE_CHECKING: - from .models import Entry, Tag, LibraryField + from .models import Entry, Tag, ValueType class BaseField(Base): @@ -23,10 +23,10 @@ def id(cls) -> Mapped[int]: @declared_attr def type_key(cls) -> Mapped[str]: - return mapped_column(ForeignKey("library_fields.key")) + return mapped_column(ForeignKey("value_type.key")) @declared_attr - def type(cls) -> Mapped[LibraryField]: + def type(cls) -> Mapped[ValueType]: return relationship(foreign_keys=[cls.type_key], lazy=False) # type: ignore @declared_attr @@ -127,21 +127,28 @@ def __eq__(self, value) -> bool: class DefaultField: id: int name: str - type: Any # TextFieldTypes | TagBoxTypes | DateTimeTypes + type: FieldTypeEnum + is_default: bool = field(default=False) class _FieldID(Enum): """Only for bootstrapping content of DB table""" - TITLE = DefaultField(id=0, name="Title", type=FieldTypeEnum.TEXT_LINE) + TITLE = DefaultField( + id=0, name="Title", type=FieldTypeEnum.TEXT_LINE, is_default=True + ) AUTHOR = DefaultField(id=1, name="Author", type=FieldTypeEnum.TEXT_LINE) ARTIST = DefaultField(id=2, name="Artist", type=FieldTypeEnum.TEXT_LINE) URL = DefaultField(id=3, name="URL", type=FieldTypeEnum.TEXT_LINE) DESCRIPTION = DefaultField(id=4, name="Description", type=FieldTypeEnum.TEXT_LINE) NOTES = DefaultField(id=5, name="Notes", type=FieldTypeEnum.TEXT_BOX) TAGS = DefaultField(id=6, name="Tags", type=FieldTypeEnum.TAGS) - TAGS_CONTENT = DefaultField(id=7, name="Content Tags", type=FieldTypeEnum.TAGS) - TAGS_META = DefaultField(id=8, name="Meta Tags", type=FieldTypeEnum.TAGS) + TAGS_CONTENT = DefaultField( + id=7, name="Content Tags", type=FieldTypeEnum.TAGS, is_default=True + ) + TAGS_META = DefaultField( + id=8, name="Meta Tags", type=FieldTypeEnum.TAGS, is_default=True + ) COLLATION = DefaultField(id=9, name="Collation", type=FieldTypeEnum.TEXT_LINE) DATE = DefaultField(id=10, name="Date", type=FieldTypeEnum.DATETIME) DATE_CREATED = DefaultField(id=11, name="Date Created", type=FieldTypeEnum.DATETIME) diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index f262c82b5..7cf065694 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -38,7 +38,7 @@ BaseField, ) from .joins import TagSubtag, TagField -from .models import Entry, Preferences, Tag, TagAlias, LibraryField, Folder +from .models import Entry, Preferences, Tag, TagAlias, ValueType, Folder from ...constants import ( LibraryPrefs, TS_FOLDER_NAME, @@ -148,16 +148,17 @@ def open_library( for field in _FieldID: try: session.add( - LibraryField( + ValueType( + key=field.name, name=field.value.name, type=field.value.type, - order=field.value.id, - key=field.name, + position=field.value.id, + is_default=field.value.is_default, ) ) session.commit() except IntegrityError: - logger.debug("preference already exists", pref=pref) + logger.debug("ValueType already exists", field=field) session.rollback() # check if folder matching current path exists already @@ -178,6 +179,17 @@ def open_library( # load ignored extensions self.ignored_extensions = self.prefs(LibraryPrefs.EXTENSION_LIST) + @property + def default_fields(self) -> list[BaseField]: + with Session(self.engine) as session: + types = session.scalars( + select(ValueType).where( + # check if field is default + ValueType.is_default.is_(True) + ) + ) + return [x.as_field for x in types] + def delete_item(self, item): logger.info("deleting item", item=item) with Session(self.engine) as session: @@ -579,15 +591,13 @@ def update_entry_field( session.commit() @property - def field_types(self) -> dict[str, LibraryField]: + def field_types(self) -> dict[str, ValueType]: with Session(self.engine) as session: - return {x.key: x for x in session.scalars(select(LibraryField)).all()} + return {x.key: x for x in session.scalars(select(ValueType)).all()} - def get_library_field(self, field_key: str) -> LibraryField: + def get_value_type(self, field_key: str) -> ValueType: with Session(self.engine) as session: - field = session.scalar( - select(LibraryField).where(LibraryField.key == field_key) - ) + field = session.scalar(select(ValueType).where(ValueType.key == field_key)) session.expunge(field) return field @@ -595,7 +605,7 @@ def add_entry_field_type( self, entry_ids: list[int] | int, *, - field: LibraryField | None = None, + field: ValueType | None = None, field_id: _FieldID | str | None = None, value: str | datetime | list[str] | None = None, ) -> bool: @@ -615,7 +625,7 @@ def add_entry_field_type( if not field: if isinstance(field_id, _FieldID): field_id = field_id.name - field = self.get_library_field(field_id) + field = self.get_value_type(field_id) field_model: TextField | DatetimeField | TagBoxField if field.type in (FieldTypeEnum.TEXT_LINE, FieldTypeEnum.TEXT_BOX): diff --git a/tagstudio/src/core/library/alchemy/models.py b/tagstudio/src/core/library/alchemy/models.py index d9384df55..3c4e0402d 100644 --- a/tagstudio/src/core/library/alchemy/models.py +++ b/tagstudio/src/core/library/alchemy/models.py @@ -13,6 +13,7 @@ FieldTypeEnum, _FieldID, BaseField, + BooleanField, ) from .joins import TagSubtag from ...constants import TAG_FAVORITE, TAG_ARCHIVED @@ -139,7 +140,7 @@ def fields(self) -> list[BaseField]: fields.extend(self.tag_box_fields) fields.extend(self.text_fields) fields.extend(self.datetime_fields) - fields = sorted(fields, key=lambda field: field.type.order) + fields = sorted(fields, key=lambda field: field.type.position) return fields @property @@ -171,18 +172,11 @@ def __init__( self, path: Path, folder: Folder, - fields: list[BaseField] | None = None, + fields: list[BaseField], ) -> None: self.path = path self.folder = folder - if fields is None: - fields = [ - TagBoxField(type_key=_FieldID.TAGS_META.name, position=0), - TagBoxField(type_key=_FieldID.TAGS_CONTENT.name, position=0), - TextField(type_key=_FieldID.TITLE.name, position=0), - ] - for field in fields: if isinstance(field, TextField): self.text_fields.append(field) @@ -210,22 +204,25 @@ def remove_tag(self, tag: Tag, field: TagBoxField | None = None) -> None: tag_box_field.tags.remove(tag) -class LibraryField(Base): +class ValueType(Base): """Define Field Types in the Library. Example: key: content_tags (this field is slugified `name`) name: Content Tags (this field is human readable name) kind: type of content (Text Line, Text Box, Tags, Datetime, Checkbox) + is_default: Should the field be present in new Entry? + order: position of the field widget in the Entry form """ - __tablename__ = "library_fields" + __tablename__ = "value_type" key: Mapped[str] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(nullable=False) type: Mapped[FieldTypeEnum] = mapped_column(default=FieldTypeEnum.TEXT_LINE) - order: Mapped[int] + is_default: Mapped[bool] + position: Mapped[int] # add relations to other tables text_fields: Mapped[list[TextField]] = relationship( @@ -237,9 +234,27 @@ class LibraryField(Base): tag_box_fields: Mapped[list[TagBoxField]] = relationship( "TagBoxField", back_populates="type" ) + boolean_fields: Mapped[list[BooleanField]] = relationship( + "BooleanField", back_populates="type" + ) - -@event.listens_for(LibraryField, "before_insert") + @property + def as_field(self) -> BaseField: + FieldClass = { + FieldTypeEnum.TEXT_LINE: TextField, + FieldTypeEnum.TEXT_BOX: TextField, + FieldTypeEnum.TAGS: TagBoxField, + FieldTypeEnum.DATETIME: DatetimeField, + FieldTypeEnum.BOOLEAN: BooleanField, + } + + return FieldClass[self.type]( + type_key=self.key, + position=self.position, + ) + + +@event.listens_for(ValueType, "before_insert") def slugify_field_key(mapper, connection, target): """Slugify the field key before inserting into the database.""" if not target.key: diff --git a/tagstudio/src/core/library/json/library.py b/tagstudio/src/core/library/json/library.py index 78870d955..c95a2c791 100644 --- a/tagstudio/src/core/library/json/library.py +++ b/tagstudio/src/core/library/json/library.py @@ -1253,7 +1253,10 @@ def add_new_files_as_entries(self) -> list[int]: path = Path(file) # print(os.path.split(file)) entry = Entry( - id=self._next_entry_id, filename=path.name, path=path.parent, fields=[] + id=self._next_entry_id, + filename=path.name, + path=path.parent, + fields=[], ) self._next_entry_id += 1 self.add_entry_to_library(entry) diff --git a/tagstudio/src/core/utils/refresh_dir.py b/tagstudio/src/core/utils/refresh_dir.py index 5d56dfb41..b265f3953 100644 --- a/tagstudio/src/core/utils/refresh_dir.py +++ b/tagstudio/src/core/utils/refresh_dir.py @@ -28,6 +28,7 @@ def save_new_files(self) -> Iterator[int]: Entry( path=entry_path, folder=self.library.folder, + fields=self.library.default_fields, ) ] ) diff --git a/tagstudio/src/qt/widgets/preview_panel.py b/tagstudio/src/qt/widgets/preview_panel.py index ac2573881..386fa9ddc 100644 --- a/tagstudio/src/qt/widgets/preview_panel.py +++ b/tagstudio/src/qt/widgets/preview_panel.py @@ -676,7 +676,7 @@ def update_widgets(self) -> bool: mixed_fields.append(f) self.common_fields = common_fields - self.mixed_fields = sorted(mixed_fields, key=lambda x: x.type.order) + self.mixed_fields = sorted(mixed_fields, key=lambda x: x.type.position) self.selected = list(self.driver.selected) logger.info( @@ -946,7 +946,7 @@ def remove_field(self, field: BaseField): self.driver.update_badges(self.selected) def update_field(self, field: BaseField, content: str) -> None: - """Remove a field from all selected Entries, given a field object.""" + """Update a field in all selected Entries, given a field object.""" assert isinstance( field, (TextField, DatetimeField, TagBoxField) ), f"instance: {type(field)}" diff --git a/tagstudio/tests/conftest.py b/tagstudio/tests/conftest.py index 2822fe9fd..3a431b82c 100644 --- a/tagstudio/tests/conftest.py +++ b/tagstudio/tests/conftest.py @@ -56,6 +56,7 @@ def library(request): entry = Entry( folder=lib.folder, path=pathlib.Path("foo.txt"), + fields=lib.default_fields, ) entry.tag_box_fields = [ @@ -63,13 +64,13 @@ def library(request): TagBoxField( type_key=_FieldID.TAGS_META.name, position=0, - # tags={tag2} ), ] entry2 = Entry( folder=lib.folder, path=pathlib.Path("one/two/bar.md"), + fields=lib.default_fields, ) entry2.tag_box_fields = [ TagBoxField( diff --git a/tagstudio/tests/macros/test_dupe_entries.py b/tagstudio/tests/macros/test_dupe_entries.py index 9b10a5811..2272e1fcd 100644 --- a/tagstudio/tests/macros/test_dupe_entries.py +++ b/tagstudio/tests/macros/test_dupe_entries.py @@ -10,10 +10,13 @@ def test_refresh_dupe_files(library): entry = Entry( folder=library.folder, path=pathlib.Path("bar/foo.txt"), + fields=library.default_fields, ) + entry2 = Entry( folder=library.folder, path=pathlib.Path("foo/foo.txt"), + fields=library.default_fields, ) library.add_entries([entry, entry2]) diff --git a/tagstudio/tests/qt/test_driver.py b/tagstudio/tests/qt/test_driver.py index 715ad2245..7116ff6b4 100644 --- a/tagstudio/tests/qt/test_driver.py +++ b/tagstudio/tests/qt/test_driver.py @@ -9,7 +9,11 @@ def test_update_thumbs(qt_driver): qt_driver.frame_content = [ - Entry(folder=qt_driver.lib.folder, path=Path("/tmp/foo")) + Entry( + folder=qt_driver.lib.folder, + path=Path("/tmp/foo"), + fields=qt_driver.lib.default_fields, + ) ] qt_driver.item_thumbs = [] diff --git a/tagstudio/tests/test_library.py b/tagstudio/tests/test_library.py index 00ef8f968..4bd8bc641 100644 --- a/tagstudio/tests/test_library.py +++ b/tagstudio/tests/test_library.py @@ -27,7 +27,11 @@ def test_library_add_file(): lib = Library() lib.open_library(tmp_dir) - entry = Entry(path=file_path, folder=lib.folder) + entry = Entry( + path=file_path, + folder=lib.folder, + fields=lib.default_fields, + ) assert not lib.has_path_entry(entry.path) @@ -94,7 +98,10 @@ def test_get_entry(library, entry_min): def test_entries_count(library): - entries = [Entry(path=Path(f"{x}.txt"), folder=library.folder) for x in range(10)] + entries = [ + Entry(path=Path(f"{x}.txt"), folder=library.folder, fields=[]) + for x in range(10) + ] library.add_entries(entries) matches, page = library.search_library( FilterState( @@ -111,6 +118,7 @@ def test_add_field_to_entry(library): entry = Entry( folder=library.folder, path=Path("xxx"), + fields=library.default_fields, ) # meta tags + content tags assert len(entry.tag_box_fields) == 2 @@ -208,7 +216,12 @@ def test_preferences(library): def test_save_windows_path(library, generate_tag): # pretend we are on windows and create `Path` - entry = Entry(path=PureWindowsPath("foo\\bar.txt"), folder=library.folder) + + entry = Entry( + path=PureWindowsPath("foo\\bar.txt"), + folder=library.folder, + fields=library.default_fields, + ) tag = generate_tag("win_path") tag_name = tag.name From 3e4153c2807b60dbf59f9b88231b8e3a8072a7c4 Mon Sep 17 00:00:00 2001 From: yedpodtrzitko Date: Mon, 9 Sep 2024 09:20:57 +0700 Subject: [PATCH 25/25] remove field.position unique constraint --- tagstudio/src/core/library/alchemy/fields.py | 11 ++--------- tagstudio/src/core/library/alchemy/library.py | 5 ----- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/tagstudio/src/core/library/alchemy/fields.py b/tagstudio/src/core/library/alchemy/fields.py index 5207a8140..6b079b0b4 100644 --- a/tagstudio/src/core/library/alchemy/fields.py +++ b/tagstudio/src/core/library/alchemy/fields.py @@ -4,7 +4,7 @@ from enum import Enum from typing import Any, TYPE_CHECKING -from sqlalchemy import ForeignKey, ForeignKeyConstraint +from sqlalchemy import ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship, declared_attr from .db import Base @@ -39,7 +39,7 @@ def entry(cls) -> Mapped[Entry]: @declared_attr def position(cls) -> Mapped[int]: - return mapped_column() + return mapped_column(default=0) def __hash__(self): return hash(self.__key()) @@ -66,13 +66,6 @@ def __eq__(self, value) -> bool: class TextField(BaseField): __tablename__ = "text_fields" - # constrain for combination of: entry_id, type_key and position - __table_args__ = ( - ForeignKeyConstraint( - ["entry_id", "type_key", "position"], - ["text_fields.entry_id", "text_fields.type_key", "text_fields.position"], - ), - ) value: Mapped[str | None] diff --git a/tagstudio/src/core/library/alchemy/library.py b/tagstudio/src/core/library/alchemy/library.py index 7cf065694..72b3e4d39 100644 --- a/tagstudio/src/core/library/alchemy/library.py +++ b/tagstudio/src/core/library/alchemy/library.py @@ -2,7 +2,6 @@ import shutil from os import makedirs from pathlib import Path -from random import randint from typing import Iterator, Any, Type from uuid import uuid4 @@ -632,7 +631,6 @@ def add_entry_field_type( field_model = TextField( type_key=field.key, value=value or "", - position=randint(100, 100_000), ) elif field.type == FieldTypeEnum.TAGS: field_model = TagBoxField( @@ -656,9 +654,6 @@ def add_entry_field_type( try: for entry_id in entry_ids: field_model.entry_id = entry_id - # create random value position to avoid IntegrityError, reordering is below - field_model.position = randint(100, 100_000) - session.add(field_model) session.flush()

hK zuRB`LZV}Py`!5G|YMp%7#i}?uIteYM^Yg{>a86Cp>$sypjkz{mgAQNMQnEltqgB?VM8FE^VW9Y@ z0e|i`S~QWKROF!5ZK76``7z98On--TK$imJzuevX;4%7|@{h>9g98_s@;aS!1i3i> zIGz!C8mjnVo3&#>S_60hvePniJY-QKDJOO26 z*4&7EMuH!d<}5a?`@nDO2^Lh}U!9!}V zVers4;(4*+2%!p3_+=buOAgr6?8Y)rMFDwgPym;it(PYOiT;*BacZKWNKyH?`}mYc z5+lHgnSYaQr~)dg^89R<0j*@)?6wWnCRk>4`f;`9e&*E3v(~p(w4wbkcE9Jl^r;@n zIAn+E66GH8Aj;2V-412&>dg7B+8YR8Fc1KECxpve`8>`o|GcXUSW5a;NHARR+f>|V zGjvU3O(i^!W%?NMY~GCZ6nOd?3mH4k9^YTC$} z{G6WT6BP!S>}RQHwIKnUi$G6OsxLL@&on$lVj50FkD)0i42V5P5rldth~o7QR}}(e z?lax>Jzqi!h0zQQsy|-Us5?QfL{Fdb&lxtoY0`?C*s$_YKBN5rYq7}*wSvK1i+g0l zM0jbKn!Q2~&D#Lr$5<$vCiqVRbny!SptUT-)jcXGTWLoX@LgVWP!OZtHVEp@IKvBK z{D+s>JOT{{cYicWyZ``&gwzxHqM%IOs>9f|V5oD0GIYh$2?1ZDZ;<}j0c8*f#*YN} z%WF(&4a#Jkdvd9BdXmd15C3^i_mTBc$*DkUTIeNbosnis$In076%_zB;PO_kaIcvC$Q8D?I$n~C z;ng3_>yPo%r3`Kul_2>ARPHV_af33BiA;yLv%Ya#q}+rQB&>v^YBkI?-S^aE#2NwHuvI8nrq z;h@~EM&!7`t#{xuDWJo^vJiaro;ymluMx#%c?yW!QHMVhI#psy@#DQB?<7aO6~vxj zadwa>ThyJI6TI+R=e0vP1@c_R zZrBaX5j$f170lBD3mE)NJC`^BywE%0b6THRL4VizJoTMxKc3T2`=I_`X17pT|z@YSSeh} zZ|L2&s)H)@NWt!p+`h?(EdA5Uv6iCh^S{Mw$NVk-<5HcKr|*;+KX)G9{2Oi@e}59< zT`iO}TXUdo+B9Wc-7I<%tLG-TO;^TDyvGtmx6QPruR#9i5yN>$pz?1YauI*NKN#T- zlsa0;!;kNlw9>WMjsz)g3tE)Q*5uq!=6yDr#Isc-UU_Ef<<}OX;`r&g#Z9!*=48d{ zH>%R7FZWHl9Aq2e^mu;_{9P6&-xlWTV!3~s^L)q*ToqVwy^$kPXh0f8Si)FoD_NC- zpBaV|;`KOyTL}bs7xRs2=_^tEATGrbMufF4>PpXL?!^#q&Fv}maq^+LZQ3i zBdot-@h;3^mj>P4Qq=#&cBwG7s+Yo41JP_&M-R8Z>Hgu8Wob@rJe6@}WRUogMtJ+` zb*Y*swM-!Uc4eeK!q4@p06t+*2%5RbAPy7m300%E>+lAaZjo4EAI-Z3kZcnGawj5| zw&bO+9FTUqgFgK$l^wF|UK)pMQB@YjrE{J4XAI2&FG6Y7%VxL1__M*K(k{CO8RH%KzVml#5 z<1=uealk@_iZGF1vM>X0$8VBjp8)f@=dni`&(Y)sQBv4_vVr>OV4KP+j3<$&dHXeAME$5nD>RVk*ZYx1&g4LhjC+@rA-+;GRQovkjsYH%llM(XDd^_rO zgn`RNi0BqsZS>UcjrMDe6BTmB9dbYBi<>IVMc)~1I5b%(i%Ai3JRt?nx8MQ2+2yqD zK>t3ta!}mc5Tjs!6qWy6;u5ynhG%;2_V*AqQsNm}U#&|ZbX8`Q4E47o zQ(KVxS?hMtlTSqS!;DDi^W9N2OSz9|bgvDoK9Nr0J_bWKoNaA#<@(c=t~IZxSgaeP z-Z$+7$=4c(zu@k&SWR3|_1{+;0u3*ZCZV~8%9Dh9Ys^e}orr`%ioyp0{o}UBpZcVT zA)xcmp(WsnAhCF6Ck)2U3k4PD{GV6F33~EH?qXNp*9KDvIhm>Oo1cUPltY4TPEHaiboX2dT!` z?9%#RZ?$}(#)iC8U4I9mcii!uTtLZqykz*)A8RPg54vhVErWgk5&IDLKs+(Z_ZkZIzu) z2(K=JrCp3TP>T_x6GBoczW?NhTIx4eB=DkW^jX76`Jj)Cib+YG_{;maO;}aNRXaw1 zdA}Qe+|PYcFeg?f#uh#omQY?ZmU%WjXAO^7l=mwoE3W7jDUZ(Dfjmc*1B210{J%Ds z>^@4kpvQOW_5Ot48ohk+oS8pNS;kCNCI&)yecMTByD{=Mhf}7i@guz8Wlk#pjLi{6 zoPN4B(7~@Cyh(KhH(>3@J?G3_^U~Uf`cy9R96u(6$?h48BruH}Q|BAxk9|c3DP505 z6#jAy1&K)*=RlJMuyD+5%Zo`u!3g#WO4e{kiGo40OrdvLesHw%}T3Rp5=Sm&fg;aWwBO zIH^%y>Ox%Bn4J8fZ~Bl;iiWJ8@o2-&%Y3deTb3Sk8u2<2uh8Q}@u1W)OCNz1w%_ej z+4wocV-H^#jBt(q3#SNrK7vN4WxeMhWA2T$JzJd|=L_S;?!Sd$^AleiHVoDT`!iBz z0nL7dA1XVVTZqq3=mj&GA zUL47%Hv^G*Tq22c=c3%$-=*r=P)Y_$G;k*R8o-M1uX&~6kOR>_NJ)GP5J;D*3hu3} zrz+gmoc(6NTGoJxZ?Sy)a~giZ$2{k91!U|NOOf>YCpA{={APnn0--nht`xxPj5RtS z={NX^pv3#?zPQ;IomtJkS}oJ4_pM&%Z@ZpA%kh^1eJ56MX1YS90>S-~--=ZXhVJG+ z;M@FDEy7SmcAjSYh-9EjkN-W-mtanJf)Xie5M#8+(iIAA1zY~w3R2Yf39~wlZXzl? z=nmCcBD_swP}^$2`m_%uGk%XzHVwQ!cgFdLU-t$s7@rbmwiLbRdl=dVshoTGAsM4b zjVY}E3&e{(%jVGq#4mM1N+#lvu`f^(NL;QEvzR?TZh%bF4dSjR_`N3YiCB{Ry~*-_ zwztGB&-e?s^Qgl@M~W_=WgC$cPO99NoSz!%ijPAsQhN;PV(GJwF z7SZ#bG{k@A@s_M4pJdZih{EWl^qLp5j%#t(HkRJBM>s$Q-4F_z(`xeGEg{$$N#l?F z5{J~c_LQKaN*|1*{TxO$*%4B=bCQij&scmT2ExuYyhq9YE*In6>l$g~PovrZO1QNh zZ}r6uOHbWixMj)($v)B*+b5BzGXyvg`jqD@8xta`#$$U2nVYcWm-7(BA#-5yVIAccM^CTg zmf{1PSJ4gt(!o1`A;Ck00~2bpW`$A}{m3Qk;QgGs!9#qq=K4uV#u!VvgL}mHX|t#P z`)7ZfkhE^;tyK3U4whY*?B4J*G3IvPiXu$b=%K7xka4H{M)JWB=_V{=9!z&Ks9Be# z)EP+>rDLM>kw5I*x{70Gq5^{{?}Q7>OLt>1e+_{02YC^sBkYjlLWFKA!!ocJNQn=0 zCC=36A%4H%Hpck=OAx92`@P}D=3gan%5tSi7A2VdBrJr2=@bA|D%2^)XOtsH*HQQ7 zyTmuO*~M>uj8kL{95(p#mnuh!67-9m^Ej$;yAU-lcYN`($S(+%47o7)8oWV=Cy zZ8rbAW`1CdQ0r0u4W_-vMuQ2F*|1wG{`a$R&Mg}>Juz@|f|PHedIC0!n=OGXh!5Vv z%ZT()RlR}=5WSzx0l!Lw!T!8fL@=)|^&h0XSS&nH>hUqk+nkQyQ$sKj@Ma+}jtZxX z5ueCxL&p2F+$#gb!K3ra_c6JDkhd^vA8+wfc^ig3gSaWib2K|D)FQOJ)`M6C0)%op z9uOm7?DjnLUys7u4$+XKE4S~358bk68n3c>#Xrb!?)KFGs(alQhCwD72y@6Shn&Xc zY#W6T;K~jPtn{kl^x{+_%%zIUpAe6c8-S#r1DXKOe{s17YM-2U9T!KSlqUSUcPVsCbC9q7hUnyY!Kr_tn&~W3t^cFG1_>aww%!a3N?68&O)cD`%GY zK8#vX$cKx1bRND5-yDk;*7D=%H3@K11?ffyTvk_yva@!k;q4Pg6zGI7DJV%7_8zB^ z67HDDg$fBs)ojgAh*ob)8V7c0-sB3{C83PX%zRvy)$g+drQ(x%R;8I zOs!mU!DzEcCf`3XO#?RP4?UenKFdF7d@?@$W_M|+eF5EbC<$!OH2lXnP`Jh$-0;UV zhd0-Q(Ni_*wjn{c7KPbWG40pEXLkwUdn<;eQScn(Q>;gHjxtU2m*xm~nKG9@qi4cS z3wOR%!<)}Pq*-glmAOHmnL80LJxW1m_w=ty#fK;|DBpex2!7Yaa1^9558Kxnvtrjc z1uL=RCwIPq6lBewK#aYrJ26(v0tHy#I$I6wOHrXdJsMHRthkNvb{=UbojxnQZsVbM zewHJ%88&*V+K3JixLg%bR8$%Pq!Pgr!?aER(g>xFm}BV*>~*^lo^62^nr-GbL;Gf_ zWK293(b@u(Ai7P7bOI|3G1rBNf>%k{hjz-$JxJkBRPOAE0lw&8Pd_JBha9EQd0Ga1 z$nrdm89xF_S8;6%{X(0V!5AS`s-$Y!ZkVyDxvjAle$D;TFm7qz4G~DYIg&<54A3np!{^2FwlNU`vZbJ!f}n08l+`~-`hx5Vg7BmhF-eCI6eFhdAJOM*AAtdar2ikiY8F zE+xb865#F3K-;#L(SR*Kta;mk2*Kfn`~feRmi2vZD=YHmHwkMJv)AeDAy2A7$9)xI zNAFY_e3F{p&xgYn$u-1Uf+r$^xfS~nMNN_JDDkq1{0WUc6}GVp%I` z)e}Stx%UiBk@$?>lJy3*Qry6b{n1UyGHBg@!FNyWN)M@Pgo z!RWJp_cd&4!BH9cyf2ugy1<~tmhWOEngdfzBXiZ`r3|{m!uebbIzdbF1a%*DS z(K=|kWAS-LyUQ>~kc17%>AQ)j9A#|0=6N;e?&{sH-0`myz35~Z)vheap9}Qn$Iu!* z&ag@pGhQ_vyixaG;^-x$@D(RH!ha<|4m*>6{Xvu))jSBiuS$hb)UT2w0uF;N_aQ^k z7p`jHLJF81QA}B5kS;-2^$At^gUjT`yE0Nt93Gk)3Mq|r(yXD4w*fA1Eap~>2=K<) z(P=HE>A7P9F7?N`hto?47fRkD=pzj5&_Ez?|8d0xQrNYBQ|*j50C@RgF`TrNV&&DQ zGqwze8YV?VgRt>8t!2>k5XI^m^{?wVV`u#68yjb@Hq}=ML^4Kxt*%k&3)@&%!i#;? z2N~Xr)8~5j9F&fFT!#`hPX^4evoV;fnFLT(!_3@!u~;Vn!r@0B`3LzcdC|eKD~+*S zSD6|0-#aF1%Z01w$p7A&T=Jy#W`bB|=ZVu;{(8`-O3** z$J(;XWR~XN(V-IAOCqO<30cGY9n#^O2~j6`HXj@Uq4_U54&EWvnC$S?YgBAFz7Z(n zvi={Ps6cHoaLF>eVU$VwX(ntXD!vZSFL(*|`(LqG&r|P%5vD3`GtBtEfo0Ijorr!uJR05*mSvrL1z}g(bW}x0u3s#b~ZW1UKY~ zpd3K-^iiVH-h=(dpU*-<-Q4ECc9s1eln68Z4{br-?GbOQs&LCqT|f>SP_1wiAqB;^ zu+Qvnnc`e;O0zdRF$*Zl{2Mh)>iTQZZ7ynMW=qc34v8whN#UCMOj+wtIH1Zr7 zEOx!VJo5LpH}7NyQUIu{lp|;cDG{~q-1Y`ALnYM9N<8*3om^c~<-M4F& zMqxCS)F{V0K;&VSS6I|3A63FKq-047Y#9d%nNaMwZs`D%Sk#BSGYlDi`+|8G^6M7f zCHa!T#|Q4?tj}@HtAu^=f#Ux@UJYnDL$0dK?7uX?Z{(DH7Wm|{ZBzg;sgTv;gCDFE zDANXJ(0zrZcRW-@@)>v0v)srix!!D8Yh&sh=>$#i5CUgPV}QpGH9JqO_Mr%rr%MVNymI9uS75>SQ6FTIwKkI?fyU?uIm!f+M-Rm)=5d>46)1z76N^eD z^I8ZfTCjUjdYqg)gaVnY~#zt3y@@_zrbq+*(X?Ba47 z#**6Nd}>jZ?*e&PZMKSbevC$^ojM{9B^eJWl!%cZePw2@vljg+;5&IY=VC=7{Ja;O zOXfjh`Vci1?itA7_YM4^7$$MPenb1obi@0@BW3^H3qFi=fh=g zq`@mnT5s}BQ%Yb=(|Ci=@*3mbr|S=SIqB~?y3P>n{O^~(2u)k{5MH^OT`y? z-DCx1_Nim&x}(GaKXNJ;aJSXH`zB~<=+bLah=D)0my*J$mqbVSqF!-b_ru_K`(XWJ+uT19Hw};_(m=JzbTemd5P*viNRbKqC)J_f0n*WJ* zxbE{2YeF1)=Yag@w$#?1zj~RLFQ}MhPW^xvMN0n(E^y#YSF~>}Y95Tg{(Be{lR&>)xR0%1Z|$^TvKa{6 z0OwNdRg26I?%0!7keE$^Fw*b~iXeZ2!6fh?2}O(uCfm-xfPihiKPQM;2}OYUV=?vY z2t|=Md`mPEI1YqVEwA|L)5~9@ zgJ6C)Nl4QH-dq4C_RXr6$_Zm0NJ1**GhHn`SuiFES#5(PUbW4h8=<4DK1g5|2w9Th zhRCb|I^1cYa`h?g-|jx7pWyyd!WA|j6y`A9-k{BYZalIo#-Pmbrb9hoklqH zy`Rp}m#?3!nL^>Jje&0vU9fkzvXmJVgaf_r){94I?00@ISksK>1W-?G7%}4q8i<}G ze-(-0=LiSf(qmTo{GXLwQEfL;#38$i60?{~5t&p4*gOR8Zi}rTCcqyuiSOU>ykf?$ z2s3Bo5XH(O+94hXQfJrOm*;%gouXuTNbrx0%qPJY3C4oxn7Cd-|FC3N3qSftC0qvUF2B$dhB zeO6D?x=A>lpz$Y6eVS|p!{}o99(Y@<{TZ#b0hkrDlR4+?#Bn*0YB)~O23#4)HHDEZ zW!1)h5bw4nK21{I^j&x`*AZL4^6cwtt;-p{ZN*{lFE+cgLWHa5$v^)`lF(kC4yz_h zTRT(P&b)SC2i3zzm3%otGhLy6XUq3wQdswKiG&B|3@D=$^KXEujP5CzR|zkK^$yg- z?5t;f!d^SLVNb6-Y@ma=5d)U4M?1t(30#zP~KX zgs0AvXWpmb5?}<8wACq@IF8N*H@w8RKt(CLI0Fx-XyKJWvE94#jTg+jBM(C43Wq(s zTtEJsd8jITSXg0|Hl}~=#po5mXr}v*nziqrPyIJgVf@&67O>|;;$gU6vh$Ye-0P~b z#;0mOWinzM(uZ|Wmvmsv)0d`KgDtMx-VwiAUj7h7d)1h^%lJR#@^U1YCW_uNGj;#( zbBW@kbgd^cd+5K64~)5~XO+(Kk0K`a+(=_+!*1zzsiDtRXKm9h2stje0`c1oMyw8T zBn-pR_*(rC`saoz_IPJ?9RC&tH3Ma3V|j$L5zDlS38^qIuncEl)?`MakQ$c9vJ5EM{346_YJ{kj%J9r~D^Oi`H|xw!7F@Gokg`?AWq1;lcOeLLAB3 zuQrk))f8@I>!dKL46!aQVg{SLfi+S4dRoNRPDyjVlFa1MJtUiTYE;)UH<0M81~$K| zOBwu2eRFc?F{|(J!|53Kyx`?+49|<9ov7>XjdGBiD>Tyx9#?7yq~>D(PvG2}$;ZB| z2sGY%n+xHu@+pYUGa!%IESB{cgP7u$TCslilWNQj9cFt7^;BJ3dDiAdRN}s8@LO}6 zneXQ#0$1*yx~=AAm2d-a@M`^Cj8qO_@Np8j>0 zMU*qU-+$5Mg+??BJGfA{nS}x}^y3$?rsqkzE7fvE+d6qX7q{965fNT-&$-GE>QBk~+(M;dh)2)gqYrm~h8zKIZ#}@YEXg zvZwodR4##zpLqJ1zC@^@a@swqn*aOi`9|M*B#snOqptBh@3fj$c%4*4eW`*Jt-3!$ z1FXJ?L)`jxu~1CL#3cjol$sc%C`})|UQkc1I8u1P*C&mweocZg7{Mw@lIOvRlbn>j zcYuoAW}pk~$Q_{*X0ijgwsO(c0#PnOvha>DWBBheOgc;kd~t3NBw!lB9IbR7LbZ2| zmd0vNU3`_~zK4BWd{i_iFu=5SXgmZljiB0VMM)69=gynsfw&Lrf17CaL#`pZ?g=0tvW1SM!+tCV z1(mXaH=7{nVJ4cADfvlR z_q5Qs?gP+Z{F@Q}VE5QJ_NoxY%+Y#`S-LYOn?)D^y)qd5pgT2_=J`s$6QRq65wdkSbYCzbJmPP^%n`5IkE^;NP$nN<@J(QY zGo$Vsc1X{5$jC3eI=kIBkYV@`O9zRDw18{17tz-6tcj?4lZRbfJ2$MKstgE zw9U*U_CZUBPn%!%d%m0Uhu!aOeZ9ipUgPB+fA_u6WgUj?v|Un$&+YLaPlSdO-<^Xa zBqV?EPT=>#xI71Jcp;;19F>?CJmWo20Po93 zoihxol&tI#6hHT<5*gm6F@qVDKV!aj$xyw-UrvNgiZDVM@7IYHf`6|KtyA69*NemE zmo!|5o2vN+$yb>FMS!O2mm*kxN6eqbJ!;&XZL2)J*z5jFY7ae21GY6H-k*&mAN=iz zpF0dhuNLQD7}xid@KkKBFsBub_oWGI+Vm+7qYdbxo?mHr8BPT|OBjb!U6kpX?>K;l z|C#99U|NpNIIw%Ed%T#K&Kh-hMX*g#*db%(+nK3Ho#3)HDDX799--fqYgRQlXa%&q z_@xXl??zCP=pF97us>hJM_9`xO;?e_%&pf7P}ls;%TW861g}U%l6$radUIvB5#4^e zTL0R&=gE0jTV`qekzPc+OD7QUtcL2Pq#_Y4NT31TPXxX~a${`yYR+O8cTQ;&`o)S@ ziRn$Ko0QwSI?PyFv3Qp?2yX$xbxvOr3DeWV{=xP!4U0g7TR+r=$ZMac zf8DhRN))#W9;l{7a6hr*j*@UI^#yX=e#7=_B!m$%s7PjQ==Jd-%rkEpurF>!8Cw30 zZw1oDV#+{VC>e~Y7uQ8tuaWDt4@HMd{`G@$HM=Z04qiwH2M{A&+I@(Yv9ouxO;`W$}4(4Hm#IpJDP)T+}%*({u z`Vuu&%bnuFzed}nb0rf7iW(Tq?bH5J=Qk_)OCy^3uxrNU0a^7fiY`|NH+wrq@YT|v2t>z;C$mDm_@RHlHK@lge2$}kw`Yw4@2vORWpeVrj9yd{ zLdk1>_cn@REXYOk97*RzKjM&&{V%6r4$qxn7?&8-Y)Nqygm1|<7gWpBMcdj1I+yw| zb@CuR1UvdZG^Cf!Z0T3eXXXOUV5wLuf%h$`BiRBC$z0~V`E+&r*N8knN}hYb#BPIi zra&5!JT-U#=Rq$Oh5dF>`dmu~)l;Q(A_F$Z(7A98UpnUjf1IHKb5t<-y)n}z)c#2Z zBn^WG-5`v5DvOIZ0DNVl zm1l_NtcRi{5XUJHS=lbX{tJpq-&AEA?Qt}{KG(O%3@(9y&RTr;0`{$h==51{D>^@W z>d3LtRyUp9Va=^Ohoh4lmy5#&}p9_c(5`0!*inXRqIsUCaA`Wm*viGl&ZVO2Rn6 zH6WGtC2oQi#jUr`gX`1$4M{8s;MNyT{E5-~;LoxxUC4>FA7_ExE>TbF2;(o>j9%yO zrTa`LuC6{1!E89TXSHn=)!qk;+e4f-PX_rf%(*yw`nf^MJrpRW;$vkffB)U#LRT1< z8qb1v*|uPxzcpR{7+<6>`z|`LOPdZCo5neub9xzsi(j}ah%svJ1)J~Ss9U-a=KgT? zFmN+ws^R|fV>N^w?!g4MmO+kcI@RdO1iAfz=6z=SRIhkXy39_IXiR$ZOSsP5NRiYe zFeeSi&Jei9NF)j*Rb_nA2wRJC;Sy5$7SUMqD;9M`puqRm<>Wc-?p>kza{eqd^ra=m%#){rHkJ5lx;Df+O zgRE3)(sdx$FWm~m(53QJ^R9WKG~wa&Z5}DDO~yhn-z%`a=I<3%2Fz$?YBCi$uTd?kt1(=Z_f%F zL|2h3v8?Y+TX=~v0ov3oeSoc$Mkg%&X6%K`Psr0ghhwe}-@?pLE}1oNVAfy6Yu20D zBVw2yux|HQa1Q+#M~NeWE=+4OwP-$_>#N-t(`hg-Dj8^C=>T1iPN{;viZ;`-+)r+l z*%33alA4jnynRWSt|xHt*eP{sc%PaUe~`{S@ToC|;LqXn zz&Ah!+;bvfEeS(CAcK)DKUCC}2Ga-`o4#nM^&1aOL(0jqftg|k(dnUHO@fh;P5-DK z2dYTmU95OP#R91P`R0$6JA> zCEUq!)u4}HOx$O3_G|Lu&PD~&EXiT9WfV%3t}kn01FJ8jGlN5Ma)?I1-;X)VGXr>y zP=&z^?%16#CTE9}Z=6c%a#EPY$4)RxeXAWafRDo| z?F=1UcyPQU^*4OUNa~9<*{_xA1XxzMgq>Iq`7_x(O z-7gj06Hxqp&s0^hdT(4X5p%6{Vu|0CoD6SU&e(asKgdC|@#$2#V^;0((&?6XtEBlv zb8?`V18@>h?dXSYlu!6q5hMIF>Q!{5yc2a%28~jwT!srVapaO3m7CRBu6~f`w(r*v zJCgHS>E7ppglp#xcjqo8VczMy{H+nubXdLO)`-4&FoOUnpJn=$65o<&%Pxw(QZNas zT%+IFh<4s5vzR67vXeB7sLT>ATBs5-rkWuME;nhqY>Z6u|GZnnD7O8RK&Fz8vFOA! z{g>ezCzZVg7&f-WOm0MwvR)g<518QLE=*E$%#63V8k8oJ!Hv&xb#{=K+_ zcf!^9s^bzd76e}-=z5P8rH}q0!5CcA%>=Z3Ne3QTvp5xtfc~;%P)lWBwF9qs!~g{@ z@ZAtUuXnuLLz5Zgz~OuDS&=VWnjZ`hhOSc=omDg(-3VuqNx!KEH2?(?%KOT zL0+z~MA^IL<*BvTEo-z;lq2U>aW3=;qt`6_z+XoNcyMpU68eaTu|>JF;6saJ`6xfj zrw8#BMt;iLLO-v9mbL>f>30no(B(X4X?E3^k+aLE8o4tF3&|IW^AsrGHBjl6L1d3K&~ zUL>1jFTQap|y4af&$ax6k4 z1YIXsL#|S&$eYK|V}0*>glD(71Jyg`tyWsP4>|Pu zrqUqr4e_;Un$4h5zk~9e*vlM)M_U6qmz4uc)~6*KB75v#KZ(jE9+Eqz&`=C{!FC!Z zyZQt!QaK&q6aQO@lsD}egOQ(88xF|=^EX}*gA+>fwW}&q_Cwd z1ty|>us$tv?8KS_dy(`Abo`qMbow?o@I-7e1BfuVa*rU_I0mSLh9m7z7|DDugr{gU zaaZBR%EMtAnY(_Yt8_3;h@G|W^PbVKA`87lFU(B{sklKUDbYY7fF zz|XbUs(Dm*V=HnU?_|vN@adGDQ@x2?!fjS|{Wr!dhtZ!M z=QRn@%R?C2uke^efRT>`LJ+kvGKwQ?L@RY@3yFS@*E{_c+H7l-5FvHnYhRJbt3K?2 zvzKf}c|>K*wq6?o${Mmv;EVm?R~oan9z*Ua57AsU5@y(@Xb*558y0+tuMS3E2%t0^ zC>dn@6T@{f@F0JTe({U)fnuk3#RZ1tAEw)CM!Hw}LYr0%qX^6=i9AFW+WC8gP8n_J z%Vgkf=S^Pl?+F&rf}-m+0pkY*oXcFn&6>mW?4>$AtqevLhowB4LO58YguW)N>6AX^ zbo=TQDo;7y0NS(`@B$%q7chd(+dy|^z$?H&6+wCVmxB#^EnaB8_-|fBZD~v_QAq++5Njn!?*DcRDBJDUyGta|k z9~~ZaRj`vhY>T(s*Qhf9XAt3!V`r6Zc_J`wt%>J;3LM9{YT+CABS(-WgGe>#BuBuj z1@`UgAdfo`Zna^*j8B&yI__RB4!}iN7(!A6_)pWQ&ys3lEOscv*_nZBPzteV{;feD zPIf8fk|3b79M&{)U}p0q32W|8HX;ttIa@r3%3Oo0d_jQAb|lpyBPN-6gv-=e1|F0|mOAbtMTirf>VOTFdR# zW*d%kluYCcmtk`5JY~x63rBzwKfD%mVRhB|Oi#q(ESr&};T!{Sk3gd@XyZ8dfLgiX z1Jw1ikMIOH$vc$v6khndT0rq#f7NaKlT;B7&4Mx^xZWQI=6JWbKx_rt%lL_wXF+6T zTC{umz!M4y;?`{q0f}tj`LBS!XrU%pa775IrjFK!7|UG`=&=E;rylA_DL4={MdD`lMEAY6k~eJNS*d$eB99-iyb)Oi*RbfJA)k{h1qCcKgaRRbb${Q&{>uDW^JciQ z7#>*>47BkS{`7N{Meqq4GXi>#_f-6NMEDd97~(M*uKt{gv7OAsOPZfd=MjEULOdX~ z?#NkeYD;u9O0{Tt3x9T!wS)~Xcd8UwnxKXr&{lyYw#65)=HI_EHt(xV1HwOF*bt<> zS!ve=;y)9U-Q#b@<>v*3E#K6?{Qgs2w{}<3?`aFh-0K^SZ+h=3zG!^1w9|%Dy8igT zF&^Cbf`cK3K`6=-S9qKV<}H38Mm?gv`Lph&P5(1Ie-761qSxIUVd(80NGJPo6sFH%m~!h~5a+#niz8F?U6QzD?FBcB@x2+$t(j<9l? z)-sL_%suPQtvE&Pd6`IQN`_eY)pG9#S@18nYIEyh(+q~)Pyr?%-+>iawptnK#?_KR zlVV3^T=Sf4+L3rFHZJ%`+O2%(iZLvQ3R@Q1eHl_!l<>SWDir+ufrPeYg_(( zzC%!)q$X^b+t0wC>`I2QdUh!B()=-b1Z+@_I|pkz_J~2$(q&+03XRclyM!52-~uV9 z(5>B(8N*l>#9;Q=mq5Z_b7)X{8FiIO_g>XwM}h4vP0H?+PW$It)VjN3zeOZj{E;jVAT_ZDnao0j~L>z z>g}(^;}!lIaR_TVSLUwxlXx-k_P-ZnFAp)XhuVkWDBD7);Ld6YCfZsMwID(Vl7d-q zOmH9slo?9iQqzuRQDliz%3Ip3o{dO=uK0F>J9P-S+k*|!{D%B3=r5AnJqq%TQ}#^9 z;aznUhiKz|42D~2;2^+HVj1v)=RSMwh`o44VUMEx|1hpnMNA2+GH-!d=h8w*V&)M@ zIDG(M`5FxEDpO%*I;Q8)f0NuzpH7hYgVdYE!d;tuzrib9ljLAcJqj(9mjGaCz z{rjMUQpzdIz^qN=8$pw)y!+@@Aepr~DBRwuQ2uAXbD^cj))~?y*F%KosZRWzwjqL= zTwPfMtP*$UZaZx0U>*Aq=CfEKM2*P3n4xSg52^L*9Gf%5<8jvj%Jwr+bPYOBe}T(} zIUR8iLMMW<{*wD2kiS5l>Yb@S(>tvW=(;fv9i4AuFSjij`6sZ zHoSO8GAN|QN()K6*Gt0vkPLkeW8&$R2ey8J2gp+S_c>NP8T!O8=q!L zv8#tP@to{5YMC{$gpkH+bfYMlCGzir4(w}$gVlv9O$KB!h=BzdwhYdIb=-c6g(B39 zJ&a>$k_6SW(#-@1K4`ztGR(?HuM!0#t0{uwFT`sy-t`-5y7bYnPf%af{$IgLa3E;kcMwfpuo z&{Y%P4w5!6N+%>m6wiDa@{owkJcn+VuU`vN82zXwCH0C!hbKE{7wmfEfp{v={CsPl zdHDJO6*2$d3CB1C&J;0hiPJyJUX(nE!PEda!www55*1KM`7?T$>)r1ZSR$usVA*tB z_(WX{T`6KumtXZC` znOb29%U{Vx)Kz(j_84E27Kn5P6OC1lP4)EQD_&IS0^e9n_C{pfz0wReaE%sWTo=H> zE1CR$T;%_}3n<>dTR_=G-p!8a;Od(7frz_I2*G?f`z#K6%Jla1N(^_vO9SF*Y;+4u zP>6HZi|&Kk`lk6#q%}F9d?&{M{|Pfq&{JH8G2l8Qi6JA30FQ8I)d~-k!9NZ{05Saj zwlPK`fNt(xt;zduo$B4HU@&Ov7~T$&U|3~H{#wlDm-#spFkB942I4PDK#8YKRj^dg zpcGiJH@65XWc5lDCY4B?5>ugaaOPyyJZhZg_80!c-sqzs73wvYB%{y1T*a#F^1*)x z>xpP_T%kXIDN2tCPp2;l&V=v|*i-@|(PFaz;}1(%L5XcSY~;VqVu|IzSM%Jsvcy;1 zy2Dptrx}~Ch4lkI#^OHnbs@mis4E}|OjT!DJj(Y=zcZ1gY%;c&Wy0%j*w0M`5Xa-S zt4IdMkmzn989K3)q_4;y&4D1MSX*^yE2196Awe;W0(d#GDp9ik}X>2FUd1jtogDqnc% zj^AwW>{Ftz-6?E27(8-mpX>Zo{UjEHYlE4fM@nu=W$Aca+ZOY%Ys9|c308;xUgpz< zEG_V_#3n^V&V+nRYNe)6#9>bh^flR-coezy| zo%T)2=TmDCGDVt0w@q_Olhn5G$7{ilwzjQJ$WPEh_I*O1;=|52+kbr45^AMO-yuL{ zC+-w&oT?97of_~HeK*I8Q7$Xdz$0Y`FHq+FbO|t(ilY$G?tyvTfrYLB5|qb0JRO&H zyU`gt@Df4D%T2&o*lOru7zTL&n*?UCp_C>cY(xJtFHH5j@Cvo1Q#>VWFT!sqI=m)Q zZh@cg$Lc{c-0sdKe?qT(Bslu$e|1ySHThl3W z2#SoEDgmym5Fun6Q8nLS#thb1uZU5V&~1$l^{v*f8VLmZDSZ*D)i$t3#grC zgn5x~Fz3Mv5!dD#c?|L85mgx;v>0m?sb zXuaZtJQft)xAk-ATlsbu|2809czEY`4iB<}K{ZSVb0S!9TZxi;_`8|H`u?372sVs*`Y&UO{IKFt1KQ^MLzhBhR zVOAHHlJ0$QynE&`cK(+9#cf&R-za%aIAV9^r-Q=TgAJSMip4e9b^;sXs;z)i3TbP- z$61RQXr__K6d@J}KO-DfeV`fmmpk~YuP4|ED@Rl!JosWIr2v6 zJYnAjT=y9Z+ytO{%I_25OZ0KJ0tAHJN_GLB%={ajrB5lEZM~+xX=+VZb-tGr&j0Tl zTb1T{{Ul%|X?BU!`lW=hP-OLEtDZO))xBE6pAj9^)*&c^RJ_MO2i9qosNB{}c_A{T zA3pJ~WkPF@-1Z;wx(dcy=P_0JRpZ3Y(Tf|f!pJ#292dVOcZlc==VE$lI{CYO3UR-c%)sRFgKSNA%HOZK`6d7S3~SBJ1na8PQ^0iX zULL^eHOI=}CF_jaql>nhLlsv*xCws|L*p3b=h%>QH5BgALKyJ|Z#hYz;Xm%)1N@}r zs3cZa=Q+b4SNIvr|D)+V!;<{p_kZ7jfP1Ir!mYViZUy&BO-oaAl{s_dD7T>IC{5DT zGzXcMm9uhWnlv4ml9?+9mgY`#We)h?pYQ+pJ>Us&@Bld8*ZaDz*Lj}*GVv;mL~BoN zZ{K>`kw1t3#{x)U8cwQc&MY@Q{lrFdGqt2jVZOS-Hn~AL>;*${GUktuIN3%m{Cz!& zxtRgF(X~RFtyp%G4u_8v!)x0+xyme0C24ZCKt{1S!htglD))urV;HK9GfC-$Py)?cuZ z5H$^-0@_()D$XDYq2lhX@UVPrG9^c7Bm}F~LSb&h@ihrV=~O%y*~_qdAVU4Y;K8_m zlkL9n;%B#lJaTi%6J`Qad$Aq?E%Hl<_`o0&H4yCdB=uvk0xzyLxH1XF^6gA?YWc|8 zarhfP4aaL(KRtuONH&;qFmISkjb_*U%aW%)Q(3p)jfxPcJ=vKQF3Rop!W)>*dBZ-! zEpf*_!uFTwIMiay%Uo@u$Q{xNs&vV5QhxN!VK5f=d)OW8@0hZJo#4LIC09VwUsP@v zRqi=*6xE7#E)qI8C{Jybrr7EtJ%(O`SBg(uiVIB`Cj5^ElJqn4HIns zPlV{=X?-;q_ldw~T`XT#Oy;1DdrweX`263a>q~@u(cB<<-(k^LyQp#X;PX%8ABZrr zY_o6g%%Y@ils(~|zDO_7XeqY?zD+tY2+mjQQ~;yU_1oDQEn96haFNXMOj5X1kFyM! zV1X?mT;=LwLrFW87mzw_6G>l-&b(yXE5A8#jHLR=qcufiE>m5&is^u#)$umJU+Ni& z^mSb5Xu)N8EOsJ~Euvi8l_5#1u9Ho4);!$2glIZ-nDtM~SCRhve?vKmR>2kfusPwV zq@~}*!cP}EB1aX5Tr@fTQ$|Z2V`T~2#J<({eALT2#DHEh zJUSl3Tfp`!`j5$lC0m29$Y1Zf!%9{7+>Fj|iBA1aAq#VUe~Pywi1ivwZ5vNXiHSntV{AW{c)$H7X<<%9 zTKS2>A__ZWun(6+I>>%ke=-#I@eT2e^&Oi6gf74hF|Mov)Vxd37?FIuEV~!YkBE?e z6|LcrGc$h*)5irCmwpwUz1;st&Q9>$R8rixRZhw(fvr2Cg0S$8d^B_{rJ)C6Y)S{l zQ^%O%iddN^t@%>0;yIkz0ET2$h}bi@1_kM&{)W4gxvw^|kAla!s9g5vr4e@*_@sdw zEx>ut0~euvuZ=k)_9HF$(aCs6PnP{b2(n{%N4x4MEH-%1CDF=8S*>XLo6$-CzQl&@ zG`^qg^PZl5-k7}lec~YaIx0~ft#2NNsDT=QV1@mzY7&5zdZnbg#=0-|wHLXN^8D1G z$p*ut8&PRJYq!768)OFYeRFrl1&zLN-)VrRTJo8+g*f`wHyWr7RYPewhW&`nNY*tJ zdpANe>*?ZMqhA!8?K6HeWIrmC@tdLjlS?h(<7?f)_97F^kU@qs@k@pMIm8>z0c&|g zwj`?wB269Ezu**AJp(arlLSl?U{-!iK&M!8xm4zKC=MOaTSeIE$-=BrSbpIJYBmhCyB z#;s%AK>z}beai%Px%zm& zl7gR{{eqxGzdQz-z$PzS;mynes`=!3SXr!5~%LeBd#rP)Q8N|0S{c29Pb_QhQ8<=qoY%GwGTit`rey$m?#eem;Uz@3E4 zTQwczUxpoTyq81z6eT67@_t2^Bs8<=Mu|PM;7Sb@K6RF=!5YQbSmdaW)=hOv!+u&+ zI}#YK^M3&bq)&g?A^8WGYI^He%b_lhKEDGvFS?#5*?c>U|86NC6xowC>MbiU1HZu;?B!K=EZbpVaac2s$E>`e2d)uXVgn#aTtr!8jRB5=K{7cYQ+~jeO9JOMz z(hgPpAW~CM*5*<$f2;7K)nnN>jmtb_pM?Rgf|qoy*HpJR$1u0kTkg!~Bo z2{$8F(}|1EH-|hb@~D^b4pMfneuE-kO$K13YK_8K3|(c1Fm&5`NVF=aK2LzUj9K#q z+KzL`h^DNAzjyW58Tgb+U$%Z87q&Jky^`9slk>YL7`{-n8P>|!fu1>8)!8rd*zso1M8G=9rhP+|yVFCcww!!jp0<0} zI4CaTl8RV~Qn_}HQlEZfY~>o?d~TG2acD3g2#wh*piU`ER{&owDM1~+A}v9_xa+$H z{t_6pt7&?H6uET!7INZK)Dno8(-lT+``?Iy(X6ZN2wckljCKd_d`y%++_4~-a+=VI|-S+-T|YPcYMLAy<1KMWZkJ|_m4F2G$!R@NmhvvSxVWIZu3_J^JnUqsB> z*7?lO=y02Oa_X{>tvOy$Hp7~ag_9$c6;e3;ShR%JC55CGO>DJj&&8K@pjPG|$+a&H zI3w)5D@J(3_(C%@z01()`jC5fgZstndgq8%XN~}lE0S>v4%SJ-)5Nmkt-DFA1EGmd zy7)-sHv!Dd@)>>d>3{sn;kT<}@ERV9O;{qU^pW%Qa~V55PO?wyKb~kKSXI-YFMKR& zhg%KFF!jze1tU0Sf}XQT%1xOYhxhnez4lp0)FjeNvf8yCF)ZXAApyi=Gqdk;}=McCZbM$CBUCZS654JO^- zo{h3|F24D>Xrc;Kt{2~hfU%NTP#g;lD9cj{0VPfs9s$xU2id6pNS^JV@(a&&8lTW% zq&E)r*$9bnqUUfl zHgoKIeJkG!Ye^x$>tMiMddxt&x5#|A{0eHDVd6IOzKsL5aj~QJvUmZ;;UA=Ic4hu*&-0qTn(RFTx zEXvHtbqp!KHKY9b%9|l8!8{X1Y<^qrb;wC|O8R#J$A*oRoAF1zH-!r6)Nqebb(lFH z4@_yRoliGV`G;v|Pxq0Q@j-nMxy#rb$C=um28BX-_~Q1`UONQL6qI)tcrLJux`bC; zRJ6;e<50QB!A6<#1x=N9?-a6Oc>@RD#dcLVp1S(@4=t?azeE11_m||KM^J>;iYcX% zJ}wQ%(ke$!N3D5{9aNyt_`T+Z4Fo4*ZbfRmAEwR!rMaj<{JhM^gBfJ1ad!kI2BI`X zKPhB$G-k+R(%oel-#0pGO(e==((gU?9_1&c@QYFJr)6d30#@^e%8^XVs~dxV)-y6q zi%n2Hai?bd&}mmyUEJ}0`S1LwFL+^ZH3?FgzDDL~OUT%mBs!g80+ZOR3*>q6-%acW z7tB#L)$s%|eGnoPO1v5Ik+CCy64Avv_+S|bd5Bay~)a9Ei6 z5SFnv_zxCYnS7HIQ+)dA$ z_V6Pj*BNl>{z|p(VoYHD4sN=V=Ns0DBsrS0{Viu0Q6~Ah`J|=QHPM5t9sHcAbOi69vft( zxeF`Wur$I5D7zCRJKCYS2K53Ouhw?%R&V+_7SkVfD2=vF}koQ^2*N~B&Hf=E# zf5*|?m1DncyT;}RT%Fu#{QsWNC6YzgRX(Z{nI_@-%4Nk!+mQ+Z08wL#B3nq60cU2f z?ZQ55-;X3DNxlraErZZ7y^F9FGZsazrQ|k|wY?c@iZFb57prggsjE_NT;Wl_vY)c{ zq{eCA0d?S2jP|M75=uPGL*>@l$kC(PO6j)Ok8boMM`vnI2)^Mq^C)tzfuM9>D*-G9$rhi;Bf|2Q8zUQQNCCun;>`hM9>9u@{ee?xY3{jH z*5T-b|ES_=2>3N2!6)1e?+AYvhSyf(0egP{u)L4BNetiWev`<1*41Bth_~Xw23{#Y zaEN8j=ISDnigs7vY@ANG_3Q<}>2U9}8h?#{7XBQYwJ5opF#*f8u#M7=-`*3pb3b=B zV*lkT7`{*@%$=D3MqQIybP_9ib+MHrPHh3+l0y6T(v+*w%JJL0Z{DZ-H8vDMa6%7W1q@H+MYN;`(^p@s?0ZS@9ciT|@gKFqfU_a8RKIwG0nc33 zc#q4LK#GElU{u@Hd9HBsp-0w{&TeI}bopQ)LowVK z<%-q;7hLi=$^7z&_f&^QO==J-GV%h=|2at0mY@bD(JrNK@gleAeJk`Lds;&!@=uUa z#^C|}NH1FX+ZBSspwzb(P_k}7_n`+t<{uGq7{dHJQ~g2=sCumQfkco7U0sXTH|men7wK!~VdYdlM}l?fw{LUbXw5&n&#!kG{2Oh?3bnee({VqK z764B2gW*wDtir38?2^vQPE0h>_;mrPv)`g;4WB+~f70GdA*4&+@a5nzV=!aESE89x z5DbYphWB{b2_z50X@ry;a069st28)H3HQNH5BqQ{#Hcm7{gA+}=RL62 z2Q-hTzsKwg zkE#7TbNvr(feSR0N9@&dP@UWbd#+IPq~LjLB>@Zs5@b+bFQdpwbU~{$6GT|KKtB9u zXLL<)9oQu(4>Yqfl0MaOP~>x^FyENqkVoi6Mu?VspC`x^Bj7|YV%u*vSoc9Cb+a9E zLvIuL@`=ol%}cd8|2}mxE1YpGBn?;=uL90;=BXCLt?s_wyNQPsXrQgE_X()*Vs%hM zHxx$jCnd~gfEygtSlJ2l!uS=ap%r?ESLQ5C9r&Rp55*Dr3oNo2ym#~l?ggTC3)Wp> z`X8c#fMQ)ozLcalHu{W{)rs5j-uWlzI7#WpesV1y#1UdBj>jokBp=h29FprklO~p1 zGry12t(kR>)DW4TM@AZ8wHHqqWDCA%FR{H>v7@4ct@{17nYgCih58}3lIbM*i1p6X zxRv<%*GR9?%8ETZlV5FjaIW0AZb#FnDJ+zoGYrBKiftmk98xu~eSzw#6FpBaQy!A? zGppEoZr-6vQQYz#;vx zM)U7Iq|bV3O!DYXuFGK3)~!lN5Mwav)bDZQ^=bFVwDgK`gNf$TYM#EaK~tQ5wy+l-f=3xE~s0@&A1C{QP4 z1>vsO3~Y-fQPy!^i%+n_6|3`a0c#(BZgK@k)4zyey?yAkd!=A9$LPD>cDsoc!C@@nzN}b|Akqw9jy@Tc8sofzf z45F${2O?PKqh#_q{_UGQNKzg?gUJY!oB z8giu`{^}6-zzsfgdz3P8F%P%*Pe3c&+~p^jx98CZS%$TJgbNfRdRo@F1cHo5hSt0b z-iz^Q%Zrjo8MaTlF8>7BdNiJIKKNaTBn6M%U%?r+n3And5?Rmx80FyHkaBQ4_6bWY zIVpj0yVi>(OR4vOjtZRNd+!zP;?HgU8@^lnCOkf0>G8BOSzSWkX##BMUDp+atv!== z4JIo~g5$%@0-!ly$-)Txm3o!yHuOP72z0#?#w^7c%ZHFa^K)U&(ftRUR%7eE9ViE3 z1Fc7~%gL`Bw~^3b8FSt^=Im`+pdV@J&2j#%Prp zI-R3VnPcLw{ndWv@`X;iDEqQY6aQh|!qEqB+UOdJp?-mpy^4HrSd4f0awwcv(RN)W zylj?}2~^t0gfFjss*ooqmHdSDMZ&oEiyC%wwO@X&qJ5v}(uVd;w}1k?_R9|=G=cTx zagP14PI|V9M7z+n2xS7t4`3PUthIL4ri4)|5c?BpNb!?s{GlU8^5t20m@<# zzV2d}uWX0+C$Or)T=T2MU?hDj_8I7iwNF!k`INA%%4Ek-ru;UFx@!SQrsNT3h5wz- zPfBk_D0G{sIec@2B|S4HKlf7^)$uEshBg#|$U}!?_zDVJm#1{Y-x)N*>&FiT%iwrA zqddYEYsnDGphOsczVIy@z%sllgek!|vEqO=jF=uF(uRK*OUdn-RjJ8fNrF#HWbmLx z`<|C)kwX$5Gh=106tfe)#Ca~jFY3J2!&>>RoTqX-t8n!Eaz8q@Z6a(VO7$s!$kAw3 zLFpwmED7r~xp~Pio};X2*ti4Lngl)k+Y(C&^1M)$67iuyl**!xVl6M0eJLpi?DYKl zfM18y(4#Vx!aKkcO*l4W&r5k!y1oZSydIJp;Oe=2zgTD8Edec0 zF369y2GJfSzQT+1JWx;}a1Fz(m&}7;+*Q@XU}e^qha|7%W6LQWN`GBvC9Fe{v040` z@NK=RURKPYrTQ}bx}236#q3Tc%rf`}7r3`0dmXGgSxicHNMj)iGRla2JrRz~zkY?k zE!)czHFbYsjKR^h?>V=nl&u!jTb;r_lLGP}H1XjnZ%v0%l_Q3WjC(B5BWrSSiAjxj z>t({R_sZ_CPKd3PCV__qm^Ej>YZ{o?#XLes6V%;nG*%L6i zDjb=kn#;&gv2=C^2f2xM-%9E?3leMkCNry_DV|7$W2OcA}vsv-bU5yS<#h_m>QUyYu_0QbzyW?4beeW0`KfJKN1bfEB>(!u?BR=Tgd?F3te1af zN`calA6BXM-R!c3|y)M2ox^ z)&J5T$o{mVJkEaPd$>iX()e^1wfLv~on6xUM&$tVQS`3HN%*NUp**a`V&q8)asq{| z-pViIRQ?Av5FZkh9HGKZPKP5E0Xb+=@CKJ`5kOI1JW>u|T=Z-3(pkKBkTRXjz1jND zUZ4KUFrRfJ7Jbx!#^yTklLR(euT$t4MsMW4_gaP7fyt3cq_eue!fpsnhfmRTqNjZgUm~bn|mFGiLOy-y2>|mW}IPDheCQ zX(_z(BMq!k@RrSE`Liv`PiKC4BB64``2rE@&1fU`z2TEKY`|te&=QUILmZajR!#AB z|HlFxR#fh|u)TkIKfz^2jQ$E9V+AmJ{-v!o=wK$U<{`9h-|uh8fJ zxn6X0QoQim&qCI`9(0{OaQSs(qAT4QK#$PMq?t1{7mb=UsLt7dHznEWfG>V$!stQ` z-!?6naxlY{>7#ZUs}U`iBXIcNNu*5-Satvd%tB7WPY?gnJxA?4cjCT$qZwOKf@qN8 zQnF!(wk4-spJz`(y2~~tx7;Fnp*Kv3=ho*+pKc<#tA0c7$5L#LJS(p*%bo&cc>gg69;H|uy6c0l2z zZX`~ADfAccz;Z6$Tcc};BTDjnF>A+w@)`J%<-(FJMPzs(mc*^R!b8z&mYKq>j~w(O zjt43VqlV8I)!{eeN+1U23zRFm{52*7D5-w+7%Zs_ehho@#kLQIYL-1s`o1l0hPxQ0 z$4UB7`XLQuUmA_UU(yxkTXYVoEfZ5Y)AiMp7@aGPxPdc`tg|_JXJ%}>`-Xw{dVQ?> z-;>~y#OF-pQJ&*4AYjR(g-?1TUB|vDtvd3I`|~L>&(w(={JFsUpYg>|3IV2sKNKu` z6yL^J^=eY=8{X0MG((iY2RX)}!aN?-+skAA{2#)xqfVL3m>gMSaiW>F1;JK)oByFw zq}HO{K$BpN>D>-?M>bLS)nt?)-Zz+4_R@8HUW~7X1CLf0FxwWT`j8f{hCxRXfLjBw zp)0;jrAOf*1@D#LqOxa3W}D1{m5WxRFORtVWzAh&=~6#@*=Uf^r>;PbO~D8Af1Kz4oGDzRP7g%aw{tkV zA8$LEW`>EOgt1OpN7L5+c!AX9@zdY6=07)H04qZ4Hf#EDks)I4iew>(|Q~ z<(khfYRGMyLRd{vj4)dQ`KcO^d9i|x!R-?YO)2*JAY1Y^T2-aL_y*o-GS1(~R|7V8 zE|?sn){77xY^?%*?g#>jw?3g4V}x_@w7I)~lhbr``J%*UtlcU9qM-D5q&@6r)^M-GoHYF@%{5{w_`~nlV_~?x2PWMO+L{-xeJswmf2a>t+b%w)LNxlE zI0jQkU~!lEA?@uBXAvM0Eak~zy?|&agy&UhQjISMI zV|F#%>mgRM-BluMsqgch!-jkrpsW6iGU1%75}dH2!0GU|1*xrePl(zf1@kKp_@&SE zl+W&!`btRK1Gj~%)5>r1g_0~Ma03p7iM!s}&fuHf`)iBDOz{?SQ zRSI-0GxWb=dKm)9>4%OUh5#@14&O9(rMLp~4Ocv|0J<53ZtVrp$-Gzsrd)&baPYt{_@Y+TzBs?BCgS{Lx>$e&1IsJ zE|`tMvYYU$rr=_57tU+Sf~yOWc=h}NUAr(Uc)XDnt)6y+=1WU_@`*%`4Wi$IyA0Es zs7%{*3q6N8n#g`w$DRx8%BO2b%C&G8PA171gOrYxH=1c_`o?A=gbN?u=kyX5@C$b# z#(iyTS?RC%hUj*Og$*fmK)TTQs&<&GxHq(k=?9XF%^8sEbTM%{`m zfAi$nSV2t&&<)Iid8o>~gk(ucAW4Yp$=M1CWii?x;_+ES&S9LluVpad3qcq9m|E?cD(tdaU=C+h4e6xrQGyz_>sed z@~&%p@zn1MHs@Kzwipt7ixGqe3ik~IPU>8ic0tQR0eTVcuxu%eDS|^1HUxNq;^Yui z(whdH7*)3BGm_}uzyV8eMyovKg<1CS!ZdibPx1Rh@-FSm&QoDmcjq}@{p^}zI~2^} z#p=4-kawVI_Ben8v3#yvRIBG)Iqo?e)L&edTIWmQWqv4HeIZ9rlPOs;zOznvZ?NR7 z(es6CZqG=!d11?Nxx2+5(40gTU9cI3hYVVTJVWi>B0T1Q%CGs`2_^qyTz*{?ZLlZ^z?f%I*b;Vsc{KgCxXc z{goG}wHlvQSf?HL&MC@JyZt@xoFdDZ*?N$z?P z!@dH<3g;?E^xTLAN3G*`2Rtr1*^{-m1%)h>gB(ze|eJ@>DI`ljI=VQ&a@Y?PvVx zchyF)8+|POAHOTu-W(Ou9!;KF=bOn~R80!c2)X&&etkG{t#-78?~l`B@rLK?QH1V_ z#&6^ar)(c`cwk*5PIOkw56&7TKj`z!wgfg_I&zpPy5t3^Y4u65O0+j z^N7R&?1sjOMi@77+Od@P+uMbQ+Orn7Ivr|5PP@caGd;-rm(iWffTvemtff1hxXx;* zpSVyZQ{vkd?XEYJnLcwo#`@*OMefdGq}r*Ez{P)zV5l@M1HRs~G9;SG7!$?6_$~o~ zjh8WZgI(z#`aoLOnE@<)1WPotn}7*O_j}HU-<9M6d6wLQO!l0gpAHWyIe2MNB!Sg? zR6f8!wA)k(k0cRp0g2@@)uM~4vE~`TTFB=~@uf%km>-ST(&+9dJAb^6jP5diEhgIH z_R{ZtN4AcP-a`$Yp*M7N&{y1Zjs!q^lLJG#i5I%L>Ob2ezX?zoJZ^{~Q|iDFKvc;C zCY#O^KKqb^`cB>+_`q?*c2RTDCNvnE{%6+)ZUQWd)+7Otc@#_4gMA%Ki{~p1UhZO4 zfvJY)y;3kwTmLEr+mwD3`P`Wu=Z%v{b)+sBA=$ZBJj@p7_pPhx#u6mKTmL$>r z(VllK2z3fy1If0A62N7D29WY#9|aQCFBe)85du&CV0u?gHaUP^KmW6ktCbS*zMn(1Tf7*{P z@NUz-Yicb1eUZDshZsPh=ng$I&#_AT^eT^54$MDH_3mo!f5xzpK8~CTJ4`h+k`;K>+uflR;JP-Ir+v0PB`vDK+_Dq?|FZ&BpQAD^vH5sqJ>t-Q@3th2jH zpTa!tG91+I(RriR$)Ir$aK}5Djtw8kGvYNc#wn@E9}-fN&sm~3yi(B{x6^ykmJ*}K z!|RA-U%fpq@~1#sckBsE8yloht0Y-W9=!^U*vQ z?e2sV>8{%5lS#Jp=CeWAK3`Z{!zVK~xdP_=ZO}g;g8d&FI1>7~&;>#)9x@G$_a$xf z!m#Auk_(Hmw!g6A6rN0mmzgg6Fq>X)c&U5Cj8{EsU;0}V6UPU;bPyiZY=;zno67?8 zywI4$HsO--h3^G(*|rlY=PD(XWFO$6n=)Ois*8B>&@b4AvKe>Fk5nCnYQ^tIy?l)> zseAqc8E|8(*cq8KYVIbY;rJj6nd1OC{Vblo0n^fUmxZ+tJWdhSu<*bAZ26ZLkGI5H@43-aIqTj%E3Q?H(*26laynB4`zG6$h!qTcauF-Ic+~^$ zmX#XE6UFIYS_D`YR$~3tiWvvCGItRn=|vG_rnG4C&4SA^fn~33MPc_4lGnGmLEA6#6Uu))wQgM?jf1D+UdSlwPSmh zNeJrID=XoYQ2_-w7QRZkk42e_lR{kO6mi^pc6?xJvkuHo@(-~8`qqcl8UZnH=xF4J zR-ZX4reuD#91N9zmL%}8ghD9RDR{)cC^Om%(LH`E98}y(mvV#w_K973 zrOZ3N1OHwUpb`&pxTl(eM{Q7V*JH&x{(~ou%lhAW&B5s)XfX|&3b>Bf z5Q>F!J8B;bu*dGhiX#kWF$NHWe~Xb51Ura(Ngqp|dwA16!0WpIk)m%tk%;d^ao$Yq zWQr&F>QurpTbFlZA@!2-giCvhCpPNDBCO1kzlTTQrl59@n?Bj9#ZrJ;`WwB8aAsJoC2701yS;MJXHi#E0b+FP4dOBF$ zdsTweQKM~UjI(numbiI&_%GhL5ZyzJox_*U-Wk9@*D4)bfO;eIQwK^IZ3Ze7YImjjkuOj1m*!+;H$Cjam zS~oPvx{enL#54E&u;A%=A&IvMH3yQ*aR%C!_k{QEz|i!H2S>4@U2ZjA(`V>UC1@^8 z4F9eZx+*xS>Ad+`YfLPNPO^A9?q}}PuAiRh*^H^90I=gzm_o+oF4}t^N|J852Bqmv zzl-Reedm_TYK_fC2jiQvYi+xBhckc1yb>3V+y)O?#nO{c=^s8FZSt}}oC4=m>$_R= zBs#NzT#0oql+PbW_Y=l%=lXT_6>4cqw$HIP-pJ2ZpxmI3?Q*ixepnFw`1dmU4D_8K z|FO?Ojt=szV-sDxV%tV{|9df%ZG;vG?dvG?dz;^AAAXI{mK6EdlR1v~pdJe)`?>Ys zm*mHgwkh!BP8VjM&U^bV2R8Lka6K7)RHS{M zqa;o0-Oo(gzRs~daYxrC2s0x{Ydss-nU*wiL)*7umg8cOX(%JfDC-^%almvu+?0Fz zZ<)vSNg`Q;l*5Y1_KUB#)JOt$rxW=##;v~Qk(vg46q$}c`uqaz@Wu798CFYANfB96@6jw-V^I_k zNV&Xltt53fy2FhJCK_xcjQD-=wZFmN)am}9M5Z|-(^=$+WzQdYod~T___rEN4u1U3 zBUOi+9{U)Tj2qCxvodcI)j!%f!B(TB_>y^B=EN0c%dkEh6Urr_c|T(PoiG0QXYU;a zwy%lHu|#y0OTy4a6TZMKj|?}y@_lOPgS#Efj-MbjF{ z@u+)bwK3t#n-}$+E`x64j?B@UF4+MBBe$2LD8DjSK&1}KWRssb-cetUB) zvhq~(a#-j;04%STq@Kjw)FE4*uSIk+H@t@Ha_;fo#kIT!2e!`piCOC9l^Jj>l66M# z9RrMVto3F>5!S|6&rWgg!6yg%OzIV>OTE&gK^y&FoPPhNCfT0oE3AB%!=XZ2I^QjK z#djSa6yi~Tv*5|ah}X2a{}xfnb2Uf3qxRQX?Q+Ir7r1rC!!%vJYr(GHoA29xE^>R^ z^y|-8C36UtMDrCv@w95|R>_!@5M7o<$&UxgMxUys9KIAkxQUyg$^(BQ*ueAvCC+e8 z4OztH#vOwH@HxivlgVsz_~Y_#)&@=hYm(|wB8#WnE!|1d_KRq5OjqA0Y@*-dEAmur zfeayOd(_H7_RHU_Wa$;pd&z?+-F1;x7eqB4Zk1@4i>S5VnoWSqHms@|K z7K)L%LTU{^=voWP&gD~+^MJF5%Q zROyS4$J!IRZ)1fi*C%hnm*)Z4^6mXLtVci6VOE8$reySN#=kaPr0re>#FaCAt_0Zv zq#s=YLwZ|a#y5m1DaPbZa|ez{k6JA874mw@oh}Q+c`^R;S+tBuUNJFyRv+3 z4ez=$({W-$gmUUhuO3Caw&c4e`&-njifWyWP!hu@v3OO{2>uvLqXo4^e(UtNu-Tcl zF|VUTx9E5mivwnsF)pFTPWnY3s+(B4OZqNvf=Kep!K*lDW0iTj;fYyfEPfy@)xRvk zG;pIL^2ky+JV@2XX*Od?<|NY3m{ip=r=LclM?5n*wKtOvn@(;OIL!HL1i3$4+8n0c z4h^P<{(CxMl&9L9Y|fFnU`{;eelYrFg!aKL_*|_9Wd?fwl*%++h3LIDBhe4pD2V+2 zNCeCXLzf?Ervqcy&^0fO=>CDO;V-AKEM9Xa^as$779#S2}Kp(P|xUm2j z`y3G8h!l{VWZT`@ItR0zZf#KX%%IBNpj;Ui<+HiBGu*;ll*UZkvVEwFF!@x(@^@Zs}1 zOn7e|%*$1b>M4&9QVs6eO3rt!W`o=&Zsf7tVP^sm+u{ysLh?YHHvGMqFIe}-N`h#| zQ}B!WIwx<4?;9*b_KWi*`b1Krs(mVKcMa(0Caw&LqmU7mnX`f(|S zwP&L^7N^|K>ETZf+2{kq!;$+!4xUfID+ zYYz8PbWZWf;scUVxB+`g$H$~V#^Gu;MSS%ciU?NI;B6i0X%*vHTsg+>33&Ux15XrO z^$zgz(`CeI%Vi>Ko7_hoO!kEmpgrGMh~cpr=PhW)lCEwu_{kDu(&q}2yV*-5*_@x*!SUviGtgg4%1TO=KO>ik5}tO z>p7&n)n$0WxG<-(=<6uJYR5mW%N!$IG|99N3oS$MAMax)>!>Zk2l7jv$l#7y@p`Zl zhTiKLh;6XQ_!`c=m?hb!u6BKBWB~1>(C;aXC#k@1`YguVl zaMJ&O-CC{b+zhrKqr%x&6{gqzthR=+X_=BI>dMbu6H2spsjgNaHjz?SQOhyUqO>4#mpdUkL-PWY|OK^{{E zc-?Y@K#Y}>vGON8|G1>+f#YtzX~*Cp>&1msE6|L=8?-aBhu;3owa>@e z-zz(-^G*OU*T(3SZ}RJ}vS_r+G3x+LY3n(^n{X4d*jMj3A*F%V~EM0JYtMdnXdz~fGyCVRTC4ysp3O;nSyUZ zAQ(U!SO5_{c8muB9cK392dR1oI5n!<{p4iNgQ8c?h=_Nz6jQ7gqvtv^R5eBiB+zJw z2iV1#LP!(E$WZ@0g)AMPY?h)&`?YiOoEVu2xYGoCqkEvsK5=lufg68A|0b&HMwJsx z-^PG`yPf)y8rt_R+;98k<9&nLskS6rD#js?{~1B$yYsAHgK*9A*bV?Ai%nv&%UtAP z8YA(ustI73ILW`>1(&`42e9U1x=QaoeE)&=tA9m??_BCza;FO#5jz;kkdhoAw)aYp zp8Vy616KB&>y^lefpBVV)Mga^qt98vSFcky{_0P)@$qwMzl-Sc{BhXBNz&x5<#1@n z29sw*8|ll3K@E{o6BU9J*Uo~o_Q>DCjGXl4SOx{8%LoiP*r4QV3PnOBotTBWNa#{g ze_`WRJbUW}VKuDvC+`#(H`YhK4SHl)I$#w{GFV;Lgvs_X*n#tMSL-H>K0~#H$0`~bULC$=SkJJ%%|Bt5g3~H+Xy7ozf z&_nMMK$>*vJs{GQrbv?}a7&RUMLHya3W{_TK_EyKqzQ=hphyvcNS7Mvy+{=ZdGmi~ zo)4K!&g4Vp%;fCf+I#J5Nzk3fnr-uur(4qTmpwH(M@U)!Z>$Z`+~_s3Ek$;;fcD{@ zlG>&4g|zKY$9nf}CS6j6Gd{ZRz%kt$#iCF~qkHE@BdEBDV)nrw-afu}(bUS;UN%dG zW+R)s=4C3UD;pG3IxEkIGlTj49IR@2&R2a!?ut?$0Z6&62G|MdnX$%r%-GJt0Av)<*-0*8&lLBn6EA?IF}I4SjP z=781Bn&Ay$b%mb#lUH##kgP!~x1X?lP2LV@mr1Y_csS zs)jSYJ8LbzLSS5xqYbP#84t#sd-S02R`vPq^kI|}2l9RubCpA}<0CAQwDq5N7WoOP z#^fI3#O4!kYOcbACRlxq-0L&!``HFo_mUCAYRUmWp9yNLPigjRkevyYBwg5K-!B8a zOsPyN_KyjhkoHT}Hwn;J{&x~6Bug9D3$bJGa%~yw($nVEQ(os zo#+j?@x@`*;ZC+-TTIoGXk)DUka-iN?8jqIaK~MTb4YC$6IDh5Z7Mb=z6<2hZNsv6 zHwKE+f6PgbdXR+Kc`qZPUCuPm(&UWo>z=$_CW%TWj5MHR z)vr?(#Wz4hWVwT+@GuT0!hL+8e-@+&QF4HxiBE({aa}*%`W2+lJWmzyVIl2x~JlhTig+&*j+h<9?6uMg`6eMlxb*-)Z9vrIsWBB3$E;k zmCC!bOZE9XK~>Ip+_}~57=C{J?oY7c$%Th;bN-c4ZcHg(Mztfg-ENpLmF6B3vIieG zL3a$p@c(E(@uiGoI#Y=JYIafZaC?T9XbRKN4;!9#U6WUj224RURRdaT|1+fXX?Ti6 zDhO?DyR54XHxot9x5JnX(m5vEXYP&@5p9Ky_-vu;Y#c~A1cBe2mwqZe`J;kUEIlmm z`X9TgL?JSagK=e3K<0d((B;knR<-PbqfgtY>vS}g;`X{Iyy#dzH*6=AFKo7 z&R()+hyN;(`5D{s%U+8jtj@*(oGoMi98mgnD-UXK@WLeK>xS=cfZ?W<{;Z(1?@{#i z!^2MseA`Czk=GQ9_fskUC_MIi`V~xE^8TOmk8^3#5@3kJk)t)hH{ZM+V*)kVfEXYz z)O4Y`PS?R#C|?MuQpR`k^Z5C_<&AoIIibJAi-ZI!3kwqL{S(kl`~u{>bNFOMowok_ zVa_EXi`5$o=bU^!TGU$Vb)?zHa@e4LPrC3n;7{(w@27Rzg4ccVR{Wb0$2W6nF*nZ= z1EH1+zt(ZVstG)JE!V5`y(XU#fJ(0qPl`*J*$YhTH`zA$yU%$Wi?0D?JutXAndS@X zNiXFWZQ)cjjr9+hI=Q*X#?Y%fXu#}LdS&7WCw9Af>%U{JThAmi=TIJszY~EQ(5^BM zI=_A@b1vSI2{Mw<7;Hw3^|gdrEw}nbSpk#xPBK znNc^PG_|VbI^QU-ryO595p2dxxh6CVhlmNdh=F%{W*!xBoeIYpuNd~uy$eWdsB!xD z{&Y(pHMFO~jrsRi61b9c%*O;2edcU^`;x{_S(hHm(+yiPV^>OHW&wWE3vrSI-kh8p zYR=79lwJ~z-Saj(JGFw5rcE1MGocK&l>Ua`^NR}Fjp&#?l3)Shnhnr-&u$nz;-3=7 zZD~1|_D+s^?R#V1F+Rkv0sV+RE}F1o5a0xV`4O=f%L;BM^Gsc%_z9+8_o(S{7vDqY z&Z~L5gM&74;J><_imPGZTH0M`XW3%xm|I)l-D zF^EWW$^3`fz5c?|W9S)VqzOgc6sNC}l+(%AQAw;*BMmECpR?Dn=@=rg3aY>@tRJul?+@v0!8H{wFw8r3UGiZl#9I%|5B-Qhy%z!o$!-kJ zmc07d4py*oygnTx;@R(nqZH^cH8VAbnUGi9+Q{^oM6^Xp z%Qh~$`$A}}S~iw6@GvvX(!UbpdEG1lEo2OS2Qo@KBGsJC{V{)ifBMAN*tqZw3o=kg zUt7ZIrn!Yv%RT7#n0Nu}6Fu0po~{S=>&h}Gs~Q@C7 zpkJksoS{y)DUcdor%6eS@y&v>thJDz%8t+WuGai`e-PG`H$=oel^e-gIj zrBR~2mg%4Z=}DGQ`iNv_t*8)0NJyVo{)a}T$gPeln(!orZL>Xzj(`gx;6f`Z(EaT4 z`=#3_;nJaIImyGHH+{`-3tY@}O-xS5op0xu z2|RKDXeZ?)kj*T1dN~YfxHBw<4VJag1K#v=tFoQm(d%_`O0%oj)v-G#tP$KKIRAZL z9=Wp4S{Hg}h;J@`re&+*hU~xCW7_y8P^@Yl4z7*r429xOtn1$H*7|yp!`6EhtOcB? z6r)EeyOf=AFO~VV12qwyC6uRoeU&J6Z6dU6nfx?R2t^b%JvvlEy#8hVfZ!@X@U(D* z5P1X{ApfPU#G@-C=+Cm{xD%;C)fpPOV|?vmQp{mJuf%jT} zGIx48l~3aGm&KB5(=Gp@y##PpJc8h6REd#W%D(H`HG;q~BSlh8tc+RrLr!Sdx@sQIFGUBuC&*)S1D1uSr0i4orqxQbp$az}KP`+u?+AGCb>`lCkyy=6+Gxd#0 zYi~Y1u4Ox!KA0WThHD2A z)V1ktk&=wK#tWd}scKFniZAlHGk&6MH4d$nRhG|hvYT=3SC4(j#^RuQ!ke{i%(V&P z{>~+z{9&}H>$|Y1lF1t;(QRE*)>>5sDh_+^dVm-Lui*3thR$h?`|b>vto|+Xm{`cI z>_<#LKX$Z;s`Co$j>NXT%eqiYh8zAj?a@LKo19eSJ`e8nSR+N5=#Z8pY{7IxHRmKp z7gFzgj`;T^F;_ECkwy1qPyu%e%Pg^RP0KK|HhTJ+3yG=bDlYmZ@n!R+h>`rUZ}v;# zBhzbs#{X?Owv+*cC#317S<+_{ASB@!dBj77AE^S&-i&jxf}a5mhJw5+m129m;k+UG z7o(WZ(v+0lZS%Gbs~k>r5!V4B-ba^Ghd{A$gS>PgV%67&66hIPja+u~}){B$jU+Iv7&p%}xto@8n4E}=+A;>H!!cKo=u~;H}rVnwvIIQN@n(rYphtwpE^<$M{9mhY=0K9xL|BZN2FyD?=@<81;S-FYHY2eAyRSsM1S~C;1uV z1EGmG>_E+FwCkYj8MK2Gg9|m949%|vuVM}tMa578HX*2yzOR(Sxh^{=rSvdV%EsSo zgkrAepaRtvRAV|$D&=9C*mm+s z#J6hfc+l3DqURRhJkg14xDFD^3XMC|mhp8CWLb08)sIyXFb<*)dGhF41W5In9g)se za#;er15u6DjCoTznTht*p2x=YVFg9USDdEOd-URI7qT>AsVBWMl1Mw#lk5gWnC_Ww ziT2w4&tX+DxP~xX3vYzLfO^!t5*#c&9dw)GAiT$s%;88Y{5C&7y%YVg2Y*<)bDLIU zWS{GWK}?WPyYSq@nKwM8jkR_Tlsl;>2!bVN5i{n}zc3NHp?4=xea&y~COY81JUcqo z+4_lH%=4dtKsq_V{_NTa&^sQw1eUfDK$Y%s-o&{Q-`&Rdt>^){3!HIiDTOCrRkMP# zV53P_!S0d{zxr^f4^o1t>J?UA8rwl_PTGqc$up^|@=2j`9H~!|BLn~aV62FF%*;EI#;TdJI?! zL+!2E;bMD=DavLNguv|pQ!P}RBUNA9w(!gsKai=5OBf{sJ2)aCWLP@zqo`pJ;4GhZ zg|GE1&Y~`WY){OPQ2K&E8dueYhVOy^^{lJlp59FpnF%yGZ(*D}vjt1}rp^-(Q-KbacCw zbLC#lix~rx1ZUEC3stoK^BsL-(s*jXHX2HhMI>oUtHN5E$Nrux@oy+U{KDc0J&Z z*19>^@FfL-D9DmQq~7`nAaCjdtm5Q10Qq@8xr!eCviZHd({Ev06w@aW$k28_hsEbT05w!HU%v#Wif?O7h~)Id6ld|a3#r_Ak| z)1LXJy|W>Za{EZ@>)?)j%{@2a-43lFQT=S0B(wKDx zWC@X$7o*P!xq0@1aX5|Z2HBmRGihzM+z-d+kDO19;k%;oW3hVmJBIPV*#7qv5MOr|}1n|=o}y42PMvo%*h>o{$FXqQzLw9D)V6$rV(XqE}zYav%m^?R0J6B8OykWSSf zX^mKfS=@5Y3$%;RJtT7n8`>^0e-Y;(0^O>A)!`GokI{ICNLhRVaVK>Dv~O#4N6X62}Ji1!1AfaH~|RC>(4owK&_5O zksn|f_Ag)W(#YqkCw0Df`L*);E&g4t70llToK@mz0`75c*9i8_u)aR1|M1}@Xv&6r z9fDF*p8)T zz(`>t3~boIzqUcts5Xo=y(db*02sa9j&OHS)ou`9enV`FXzIF#J ziHV%9wU|*0^p+&nJ@~Vh+Psrs8!WG!{Xf>s0 z1$O!1k(d-IV1&JpFJ?UR*$jBfJbBF$e;~Txg1`Lys}!6(2f6^fz-`&_CNR0o>*KKz zxA|qA+6AV6J~y~-^NIFrp|X44(>>5}HXrf=srFME(frbTC14sK-u~@M7%}y?tgy%K zS@iAZs-FQx>o)(A&6iJa0jG{n0z-D%7|Eh;rPwiG6_1x&jnroB+q=*1DCZlnbIWT8 zVcBPazyFpVTcKRVjM>Z*E<~lA~ zG7o{>`2*0FV4ADrJ=^YR!2m8v4+Vi#YfRq6&_8Ilqc{`qXGG@psd9cv&Akpj&AI)n z7WH0TAQ%+jifxBb^~r9=I(VpVEQn9P5>2pIuAP71neHgb9Z(h>)u91#ei4uO_4{2s zgT*Qpl4G4FB%umENfm`iwu59mFfpWy?_9mzRzK|Gq^bCs%wYBtN35!YH@>Xi;um1Z z*M)xU>6M$zKqsL18UL~OHk6rsX96*R{!1(2i=8Q)c9mJ{z;L8QI|b4&nTUqVR%re7 zXpX!X4a1*wjyKYhsGNNn=H3w~+v1wflryXt5~3&`=FSN>V%QcZIB^j~fNu{{u?MPf zrFOu-+UlbD^cHf5(pBVbHsxRS9!94YK|t|0h2g|qD6)V1>b*HX<}n)<1O+i$T>x24 z-kX303fVL>Eosv;fd?N*^J8PS{V5UE>t~870$TkBSk;d8091S+CzV}=+L5=@%ZMIc zKiAOii4|dM|LvbyFaG%b!`^qGX{BvHcS-7}!%1vxP9QuGKim}Xclr1k0_&5Cz{*nN zyc#(LqEGIjYIOnYI#i7=TBC!KA;qdmz^r)xjr%p6nFvQwbHKC#UlHSr+(Q}>JV|sx zYjUg%sF@oxF@JzlIBxulq^=L5BXI@K4pe;(oX{7!R5B`mbgB=mRr_TA+1)vsr~^O0 z>?!6ud`yZqKh#PBg=qhErKu&nI@rN-Lc9NI5j^+2j(AFBz1qCPpN7(`Xlg_|_tKpH zCrpV!3rb#$>VvA)PE`JWedIE-mlDr_U?QjYrcJLm@~P9(&FX5gY9Us_FK~s>4-~ey zsVJ^?Vj8blMP?9!#(JnQ*gG74_iDca!w^`?*SqEM%+U7_WA0f}#Ld4XR(MK<=+)u6`d-JAKfE?D=<)E8Pfn>%Jc<+urVnH&*ciK8E9TPrBdXlgcf~d;!1%2q5b$&95d?pm8Bc-Lf8|a+PSj$p z?}}^SER)r?jp@!|nTqYs3TdO4Cl^gMcX4lH$CS?xDtH!Tm#tzu-AQou7YfjQHY zaNNXBE1Sp~xOt{w?5TR8trqExAcnEu5Z!ne@ip4fY}Wtt0+=vOQluGu^a1}+NPdO- zaJBE;`p+YcCqhmN3_ig zL$MYAy{ap9Ohued3!^=GWf6U1ogw0*n#x$uS@uYsTscrk3X zxxlN=!y;5Px|f+}VjZEPThhcOptJ_M3o0-ko{+$#)W}_@05ha++C%1~Mlh>wMYhE%o6d9rVi^ zK~=^>t57RF@Q0c5MGT{}@#WP1t3&YMb($cMAA>Ky6W0_~R{O^hkTSW88Ip=v#!63( z0m8pqeRFkFnW4ga;iKb^x0}yry>!3K&+7McH0N-1Sz{OfHfnsZPac@xX&%+p74x~K>Y5nSi0MVcd z(1R2=?K4|BZ6Y-fBi=}hqRFbU-^_3D-)s0On}5DI_gk`}o2l8Z_z+YubX2*@K)T{Q z#84tc)~BPRBG}e&jz~TMtsKxW-|4&Ws*uA+n!?~u@ngCuhY4>Er^=uQZgQM=m;-6_ z<=I_Z67^7u)A^0Ji{K13*-YxVXwh#p&1mOVq~o1L25Xe2Cc)Fcn7ig)^7=Jo`t4Q5 zn(8z-2gTJo!+8p>T9a;Izi5*MJ6cRp@1~_m@JW)PNxFz||GeLscJcPkV`)20SArnM z3U}hZ1!}r-synqtE^|B^LtOZZpU(c650043bJYYYF>d(gi9|J=nuO1wv$J zcotjDe{09P0_lY5f`jvCd6Y*7{ET??&Hgbr`@}I{7~7Ez2-S9cUUg+-0d#bSUAoMS zfv+qjp3lNS04Ogq0OWNbG1wpvJ=*aWeyC`%s5|3-z@33AY z8ZKl47kyj-`tGBEqXNDt*_;IT-{Aih2vReDk_V~^yV`sWar4}?iy_aJG)W{~eSmsT z=X(1d{~uZ%Oxr9qXh3P{`6{?jd7%Z=Z0Zp-a{%fxIdRZ2bpq$tu;{!zqG{yXWBiV- z)_aPU%lh;CK(6qJl&(iKHQpmZryP$S->W|NtyT-W!Z*7g{3<$_f(bIW+LWUUCv%q8 z;h5?F;^m*l{uYv}XZso9DC?B`9{PxX!H-E`tSG-FyAt z0oYp@inw|e#p7xrwhgXsSs?Y^;qw-vbO^*Yp1_AoY1I-a-EQeknxs z*_sa@g>B@soKLVnx|{%t+UtXBqkWlJ!6tfS)ijuq_XU7UlNs$T+SMLRy||0tsU*m`pk7bG%3S>>rrX0?gHqly$2}N-btJt4ZDq_rgfH>)^3U?)e_brh zLh_DmuYoyFDs3XxsAYO?JQDnkavV(i4@W=nK&i&K`S-osNz9Puw~O+y(A&ww^nvMi zg}$+gD`kg(@v0~%(nY1*xA{O5Ipk+i1yOvQs6nov>+()=`o-ckPhCEOKKsA3u;K2dz&BXz0CM=ug+XPnNHKZ1Ps}|Dw)cRnz8ttzWLC2EkN^vTjqOH3Meoob=jF>+k`X`bx{&T}Bs=BmCO%}rEkZpVU=UXVvYsZo9`#f9gZ8J*oS5noAbKX*>z6`8t z2c@!54=~BNnU^o()OD@jV17#n2{s}52hty*v`XI%;P`dVLH&%1Kr-VW4ViP%5p870 z9bJgbbl=#K)7j&nKQVhlu36Ij{+j0l6NsqX*@ud89TT|^MM6?(TIFhq&*yxf^F~tv z(Y*JP>GkJ{y;@-t$FFY2?lQdcFn3Kr=kER+ZuOuwZBA$OdaZ<>le)6^z;HMLR~&J* zklX;XRHZ+Z)x3!saeZwEJFD=hq``D{Q^Tt#8G5@zDb`d}^Ld<9`1PUI{A9BFP2sE@ z<7mL<59bR&(pbZuS~YQ^n9}{5K@c!Ic%TaVlJ@PdDITL<;i3)6=yf6p{WF|@>PhFq z4t-^JFv+TM|Ki{xt4Fv?(o6H7uMiD`sdVq}5v?5~vNl2KuA;xCfkqiY5A>lSRL;S# zBtTeyIt1>%7_fBRH1`6{*4Y>q-N{u@0FnBQ@ZNQog2%2K-UaoCo z^KD|#*FB2)K*5_&I1`PP;vxS^1;O2KX48Y1Bh>TpIWdT$V+ORs>4m<6d2-cJDIDrI_ggQFNvTan5H&)mXblSUje*i#ou;FmF0L zgdV=JV`%Mxow4GTms}*XI9JU5G5P;esqu@W{=PL0ru<-&5SVTPx(q2pzI}8u7Sxa> z6#?Xs@!SE&Gl1LcMinlP9}Ik9P{Gpl}u=={?QGz9JQ`w>8^O zdpn$VTn?T;RfODK(zpijMXmnb4SKA7MF7pv`Q=56y>9bu)MRG*%Dp-pA=meHROR& z0!(1%WYTO&9sI&xvYds&3&L#=6IIWI`#p>l5k5A@V2y5^QtIOJS*fE=qQnsrk23T4 z900D=q;?0tKYOp=<}vUe-`M?bM}PZ8MDKODpWF(Mwi4pI$<}I3Pn25>L!1&?NjnN}5)~hcr85Z1* zAA^a$25&Fn@+t0H^9yp_m&NZ`&fP>VZ2~C_*;>x$^L_2M99f9F@5Pldmm+mEe5LP6 zPZoS#NblF81^DhT8#tu zWdV0e)1dC({ji_wzfuD(UYW1GFrlVwnz{G*vMjdK&pzOv=o4z=g87wA9`0YY__<*2 z6Kn9cYJ~|OFL+E=e<}v=ris4#u|`bfi8%He6(L})W0h71C8j8e%ZBA4z(&fU3b@@k zK&EFlr(vJ`JHmCMRzwx5;1Dlg5 zstCe*n5ZI#O?M|=Ne=ocJ!${i$lo(yTdP}#mPm(ydg(GNr2rxKr#4s()PcgiAto3+T&l`CA5<70pN=}G3ZMJXB6tup# z3lFZJ>Dmm%dTzMu<2f3{A!Ejr+w|G)gT*5FX}bzj@cg=8bBu41&Du7VZ1@90k)57V^JN+Bvj|9r~ zn^GWTs@)6B)%!v0p<&P^uSRM@aC)@#*nbq*t2JX|yr}pPMa&Mr$%uBE!ka*>b=rTz zW(MdW)D>+@(AeIFv^B_y^K~x~Z1(jh>>7OC3ya8)fG!dKqxa+Zwv2sFCCfLdEoP4v zWT`{+7R9|zAsU|*{#);yCdaX+KTy+Kt={}~F@9_MNx60i6=(ZZxZ`(~6{Yw? zicVKkNWUZ2;F^sp?bnfBnOy#vG~x;(&e7R)DI89{Fwo8GX0N6;SF~j>9#AcQ%;Pg< zqYySk9^yGt0=(QE;d#-4=H1=ZyQ(7cCFgD|X|x^CNbgo}j=YeUnqq3M*Y+Uf9$Rv) zsR)>TFCh`?{Vjr*L4B|ikY5&WqF(!P-5v;QX{Sg2-U;@7!3-9*sY3}f>Z{0fQiG_p~=J3;ff!65-&I9_CMT|Hf^f+S}Rx%Wqv~Gatzixd+-$P3%bd}h_-t^z-|xE zXqhg|13)Qr$9C*4+?+X5P6@!?nmXD@04E&`jA5iPXJt|7BRB`B9uzs7n@s*~(#{DO zx7rUNUrdyD>E(3;TQW4xquGAv^8|BEJ$eu`Q(K%p#?VfDIQ{8^F?LA5a)`4XSQw6Z zb2Sd*8Fxq}YDx_|(ult(SGpCAcYy{{tVw9{ga5YsAi)=1CbTuf-P-H6oPrA4 zNbX9H?Av~2N1oRZ_~p+(n7)Q+YrY3FtV#UX$F%-2+sb4yQX(+<2MM{CT=`DvufaK< zXui_lhGLpN@;6wRkouA~dUFAe*h3zQEvEgo~*$%YUP z{#W-D9;VL#s`skKtF?{GC?280-ySc48T0%eVj>xlEorpMV^?KPe(D0DnPMQi*=@Sn zgURlQ!Xi~LK{I?O9$nr`3e$%M*0hkgSOE%E403=9)X}uMy$gLV{Nz)$cnBO;B?5z7 zotpI__9M)|#tRVJ>5!x>3VLgdfr}IMvIQp6b`dS4@ zYtUebL7MW}UPd*gU5Hh`M_w@hlV>N+VF2?$i$pAb;l~)-^c}*l+=h_vGb$duBdvb0 zSPqAm_M8)=F6K$UgG#kbl*I;z!c0S?T-9p{xuTq-u>?O;sazy}NbYuaC#LiTr3X)h ze?-sIKVpcv3aU(IQn}it+u6U-6%I9w-c>Uk)nNytco3Bghxz7NvLKLnla%nDv`n_5 zJjXm^X_@ReAUjf~Kw{+3%+Qm0@(AxeDG+vN?T*l~FCSp2`-^06_mk>SE47aItmCFt z4EaLGul1$bXv4tTXg3teek(sn_Jhc_NB3K2lEj(gZm=_H!zl11I?emyyW&Lj|zAXMOR8rf=7#<`aBI~emOV91@baDP2I*TYWO|+8U;@j&U7nSP+PBtAnz1B z5`ujpukMZ3i6?j6D}lX3f{bd#K_XW9Ewff0OdQzJu{9%923+|!H#QNiM_zq3>7s0& zM7=|p8A4r~yGhu}=ArL2d7E+QDYARTZ?#q=4j6!_^izR~;h~eaGwqPfH>B1g$OAI| z+n`>v)~;AD5p4=|V;0372>3zM?Cut@GAO#d|VCmYTv^1cXK%9&#b z(Bm7I#TAEvNYGqe=17Elh2}Ff{e(mtue@RMIvpx#wzmQ6aL{%PrCS%>rnhy?o2bI7 zG~24h=-m#ViaArTnZ|I9+~bb5%<}i9we|Yc!02A9eOLRy7<1#I!ovm6#B6#CU-O~Z zEw+6BclUhPS`&6m;ifdS(syvd2tQczW{%ElBHS7>r@A6Jl!Kt%pr5ZKv>3$Zy{b|0 z_*}G#+WD;E_|OWnfxi<4OEUZT2=k4lj=2|`a-EP8PEyw8|9}Hg7r|=sBmUY;!j4m` z=Rx6ODUWJ(v^m6iIPM3r)jYRr0#Dw!a*kkwp~`tzlDbmVJk z$~2v?B#fOod})pLtUyYicKp@)6PW|L^ir8E3hC4A=+c zd-(sh9?EK=zT`54r6yua=uaKqSeT~z(gRcBDnP`e`CI?UBfQNr|G?JE+_B4F;cQ+! zK@*Sy+11^89o!4d<&(K5w5E4T>a6B~S8v#XkQW82RDsVU_ysXZGX;xiMt^Z|(O#1s zEUf*XAv?HnPZeAuRt)bgRWvbc`K!@{W!~}xwAM>mKx$dWWRbV!Y7;g37@p$kGx8ty zr2m#br~82c0>!YGVFDUVCrjY9wu@Um;Cw=AC)V_bdL;Y83Z)W-*77u?MpZ9ywY0M- zy~FN;kl|2VaHHX`!*5>Z%^s{jGvZTvVB+*F`}=he+rD&gGRthSEPH>T!kF^`(I&Ayh_m`#OAA1E7I4U!of*MznH!zTYUg#zt{>y)$J!=|7C0! zeKj#A$}|h!JFOcJ$dw_XXLSF?q4J7#RUvMD_s_&@au+i|9lq;yemQjNOgv{Q}#ilmJZe0jZHj_CAj=dHpuiW@@}K5tnON8oGQ)f6L%jm`F-?PH*8aJju{P?!J&y8p zh6+S(fp-j6!Pjg>IA)f)PECCNL#qDin4O*L=0ES)M*;)6Vh`>r{UUuIb5;6Kc`K5( z#=`5HNorxJ*B67d@)UsgQVcovQWKgtU>AydOj@h%6oy#CC;3-tBSK3|TD+AOGU${QBcyJ_n!wdvrHL z?-F`O($~?xSJyex3Nv8^KlHI>ae%q@-kERm`?JP2tG;_*%Nh-9~Z@UX~3aOY)&6CRh(BVRt0s2r+MUSXw`Yluh+)9X_7rbZMhYs?=vP6 zAtHvtNBpN~H}+iQUT~9XHVzZ_{?5@ik)9_GEP3!hZDDT9U0U?zRSp+b`Zunfmv>#l z$ZD?f$E4QuX@%6X?v~@XbY~JhDA;TRPjddGtZ=;RW|BmXTB?oyHA`x`=S*t$(@gm( zk&$jkTK0-SG3BdGkdtnxuja;G-k%`7`UpqsbdU#g$}>=cY5sA&{A*n3+xrOev#sVrT>-W{JVCIz}JAA_>)xN|Grj z53Cx|qwiJjKe)VkbM;CnEjd(6tyZnxo=l6|_JpgOxD08QSzlGk{trP_n{*XlHR>c@ zlguFkwO|6cSfTXJhUnpRPYZY%+3FZh-HLODz^eY%QW0(0v2fb;PqZ1jNA59gEMtXKEruWiibIdw@YdVq+uHpD1N9WIWT~c@ zMmL2o8I4D}vCAtdB{8qFsf}Tha-m7^vI1k&w-Uullwe7F9&AI7DvZrN=0nLU#&V=P zjL|)<>0Jd%Ek`qqUFPoYqTt?7#kYr+ao_J6{-f9-s}6dW zJ&9Zl_(zahihk2a*k4hX9F0gOC08B+>t{LUl0#aq%L&qSbb=_+NqeHkKNSvR97n;15a3 ztkz(H8!man*=y@W2^CMh;^YEbMXwN28!-@EFb zaks%kMn=W(LpFgksOly0jICm_TK<|6OX$K7vq9=_LAos z-CUCS8J^{N*P#U{aN`Z_NpmK&w>Ey#_*>5bs&X(;w~=@*y#Bw}iC|TI)lFqQ<(6;G zPKN37HEfGVIStD9dNn`t`L>Bvx?M94O3dTIZ*F@6i>Glq_`x$eFd3_>jow$M#4_Jj zu&1VV`E&@_HqY$2s250sa(mxIgI$(Xcxl2coLyyPVt$`VS%|;*&B(H@;lF*aF)2ju z`1QVi2kC*o`TE8;r6tGD=PQ%E7U;vpI0T{-0ae#s+O<|1f;nl$1$@1Q{2wY$b})0) zN{l5WffBVOjM`fdMj_&1<*~FdCUYX9sP+hs=T%fd8Ubd{{tCQCmC+yS1#ykbIYE23 z(@Nw}^O_0pnh&7vFerH)VrO$YFWQCwFsfr9^}(APGj}`1#=3#$y`v%ptvAb_wKJrS zg%#2Q1N?eg6z#u;;(@x!+fZwXA!ZOU^)_uU^`~Ot-mBE|?56|Nn$0l?^u=@dUs7#w z{$u~KWzydK{M+pubE7^ z;$ydsF0do3HI~oP2F%9#0P~XiCT<}!plqiGM=Q-;rQ2#J3Q3ZDcv3=Ij!~X00xr2( zlRM0Q9#JQdI;aVxql}?clMF^vPJwS$G+?Z+3D70jbTSupR*tphvv-$Z0dm(XQc70u zkp50De;5qxGU-l!}gB^@q*QsB|zxa=h@$L85h)f=L z-7NNh?;fd`UvDvk{*+L&5U^$`@Y2BRvE^+$mp%Sna}SJ=64)h8@bIK zLn74zJ@nrU3J7A`YM}Mi_0T2XgV`6s;T5f9|3?kLl0zD8+jk7OJPGR{d20~JQE#PZ zozWHiKNHZk)xNU|yv&EyQ;EET)~l7n3ZwH5no~X>vj|4X!$FoatB_A43!m*6Qz+OT zVD6Ki8%N}u=9lhlD&*Iuo;VI^9Sqeybe|^)tM83)lY9srE@!V*f3TNEQJ9~{4(lrx zuk^1M$vs)OuWPPfjT}AkplS z*r|6(E&jVCwb{AohQDOy+?kd!LsOTQ2%y;CMk<-om-@$tJh@=_imUJIAcx3)Z5J;l zaaRfx`S7`C`Tpyq@~Ms^QsuEUt9oi<0xL+dbjpte^%M zV~yvN=g_K@`(RCtmO4-~E2u&T{HSo6Nyf^7@03#)CBM4rF`^KUHobFC8+s@$0S?u} zJ^#LSPZYMX{DHLW{LED>HP*}{u>IeJ#7N@yOVtjc4IYz!yn?sd$~;V4UXKg$+Id|_ z@091Za1K>X{HsuT{zsMlJ8Re7#~3P(g;-?@xj>GNcKSzo!!-S=*a5}$>QZ0<%RtZ}+*8GlUoW}feAdR!xJ zNc<#jhZ<9nt*sZMldLrvKstMKI|b?PE&=K8mXHqV4&^JI(v2{>yFn!;4WnC!2%`t1 z2HW1f*YEdU*Z$bEe?OmR=RWs2_kE-=TX}n|$G%2<`#QJ4=<_}^t&@+^O+-nM{Yxo)*lZc;8h5DM_09uzr%N8(2qjGY#yTezhjDZ`JuCa4W}3EI?@+8 za>qSItGfnc@Ax!=E3Jt!R&vA0DC7p?bmhQe0kwC9vNIR2I>$&`-n-HMwi_F$mrKUw zV$5>!MUVv){P&8`m&4MyW&|w9MkbV=pFk8?_5A+(Rj))DgyUYZ<0c_pG#q2z(iwmL z6fscgjBLCk9I(DQE0XBt88#WPA2nTxudy{*E3l9%s-I5fk<(jYl-C`p0_{C@%I7Tc z>Xck6HG0QACB*GMf7^#m+LjIGK zw#g3qft!YXz2xRg(`I|DUGqMIgtE6yU2T6oz{*+;!6jC-@^Si}_3l{mLj>Y?GH`d~ zvR!TK)&2bnTjX9{!+_d{Di&r6L`8vgqdVcR6Kt8kG#Dm+`gR!0uvY_fex&0o6{OqKOJWhrDlKlYoO*XUxEm=AUxxK~AD4n6eU?dTC&}{oO>qWH9+herN z8<4@zg|YT-bIJKWZ#9I{qvnJUXer-DKRT-!Uokh}OBhy|8I^JaUXi4_23@}-+@CP%*zK<%gbI85E@TZh?>)sNg6z#rwaUR_bUgpRNyVP#3UaMw1q|c(u%8x zyfH5xb3$a?8I=KkVD*b(N~fYjqPWD{hG01)cPCAs5jTcP8bmf@3M+?2$Ymp{Ape>a z*xN8u)+(xLg4A;7rox^j+w5im8}FA(6FOQs483>#lT^V`cnt5xtvkbkvYo(}*Nd4^ zj`Gq&ozRLy#uQY+9-Uyv~#R~5t{0nl(%mQQ%^rmwymZ~ zDBTrL2-y~{RA*hT=@K%WG*2-Ur%V>T$KfmFRVtduPve&ZN#hd=JA9J<03sF(#s82g zr*Aema3; zW7G>LAJNOS$sl`{o?6n~c}2;uK^9TQ5Y<1I0y0_NE3v{hwYKitb_^(i;9g{vID7CU z@Fby_@>-rRn^nVuwI0ndANBOcuN!?49sf{if=8y50anCUhZ2-^$VV%)#7-l4(CZtf z_rzTsdGc*sVt4>1n%Vf9CZc!X)s~S^!?lNiAsV{Ee;vn9$lw;eCbmtXw`C#3tV_45 zXM6raT<4`Tj#%qKV2 zO2hyxx=c?4We43u2ij7`+@JyVnbf78BP$2SoZal>-nzc%kLAC)NP_4K-~W)Fm#(wBa^Bx=RhBfjmXHBY!dqFIA!4_j2{95-ZzY_7 zHQd$aPMk)06dB1v2}wCKPJ7O3j5|*w3)W^b&6r}zd@Yibu^;mj9e@|{~&9N)B@Z9=_nSRRHJ9YP<+NI{>zJga1( zxXIMup7TiFFdF78VdDJ4`Gk10OkQR}Kr zwpjPA2;=gFZyXt+3D0zS?GdWc}(9Gui z|Ly_+{?OsoUZM-DLfr|x+;OLfaw!JY$5{gQ0Qy4Pd>EWUZMgm8pU<)0kCW0?5_d)j znWF6IcP@Y%9)-#XS8eVgZ3vFP=9*#HPQ_^t01e_)8iz;fKImPhV4pDs6YcNhga0W^ zZfm71p4)`2Zb}5qBH9rCMI0;7X8p{&fqvF;2CC-2iIyO~jBO^g z8ENd*eL~HZP^~VXW!v}XbsMD;rB^$fU#L*mNclXkR&c%rhkz8gq#y1PkPtxJ|AK`% zi-HETZ*0V-m&v5Pmx)1D=Y6WWol?npN=!tDIUhw*j3Jy|ljU5==G*fR2CnXJ3|&*bKaSV?UB5T!y*Wb)h(s0(+CGakT)SPQ(~OLNz2Lxn8l zH++45)0P60DXaZ$Fm31~YahzX!ifHlfz@hf@EeilXV3R4=P>VNTf+C>{Yh5v)(Exi#`T_<&zZ#`n^VbXlBi2r*?=ZscM3dBn62gb z1lIXA)xe)D))%HyB$q9s=Ff{{3|NpR#F%PSG+1>1rapxZ4-%VcpyAIAV8eGkTBu{p}H7 zgtR^Kk?%ejokuGFoE@*F2Wn z*S*?6W}ZggZ_07mR(61j!17MNZu0M~p{ayXJ#6oFJigbbLR~bUmZHDjqGT3OqRciF zhP_T*4|Qc)4>#k-p`P2FTih39k+Kk+pr^7xT^__Ic;Z3H>CdF+8~+vK|6-h`z(m?h zLFsmqV!Yq)4dkQ#$Od9RGPH5aFhhnWCZcC4KdmIjndE+6nIB-X>w?;G;30>rO6#Ay z4SrfKwX}3La=+E6p`MKoY>G}AYa$wY&_8eq@S;g4{~kT5S%-455=0K%IQ(Z({r>MU z2qntmOkm5bOPsJdGL^nH)rrk}T8h^|Lu`8*M|CcZYUM$#FpqwpHDkv^SquxRYP zY;0u3ZHqqE-#Qe+rr#3H646JyZ#YN)OGbxKoQR8Ch^8w6;Ld2Q}`Lb7Kx5z^bpExdV}T1_8sfMnx>rFQm6p~ znfjG7wA(=BEt(Uv@dESMhSXDOm+Z4;QZ*v$>Hu!l%>CMml$>vMfA5Nsq z9D8~8rHe_Ziem6ei1SE#fD#BChZD)ELI$id@Pi<6e4q`_pp-|sTMbP|=^As5lt zCsZe-CPKX8Z)iK<>)F5BZ`YHKa>USl(^32<<5=tcTl)VlobV*+%z#9mY7ZMB!^?Sa ziHhi(oMpbWA1gHe>G_rtk5Q%drGht^T9+JzP$Ey)kp9N|zm~Q$rf=#(cC{3U8pDPlk8Glh3Fr?%;3UC!tS1M|1)gv3WP^Ix*s?ON zQYyTTY7rA>mze~)z7FT71d2H20V2Uc+#Usah?FT%epYHZua~7Q4I(&zgh=whpWyo; z5H;Yql-hD;61iHeTx07$vsiNICH1bueh_B5Le~g` z*0kw6+oV)rVxri3*kLDC&~zM-{Itl1KeH*13=_&Dp_yNRW?Z4;?Dv1(l3d0mW}GpF z@_ZL)_h+_++RMJB|M%2MATFZy)&uTqt@~0}3_?zSSmVnW)XK|{ngz1)l}p`gvN8yd zJeG($914DduH5iZ*~mZiY1K5{LmSI@H{fVo)G~9%>Flt4r>H?bG{?md6J!I8p}#yV zLa0AlRpHl~@(W}6t+v0Zip|vU?Xf1&k9}t$UAU|^#y1wf!-w&csa2aY@82OL4i>6l%^==1P6(ajGHZ|7jKygJmP2YRpIw8t6H>oQ-$2+L&H-u+2C8`k% zz~bI#a*WSucbPD)~>%Jny%bk5oFo z>VjY0Nc(=X3HGyl^i4E+My9gaZe}=1g|xEmn5a89T1ht3+UsG;;LDeTne1 z>&JN73KwmVI!vSISPgauP#RO5r+N1h+TPU~Iasnf+7I8vIjK?V{*D_r)-*P{p49mb z-pQ6*YtFfcFIuTAaQ5qEf*U7IhB*5_NKK)r;r!1R3$-Mvb+$yJ)$VeOnOAgebL3Ja z8U21N%z=~=PCF`!9+-+|^SDRNMB1y{FafLmh(zJKBEi~@xK7!#wn6}EY4>`ix@vrH zHX}5%w%HHMPz!2DO9r`*0T(aoeSPm$LTv zJMs;zty{vh$Wfd1W_Ab_#EB8jR5|oex3T3avbbgGEd0PUtydb3zv|a>(e^`5tVew3 z-7EwW>}DlhJ;BypV|YLq{wEr6&j+WX!3^}^#Bw(b7XSV`X9FWNNK+?(4AURYFDpW1 zd9pFk&(y_tw+Y|XrVhWh{jDwz@0}YBG%?hL6>pclNk1LJ!hHN6KDNzY6#Bv?ej^P#3Y3Y24X{YK^3wzSy%6rrdjtRLH zb1mG{4w9Kkf+N2)g&Hwn51|c!~5S;2Ny?#FbMb~in))D({H(iUHL75t#yvi&;(X~hv_IkPh9 zb&bmXTsexV+$OaKl`n4GSH99N*8$B|+!Tp!uu_ke9DRojn!==fOL|OK$nOeGus)dd zD_SI5E460-+*L2qln^R^HT#9lzwVWeAAu4P@3N^AYu9lG8P;`zlqD~-SH;TXkx$>< zB5R=|E-i>t#K6%aEaCo_@RG);Sb=*EMY)#$;Ae5dFIeT`I+;8cNVfU0dY>390udHw5r8KQd{RC?*N7S2SnS|g#>6qT7Bz!QQC0MAPhcA)K z_`FRpJZ6f#Y;3KTS3*G3@9Vzlc-yQ=@Xbuz$lcpBWEmMo85623O((tYU;nYl;h@aT zk)!UJtPe2UpXGPrKz$FN&)NMCfdTW}<_Stj3+bGuLc()bb-Ehpn0#6y!KIj*#xLu# zTb5gx;dZ(f*f4>>-y_DZkEGb=>-ZS5uyen-*MtE2?5;4`?p}puOIk+jXP34|NIbErmnHO2oFqjz_~wf;F{vK5LEm zq5e+SbP*!%yT@II5vlzf2{6J?8AV;3zjkSEAMWjJS=VRPgUHj@DA!Kj5PjnvCzpD@ zfT5}SPBAI5)(GK-u4^+~aK=0}h&m@hG=9lHDa;LsF$YLS*v?5Hq5*#u%F?tQ_f5f@ z`2}gS6qvqLzRw|oxlexM%b|Gw7ZNpI!7B}?Jv+E}Wvw(fBh8T&5Kr(6qKSDol&y=- z)0e3u&kGo#g9Qm?h)=&Ym2YxzKt{8O+Os?J#;p7bwi_1obs*5l!>7=mdmfWtTiuGY zO#UY(kb7*l-b6jGepPkbju=;g)J-d8g+z~x$!$er^9MM9SW`aHYC%SRH4V9|QBxTl zCO$X*O10$obhQTuW6~Y?-9O{mC{CID$@%Tg)y28pjn{T{eKE(CC$Dtyz#&HH8pQ6Cq9Y`ajC$Q2xfN@N8(v*@4Ght_3GJ6iJroV3~(n82tVxUM=fBk0t z()IfU{%b)7b?uN;$}P#9cS}InpCGe*#2jQ+h5ZQmhHbT>`~{}&Rx7QEm{OzFoxLhl zf7F-mfYDVJtom&3TOSlK~!+x69AX%rnnV#0|S*+D=_P{o|x>q0Ppph_9cWheoYQ75!~ucMDj+EyHw zb;w%|JOO)a{dV)0zLn!r2#;4h5y;Z{S5*@esUCzTtF=#aGqcl8iI+_->GB6===6Vo zpp>+r-<>pVoP-O>ouexsQG=sjz->zcGPv_^jf81)8PvhNQ!3(xz^qyCB{6$IGX8DgBP za?3_t!^U$SLiT>7xd54(hP)s|%L<0_<8zI@y?$vx2)xcCLNTaahEL6aiMlC_;_Hfc zIch`Xk?64DANOYrmOyMIgDES{aq`_#R2|*DT|F8#mxN<$5pIdIwHsA1vdV|w^|`SX zu_NBX`_nmP3u5x!x0%K27B5x|_RZx{B24 zD6)ROKo*<4Iie&E9o)WF(0_H0_ZYBlXZyIOgYmM}{u|Ev!jG$uBY|jIDll1vh@Tun z&>e`_a?PcG^|`O1yf%T^NCDrydb-{mk$1Ccsq}e`xX8L%M#;~K+MM6aQ(#oG`e0Mr zsH40aiPkI)WX-$E(bC%6 zgIlb6t7u)DtNHuDalq6k+aePhOtJCMSxvU@h4!DszKE6gGh>-rV9O`tAE~dPk9Obi zensO;qY?&J19C7ZC)4kX=>EdO{pH`BoY=UM2iKTK5kwh-oV>~jW(OI znVsyz+vCBIxu%dcJ}8lcyY(LyS?>tzSdo2m1vc*l21hKz#sf)udM>mQhGzZ)Axp#S zlIQ8li8dV`j#dtePm)A;(`jw5Nj4bU_}XNLi5(ME(Pc_;?OfQHAfS;{d9uAxDsJio z8AsNB;HUciiaNQU*CDb(ZB;~^ZOS0ADPh>c=u}bEne|K~B}HKDhos!&7p;$0PYb)o zC51!Q^_Ax|h ztx<#y7ACx!E8-5FP%M#kEHCYBps;LJ95}f5Rtc#1KQ914pC!`aOK5(>_!f0J+pa26 z6NF3DU5G)M&&3P0DI7xrKP zaI;$9T5WU8WAs|=VhKL?SM4`s#0n9B<_2xO59{ow@;Riz*lia)AJtA?5*m}C1d_|z z`@-2>WMMG-&Q}F20Y<7^Kpp71U$ikcbrj7IL zZNHL7|8ujtF`Zt$n&JF+gYjlg5Kjl95@)g=w>#;Lrrbw{4c)?h$##k4+!z_YDMsdS z(35AkSQ;|@uw#DX{{g8PuzJm=BkHEWM?oPF;Yf5O>w`#ao6+Bu>^bhL7m&6;vOu?0 z0S#>>BU)n+#^mQX^MEC2PJpMi`l%HSZrd+0v|dT3HbxFEJvB`9jE#t;JOs{??g~z_ zfaxh1LTd2|WM$Yj05UVNk}^^JBrP8fLdfx?dCoq2@nTrO?XBbbmpMv4^E~m+vAz=I zh>SNc^iDW|K!N>pnG3&hXuE9G0Jt}xAHrXwwz8i);z&;M2!P>~>$n5+tC*Wx#aE)V*A zypUhQwY?F4Wa#`u#^I?oiVSQK)%mU+JWkV@dU2YxRXdX`w`8y4no@LWzFn0vFxa5| zG%K4J;DxDv-Yt8>#tupP0!XaQ;Gd(w@HVR~=`|@~2VGndYFIbuDuABV6TUt-?#>SQ zEL+f9)*Mn8vf`JlZe>+?t76j6<^5KnBjWSB3#nJiIucYoV3Qg$J8c2Lg?+&qxM#*f z_HptQ?Pt0_`M{98{s>p@ZhBx7(+NBXU{2EpPVBX*9-0j)1}WV-CJT-mGhAgKaBtUX z5<>L?>o1jWzTosKJtq@Wrv})vuv6L{#Zz=Bm8A4)d6Ar(mBin2Q6(iVB+8itICQ@{ zBOg}S)!2ft?BvWDYC+;aezCQE75^UH1T;Ca1==Z>D0Rat+cU!F`z`=!M2YEg#1h0z zRZL-@?ghQCWuf-)%>Bet-_$%I?h;xfAp!ntX(0u~_R9+Pxf}k)uX9&IizInlCBHy= ze6020FfWzLuI$8Jhzo7PC&mS;QSjvbi?(5^CY{C-{x~tlgYWeQJt{jT{sD9%VpPK>&yR`Y5MR8JRZl=P5n8cWZhMdQ2H5N9PrkP3bQz{ju0LVKK&5 z#KNJVpY;Wz6sQuNMxiWOZSIk8XMJDI)ob}SrEj~^R5mArmkk5m+{H3BMYOKHG-vZP zfz+dTX5F3?Ufa?^6Y1PF`VR)41{kOhw3&v%LCQTp9dz7KBk@vErj*|muLnPq(9X7@ zX=sSSV{oTo<3EJiqz4Mr{pAj|h;F(z4crRkAy0CVsKd>Xw{RxCNeNWw*`?T;QdivhIym}|AM*G(!9IRMGUWcLsj;LcVP27&=*|-qHss2?X#bJZ=m+2W= z_iB}8d*jIf@1F>PNr?x#DT!OUNdQr%%NszDU8~ z!&O^H%MT!7IuXrOoLxQAd48)-io*sRB)tpWS})f0npsfvZ2s#tXhV#qvBa2P*qB$y z%S28nJFK6q-3~%^NLs8r;#kVV2+@&(B>E=iI9Gk5ct}br(DkP;%MofH5bBQpzwNq$ zFrP+BZv~o^3oCbRWw&blSGO5Lg3_Cl54Fc6qy0H-lN&sf_asNlKKjsm5c_JK9t%gz zZ(1f?=j^{6BxQQ!ZZn$ssL-8&2gY}}JB6>uQPV1POD&4XtV&jJZ}D|=NINW1t9Ckh z00k@fPUw^qvQ2>R9fDSAViPT%VP>@JvL<9F5+2YYZxXs2>5IyvS2>2&MaL{vOZIYa zUyD!OCXRkorQg2;^nlHDD+XD@D?15q)YQCY3q;SoI|_gB?z#W?dr$=Zy-;TBKTmB5 zjBMAL27qtmDCCwY_IL2Qd^s^$5EE~D8smUWrvdPyaW(QWptLZ zKIC;H)uLE65c@3f1%$_p7^di@Bx~HjFCTrKTl^w7^A!o@`!CuSjGFoJw87>q=-OqJ zg(o6Gk2l?7OxA0c#S0)NmtHEhu*Pbst*k?9&s{GTjtxu5OH3E70&2waW$|Df4>Gs( zMPLpY13O%eb%`eZdl;20#<73Bn2fTj*XyZN-EiX7_hb4urOhIrAdCH*j|UW*Z-If7 z5UVk|tgf2)%}1a;$0R}QV?j=Ixu~osKk|E8XKBA|1G)jk%|V&Mbs%n2tf`ttoD5#( z5RPnqF~P_Z&(Z*Q_*G`tX|#o$H+4j~zZs@VU@=e!n{Up}C4NV)Az&bL?P zWh-5!Eh3|`pj>gHv!uT;2%V`2`E>eX7OJBed{ppnE$gohhi4ME&KC-*vm_~-XO1>3L6F9~-7d24HUv0jaL3hRf z{qx-Gq-q;k14mis>%`nw1wB9Pqj_SvYW*H*+_}$-cCZ#DN+ECLs4GZb190|sSTf6pKBl4J6`C`Y-ees579IQ%-=s|Hv~-n*U$ur7r` zLeR|h8y@71#Wq?VjBo$@nId@RQTlE!$}wv%Iw$gO?{fLV#I?8aJg3?}p#V~SFLeeC zXr_v;0EukLMaH`NMsKTZA2Qki6hiDcyv6J|e2c22)khYapQC2%fcG`+uRym_m>E%( zx-!18xH*>5M<{sTCoouK1v zoK?UjnsrbElm|@HQho7lHszkxA1QuvF!e*bekgDtO?e~u>UG{)vD(e={+5~+@cWCg zy~HLNU})(&$Anc1&>=#}b@_N~v)+UR#S4h!`1j4Q`Wa&=TiUo9*5b7mku%9=Y1ME87Dn&J{-wy!AX#ux7_ghs`b@ z&mFN;4WFGmV_?8`M%uL~w7YHY&8}ZON8&CVn5ClpG-;%w123&fG<=$nNXXW`Wq|`9 zpwexEi0vX3lj4iW3cgf{JYqA;x5hO_fy6qSQP zHdEHeJh#AjZbSE&Zg_N?M0Ub5kzaZZI@Vh0e8t?%E&wg|eM*-4ZMpnlRSVg7o3>t? zbaIA1I!A7?4x_@-yBw6%ncZ)iJQ6+~HH>_6UFu-QI8F|VDSZ1pBS|}^@t*t$qpnv@ zFPG~3#~c0csMZI)9n$Mf7SYxsYR(p%ie$5*P?hDH#2D4_i#FE-c7-wm0<8B+->l8E zv7-sDc3%;&2wZKmuP__xmcO&q_xwk71cqjctkwto0R_ZfwC|@+$XfQt!KtRUwh}4w zKGcL4H!p!rm@*KY=c}65rks|RMvh82r?n}CpNVU&V)4zz%t)}%@65ceT+ZLu`Jb?x zcrtIb_Ds}PR@@k3YX@38XPk}o_#^jRA_NE7SGJirbptrSI#% z+iQiXUPqdq?R`cos(mn2De5<3#E}hC|L1SmvkxzCd)~qoSo4rII%q4dPT2eMy3(wg z$GabjYb|JzvV7zd_TN(EH|mUuae|`@Zo4d>dw;C=jY3zch~y(PVvNW}GJ&bl`!p3h zGEd&UnUR#;cT$DRTbwQLsaS_t@QxqoNsKgf-bs?k&@oD33eKc2EM0ykrv4KsN@I&| zuvAZfLW)Yg&L}H8o&|^Q_&Yz-_&#?0VX6;4mMSW=qp^f1 zq}?r*ZcG>-579i56lv~GXd}>E*MiP{fcyDT^vLjdq?_JxM>tk{Y>iNiA|K6bvR=aEWsomzXPf zPJ~l30x8^(2j=ldnIGCn7Bg1HDQlk!KY8C;>L&McaZB{&k`-&*iN~{dY$-S&p4t7po#4OUt1`lepw?Qb6zxjM9)@qcy1_6K~k7?0px@g|`>PPh}Yvnq^x zmRs~GG?(fki=w(hsX;=&lFc@g48ustD2z6T{vH@ld-a%zR{A5CC;X49xaHz-+4O$J zb}_xvFq2K-_ku8Xj=wc()i%*)r__X4&hWQ?b~ex8pV->e`5Dk84h)!10_IhdB|<32 zon$@i;gq|b3mBVJ0;L;5LT9pwV1ZNl!`kk6VB~&sWxS_FkH^C^R;?vYyPUY{ea_Ux zNjHqDlN*f=bs0C!`3!G^vS#KX*U(d~e!iBalp!`7KZ1R2@TzR(Yx(5@eh&9epxaoi zWu?MK-6E-%QyBmO#%_1gm|=gEq#S>#39{r``~D|+`u`)aocPH#kfDD5>35bHJ7bUX z;loymeZe1D6SFS=P&^=^Te2Ns7eX{9oW&L)aYjY9kbTz$w1?dH#;msqE3#0+BR2`~ z!dTBa*nv2Sp8K2{V*=aUe@yT!wTpAGneKpmh0qEv(v9@Qy_M zPEu2d`sj<;3cQh-4ycMv44PE=kM~?PVv4p^p-xkZQ6g!D`}_cZbOCxi=Z& zIBBYSDCS5Tq940xo8UaYVB=+JPogLzACCxDdjO`JOHOY}J}-!7e19$LmEK8JR)WG< zfEeI{Dk(FCOHxmQrV)f&mH9u~FrrUyh}D@DUqGbXYR)?DA`}{PFm~tH4ns#mQhg`rPl`oW zcRljDR-fjh|12^%-y%k(PEkvX0#Ch#aV@KSZo~8?NMfo5WF_dTH{MU{v~*aFTy$*j zGUCaKg%MV#($6PxHZWbdT-K|m#`^{#Ugoq{Q(Mw2QxpSe@UP$Eqg^49a9vjEH;iQ* zvs*b)hSwd7QWL&_;zF&R{j!2)_f0idt9OLfukLwhL3>NzIN_qrD#0B~lhn@do<4^N z?P_y4KEm3icsHH@C>_{cAJqg01|TyK(Gh=%+@n-)EV?|FKB@e;>P`1ETEl1|9iJ*| zWJczAz|+_263(Sf$a)U8I1YBHdJdiQ6*Am@$p{$X@+T5Sp4wJ<7Wlc2i?OqHK5igW0t~={1Su zJ>y2y#Zm7ig;<+c(#^AneH1D-pF+MS+<$qoSfIP0+L+9AQr<3&U>1L!JFy|1VAA)d zVn?kWAHuAoVzXTl%gJ5G-d}a$RDTvSM_jS$EY22o&z#DN3n&{qzfWwXx=q-VPU!uW z1`#|h3rbv4C*!?}jHs7mP~Ijp+7AX`gLWaV0W|ea%F2{RIdWQ?CQau@&GEZOAIjyA z9J52s?RzngyvmWn%;gHw0n@WR#{uQV^%Vq}0A%?~!&gq##m_UuLogc=3SpQLx@UH% z$9cM^n?i)PSikVz9Nl9lUM+y)!AZdbHXT~Imm_gM*YM2U_iOWll21KZa}xTuRK zfb7Hck9TF8s1*3f>stZzuWa2}I!{|dm(y+0Zttf$QStD2EZiws8(e^|`{JGrR2W_a zIT5aprbP7w4ssVAUF5CV3X=^FAC-tFuPtnq(W@VSAd77sNktD@Nc?==t^vmnkPx+1 z?GatibPeiCT?9lAawqf2;ikgZU);b$2yen@lEdWoZyKd(0>i?vZ^A;Z3A%LkjIL4P z$;LHm+N1noQgno26I%~a^V5t1!cE?GR24!u>A{OfFR3agWAbxM@+uZ%@@M}7H%o0o zq>v60J#8yhD>8vrif1Oc6qE}>dpbF@oDu~Lc*K?d@4lZFlUzy>w+{b7S<_ysvMWLr zVq$*cGXI_qlw&RD$$Q&%*p)AI3BnPBe@$B!VbJg$ja=*0>-OS$30_WZz1+ z_~{kCyRi}KTF8sjbLc2EtJ~rbuPYtB`qSZK3CGb(mGc3O7J=kjKO}cEEb7>V(aEhG zk|8HVtyjzS#)#!Szu1Ru;8yDrrf+#E{Qckn)9_ec!Q!-M%~R7e-7M#Cp&r zl`o;dwD3QPPU@sP-sH*r520{O`8f=jM03QY57Vq=&--bPU~|cPyTGIt+G6>Ndn4=@ zm0VBCWuiqeJh>$;KwaU|9@OJzZp_P_aGbYFc7-|1dXUdS-rG=Po&;qaM6~#ANoLIN z%#`fT(nUHa+MLWN!K*1Lj9bkT-FTj*T3y#e5nwz8BkU~pU-d%19A6*W3ZI>l%?s$z zca^QU;WIIs4!J;GIbIE}eZd7N{E>lhC9ulTn~^3L=g$2U@=lm;bBvNcQkMWkCSDka zO*mOw_F1#=&kY#}Onr*6Q)MT-59244+_NzyvjRGj`r01E|9<*uLE6u41AW<6BeGOF zRw^HRlp964M&b|p6@WWj$e*%D%mLT|K~?+<>7&27wZMOL1p^F5BtM`cql2^%V?0FR zPCvU2dypUhEb@&@sfo@|NMV;Rg>grZRW{z?Cj_!Yl8qO`?UHc~Q?n0K&HRWyWf$Rs zQuNi;6kiE0C*E`PXX&aakVGrz%~TEQ00^AOPF;6xb?F@meF7uLC6CDMDo-lTfJ?+uvK%PQ@H&c*8$+Fvht*x=9Y%0?r z-FUJy#`F<~_V~cxw%46xJr>_eU_kS{FtBcwx%xJnTaFuY?T=5LEuXIkE(T!FZCUZj zpu-!GNPEL}zWAOP;?yPn^?*iz!u~iFY3tnZkx!LsZJT5y(+&$Z7H&bw9J9EN z5m?squqlA_U(qp{{NvWolj#v~X81*a^|i`v^y9#IVd65qVRQtX(p|c6pV6TRG+~QY zUU{bz;`d*Es>~)|%*V#)`O7ZT=VrZ1xjzuPzxP!P2?aJn?+6=m1h=@)?%zmEJ}ua4 z0@Oe!?p)ip^ximPH7loJj2DmOE$Wd4p|F(we&mlzpj>zBwWLE_^n?>j)x~D9tBK#s zHeT;b;zbb#QX2vR#XTyuMuV)q!I_oz-I>1-HZ_;_0g z$9Vu2R0ciCg09i<#@Wm()!;(V_cKXW>U$b{x*Qb-$<8qSBo`qgFE~FOQqY%8=ZX)f zqq$C?5Z98)?A9J7MCUENCzLk4IE|AQJE+4LICvSeT_Sasqk(D2{*1HQ`bCBxyW%-! zYn0#9$j%c=VqBr&r0$^%8-rNtJ7sRPuVD+%Sz+WHh(%vRYk2uSFCrJ4wd^4M(Lx?- zJF+9vvU7E~Mz5t`r9_eBmd`S8cZ~!$)6$oT*V9fkH?g8jd&}Znk}o!ryR~?Tir%hu z=SW1VJUH!jL+}QFa`ejHLvG+mG2*^U)*h)GIyL=N@T>|C;!PiF|o6->-XuK_YQYbS*$@s6KPz;Yf zRlK)8&v32k#`7Fde#e*K5T$Aa%I|b-&qisO$PSNi zQZ(7{LD0_PyeX6xzWy^R^o*zI(j{ht6CDZo6uY>iuDq~>kN*BRx_C-Ey6E)2`G&#O z(XcW~eAE{H_hB#6lgne-PPOSg>ObinuUN}jTjdhxmP*r}5C*Y+Db~Fxkv-(IW0;^= z%aVRgUe7iB)iF%m#+hA8B>Jg9SP?8IS{)^f@w;YMtr9bo!Bsq~%%{QfQWz3dH0Iv@ zqNENo6f-d%AQ{Rzt4SRcys_vJ6tSxWM#7pmT{yK>B6V%RW0nW zLr_fp_5gr8kJbq^;M1^@2O}Cs$bEi7s z*w*v(%3tn)W^mt*oy_QIaReGX?Uk~%|M#B9*9+!H&X4vS=1R)#CixJny{oZli=Jtd zYYWy+BuiXe6cK+NXTqQSO{Hb(ols0|i|C0eK=Vi`o4Y}bj3I}jpR8&MbpK+}z#<15 zn+x|BCu%gIUh{7KKTcElICVvx@Kgva-OCZHE%+e+{7em@^t#hdn!<`9C^ z5~1o_`g#;eT?kE?oZ#8{vl@{Pv#O&ULn>Td&BsrKg)0zNk50VG9(Gm@gpov%W9PSt zU+fQC4*%J@;iZ6ptXC5wF;O8)d**O4|QGi z<~Mb{d!6ehL7>%n5VO%QN+_0ziDlxJ(L=HtMqsl#Vcc1#*W%D**<#9L5{4m z3d@3tq#{+qtdwc>`lauVdo^-$@~gEN{%Pitph$OrJev}F-omLW;n0|tuOVB*$H;fL#>iB(=J0Dxn&h!@#6xgoHRKs$c`v#uGG3`E z1(#Q^!stM)t4X;+!-UkPGXqbJ-8tFBtWPu#dd{Ahf&nJTXoN1Et zUd`{%N;9MLsZ`E+J?3Oj-fgB?DWW2MuByj#_5w10J1SjP_>O$Wm%Kl8&G=7F-VE!)t~q;za}d`KW#-t5mQ)u@4`Rze&S7+q4xCjLkV|hm6=nGodfO;VNdFx%=3d1UbJz~8L&u$BpFmp ztXot9L`)0Jz3390zPJ~dwj`S8?fg;!JdYy{-1JPl*pfd}lzuap&P3)Sx$sF+njtqz zQF>xZvZ(hpqxxY+nhtR6LoIdg+-sy)&{GafuB>VaZJ)(Xa2rbo__%7TIb;k!{B^2L z6_oUHcy0Qx@4$nwgVqavXc)sNy_i!YWbBv478+ivR+l_s&JRsvK84nAxlRa(*^r%%vs0dm)sGx&HUQ!T!ACJ zvOa3dE`;}uGFGL@Cn4pxu^R_lTxHZi%m4m$mN3>D7$);G92+c_)<~Zg4yD)e;rX@i z^8>7DTZb)^QL!q_csOx|d16TS5$Z=5SGc7&@~*5c`lJiBANiIRY;g_6Zlb|xwS7b} zwMEAc6F>G$9JWPc^G$J}PU=uw-sMO<_#^R4^?ko7lw43|A7brWG;Jj#;)%~T7rogc zSL}Yri9JG2eQ0R{`r_5ctmo~MQA{)xs1KraM^>(yi0ioh%@EA4FiCQ;Lc+IQaUrJv z1J^((zt+%x4+T|*XWyonf>3cR%DQZfP%=f<2yBtnn1YfvKS5c$v3Y$yf1h_iMW>8< zoK4^=%3eVTucM&g4N_5+sn|)4jJjOs9n|D`_;-AvR{uxM z83C!7rRo@(GXSboM+XL}wgx{}Zv=(Y^-D)nJlnXm8MVhdyfBco+VOVerFErgi$?I; zv0V=>It??;h}SUBi>|@UcqE=7UUHH1aerJD_v4P(&B??cVz3%q6m9Vb;+HYQ{x~|` z^ExKS(m01WD5k|2Voc?s*oYWj8E+`jGlu9xG)9wNL_~Y+OLWHaXd+rG3!5mw8mhfrcx6lh~`TF*pe7n8SN|BJob#kiIXbF#wpk-F0_{7!IgUp zmr%1qEGa%k)2Xp@-Pb8BiLUf2AHDn3*!KtW%FEaNXi8toTXfc!GXSiIVd$kPZ7iGg z|CBUX4`XF!z}sUZebdg^Oh0#FO9QMTZDmlJfCCKj2F9hq=90Fty@mLp!8$O`5P_`? z)`-yt>rUFjAboL){sv%_KB+&3>6HdxknX7`sjp_6;V=zqWu}8Q`RA%r#stVr0_E3_ z&?ZX@SS6DLEW>JD7Gq@&1T5F?*c%}@U#B{0RLV8Pu95S|2VDs{JGzm z=A^-;OSV5cV+6dqWz~MgW1TqoEN9sy{fRg(Wh9kvxHqoENORL!I5%A#mlF?{*K0qo zN&Di+%5gCnTbh_l0I>fvC0wI^Rk!_jrA`2wO>x+Bh~ z{ixCzaVzDymF?2uM2Gsg4Ue0omAKDB>qx(g7VC&VYSV-CTU}P;FN$i2KdV(ux+SGdJg;HNrs60~H-+bbe_`Y?xRlPEnWmN3A#KK(rpEdr|%~v9>vqlNO$xOS_ z%W07!sZcmvABw5^4CAOCSu@>H_)A);i<$qPaZC9OCY}3ObH|p%V7E9Lz2o+ES3vBDlUBNnAS<2K@xI7B~Uj&DSFDznnp)e|$~Fjd5d#;Xtu zZ5y3Li*eeClF>Sd8e?=2y^YDV-)Wo*Q6JB$CYGz#h}{j*o2X0o=3jx!^D8?)p0aWQduIw_q(I!(8@jJU;9=`zx(M#ul4SG=DtBA!ks$G=ENTa(Vh z2w^kgeGR6P_Se^z7-gs}vDC&E5-mnsP8wrltI%e!R&?ru;Dl}qoI^T28HZ+Upi~fl5#c6 zw_f*;7{P%HM>keIMD0(Co7j_@`_c|JrgN(}#Cw$Ak4G(`auZe#^n1yURvIgcXwI3P3183OxrmHJrccT zHxwpdx)4~k>C zr}@NxTjMd%M-uv*lWYIS#w+M&rrprjn4AGHTBFRp|7G+^o7+c5@$Vrs?dvIX&3{>@ zq3Ax*&l>3wFG{4?{)CGpOjW5xTU+ z(uf^fSMU_M2EYifv&(m;Th=( zZx_8VP%jNKCO`vv=I8&WJd>(kjTRZ8o5dwpDaxb(T||e_N*bF30c(^j$Ld@f(4u4k zmT8Yq(WpJWCX)nwNE&3F4^WI&@1mUg#mww`n_fnnI@7UAjknOO7@3r(ke)%6idSUU z(-%?J9xtFxO}ZCrRCz`=P9 z&p5?2iP$auJ+36)O5M}h_?gS%a*Q-vLUp<-?MEC{X^6?hbCrn_m9J!)%(%e45?+l- zRuJc?=)|M$uo^FWTB5R341)M&;6@+oiM99{5%{`>qUjR$Pmf&PzX&O=>PC zE^v7|j;Qx`+)Pt-{a0#lP6N*T)r5=BIJ17-RZl;>_PT>xU!VW-%BI;5HEh2jC7?q& z9V7z+9`!j8P{_>!tA*SnAf806Dh;U47y+?XF`km$XxE}sK?5p^>ANz7|3NgUs77y* zPR2l$_%m8n(%D#*F#^&KB3(f|r5xAGfP!;L2P?-d#GPr=bSl=yEVVf!;9oLEz!9uE zuI-nLZ{*R5({H}<48F0;)<>UwH4P1QBbzTJ+B}+@1wJ2l(RD;+Z2B%y6TgUC@q$UZ z@Nl|B3A1YkMdA+` zvYif-*@`Vtr4K$L4I&=X8TVs=R-2>7%CrsMPuIqEnD5iHCtA#jbI@U?gNb6i`2#p z>r-FtRr)?J-2c*xrRRunNt{M>*w11rtJ0pfC7$w7+(OKYV~rr@_-gd0GBfR{J2A;u z)ex`7E>R&~w1W;RFW5R(6VKS(GU6R0eMZbN)O=#0!7-m$t-nu+iUH;k4F*_E3@}KM zSYenhqF^JviGDhDVu?PgiM6`)z&C7a7;#KG*l5xZ;&eL_r#s0$I5VA*4kMi%H^mLa zwdwryGtxQnSp0#wBVCZLBmLYSaSKM6m#)MMIn{4d<#Y}Y8xr53asX&p)irMPJ z7yk7W|H=kU|B`%i{~0K!9hH&N`Y@n*ppu*8mGjuha!&m%spPd)J36h{ ztV1dPd#jEw{9l*FS>qg^W+@2iqr6f6I0ruFD>AEk(bJV`J{!6EYo+PzK5WyoircW& z4gLODSVQgnLZ<2dtp;1;W)*zc0XRljl4hXd6 z07`4ljL731|D&#F0t`tz#S<9njTk5c00v`7uKmxc|6hvz+X#d6iP{kD@hpa<9V65J zXVm{eUyl7t>tp|p{9mzu+kGkaZ-7sfNdq>-{@t6${=Kg#qxe_jAb;O5@!vx>>93c$ z^4H5^^InuS`un2UAdP6yFP@c6z$c(KHETw-D>b2*U&jas%Np=zbmlkU?N&M*>nzb7 zYt4^;W0j8`Eju4Sh!s9?9I?!M`jK`sBObsqGo6YhX2e6pQZxOGw7Yk0hUMP%2$uW6 z)S89U zHTkVfqA*Lw6!?J2-)^6_ph(m>J0`JJUG;6CXo z+UTa9_+u=N+i+q$Zx7-^1HDOnJC0Z-< zqKas#OpipYsJtCT46TfaYOIWXqMleAAL>qY#d7_KVpKAZiRc%HV@!;V-H3lz&T<*) zlQ=UKsoEj-DqKw6PSK-!4vnSQq4r=(yS2Sln#uEZmmT-`Q_O$p<&)-Kgm-<*1?Z(A z9gM!3b4gAnMH#NQ@uZDXU%O!|L+pdm2KXk%8f>Yo(LWM97@i)(c*DJpucZ;DlXkL$ zc{tKYi}6(>(~t30BT^T3G{W(uuNbZt#~Gp?qYc!A&GlDyJ8ac3dJ6 z%PO5Qg}B~%X%FH%W=X`zbXHtPtW7(nGl&<{#c?ryo{mb#VY~GGxS9HwYX;Wb?S`NC zo=`gDvW88sJ>${p>pp3_b>8JGDzlHQ-|0(?fJ_=tjz_V6qTi{k5rqw=fL(cvU@^ZT z-M|5RCUa!X}QI*cf(iJOThVzSkw^DNXt{MBlGNLOmNjQDK^c%*9-6p3rpLAoL{ z1B)y2#{UA{Ag)i{6i6rM41vF75`pyXJa%V+svKd@CQ2JA*$h3bGZw6{BUZ#>yJ4wM z?Ta?E)3=F8x5r7S_h!o6sbBIP7X56|)8!+%YqMKQZNFl|>hC;P+n4_54t=}OM9m$= zF@8Yh%F1)*Q{J-j71ODVsJv-DmAm4*TCi8j8mvb9tH$!eD|%2kwD>2ZDC}N!lF>NO zc&#j5xp-7;!lTuX{ske#G>=2t0AKTVY zD(|Olq7U(49H5DK%Q)4{izlDJzv@uzfkx|sAEFUMbr2hB(~lg{<2`-m5{7*9Gy+4I;& zon7#eUf#ugL(Ihz8(V-DTUmy+#$^nEv}wiwNJBGiZR(RV0Cvl7oNDt^V9FcCQcmgL zJD(8e+VXs2>^~|0>nHv>0Kk$Q09cYYvYDnoa!C#I^2YSjtl>}Na-d+2PITrI!cJxF zM7!2J4s5x#Xw4WoTGd*E_hmMCtvd6%%38EpYAxC=%uNtK&b7rK#_UTytX6j{_~-FC5%c^j{~A?{<*n6>j_E%r?XCt_jBVzerH2rSRe4~m*p zp;}|CM6KG?U#5v}K)r%Sl*lQtm7M9&`K9{BbcZiD{_7Mm&=N0Umfmj^V+>gcSBEzsarpAL7xKV9f)uN!Ku*e7#gR zZxEjxS^nLsPtawxo3PpGie)03#F6x0(@X~}@#$}E-qx)sEtcuAR9RCz%V z&6Pn4MEA;Y^%zl^9S0&Rt78yqD^;-(v2SHW?1m>Rd$@_h#5m0~s(xO1uK0Tzrp3CN ze^EC!`c!Yh+Qo}UuRe}vPP|~rBX6?uJ9C#WUx04v4MBHJX_PE>?+6Uk%~%Z4Bke#M zskdKXv_5HXY^A@eF~*>DsEoq@chdHT>VdjEXdNVGGBnq6tGp7wK4#p1uaUk3@fy!m(ZXiPqAnPa zdpwR0w5AI&!@9@-fi>wDm}aF-iD_Cqju}=Oh)?o?!Q5DxCg5XBB~}es_?Om0yRImF zLvwnFbc`mqQ>fBlW6~7$J|q2IowrF(tI-R8P-Tqlesl;1DCE~uy6~xN1n^PLAnc(M zGl^T3U4~J*Vlpv9$pm7Bl7+-#ow{%@PRP$o>VN%T0N}st|JSw6`8OU`ecng=zqR*| zw<=vS=E02MJ8fCjLB;)?Fr<&C(vIoRI6v(T#-u;TmBjX`I-P~%{3%0c}|dfNB*rs8vV@ zA`y?tsETz6#dJ7|A|66xuI=h2(n%Pk95?$sHDJvb7y*~b7y+3_KqZ||I#^fSOx%$( z0@lT>xR-Q_N;;ajGJC$OE@_R$H!>DS(PI{WRo^~94OqT!mlUp6P=1sBITwEv)V zf80g+iOS~bP@>Kg@n_;ikLA7vpQVk7yRC{hNfWhOLHt3*64Ip#s&Hibvj_K{`L`$9cVyGKn=h`K$@bUoaz*t3dhFNt#dHzH zPm0}aL-7Wm+J@-#p1`g;l!=Sd*OhR--E|UY+F3iL(H)bV#+$XTpI(~4ix0e{^HwUm z+ScEQ*XiLb;5#4CE4MdfYX%p^Y47}JRd)tgSVYcP|j zR&OEECwo8QB|S7?yDTLkx*KE=v1uBmjaZs%=6|8LZrCrrXDI0q$Jmm%D4pi3q*LNI z_Vxd7GDCK^kE#@?6p{X%~>$RoHMh}zOVbb-j}!~-JpSV zm4Bo&iND#?%cR@98ZY3n_#oYe-P7Hk!t0jD)7V#C`Uj?IkN5DU%5*icM30z5Iz|6< zt}Ftu3Y#(kAU3Dn)RA_#cg_G9oDaZ#a{-`pK8RQ5gMU$O@o)bh5rC9C0MgfBf7!sF zZ~oJvIg~D^^`|vj5mqXo&5CUEo)7#5y>bRXC#`5vl0g{L_W58xO9L9LR*!`$)yjta zO{mL$&(zvzJ?hlhfLilo1M1AQ5%uwrP1xeo3<{Md+JdcSp9J{P#ok*J}SV>wo01uJ)Mh zYqfuAY@Ypp^M6_YGwOe}y|>l=#kTDKHtT<*%=(|z{wF`=YOT3h147 z&LaSi#0ThUM+c&N8leQ;-K!K`4T^VVmj1(J)cX=t>Y)^6I>no^jr0*nl47*ywC$~G zeFKfwDxludcn0g^%QyjR(j~rymGOxdEKe83Ls%TI`yLjii{pOuGRhCpGZ*@M7#8D+ znI86Y(t(D=z3AZ~=cA_)x)HNI5)Y6LwX@%$pJ(Dx^f%U(Sm@Pw8bjkv*J63P#fw-O zpE?k0)6FJfW6bj=YSVSz!4|9H12oy>Z)nvRpUE5}e+OZvY>%2b0xJH8AW){WyZLC3 zmgQ?Zv36FWyy-~}e!T3jeV5Yp;-YIx&!A*dx-uO~;d)IjqVR^bu`AJ6idCrcV{1rP z8DKYx&a*>0i=z9CN$->H(NQh&M=jBx*%RBAYzp35)|zT=r0rVI7w$mPk_}|)%*T2w z;tQh2m#)VQQ+$TmCiwtUbc=V1x8rta;D)HwnRqgm#FIF~@%F>IxYZ2e>ZmrH*fCPv zK=h5((Uqu*+UP*E#rjA@L(Gc;u{J)87GiZ_aMu#&Tk{#`G?_YqXTq#TJWXga15q zP?X+4nPir1^PklQwwsF9+=0`i%!jB~8t)UE6s3EyI&TS>raj$;C0f0Kg;`sGFEyBi zS+>{%Gu4|&OjTzjrrH#fFgNPc_pwZ^QY^JDCdeEC$5G$1`u(P>;$NxG;jZ+pv_NIL z1aGNKy-4Ggr&+jMnKxt*&UK`Nm88L#swnm$^-yFz@q_$%&6W*ft&G92R~`wNAbd$Y zsNEFYqAlJgexF5KV}VwU#KGF_L^@Zoaiy>QR{;3$@qgmjebSw?OMkcFgwM;)?caLs zKF6z11Nrn9CEqSS+gbgN^t!QWJaMfnBx0RA<3=2yAzenCmVOu46CK;yCE{&=PRHPE zD`Nt-i$CVjzcVFV?iq==G+wq57yGHL#Kn0u;3989TpWYI#f2LsqQ=IUOx&3+NQV&L zYwrqnN|(o9kkVo4LL#N1d41s1>8C_f`f1!t)otY`ls#t7#fKbs*#!r7`1Q>VFLt?g zKvTDx;*GWQPpjPY|KSJ_uOb^AvMg;Cn!kbqW=dvRenHOOlqiwXNhnc>*Z5krpg4UW zvkzR=W(hIs~p#93!Y4e#&2j|({f?lOWQ#)%*jM3wGorzRqL^%)u}%o zP#;q;K(i|RHLFXBoAN~TAF?;BxF+vqzgS1jq+jF%=M9-KkiMr%JLYD>LENKM33kgJ z7=yLxLh7bD)5_K9Pt;nUhG3DJxC&D(OuJ%!x-Nc!<)+%7SQ8(nLs93=IErXWcN)js zIdh+Dy__!>&wZqA80Du{400XY^{c+T_)hw)=yzJtar9i@=k%fyDrR@sElpzmzt%lp z7{%&wYsM9xV#WNGceWqQ`i&c2Eet1)wU_IO)2xmciO2(t-Fej)-CkZyTzOc*3CALQJ&1jTBxnBvuj68tfY475!ong*WtaJMm!# zy^7~_j9J8DRbC<1Wf{I`%ar`6RB1I)ovO4E>*6Whh-rE$$2U~lNGwyOnb?rmz#U{~ z-AKnc*#P3obh^=`)8jh(5`Rm-OGlF~i~GDm{4;imw@J6AC+vvvF(F+|JeHm^nRJKQ zF@bm^R-~s%*QPs7AU@9^JLw{oKEtBa(;TdHrX^UPF#y!1(Xk#4_D{!RtKBrB)$n}t z-9I15yXPDEOaLfSl5d9FbL;=sd^5h~f6@N8LH@Fhf8lEz|IB{5GKbOapJ(=$=8(Td z+R&0aownw5;U)!JuvUY5)TiG0p#PC}2r46dk(H33MT|sa3$_M1_bc}z< z=+#4sfpMA?df4qfiO)&!1_W7#z@9P4<*J}UN$!h;H2Eb~2 zkhZgjCVataKD=)A?Gk-WRrFjnCq7tnj`Qu+*D* zz3Hg<7cn=Ub1G?{bY6@{w|LZ#(K%fLI>-I-Ps~Wa_e*q*`?9~6{_G<3hzIn>taOtH zv0XfF2>PenJVwlq7wtkiI^E$pj4(M~#0nqz3s(C)Cg%U&+fiq6ypw-Fcc3ZV;A6Bz zy(wr*SNc5rg^o-BC~`MSRQOWnjF>BHF<5|dU1K4O&u%-remslcZy#7ck6jm(cIn>0 zj#VWGcO6adON+Wzyh6!d4o}acw=U^+bk{{KM(C`T7_2G&MExzXzGXi)%!*sKp3Sst z+m2cHD$8~$yi%Vz->Q9yGL;sipvfZC+h{JaL5(?>8}rP>VmHU<_%bFspP1@!@8GZA zF$oiFQGp@xkp|*{D2_*Ph`nM5yc@@50$3F3j!v;QTUeTn^pgV7^~>Q3>2g_nx1qimn}SMj5C zye|%{*qx$(#)Nbw3!a}4UmV28zxmtKle^J=MQfKr7H#R^aCFhpNc7e@?TWs-`aZVT z)j_1edO8xLQ*Xy&w1Mf{*iE(Dv3H)`-`Ak@3mj&M=W(!M#^BI2JpB%b+at}w;dV&# zaJUg_aF`L+;t<1aA{}Iy&G>%q2pDU74WvENKwI!_`)H8025gWy0BU8?gc@w8bJ_KfRMe}lrnQk6O`(J>y;|U-b&I)tWn|tj8d#8sh?sUiH?f0 z)un<0u{raYr6sa^^M%5C{8_uGwps0ijDK9{|W&AJ^oJ} zySLo=?b6?_d|_(+FLrCacK;@I2J+r7OTJrjytDevEAN~;0&Yv=B=k+U#*H{!Te=J< zxj3#vN&D7(gYl2`dXl@fI<_dFc?G=pu;SJsY{<~?8eC-Iddpe<#Y$doezwg~~5nYcuxZ~9L= zKw0hxD3Mg4TuLXPSc(b$BLuW7PN$+MgZb&8Oc}b0bgs-1@K2c|;Bsu%E_VdPjl}EP z(-krypi0IFxR-d;i1bU+PqoKW#NS0a0}To)sJo%jn){e=z>GYh`(4Iml6-Rp5js3k17m|9})YQWD7p|)BHZ6G%^Jam2ZB&94R;j@3 zSgI4=F;_QYg6VqWfporp#Cs+ih(EGbA*IhTcpF1o9&GYj`v z^uE6nXQgX25q-_JftZ>u@(-fm<`7v2QdG&l_qJg=Y+k)x;yJEhipR9g8SDrk}aQBYMRg;@Q;AObV~W z@R&wCs?t>A6Xh|5n5EQ{#4;s5Cu)_MPEa0miLNSbAO`1bh9=!pAEKMSx)Vz@Ys8s( z9b$uSI^ZbB=!Xx|_l>~!eHq1I_aXgUm5Xmx?O+Ljb2Q@#*;C@bmFn}T1+E7 zYO1Mt)xz`$PPEpW_(XHOg_(-eqnNLg8Ca%|`B-g`W!PW`t5Ihco6ukno6&5qTmTrE z3jo{ao8g{01E5oOrAs*^FGX(sZ_YFR4f)1@8%=*(z2CarL9nf^?>`W~ghFoNZqq{x z(UNwy6-{Pqz$IuCDs_>aS6G4-G1p6|NzYq^8j~%;ig?jN zVuj}|!K(C+Jj?lNtibwsEq{LAu>pTc7puos(>0>tO9d3ia;3!e)~iHkjk=?U&=-Ak zYi)l$(lF9s+Zl;rX^@>T!VYl@M%l?2jJ8YKi+CxXvnO^k#@?jSX%EY=UQ4_#yD=Pt zMxAnILodz5QpMUygLKLTir#rNpqF(ri*_X{v-(k$Ds?~yr8?S1#?I*RnedZ20;+7W z6xBL=4gJ&Dm_({JB?jOuMKJ)?;w#JlVE?K8`%m^ixBjQG@iwX2>#-w#pw4JiXV(9; z&HBHs_OGv1@h-N@YybLrCeQw7LQx;{w4=A3;yu~Me?L;sG!h(Zd~P`%6z|A3{s+ox z??MK-E1=AEF$okoShn$QLURUnwIy8?&tOw5bQsp8b8Wy1AH;)L?6o)oUG;YiF)JRi zjI>+&ar_nHRwts>Pb@}rIxDUx-i%9~M%pX=GOk8_oEJaE=JazvCtiu)#??45o$W%> zp>a)Iht_ni-x06HZE+*%=yZvzQ4x2??dX)QaTD>0@iCrsY~1Uws809CW5k#7w7-*% zOXEF{rQUW7R+?fG)|(TPQEQ>`GN;W)XtvQ5Su4SG6tv}nKysff8Zj4TI$9ue1T4fw zk_VaogVvKa9LDsWTYt3STFP%sjYUV(Z%pw=Rrk|z=k(o@iIkt0-Y)(=YsVM9*EpTk zx5mK6U1|MeENXuOW$7j7;~pzKMtaIH-$p34R0a*s&du(|7PaPMqZQ_2l{sc%iBEiv zIfnTJQ{!!nxB$gK@M?JAHo`^b}6puwc<`o`s9+t#CT8Nb~N%rrj>57938{%Hlm(jyAieHa! z7ClS(?lG`r0G%Q(s5p!`tuQqXWJ>uxN4rK3a9T_?NAHvW5| zT6YJazn%^!4bwLrhn-VDmtt1~(+{zmfo{RRhNSaI`x)W^9B!BqINXSI1&&HPqz_0( z+tsHy*+{c-jFA?Qjy5t~hwr8x(rO%LpR^7K8=f{|KSR@c>}^o0!|n!Ji(T@;f22N^ zlZNSS6%NtOJoHK3EJ9DibwDSTX$N#r;bR$0axIFL=q`%@bdm|68PlquNY)0>E^`2E zm2Li;&>@eGm1e#5igO{LpwW7?sI&%+8mvG^C9xb8QJ*FdOHw9`6)E^qc76TgYrjWx zl$(b7yd_|hA|GL$h!3$!A^i=@w0awhw0INqvbKPjtHFMlX^S^7H8$G~pR0{GG1IDa zE^2Jn88zx-5>cZ*ort;Wb!E$(b&oYYTh!YrZgW4zDKP-UbC~z&e9#}HSXV4ntO`A3 zZDE~q22IK%79r*D@0rltn4dT2Y=8|ScOGW$y=B5CEEG0io=}S~Gso<<o2s-;?+(>*gMYunYiQhZ{GDYjvBt}s&k&AYIx--oob0| z)5y4l=0jU9a4+q5#CGv*Ss(H$ydD$M5aMYo;!V;yYIP;<(x`&;>&!A9H)P*KI#;nq ztW{<+@h27PNZ-xqtEOhkc3hd$ZpSKCPuek~+v=ack#*FRQ_!;kZN0V1h<*>Q+l0-D=uM_F0^ez2} z8{-P2NIy#F*qeAT9!iIyd%7lOlCCt#E_gBCwupE+yBPM1U*7T6I z@hPTgN^fGeVjp3FPB9b9^htjsRv8q_NIys;(*>w^O1|mdCIF1pgx0iECIFYOHbCJ&lXIvCu*XV1>2mAYyX7XC-O(bf67r(kb3Wt3GKT zgzXh%16VP!LX|So5IvNm!hl$d4*I4cvflTO=%8Z`tLtDZTC<3Sb}45AWE-(+>r6y- z-v8ZSd1m!b`^pCYk8|t4coWqMF&O>RK8BI{d&dg=NKpVj{L z%WMC(OMBYl|84#6o5t81eLR&50AFfHpR|(#dU+t;Lw6(WCxiS2y4qf#Q|cF!(a}u~ zkZr8nWVHURNFrWEn|G1 zdjJ)BSjN_8{HlF_mh9O6-TE`|xDv->hje>9f`=_}9?te}EAxN9LMCXfB$euH1-hwJ zgQV1Q8O(4QwraA3*kV&G#Db`CEmphTY%KSwX_%)+Ou=;XoQjV$=!Q3a921F`Vtnk5 z1D$LrX2j9@qIV3^2^CRLjON&=1?ysk1}rViw24>{kJw1ej+bLS@kQaus3qpay|I~C z6i-D9zFT-LDsXsVu7mMWyqxZ#WMpAsdX|djxT*B#R6XIe^8IPuw{d>UMBe`KRrgGI zixrDMnX%+tJnsZAp}VSd7`mvkvn=C327Pr)`;i9d;cQur-*Fh7`nn9G4e$euF~Az^ zZE*S}=>UU0h(irezrhhkqzO1Gjr2O{7`yoh$J;U8jBgl~z94f?-C%$aY`k4p0JRL*)IIHW3>G6As!U54u zB2CDJfc>)iKyPIhTIaW z?bOEUYaU`^@9C@VI+G)B*=_mHCsI{faeL>3@tCH#n5I`+Pxlz@*A#ljkBE*Y*nkhc zl1AedHSrpH#zu>X`!wiC`l)86#LYRh>qpszH+?^E5%`^A>+nq_v6^(Kk~A4(B`b)_ z6x3i~USrca-@s94H8xpm4X#yVJ*HY@Bi=AH>WHV(x6%)BPrR>*7@tKET$VW#;z1{A z!*{YewOE@@(oQUhn+5SiIzcwn>0tH5-1vq~#8hKr zJ@LLht)cLG+EoqluNWOmiRTQpkit_2#603&ea(?=_`jg=pzdZ8_vsd&6OZT;pHp}; zb@W@}q39c*5ffFqfx^2g;&Wn}3ilB&=9&NXs>~r;b%_N;r_|jtVqn~)nFs@P#$rSD zCf4K||IG&IfOb`SVVp6B5ZA;9X*beOoF5a32cn}=(q-uegYdG~)0xDi_}ChJGreRT z=^?Z8*X4^?M@+KND$iZ zdCaGn6?gdrv(xpiz?`_lRAO#ACqBo5xYukf%_zlIYPA&W?Q50)K%rWMW+W97Dy&w8 zuA22kU!^$-zq{cWW}ux(qwSdX#IE)-4tu5p;vC`@hdB)U#`)<8(m2OE24AM*Bg=X& zb`{jbZY|6kSqy_z!s)@9j$t8Kw{#%Pk!{+rRqqIeH|>}o7}o2eB& zjZ%=&{oB#a(0B)33@}cX@oyzn=%yKms*H*G&+)#pP25H_#Rlt8mwp~kV7*x}7At&U zEV}4#4lyU5j5|mp(y=j4CJ^k8_3;NEV1*;&4_KIvio=Mf<0MC82S>(vq>*V{oPg2s zU8mqvqv9-#F(iIW++llXVqEGUzakwRS2`PW()RH?;_vZSzaSlJR9uTy#<+-h!2I|d z>6CP&D^Q3R;~&I}D%?amE&bX9=;_sXfcPk0aX;yM>7O3O2+zec*fC!5C~DH9Uc(xn zn=ET9$e^e-@d@b&8$FB`4Q3z|;tM3j9+U|y^JNi%g|ha5#ps}$r7|I4xlEXSpImHs$LJ7cL?PBk8?iAqMFUY2 zDQby%Q5)-UVXTNXm>%<7jhQjSMq*Zcrk+?^_)Ia5jJF&>`cABM4JEt8@S;UjJnn?j zIaJ*l&zIdmhYC>+2+pNd3Hzo%5f&-3&~>#6E_)gEY=iU2udQ(w}jdk)FlTX_UiB->^%%O*Z&{ zisOw=pOcO=+H9PhMw^Fk*uz57kw&JQaH0`u5e_soEhmjjgVSOhXID$HtNy;kj`~?Z z8ljJwI6zM`P@TG_L(t0zQ)D%OZKzUVGb)s)!7^bgYn{rV$puMw^4B(V0944V|0OaO zKoRP4A)qvG^+}p@CQOqpXjP#W4f*w>gU!~-qOGfCgY7cVW(guPCrV2W2W(7bW@B^S zwy{aEsaU5FpJ0_px67>m?_+7Sc#l}9**o}Dqqi}~3UA9C0DEJmx_BEi)TaxGr5fT5 z%(6Ls7prX63#)92cd^=*^j$ii)~C9%TdeYDEF{J&_yBt;=!$)_2t#~GEW#YgW90W3 zVuNgx-z66Wk`U;SI|F)T_xq_@kxsZy%4=+s?XXIbfjC4_8isy~Y({TIM&V9S`(FXz zzsLVCjxlfC@j>b3OIJ)yzr4Qny0M4mLcrrCXB3~}tp49CA7zg;9@peht&ZuYxDnq^ z9nxhOn@0mSX8qYQEM1z8#`g;?F%e~jKR{fTQ9NA~FF;)2C7X!z6>P=7b4S3h@hc;H|p3$=a*Gh+_w8m18&>W)TQRb4Ndb;t*jFwo=2;86)2ESl-)3F69THRm2|x9 z!kIe);tgMI;FU?UKf6YaU^uX}`JqSdRM1ZlC`A z-&9ppzTWu&;_=ie&ZqggmeY)2y8JoXfcGS8h|91N`v^4{o%O7y?X{>uH;q=H$mUpy&DNz`u{>7f^!r8D z;~j%-#9#GON8Fc=w;4Cb>l%rBbGqxf=>*Nh1Mx3giDT4c6yJ25B4T7*X)Dn;eM2+R zA%3ZeXv+uxP3d6o63gRwwZt6z+Cbqmd&L^!y|kN^!~?NYEF)er!VMIjH6Ruc_vz;k z#G`t~910KV>Js8UU1B=%s7@}X@M!83Q;CP8hpEI1I>hG`-cVsG@r=rtM$A%a2C+;B zUl4UV#$2LEXETW&x?4?b(pL~241idkhH1v>u{g7CYS9Trskgp3(Xoc()VRVJY)u!W zgGj&jM9d~8#+!D-zVV5Tq+8SD8u77(n(?U`ZN!Ia(sRvDDPIgj{C0N8*AyQ@c2+9!wI9g+`zV{-W44tXZL8YEqF4Zl49c-x=m z{PE3SS^u+R+va~|{&}_y{{1o{ecDSi8qKs>7S*Z6Mzz*rt)*(PQV&b9OnJ=1LT#pC zZfrCSv(w$C5Yx@_0bcc9yob-cWIU$CBPQe1bfve5&*G|h8&lK8-o;gMy`N!r`i?1> z7k8NkX^U%A1i$~O?s2IQ(=1y(I<_r|$KJOT{qpNhe@z(-Fzpi~Wwn1to_Ta|EGE>-bG(4 zV_WUto;bzRwxVy=0+ITpo#S16%R|P>1b}wg#=jL^4bq0rseinMDmT~{mAcp>8%*m# z#LH;QZ=4&gwG#CfS&B8LnU7`hUfhSWbfi6G+22ozsd1+dF~GREHhUgoEarLEI1GqW z9f*PUkMps8IxvnT?u{cIhaHTJv#_JVaVlw7)lSEr`oz!hvEF`6{6(+0jC8m@aVF;K zZ#D6EL;Vsbr_u3O()Z#8ml6-#82=>wFdgSARK~j=CMFmhkCD!>tGlp$y3SL?6z{~- zq#wmwp2jZeMK2OdO^(+{r^IKT#`^S#_fT)Sk7cbJ8N{s73>36{9>0m1u@y(gEaOPK$I3LDlB=S7 zQCBMWbyDfsRHnG4>=EjZS^y6WUe^wz~# z^wB*XOd6zzGcigA^&6SCOW%`i{C|%z2Bvecr@?L|?QdxMt<2*8pltAe4o9X@>1NV5 z?UE+rcsqFyC)nAiq!Wxb9VZ%{?!>W1r!Psz*fGt+iAI_)V*$*^L9#mGaR!-#Jq=2~ z#%`+9bkb^BmCqIDZ7BsUxnrs+XVYXYGnF}GpiEJ$L#d+loXqlHgU}L- z(V{s$foA3Ap&{1CY}6`F_hN%0pUXD?pJAnT9}~;9#)nvJWx7rl`Fj_0bNc@rTjCwe zvL#)DnKs*pn5o`-n5n@)thOcI&d%vntkIx9)@q24>3rgLt(EKD6=SvdBaYS@{~&&% zU4Q&hAr|2W3O+=YFdI{drC1_4n5ai1&dwvP-BC%}tVkCeDCvg-6&ZrQitU5}iqqbt z3luw$RHGyvj@e3FAr}A&r~Fp{`0w%mt7A;uabW4?%OCoz?pMdQUbo|{ObD3MTzY27 zcb!>%hEvj*G#;0yVW5Y<#tp}zY;wbOLt;7(g zrwfSBwa1^aL%J@VN&F^_jPX=eRUBIOnC1&BPB{1cK9v{VQuX|-+n#Eev7%|i?ekBq z+C?bDzkS^ikkYr1^3A^7F;J3Q_e;oG0j2S#OerjvU0_c@vMo}OIRe@gDMPW6^h0z| z9*?8j*BAlgWgGvmI|4q`zaTDZ{h3E;JEu?{rxRVZru~VJU@g_GnYcQK(j21II?{gHti%|tmSLo=v5@#x{)W@lW^+(% zlP^$jt=XtaV`DbXw=i9fcO$b-Kd;0WxI~Ll#I0#B1Mu5;A?6Ujw;=6=@25R95ud~z zx)N8}T@f+LuC@^U>|zyBWhe8Af|2nBvB?f*6E%jLMa(fIW)L@}?L9!eA7f0T@P>Xd zjW|pnQ;EOo6`v7L>)|sBkLeauh`V()g~DSx##G|))XC?>!;uLB&u7;E!USDnHt~|~ z<`Pr%h(*MFeJmr^q<&TrTjSK&KvWuL3o+1++K4W8S4M2MTt}i+vEC?4#~4g>Q)V>! z#p!8pe9u|&2hOs+UvgV>-tCKHT67zJ}iG?~_hh=)^BCOOuW@Ei!R${Z!)?-`zBlCY{Y56~-%={l-4*o}$ga1LL z1-!^mi$UdAc+s~U(Ydtx{~rv1*iruVZY&4?HRa&HvV_bHv=@6cm4kn=q1h4pmxKRI z>#@#zR$!H$`3{yvjfGh3o??Sd{N9 zzT~O88irB27=;P;7>k3|m_i(p4L*ot4UQAZsmA3eaB3du)5IA*6z5`IoR?oEZ_ls9 zxy0!))lzJ*D{sfP{BwMqd^*n8LL8~N*l6d{I2qM(YH6!%)0teZPCYSNW3E8g7!YI8 z)xNqbHslTHra=Sx2oIuJSHsaS4zw>ZzykZ?3u>}x1&ADufhvY$U>s7`{>`uo1GSc$ z{}$^MwSUw7&)UCDwkm1{3;5qbrS*Tc7WA_`XQ0`E4nkjxw4slA<>r4(PDd}p9ZK|! zAzE>wTeK(!#~r9s|9-p7LFg#^y<2mm71$Q%=I^oIyqribH^T&CxK3U`n=Qs;bKa8Q z#c~sKJo?6dISx%mn?U|Hk8uEc8IhkulYx0K`s$h=B>H8$V-zffQ!v!>JP{+kG6xakvPi|3o$!a=h@^p;~CqC&k5&~KhT)J zCoYP8^+882bT#>V-SR==ONN#M;zU2ijJP)cj!|a&D8}bZf5F@FkZD+DdOnGb`I-l? zE$;FHTCC2wXtULyQL#ti1jteds4d}u^@RWs-MvTD`*$lbUZYj$S`PSK%Yna}CTr1M zA8X5>uO+$`f~;Pp0MJRjRS4C&3hlyA6$9IHwAt=m?6%%J3YN%XZ1hGJHL2VE8{W3v zK3HISzDUl=|IYC^#07?6r6)C_A|$r^2Dwy?|NVy zyRYBUx#hnU&YcgUn{F{dk?k)w{)IDWpx#a(2I=EN7_Xm?VPy1=b1}gX-@-&gVk!={ zuOAT~Fg&isNk+RD#~9-PoD}=VkBO6vi^p)L@t(u!#>Fp*4;dS?ai$6JD$a;;@j7vu zN#^4WW4xho5_}Iw86IyChsUs3fMbpI3Jx^LYZz;QmoQ4R7copzd>8|x(R4H$1{w=U zvhF&^f#{;MY3Ni!Dy!ATKA_5d3I{+F_DYSEua&!t1ywYb&eIxuRU_;v88)pt6wZ=X z#pb_&4A$7LSX^yJHP$PfC}9cC&7fRYK2&tjcj@tNXmuwvk!h4otEGuWubD0*Dh z@7nrjtACfvwB|Xk%vO6`LJqPguOm;??q}pwdo9BODqbR|saSw{fW# zetpNQbN};_o7!*s;D$m7c>e9$&sTra1p`O;h)HoLd6}+)TpPFN_i<75jVm$6m3b4^ zS*?m3A6LaGIJ454kD|l%B@>_-@(R-grumf3-F^96JMoxV)>0W^p&&1*oF~bb zy_3(PI{qV$ArI^rE6CFoj)48*3voHBqdKoA2PvWf-;RO#OS&}I-B9-jKm5v7AD(pi z3ti8-W$J@#Z@p;uYb!=SL=N@!pK z9HILC)&QPFikzlUdV8QwU3|_z9RW=xBcNTlOR@3)DKSyT4GKrVe|eu75U0!hIr*0u z5nn}XE_vS(@E8Bn5s^6u0NZ{zy9ilx6kFo9S8pHqG@zn(q(>+NvMzR`9*e* zY5S~Su=kUd>O7mQ^;eU~XZ_7Q;xO;%i>=n`M&53tDjb_zwGl_!k=yaCmY7CfRl;W` zX!RaODEepz+GR1(!!`?1WurIA9oBghtE{j93-mT0lj9%@a7E6^1>`+i<7`}MtHI>A zVzQpNEdP*ik=J-N_90JmpavXnf+}*H33ibKjm_0$H~U#kR$`1fQ0Ifu$0HRLPgucDi;lJ{j{{eM6Y zSCNnCk@LyF>FL|#i+be}a=t!(L1npqR+3wb%yfpK=90aPvKym}m*l{htd4BS$Mhsa zkLZtj+w-$zlOD!nk^_o*zqmS1ChyLl=F7yl;x?1;j2X6Ijs-f%`BtdI;@qkSaewaC zh^5)C7Z$}@-SMXO=u8&dHZeoz+>T{>#X_ty&@!x#;Z|Xbd#uM!6KuuqGV{MD4lV2c zKCTUwQUJ&}q_pgh1>rbQrB`YD?_65`m5+Cq&nwb-S<>S>Vof0^#4;7MSz-@bHE6+3 zRkmZR7Mrj!_O%9UJgnFx^t1$vReK$8Xz?OB*E-WN-Qs)#GrZ&vc-o^LAfL$J=AZFc zobNFFD8J<@JQAPH-;t6KLj`{gr9e7*2kyveezK3lk zjIhIMd(qhn_2_Mnei))h48eXn=Oj$F(|(Eo!~x_v*)=|ZQ}xSF6KBR~AHt0MdVGq! zK2FPvh=0rL<6^9csd)|g>%74iiL>L{_yYFmnfHLl;v90Ok!pzJOw8%1w?0loce^#9 zhZ>Doqo-bI&_CCp!M-sT4ffF+Et>5_A8q*{`s!{J`s-#S2E^pz>~-%bESk$OP?ijU zjvPr0ibIUTIiC6dt^J#hfi~qt46wmOMfQIe`g@=h0NyPk0C7NR{r{bV6tKTNXo^wx zpwS)q92yMCgVEgpQxqHK;>Nt*W`wGohP`&>M6}poyaIOjmcr`(8an6G`EzoiDLIPh z8~f)-tnf^^X&q<;y5{+%<+@j1s@QCgLA}Nthpw6&SpNPGBEMx@})eAm>B0e0*9EAQ_0VJK99!9o_8$qVY8h?UgnMbDbDeZj}jNykoS_;Yg0{J zs;i60+YHWW#8=}0#fEsj>&XXlqo;_=9g$Cw&spV8;u1?ejmdGBr?AAFd>kuG_ZZfj zYX-JjoG)XS)gDBMibgLQN0{A_-`b{uOz^xDfS?&hfv{O9zC{zBU~ zc6Dxh1YL?=c0F`8R+-pF$*6xK4#;L80Qt@3=_SA z)9n}kjg##cui#jt;x*#v7-D@&HqcpSOYwTgY`8GLp5o{K#g&-!r|~B z(MvZwF|4TR)m3NXP^Z?Ls8Lf&1qIZuLzQMlq^q~W0nk&C{qIKZ)UGqpUAtOzXjg?j zTJ6OyEhSvJMhj|6t9q4qpMJkVG5D`Ro84BTMV)uCQ?(@u%l|@hldwSHo12GMRJ@|d z{Ld-QaTAtm&4Te$(Es1ED_>FV7>f$!>G7 z*>2-$IBCGq^@rC!kyE^q6J1&Pqg}bGxDU+J$VPjzH%_uQJIE__MmW<)54aXf)nBjZV=`as077+xh8#2>|~( z{;xhJf9J>JYOi|tYjY32^xxZWnslvghB4>bnlIIS)&+zAqw~ozF@A{)qk!{`a$SC( zyxco+B{?L%mN$}bbrgRK2l~%AnLO2we2iRDxdB{Zji55l!vdG&G@Ho-r9I@uMKr+0 zUKHe)v$*j;rSda@?Y@%Fl2`b79F6_U_xWh_%Uj3;d^xTlpRg;hN6+|rT!0%5$Y0WB zV5e`^R{Z9&ZXeoj>fgE?dg~t_eB{bNH6ws-fe>wtmDV`r6MOE1zP*J58)vDvO=&CM%kH)gLaDcsWg+d6p9b=&+d4=g@gMJo~ zp^sO{9r~C-uF;e;$hV`HKasEJ!Ja3-tubek&*^Cvl_$$==B;|Vo_t7S&ZY8zUaltZ zj)r`ld?5RHlgdMS=3;W1o|chMYtB{VtO6QXdClnDO1^EZ-Q?0ZP?CG{%9Y$N8W2V@ss#fT#Y9^ZXNle zx!Uk%F4u+ldv4Nz6|qWxtPlobrA`Lm-6Fe~OLfZm#OvzRV2#>%4;xghCAZpTBXOrS zWpDgJc43c0O32^gCFTE;($aWLCNhqRv%zttm4940;14c`{ods!U@>)stZ?zelthW|xEh?@44Y>qw7cC6tXRDXVIq_Z3V0tdhzvG#h z>JNA%r}-T@Ek2$1;C4G)i@)ZTeop>5PR^fUT%PV@_>x2O=Zf}-Rp_nJ1T^Z%zuM{j@`mzQef7qPxzr%y zLpB;sek%9IWPDclyn^^#4vW*tuQ@2M!uffsFA(SXYOfgu_deb#?PA3aJTpj+;T zW_3oRe@rrp=Y^5|sDs;J zLH-8a&B~ET?S^5SrG{d;XY4~ho&RAlI_Z{|qeg8G0+k$8ye=QBOFW7!(KRFbp>*_aTqVs|>-xacS;@qw;#gh~wiM`Cam?{DUz# zBaY1R#98q(Q^-pSh-6$))~Q})rzEb@?o;G-m_b~lA!ng`jCKg_mA-=^_IinY!j61_ z_)2axi+t64{zzP8ff-m9Pvp~BXSOG?H5YmjyW(E2qTQA(gn&K%hHB|;bXJ@1CbkoB+)ahy+(q8MZN42#G6{{42 zL=g?xYB^eSwPo1u;F1ZjG8dAobD?AL3BOf`MeeghA#nD=3Qg)zvBh3&^rEd;miJkU zdD&_?X6Hw}OFoygyp7-HvAG=8j@60ikw5Y*HJ!7eY6kT)a(LYj=<-G$Ui%_zI_3>p zxs)fmU++^=RR91W07*naR5|=_N3-|qdq(U%30-w{IJ)WPK=jrS2ct<(A0h_o?L_RS zPn?Mn`nedRV?dmz*!W+A0}P3;5{KB=tvJ;1_zq4r+TA$Hn7EBNF~-LIIMH|y<1FLj z*EqGX{>N!1#B`ixqL*-5Oz;A6y5j!zG-Kl>oMepI3d{dX#L-bimX5N2d=rNl63^oR zgFH=)HPHSzP~Z48hUpVU3rVB<(OkCF^wuqoK!bXJLN{H@&spaft8kv&tAPIvLnsTIOL?6s9AbNILYqC- zW4BtX$em?N#WvNs44Xy#RIxdK3u{%(S7iR@DX9Oi;$3^p#S*P%V~O2fCKqY(GG4O7 z%L*qz;gs8H4%U~BfsI=7#o`?2VUt$lutjUmBe!ae3+Q=K^Ec~1UALt&(Y8$TTsv|A zm1RZCKz>52L&@RV>`Q*uUR7kZirF|-Iuge#+z{PMNAARbZvO30T%pDwVzZj~DA8Ld zKg1fH;#2!;ZXBkYC9G zg1oe3@!wxC0z6Ydk@BegjUD*Amj#t!-W23%`3pflS82{?vD;N~G&v%QHiFY!m$%^H zxI8XLQ}oK~P#c%W1>}!nY~D%z#7;k~t@7d(ThBax>O1w<|L~RvF2D8Bmane-(#9Vy z{kj8@5?kn=@+Mk}?{=U&tR9f<;Uv zr?Ks)Est+Fh9^#a<<{Gu=Y(F9?zrelx<1flUH7ZWS7VqZ?0&87guIiE!z#OC3YAU% zn*Tx0@Fz1cJm1on*v~TEG1rQy#u%&fH{?y$*^0r|S&L@ty@!T4s)R7Dwg7Ebn}^N$ zZmBSLHydxL@dD1*W)}I7twx~MhvT(U*)e2!eNmxsxB^)iE8pfP7qnXAzqa~r zwEo{2jb0=Fo=y1%mA~uhE%E`qaw++YChw6m4an70UNOuj@-?G#2f5M&?c`3ARgqN= z%X+fO(Rz^`PEm_4akhGLL{2xHJj<8kD0GU$^BlbCYjFXNi|g{II4{P;cZu&+_4XpZ z9H-?%%=HNyv9Q?ud&&w8SZsX^z`9Z)-=M1z*r3q}a*cZX5bx-wKUS748S6AiY}UzU z1+-`rcG_HK{HJ6K_P8VN#9oJ&mi&Kg{Esfv?5BZn*!u!NG3f78Zv1#oZye}EoD;+HHuBGT zgn8(sJ%^$u*Xf53#uqh^ujU+e=oLTCzhEDOIYV<8PL&Gc6BynJjQ$rq6 zb)tG4VUS)puJV=vIHT%%Bk=Lu?EvE6b@4&+%Q?s?xWs`zL0pODPPrcj#1x}3&>Sl;u#6%NEH_^RwdAwJ00-w- zeAYJO{ImA2e@w9rpY*Wp=x=4g0Eh!kMn8YD8_nL-O7w}bcH;~`FRlN>OjZa0JJ3yE zThU1m8&IvTtfAeTW6^4>(TWWFJZ$iWVW`RJhG3Tw`4gsHoB-SA%LB|hwi$SklG&Bphp=2Xwchw^weZM8uhjTJ>UO% z@wkUZMdrHR21V^(ApmrGAHud4Rk=qI4Yw zM#o{4rZ^qLHTyjFivd21aRz%6lMQhhagcqr<9H+ET71w*zr=C&k6Vc2V!yZ#XBuZ3 zPANA1PBkH(B2Fu<|7RKRd7K{O<8I;<6Jn+U0{9Y6u)pVVg8kyh#4$$2vpC9#n28VA z*W;LCu&0TM2FAshte;0QLSMZwL~q?NP_H;$vHAZq(WJX|7^15Wl%K1jeWHNt)v7f{ zasRh3Dyn0!0{(Y5_Q+BI=+H}H`R`d=FLXt_c3sFVTFXUKtt!RfpG1|=jx6fza<9F` z=f$4VaaWh?uuEP15^t(1m@=xogN>yywnpZgSgjI2#0qU$uxQ%iyI5*ZzCtdwJHCa* zT5=9v)DqvoqVn;ZcEs2J$G>l~JI?d|hCerJ^(wY%H3?hov4Bp;^!#+q-T7?8=Npc! zKc{+bs-|fy*$kVmu={Qll{cx0yTt_}4{Q{3+A4PjWy`nyF z7;%9bhv4C|R&ani{fWQo6xUMKtcyE{J?i5sswV3iBNayp4`=lCXcMhN6B|9Hx*0(E1@znA1yZi9{e3{CN`nZLBPQQGEyeInmF8OrslS`>Qt&bbXr;6V5oTWLplCKzG50!<6WRMGu z(S_U?le0Iu+mQy4k!Kr0_VGm%a8lf5Gk$8MI(#j97(;xv>f=tK>X%hlVfZ9Jz^ZOt7?d?#4A-l){Q*CC*m+1r7@O}L)={A&nbA8r z%Ck?P*5PVU7h{X(#gO88(MvUIqPPLBj=hBoAa)jiov}%RRb}t*676U!sq4Gd*n#bK zmzn>O)?=;txg5*&G7oR-WIj3H9xvfJ8}d2Ku-G5*Xkq!!zvjcm;2%f08F!kWH=&A?#WH) zly?_>&+!vGQD=$H=xeti1{Yc7oZ>*6FtYM1J21BD0&T?P!m6JazKH?R<(dgKmg=Pd>H}!4V zA6qTb2c7lMTj6l&f%d#mJ$7299$QV+2^({Mo<*+lUUtG7%d!hrc`>_Vt=ZX#b@^%* zQLe?=4?C^SMs(P%D{6JgTJ$s|t1vJoXJSZxSYRLHGO$lfRD*p@%1#&^8+F6}(Oqv$ zj*sUR|<;oc!1 zH82+w-_gxmpA<&kEz#{Il5H(tI2|DXAjp(k<3VcA< z0#X}=0MOgH)}cwG_2?bFY$Tc-Uq(lImjYFzP3W$tO$ylHMn$ym4z#JW5%sF9L#+;L z70|x5NZYJJo3&P;#m;gwyC|1pvw6m0jhR|dWt;A(wO0k(y=52P^?-GlpSS0GO%h=UFdwc^)*M)?Sm$Ep2p6H*6-bZJ&*tKtNY#m6ZYJ_ zqkYdi=&HVKvFK(Zdg~Df6MZx~Ls5fw62_Dp|B?E;7-M39^D)7YxEPZRbpvsTVQ~#k zG$MY2qm1!u9AixUlsF;wcb{UgKMf}vA9v$a3cm z)gJYV%zj;YU8zxgzXrL2lAY6`74K=c6T3?RV5b@zv7>aPY*uBJq9*t~tkYpB)~Hy5 zRrY!d%k8xQ?`ZV~`Hoie@Uq?JVNrQrT-I?ew97oaRl+#m(_#*})gG^6vsSNRp*_WQ zKx=$KQFFMMfpv!++;rXclRF<8lWKn6(AqM=U)!&ya-uESkIFf=XBCwX*p&y9_t@=1 z@(}HL3wf2j9>&8e-ozFhj^RR2;`EZ@f4kZ^j`)MR7)E?p=U7KH>EcD=6kTH)RafX1 zvx%E^kHlI2$Fb`8e+dBpI{sH4Q_lX;cWbYF_vdpioO)~f_x2lYtIJvNshY1;pYMXf zzw3N#jE_5UeEdM*=r||8kDFp_TuILK>%0*<8U=ZD0Soj2>+>;kU4CB>H|HgSc)GaJ z$Rj*tGoIGV9(>Vn>?HTkqpZajydkK3D6ar}DueP_a3*wTbu-Vsya>$k&M*u)A~w{DTp2n#?;Cj)1SCB^T!(h|kHmko-yM2)LYl znC;(bxp~8BJaO3E=WlP@>=pSv-Lxd_`L4u>MhmiY_UD4&c(JOEy=amWl?+wn=Q;mSfL@V#=@AGb8%75 ziHq^HZN`%KTWvbd^R@=^HU$iBg#q~l`8j=!!RIu^pmOl=(~d`9|fh29j0zZLQefS{pFUObytn zr*3F1`;Oa7!K=fzqMj&@EbIGTD(m}pNK{!?Ztzbm2l$g!p~h#5!QLrqP!oj!Q0>&> zvBBlzLI9{%SG>NgNvg8Fj03DItp9P8B2uup>;;}z_63KX*kz|J*cJtxaHHRqwEre= zW0}t8<1Ks5#w*r)7SCH`8u^$P^Y{3b^gaA0@5-CW+wJ2jd|zW;L4IF1=i&0G$xF!Z zsdXGar6#A6w`O;vvC@|O1`e}C1NleY^QTyoPuPP^ac*wMki5&w=xvhXwJ3Rr|O_M9>cA%9s5=8wFd`QeP1;WtvsVUactE?n#phEdohyuMDEI8 zli$v+infTMK16;y+iWGS&xW{|ye;?1JBY8x)o~N~r+hm9o47cB8`q=RWBFI|#he{e ziI2v2%NoB;Uc~?%hLWpvF_@UFDGLZ&lW(DWbhaFg+Vg&*cPRv1u7{OqR&S-E_HY#j zh9{o#Zm*wwK-ce(IfinrGNzf5FJ{x3)zxu zvj$r%$||fgCo5R4F8AUcJ+lMX<^#DGi!8~+BF|8l0U1OqL%oKqa9EF##XD)y|N_N5jRwQ#Cr1HVv`=fG}cD^HIB3nCzw(=0gSX9 z^Yrl!mZ-59tL?})u-Ut2VV4Ewpu=)+pxUO=0kGRrbXD;#8q`>U2Ay+_VxW5neKgpB zKALhP(N~kdU|)T0Mzf}J0PJnELIBvTp#N{iZc6KQCmYdKwT+AZ zZ)mg8d)Q%-0p#XqO?e=1`xwc_({w`ONue!y{cs_5j1hZZ2Z5)%| zH=3%MZita|dOYV=zeJC*KG1n1doJ41)Osq9b^prh`}b$p6&qgPIUJpJc8Fr*|35PR z(X6M#F|^$H57p!}jL_^XjQ!`vKR%Cx3~^(z@sF!;ypis}kwzJ;$oSt{4*q|}M~shO z;$-9E@8!V%I8H4y{~t3xevebj!T&_#J*{vGJgaaJ{1V65Kb|3uG}R{jk?7#7@*!Aip>9;3hMuX zs8MGks?^vYR2zv7?5oKB?}Jty1|n3-|L1nS(WYG^>a<4p@_l}ZD0}Pw;eXq!VlUdt zc7WYwJ>V{NwkZbxP1qu=#U_!fuwF)Cd9Qd!LHmCjD~jt?zN_6^c&FIVds(abSfa%n ziU`0PSfs`Lf1Y!ZUEahxtzO3hdvZRu*%P0~_H46A(Q@!Mb`>zc@ztk&eV?!8H}Wng zI)cg{t*gwS;}16FiBwkQw%kizVrGscYqaL4$&YDs9lol=!Kjo`uyJTrZ2p_^?3{dB2;_RYDCyrAe4-&m~iv?7DSdZ91RbLI!=KnYrPxzMr@UP>4^)clax3<(? zxnt3sAybF6fA7#aw#HCiT2k}1>aRF|(8x|l8W+F9AB(+Po)X{5@8fjW#g*6_|Cu+E zwXPI6K8n_Wn=762F|4ZG40(z71iqY)3-Y2u2*@ug)Y0D;(SZECxq^7Oa=9Qs>~29m zXkI>xwH-&s(PR(b6qxMYyp=pHE{>_>gczOIlPla27vSQ!JAX-+o^^k#t#aoT`+xM2 zsmr^5=a&8N|JkiiwLGx$xeZ@fe0u$vDy0z6R<;7{Em;6cA%Kp2L^0?WLO>->M`x6b zfP7qG*(xcb@lk~%;1P6`15~w|I0yA=%hrIR-m9OEm`V&U2mMz|M?g#I2-u+`zDb-+ z{*3%{jEGCIJD22dQ91(NuOGZjM?TES8-`!^~_%>sN!EVR1JB(!FJ(`3R?ab^~&opw4{$3-0VQ?-W7a8K$N1%6u|~YE~O}9p$i7EAV`re zsGwA(2qYoB7qUrqvzu*met-Ck-uM0e7cM@(ydN_=*~e~ncV^Dxc%8?wh|>Jh>8dFW ztM0ZJ)!S>|auhWe)?XR7QZujqv8W@z82vnoPnLFd8lC^_7`F9r+CS3r{N`WMJ$uKR zwpZzSzw_(cPoQ*D)!eFqG~7Dmu;zI*-Z!*kp?faMdXNi4F>tZgwZ}mPN+v)7r zKF@V@_vw1vZIqXlpRf+Qxn3=v@TOYS8>k+Qs?{rYynE4TnFjQY<20hLhoS+EHn{`+ z;`rj@E~r$+9j6`*@lkbXa7?lCV5VBsRRn-q11hyh)fIA}ry?K};eRD-E4%Sa^`OTH zUFeE^w4=kHZ9|)l74-jKS%m(LQiL4!w}4!Lf8a6O@&)|82qnvB@=d?TEiu>b__dXO zOx~tRHLg;d7n9%B?F+b6TYjGWdhVM?;B(%$FL{N{ITJ?|X~=nvwj6{vY*9nb@P2k- zfi_#QEN{*A*cj(ofx&s5CFIulmKM}_LoEhW>H!BCp)ZD%ue1!!af(&M*wQ67k!O{E zrVX=7^YvoS{Hs1VC|@@eN976=h>w?B>_Xm9+7bs6U(S*F0C{aT=p?SnQE@hTL(a%w z65ohR;%f39FZu~_Za!fYhR3h{j(ovO#u6u5oX?}#`*A8p=rIQC)f2j$!)P4I@-rgArJZk(C|nkuf*_gi)TWr2TKJ4E|dy=>HzJ;uH^Q z^&j*dteM%Qrcv?FaaD{teXYj`11o~jph`{TkV;lWagH)Lw_W_9SawS^zW<7dr$}VioS8_AC z&Yj-F%l>2umf6JuH05JDiI2N59;D`nHdigC@h7oY-OKoN?|7f#iM5whJ$OAWuP>ec z{t$)ZKNXD?)PJykDvp2bhw+-6f^mjcY9x!ynW-_-`HJ2Cv$30TzC-M5Qe2M1O!5;P zY-;=fA2K!WCJu`k6~}*O+^#_VA5nz;J%&%174vbFUE*mR9y8+^;s~=ngJaEz+wc+7 z<7pgdYW$Hn$Rv;9pqLofU~g06LF{IX-w?YRZ8ByU8E0dv5q_l*dSHyfaUz-x@Cyt# zAdV;oL_bBdMhnqbgS{1k$v;q|Hs+v8m75e!{WOL1KSklzR|J3oDbo6z(XGcY zRO>PbB{c>hbZV@8o;rmwRizLLV27eMb-QXE*ruuya$BWE;rMUFMrobG`CpASdb7yB z>4~e!6}oc~Ua=#-gC%z40xZ%QSK>YG`3@H8h$|GR|2yPD?ePt~rXv?%Yj(wdW2;W@ zpjDSe*lts$g`g*w5q+!vSUSj#y&8|Wb<(DR@Akgnzsvj5JH)y?o${qNmT#f_NiDfA z<$64 zjV4M1d^C0tYYd1+O7mi16h*`Zp8@{9;sXKTgX@3AHFx>-XVzS@rr*n3FPPbV&AxxL z#ZEjwvF2;lXE|+5$=73g+)i$fxkWgir}O*dhhm?&gnY|yavmCcn<0;KQH#;+T>t5P$X*I5YpCg`6gIl4mF~0=}SF2{=7>2PfoL1$xU9 z@;UMxUyp-PAKw(@jL5tm$2mPNz@FaBZ=*g=iqmnkt$8c;|5evfbC0i|d+%|}&)=_M z){nmQ!1^0jx6fR4_r_rhPHmXPvk||ntw9{o4lwG))!82u8iYs$6_zqgtzhq ztMJWAI`f}&en2)5zEl@B7vfkJ0b`!G9fi zH`{x*9;}Ye8+Y96a>~nkhggY$PSl9a`f5Z!wfdmHb|qqfCHiB&G5TUaoTwiLxJ5to zw?G5>J3%A*#|awH&++=8uYD@#baiNq(Q42T0~JnXNfqj%Q=q1@Vh~m-qt_xmV5lAF zRZ|E6G1oS&wmKDGu`16ZFY#U;hjYB`Nb-Dd=W*l-l^yJtZ7`NRAUEV7{9U(dOxG_v$@wwO zR*beO*JE{FZw0o+cP+u7yw4_7#cQg`;nAucgQB^p(JSqrOVMm`oQARGAJ~W~rJrge zX6560$$9yvdg9>nDnrPdwAl%(N}KFLd@k#>kT>VZ_$YB{?vziHKgrZYT$i13DS2~l z%6o|K`V2#oGKMzGP8F2q@WrX1$dGuFEE3WbBd8;WM7e*=W|HNc}Hr|C-IMp#Li? z=X*s4fZ2A#P{klRB&OPiBV2Eb0{t&S4u_ep=$Buvc;mkaJvxk4IRDLPvB+TbXw92Z zqbBRnsU;~8{VuG|7qShDJ(64SmiF90zM5llE&itD2>dx$T8*>3U^Sk}`*H*MjIZWK z49Lr~SP?lTyD>Fy${^pdE{o8x7OmK#BR8Y0CpVzhj$Dr_t;O!T*4%`e*zSGQMptfC zINR-LiXwD!RE)@aOfJne5WAE=W)!i1d`@5T(bC1nqPF~Vh%-x9sls2%Z>S-kE6r0& z{8(clH0CrV@)3th;BloFTShw4qwjWQaC2L{amYO4`ttn-ztm z{jwFCHM)vuirU;pK9XN@KKe?V(MOl}QC(@N2wQAGkM#zi(+XXRy0?1lsMJ$#@=xz8 zUSC&YZT{7JX!j+ni9eX_i&U+)taKrL+U)@9Ko!4Kvn#dNT3vMj|2%H~x|f4>@BZ`1 z)n}u>KISUI?21;7MstYa2E<+%Ymk!^j{lLEWLO-JS<&nwh2wuZv6r#tVJ{Qn5*%WZ zpWz@o$2`R@{(R!F|2Y0L;}#rgc05cRRT=z0ZdTl@K>we_kufWtA`Ua#lQ_ad7co2ug&T%dFFv)MQyYY4O43e9W#iD5y_!)I z4TdTP{=vw~`@-Hzwp5Q^#q&l{57?no8QWE754Nd}>lK54@%vY{1sjA-SYL$oTBFx0 za-|+Cuu_-hcvY7r3g>?@7TaMF7HhXqQUAAyT&TlBEXoe=;Wh0RW4TTXvCVo56;A(R zY_AA!?Ru?1r(SCq_VeAI8FByidQY;pya&BswK2a(`7JHw zS1A9NZTSiE&pI4S?yJjsoUbPzCjUmx#UsL7_?T+bh-=jtO&qE&E+qcF>;DCP<5QF# z&_52KbdrHFmZ~Y5Viu*l4UPj*P|fE9@BZ`y0pNq{f7P|e)a%}=xp?J1FJF7X#O`Y* z)oL-G_kLS*dDS^i8*_}kOpn_zp@QU|67#Z9UJD{%LwqCWk%N3q;A3$~9Dyn2zWF#d z`BAaspA#U@^0dG&{Xq+!)?^2{ZzU}7e*b{-;PO`md4l{^R7MWcjsPz()Mqx z`R%6T7k;v_TUjWG0F^qY^8d3MK+zshQy~WaPkTUhd=%Bn`6Oy9LII`tceu!7#plUC zD0aOHhqO0N#~_*a5Y2kyLUOTk{!Ebx@GZq^z%|&aH?F{uF=6XQJM)O;`RA%7v` z3kp=RkvL!G!?ez8x9Mb_{I8dvyKyN8H}87i8E-OhL;r!z|3z#mP0t(IeoW^hzDDJt$chi zs?}&hIq%ZMjuW=lyg!KL5B_!N!|$uzN?JMR{_|df%t} zC!M>xk!;Khb>rDsR)mg4ojw>HZQ3v@mg>Za_`9JP;Q@m&B0g&nhWd_y7#yeShbE`$ zi$RXi2Loeo_2?fv7pLRZDf;1S(O60K?PI%Yq9%@1A~RM#%Q*bT;(Ef;}62Cc*9nIA^&b^K8hQZTt+@*Vcv$zb@?oLv-MWt%NFKW z$!~krM{s(+;8*0?`sU%}XSC-&IMGtm$uDPX9)kn?D+iI^vR)0Q>&|X+V%}#f#@Hh_ zU|D?1O0?wnEQPqTGN8SvgaNi_$3V?RdqDY8?_p@1VL3*YzGE%M<$d1A&gH-A!0ZZv zwO8p8jl_qtE=G`d=fHdsZ8^#|;`AJ?mz-~Oe2neIZk&cLTl1OX>C-8SRP~K$G&JwUfXWKRAWhaQ zoXHIs7BliOVuW{dcbqBBL9=d~&}_TS7-??B$=#fPQ3wEYFru>iKf>>{VYr31V`$9G z=P<-Ac14p(wkYZfH=(cL)}da%EW+k$a-zcdAFBv?8-bR5&tUXMv%ZzT$7*!uW@WtZ zUF}%y&sy-VkJ~{0Gk=~d@q(vwDZZPT@8K~^^Ih_xd@dJYSl*TOsP{?3$^G-}T#hYy zY_?;aC$bZ(b6mEQ{q)T)#h||$<=kLe<^O*SItRt5Djg}BY;@4K; zW6HS>qt$o@ zaHc4-*@&AyW;~L^4rnwu3$8-lOQ2$#M z1OB7LN6r4v;6LAg2LCuBW_bcfnC&Trk8z5Kh*2v zZ;H^rxr)Jm7li7Vt$6(@R(ndODyY^epw|S2p)?j%dMc{`-O-HH>4wU`ABb*QS-tDg z2OYYKEFRTL*s7!lTa=2`F=0D4VXFf5Zz0#{wL!7#zaIb8WsM>`pde~GEmsWw%M=*^ zOYvUru#{Y+-BP^v@B6K_4Dac%0_!URLYwZ&z~55|z3j===v0pL6-4bOrhVnG2Sz{J zIm?r})z#}=W<_}iJ%6#fJeS_~+)&<|@~!@weB!^BFBIXq`5nEUz+2KN z?4)D>4pwC!;$vzZLhP%~@x);o;}4Y1*Ut({{S1t=Deb8#_M$Yxkl2MdRLQ?X0JZE@ zoST2~fdKHq^}q7kthu*d2Q{CM{@`>(OZ1fJio`kU2XcG~FY>vxN3aXW^^ zhYF|OOZk0rc6>N4C6{|RzlUMH69jppi{o%|yk+?W`LFVg0%uz+C=d0FAkWM{*o-fr zlRV9x+Q`#fXdUs#+)I!rl`j_L7vr*gj{K`D;$U>e(UKe)?Rgyz^vSpYhr2Pqg)!cZ z)3IC3&s(TpUc0&G9+zJD_A|v2YII6z|RWe!+qMVBX5s=t;F|pz9r;5|1toV$IN_( z{7(Kj#^Q84TTd=DNh5i+iGn=Hcpcb1CU~EmnrG%pa-7K)kV8$jnCxqs5%=4xv1XxKmQCYxQSm9>{GIPdxES^A4to_Oefy1vprWy_7I@-r7>vH=F6 zk6KNY--{vWqb`aji8{m4pw@8It8--K9F9Kf4N-&>4OIvNLy)Qspfg*yww%eo-njoy zHy^-W^UhiFt@EfI*XMGl(z~LkD`wEUU+*gaz)sH5pr|FP$2zTAG1d~D7;9I}7#&|U z93%a-A_Saf5Jvd2{={%6Ys81*@JgTkr}ROSoz-EW5o$3|W2M$7RS}Jq^#4ZdAnGku zCOeGPjp{f=C!&H-ciUte+GDo&v857%xWP!vu~Pqh7w_R_yrI?ec+ql?;ZL^uEgmjv zncQb}UXKCZ$iv7R@_7g16o1S!$V+`JcPCHI`ME1TX{ov7sTy++9F^O19C?l<*+lN) zZMB$bLl)tG^;*gMyq+7-oYxiMO>v>6DCf;qL;OWxV7#vbgJOgxG{wP|5W`AmSwa4H z`C99+lVS&cO6li1h+Xo1$UC!mo?4O3h7%vni6)T`WglhK=QxeTm$O+j`MaDQpC+!# zDPF~$IWnIoZ_WPZ64zx<+(bSUzs$RcuX#Iuf|*|O1pZ;MxtMBG{sW!1`4&Pse~ub` zd`f{>Z$&@-^LH4e*CC1>>=p&Oy&1z}7n?D{8~Kbv0GNZ3mC(oLm}?70dZL2-Z>VGd ztTKlfVOMQ9(E@E4W~OZzVro8zCKGa&BJF>M!uel~Is+_Kp#NhP;d(`=*QUG=n=Q<~ zXv-I~Mlopjpv7&u9V|jkEP7{*YIx!5Saci>MsZk96)10gW6DmZ}sLH_K6hn2P zUb9a0DCxo$#qM!!C46+dK{~M9Ae|U#fDVi(T1jH42JPgR)N5A=0c}J@2*{m;8bx?v z4Z5=1cB0B3Z6;sSTFEA8)lH1i8$YJ{eB(>4G@NEu?PP3oN4~&wuUz%*C%#Qj>y8I{ z<|_vO{fK@V&Bh4*%*6-;D#-sJr(vQYj=}U8<~htZ*5`;ljkON@8y{c6K_>aRV(`D3 zIMh`4;+U8bKfzIE#zXk1Sso>h{{IdBN17dv6Gug{^M7LH{zJ`-$8fM|@o>fQe;9|G z95-R_m=eFkZYKE^b~Qe}L(DMFEsA=-5tyVo&Q-J&+=Q`)#pf|rlN&HXlhqhzpks)^ z`g=!F_jjmb^<^F!^@;rz&j0sOtuFRZAp6%S1fg9N$o?!v>i<-bCMz@|lpf~CbRSe2S>qoDCXxFKaBHXVATZ=G2ElSGRtY`(;sH}rrr)(S6>a`hf==DBU z>9$_s*ssM>T~=dhrLAJA9ads-G1%vO)>?trbX4yDKObLHdHh;z(NhVv?8!CQp(oD6 zj>_L#&wo~A*v6d4jyZSy%YF9l-t4{d7J5EnS$PD#cUfJYLhseq=SlSbT}zIl{AJs+ ziF`$SejI!1aw>LKwnGv8cOs5cWh!y58i(U`b@63lnT8lg>2Q5x4YAOGsG@YHrr3k3 z%MFcBQ#Hh}(h$Ym8-o12R|Gy@J{DAaKA*$uy}QQ2WUCVdnrzAI$+>ZS zTtFThAIWcGN*oYpkU#TI-a?;pUH_W-zVW4R9$z}|8x4E@_`%;FbHmZ?qgIXB@P`GT zYWTgfj8CAeg7W`6BcMzis~F^q)`0kTYe0nv$T%8RBA--b1T++nS6iI%3G^!E9~1*y z0~*xF7cfxfd^9V^g^EmopJS`Cy5e@sBM#D&*O8AY$Jfys6V(%+%EdXK{DpEQBcMo~ zK2Lg-?(g@!zT=ZTyX$K|y5$V^IbhaJr;KCt%frXcypg!O^o_WQwl{aQ<;UpSyQd@X zpm$)NY83WV9UmgE%^&3P3a4gQyd4*tO1_f6Hx7R>$}l_?pEZDdB=1#^pBEr^{w9vr zjqm2oI>@^%whiYy*jDnFUQ~@w+RqMhOHm`2Ywc?X`5XJ%g4y=XR&rA8ubmu~r|2RF z`;a92#bIj5y8LMNBTGJFFxl%6qsX2($V9T+emRrumls-2?h#+M8NYUi&k*O8zLu+~ zy0dzDjHG5%-ATScpQgqY@f_BdPI3g>zPD-4ihWr)=!J#%+{U)gZ8&%7+jz-i&cO_W z3`D)UCBpPb*`X^)VXF zmBDnB=dHz<^1R%Dol4ilcZtdQgf{Zpd{-CdWU)%IR}RpC4LQ|NbmaEfi8vvL^sf1Pp^ zrsPWd%Sv=$+C6X zijG{Efo&F9plAhHfWBVL1!PNn!6M=)ujMkd#T!OZGxMSX=J8ECU*7#{ChiLu&p9VY7W3Z|;Dm6)zU8}`#*J7ycC z3o{JXg`Eu5gHeX-A)1O)tznhA(ULN`rPw{zV1!#@Ut9uJ())b-#`as>f6J^QfwRlD8K= z*!21jSaa^H<&{6fAdU7Q252-(G5GI>Q3l5T3g`b*m}01-v2%=wld-FDt{~5s(GX2u;j!K`=+N1Gi_RUH4v6okN? z_=p+t2tI6vM~VIH?0!XkV8!wGE6g>?Zi?1{ZxAz$aT{i9j`J1m1V6<%!*ybuq4AmG z`*jUQ#=tmHAuL`^4AvM&qDh0R(N`a@qCp=ADungt6@vOc3ZrK(LdkBRq#yuFW+>Wr zrlChUCL@&n59l^tv8py2-MWlKXLcBh&ZyI*KMhb4BzH?Y`_+<>Jz zOoxdRGDVvEOBZ-q##cVvQHd=`- z>fKD~6B^?L;xYYW6s6J8RGLQVYmSJ2P};??($!Rb*oe5@|L^+krsKAiR*j#VS9~A< zd~khmVb7{-ht^!Q?!=cK`pUPv|7Z3u3IOfROKPsJzR>5#eBbn#61QVa94E+&EXZrf zk#THXLcZpioQH|MGa*lMYaC8qP#&63kgpVozq2e9l=~Mg0C}1RwUD1vU{!a!Lo4p_ z6+yW%_Y>sF>P5_qr`k$Jn?hZ>I5w z^_y#W>Ad?+TzuZDhO4e${M()zPTBsw)%R?CcR@qrW&a@px+)0&zli{P|6K#<2~?@7 z4E*#(b%hA1C7(vA(kf6b^g~(3Cy`3|r~)|QQk-1MtqStTjn3g>xok-&nv&)^EqC)=Jf%0wX@rwr+?=7 zx0(3(n5Mm381QK0`1m8OyLEgr=d$Cto-6%^o{{CqbIHSVnOS(qwe}_#rXa1DxW9o;xJ>$y8OH;->P~C;y4$I~t>#=u(mlTWl=dYoF2CCHzyqxD z?%jWVn%pfHmMLl>i@xPLgA`8w5Jkw)hcVzk1Aj5#57KBSqDfz4F;rg@Fi2lJVQ@6Y zP8g`cPWZIG##KJw`RHSgQHq_#!HN{?CUm>s+pInIpBtZlh)Hi9I_tz^sG3@Hn?>~e zuKPisCilqiT1CvQ^b$X3vyE8c-I#<|<8seoM%-&OrutEg#FQfs6951JAOJ~3K~y;3 zmBeJ{8H}SGZxD8hLkv*VjrGImn4p0e>2vDQtX2*B>#WoSeo6^qr9ejo{V#b%FB??E*B_7Nn|TMC{5JO{ z?{uFTI4XZ(0^W9xk>nNmUCrbH-pJ$dk(`+$@EgN&40%E!060R6vE-SS~{2=mOGpoO?H_s)%Y%;A=k59Ar% zChp8>@dNxehj|A3r@SA}#Zz%7_VT)Cu*o}~$98Kg?Ivx$iV%8Ht1<6JgP~5s0R42J zN!fM`F)072AOZGMgaU3Qnq!Ww_`JX6b7;0Li$ah9LCrwXH6HgHezsW%Q}k0CpHtX=xavg#NK}Tt@Xa50!pM z!;_}e{tsP;ZJ*tF4SySS!{}!x6aCEbprWsSnxg%nK>i2D-WX$`Ph-5nj=;`Q?E23( z#wEnAM#mShkFkEBaQ-jGhfH=maj2b5$5Amgu2T&5kK$uy_#^Q#v*Hd#jo;Ha+AL50 zUylC?X2o4N+{}0!2b=Ce;viGwW<`DAz1TaZ_ziYdg#67hKJFl<8SAH*V^my(iAK5s z;|-6q6a>dL7^TVk7!ifgG1Op7i6;HySjFq;chO&;_=rMaze=(5f1rZEy9(86V=n~( zbQLJYZVKoBYV;7Z6a(=05YkjdZC^nG6aqlEPGiw2jX;N9!_b!7HKDD7v~Q^pr7c3O z0`V`A86mg>w+OuqaN>*9mT4F?h1L(Ep1l}{yT_MW0OPV(U*L4+3uHDJ!-m= zDUR3PPI-q+~4Ggp_7p!Y?8&u>v)YfCO5f2b`#M|ry)xhJ_ok9+VJTCqfp zag`nicmA_Do%?|R@WJ)L^?zM^9`nO1YA)J(=1c$m)t`0$&jEkY;xhhnLG?A&U-kJh z*VXSFQ{oQ%Dt-(ujJf%J3^Xq;MMr!)=aJJZDgMXAWpOwLxHKOpJMtnyoL7YY#ozM? zfzKBi0XeRc5%8G`5%5`W3F2{o2cIl|RG`7L`5gIL@3c6WyvL~mQ6RlJJ`RiX$>Thj z-@<+|JI+9#_;ubw{f^pUHTU@XxzkTN@4WwMJn6<)?|*pH^f9)S*^Yg$O9;Q;L;0Js#}^yanbAihr=s5fsQpCB$*5CL_>skzvE^5!DDAkHEmBCjJ(A-jAw zKgp}#e|y;dFVHrj{i64WGVA|i?>?jKEXzL7|E@iKGBe4{WG21$281LKLhm3QlwK7C z1!+p}NH5X^Y$#v_l_H>^NK+9IrHItflR!d3dYiJZ^WoX*yWVxqS*LvE{&MHpli6$5 ztl9VTT>mUjjY*)CDt_OOr@?FXw=WM^ibPwi!YVq|_<57}gYJ!DJlqfFL&+?M3Xyej`j9unWx zfj;rHnN)38^{`)1b!AO|n^Jc~{kw5Cb>}tkH_Cf^*-I>V>#aSW{tIuv`~3Irn@QK* z>tfYZR9UDI^*Z7T#RJ7o3P!(EG3XB<+A2(bTV;Un)NC|5qu3ADH^yV27UMBMQ7<>p zbmyT%i!tb5*;_Okfo4rH40RfGqNZXI=vJ){iywP^>`Q-T>;XG{XTJlex~+PUgNWMF zFH9r1E5G9(=pR*jiLK%jAK{*OMHT+#Q4=uBRO7I5Txd8pa-PAM?5OC(z_nxvsInqN+E&o^#d1&ctda*(Ir(mPfU8*oE7pTSC*`x(a za=iY;j#;Rdp2^qs!{Gc#FUICNNgS7bB>tYm^pMY)9*1MAJj|!W4Y{{@xZhU!2KjSS zJx6>`N8C>SL9bsE7nqk1V_`n;c`S`TSnD1wQJeD74BRQqKC<7z2H`kM$ z**6R2L}Pw}y6no8WH}dS9l9*Yz*=*20HUbnvRY$)fHhvv`RI=C`wn_zo=;H|^%d!V zcdkeOXi|d_Mra}?n-znwytGG7MQ^S%9SzynmT1X=c0&Ig8M~u@j>s>gH3!=bjoBW% zqAvT{9$ndCGc3(Pc^5v2^Gw9Dyg5c7<7BO<_5*!e;T$m+K-7HjE&2%M@)$Ou$v7$guP6$2eGFqaU1qFH3~2PRF4y% zi$V#oSD^r~(|;KMNq$RgX+qqL9gK~8Ff+#bF=iSQ-z26Q;d_`;k@!zDs8att(2W>o zKtbf_a191(kApEVT3w2EZLxPT_`6tPHG5v6pxX&e`uMZLLbt8rKK%r$E9Xp=Dzg>D zO#y6GnW0dGY=jTKd2WJqZ~@rWA#Q2@Mm zbiw>Dsr&aUp8)`$tmKkG&-DW%Kh&)W)H4CRz4uUXBfWX9;pAcg8N{UF9M92)fH>2=WmBg8X8o1>n{~ z5#Ui_9S-xO!WxjLLAfRiCBPTTXAAPCI6t4jOD>FU$hYH2$eW@jug3se#VPno9Gn-C zJI1&;nk;)Fe?a4Tb+c;9es#jz`?a5VbmQ?i9`~yYZ&<$iun1HsX+|#4I3{lO2@bbr(H9S>8zTlFdV2Ug#-{#*8B{bt>7<-PPAojX{C z-E(OSCO7Z{lgSnN&76ZpakgpXyu8UIyb~uGOTLxY8i67|!!#fq7qUNUccUVrPEU;q3mmS4B%yYF76 zU^HtL4-VT9b!xRJn3z_@z~3LO6)AuJ%D!`Da32&cF-jp>oT%vOo}yrEPR0mrreUZy zQ;4BOimkzw|36TxiD<37Uz=t(p+TP_eOQq`Tvn?YCpp;w;$XMgfl`(hn?Wp!-{`>4 z;ueKOKdn>L@O?;Z>m`Zr#&t2C*gS4`2fpD-BZygXsv-D%yk{Ubc8+#zXfu6@X)#d? zro<3^FeI8aqAiLCp88m&7Nz*O2vhTxGFEsAR2y2b4Y9BF=X9l*p^{ zcu7w21-+P-2P$FX+*mDnw2`?rxnuUr;rP6AP9eXX%W@34qYn+it5#%xY;Kii@=u=7 zi2feQSFzS_bfdM9(wm|QY+zoomwyH^z4To{UXf3$AvVdy8pvN~Yf(3r3v{3%n>7-9 zl;2RsYjKJ?^7UM&9&cr%CcKuz)D!3Abk+EK?x>4=+M!lpk32OlBYu|Kcn$aE6whLp zY|Dr7e6ETc@o_xj=U8TL-h#CjyBuX}^ScV`(+LU*bQPKm$a@sM@mzPR z%SSPQ3J|c)8e)(c*5D9Jtj0j|^C@CrY-D5X<0loyc(S7AZGt6eH7XxRiy=7<4cd*U zd|rxCtgOEx!=Mo>d?3(crS;^JeAN=X>Mrl&Mc>KS@kE}L&*OnSD4!&MW{xl6+c`e( zC9lec44j)wY)hVz@8|6}B45gv$rH-Y8-X42uX!hVLjJt~0CJZsVN@=t003RN5-mA6 zm!KxQ^HVZ%Y1W{}{46XqZ)XS6`uq^!0 zXGCt|11!!B%^+9DE%_(nANk8d85LiQHVoFQgfWKXT1?SvF*df^N@7#J*5M%4daabR|1}xT~0Rt>nkNOy)0h{Cv>hMmC zRZq;wZ8ehT=~Pc_q**<=yNxtpni=|FsM(e0f(|uUVw7ss>9G!{#4fg?>RFFP9rfQf zxOyXe#d99x&5IuT*1uMupW}>Ic-SW?6l8@Wrme#Kw>nG_dbkTFMu)vI%_s*K%)fcq z#)vpUA^pD{+ZY=cDnj_~#jYlL7`vMsHxqlB6i;9uQ#_$i0z6La6;tC5>}`Yi19maR zBZ|TQH^jCk#doliaSFzNoGmfk*tn9|&06qI9f<`|o$rQ**X#zM%JLpX7Cp z?EZyUdiSFHVsDnuq370ouY4%IFMFoE4dqKM$vY{ZX+@q({?b~XBWLK!$H?22?Wzdx zEfL46Hk)`(U7Sd4q`}WA?V~w%pmd40(l|;tL`S?v)x~xy{hX>V85(n``jX+Lf^hgB z>-Ie=|M>d9U-=9G_-uW){%_VMA6~P2%{P|4^X#(I_UgW7ljAKlRy_Yi%?;IOIcnJT zhQ;W(9b3lD#Xuhu^Gfn%SH&5qjZ5mcT{YHR~;?LW!}o`$;)DPoJ`Jf zR9=L=72$bp@yq-^js5Df=7(-Rd8_>=oqTbhTdsff?wL1?UNe0DoJG&R{^iCGl$9!q zfZj?*KrzVt7YHcFUZ|24=D(;Htf@Ru)QH#v)iNJb9H+(i7d3*t6-7X)$Owo&>f>l~ zG5IsagHSEj=#4WKeZtpaZ6zaME9JbNe1td;t7A;wP8^!=#EIk&&`f-#0tDP3;!xuB z0-0WV#fqEfoxvaaKE34nz1eV^iC6Ejh*573Upc#peqXDZZbMeRy6zaKvvxq&U-DAA ze_9@ukI*}*{5M_rx<|E<)AJr9$o_F|j6zG^U?kZmt~3~Rd1dZK*2Xurq9!lVhpdTn z)R8rL5vY%E=plE^Z(2?EjpHpR`{(68AqP9!K9~^4nMWRwe>M+~#^PK^%#2n&ly6yCuWY@@D7Yu9LiV(=e?&S;L27^tt&7^bfY7-ovo6g|w-h*8>Yh|xM?I>zZR zU6EG3F)q^S+Zd+Z1{kJ)Y=BO!#-Y8~qbZsULA@pe(W71qRUcIEUNZzI!~?Mpu}_>} zHtvjT^dY|B9>2xkbX$q0_?fv_9nb2;&hd~B@T~8dh;8E%W3ZKL48;}>HW0I8N1ep1 zn57LHJ13ekN~Z$AXsomW^r}Z)Me54})mRh9>cyw=x56XqF^MisdWp{1$9nP^*I0w5 z*v@iP`=TW%o9-j5iy{PH{w(iu*!!8!<%nKJ0?ec&Lnp#%{Bx(deIQw ztsx(anfhR0PPHE6a*-OmoonVYv z{$PLe$$M?-U&I|I#4qr+-aL_Bu+qG|K_L~txHup4N2t}{IHIw_$X>1^ z?<4wQH=LlopI~bj z`bZJ__b%!xssB}~vIE`LW(&DC7iN*tp4qLC{4d8`59U0);*NX+f5}T-fZsdPQ{=Dx zDu0WcbF&;k-jpSu$GN#UZzIpl`S~8cQhqKsCcl`!&8NwI^5z_Yd-I;`AU7}nE?cls zKB|^HIbX@&Vt6jhZt|LJ$~9=u_FPG}WMdYfb1D0vfn0&I-dv7)X%(78u0u=I>rvGH z1qMbj0FR9Exg3)!Om&~wB}QVc4Rd2OYB39gVv_AJF1L?8Ff~8PqllULieoV&C&e+C zkqvnv`CyKXFJXd>eS#r|g3n5z5wth58i0 zr4^ddWUMA)YZLS(e{PBvykJ8u*xWGvu$hHgv3YEx4YTrM?HFP=?Wl>N>hWoOQfU?V zvsy|UXo)+iemHI}9Yw>CIIwmqEB2c|V_7pFEq$?delH%^qEJ-U+Xx++V-pP5&tVG5 z{|*=z9kC}S8}3|UqYCrCrD1Nu93wO<(*DmTwlmg)*gM9Xja^NT>xtbega3XedkTA) z65k{Cj4AOr_OM|*ft^kAJ7Omj<5p~ALfoTJ7TkkbF*>fqCdS536yE+@G2O5@17i(# zEyfrqG1LHGRF$he8qYSM<@xP71I8Q>gt9^IvNl z|Fx`Csj>w^wat(f1wc<_@5P1+E9ge((rX&l={89bidUT9X*5>qHe3WF* z-nG7RyF=(*e;IF7-hW}mdQjwVki{S_lqw3DDs@bGZvPo0K3TrifcoK+`!4SL z{<0FluptV&k-N#7kwyIYmeQjy1>BFtyCRsXz3HGA2PhEf$BLi zs#JtEHsEyNy07|Q>oWl0v-R2ff3P;)@ap?(zOiV*vzMOsgYIiLA7ZI7{P~TV2dd6> z&cqfI8MQa zaa>+V9vq!<40&_hoIjx9@w$d;zWTM}zVOb8`!zoC?a%+}up4h*bH}_NFC6&#A&o~! zr8rPg`|=xf%lsWu>Vu#H1eEeo)cm(KK$9Xga4(c9ih+N>Uj84g0S_q<>L#KX+U3F` zBS4qX7i%hmewWCb{1*t=zM=^DZAB5#6Jzox6-B@a z-2E5+e%UJr|N0<4+VGQGU%QzZ*KII<|L@ahP5l;)XHxfJZBx9?>MgofJCyam>%J#% zq35pNFT0=K+4&F4$e~*ESaM4bX(c=I=E4&n=V>GRG1lTJlo8 zWWPAsI)~X ztcy>H-^RP~8h#p27YvVIc?TE9Eir-kobS01hsJS+WBXWWFtLrRbzmzSX~V=Ar5{E_ zn`U%Ik$KS+1p{3jOVpyrd#bV8zjR}fzZMps_@Xkp{h|N{tj=z9#-5%)U#D1s23uN! zs@T{^=rZ01SfSpVSY$!IjQ6Fj$u}*x8e96nn@BI{gT)?~n5&gdCh#`-B`| zmPHumHx^@b9%?0~2LPMO>s!>M4sR*WAH%kZ8FXmF`E2*d2U`w>{9xJ;pA=kX^ccy4$8NPJ%f96#SkpYRwM9nCEW4+ygiS?ySbw_yqO1RAYX8NT!O#k1(p-{*w6cT z*c>mCZ>jS%@u9c#k64_q7>`x)BiCZR6?v-yC_NiB8uO=U(B_M1i9&+i7WMfM+GDL< zi4F^N;{-LjFhF-cjsaGg?Y}_4Aop8?POldhfS8(3p}%kER7LIIBt`AteDsM9BT(l6 zgHWnSa(k`Sh*dtS{M{|H8VkK>A>Q&EbMa!{=0!a1*!&auNS=@n@|bMF^zz+VPfp0TEMa86 znLXs7!V;i8J97=$H=8riM@=@Nk5UBys4AF!)z&FO7rW3NP0AP;1rt3yidKlRF;yG+ zqAj#yP-%~xh{&!&VxD8oAtscsjU6#F4#{J&MfPzxc~$xK_zJd*IxZi`UP5aK~4kz?S8c@@}+Odg=RXFjHX-+f<<# z+Zv;zqo`#z)CI&;gW@1;ZK&^Hmf>-{g8Bbd<>wxUePe7~f}Kr|$B11_@MnC%M1N5z z0lq`*9+To}>}ey9V>gpMPV8u6d>6Zz5WiK_2i}c2F+RS9jg5)hi0MXF82@2$4kj4t zMg`MfDBwC>fq^>XSPazeLbPj*!-#hMoQuASu)j9V&Q@6Ac2~3^oQXOOu?yM>f888U+W z6hjqE|3Jn4rvnT1YEz{7wrFZ8}n*J7{s&Z2u+&MjX{?@-T|H>US6i}G}OpS867I_2M4Z5sKM_4ybs z*K0f6g7b;fRK*s=1?s#=Y_3lnN9iFg(N5K7+G93VR~ir(Q?;ETrDLc*&hXMCs&_T2 zvYo2^Bpy!PyO85H{y2e6SoR-hZvGqlJnxkI1~M2U|vCP=ORIF6z9jzo+zXrd;v#cggrX*{httRUDv2MnG@uL6qcwG6EV=Tk-r?D_0Z&rAlx1zcT{- zTM^&^lvL;a3hCO>=qpuP1M-K8!T&6*EEE9$&Iquj%p0-30u-#RC;|@7cjF}T_5w(V z<5hW(yit{Q;;bUmAg_v9aR>{l7rylBAeP+v)W%nz$oQ(!J8ts~qwgDe!kh-0_iT8y z=GQa~shbgRvgWO>pSp;y1A9Km@6o+&`EGxucX;^-FHv4&lvU*5d`dTNv{s4S#}lf^ ztxZ;q-8?MFbL0X0Y90>{{yT$OidBZkJo z#XuN^*KkTKtJEgFp^Z4qQ#QqQ@e6Me`}>7SI3dn44*R$*MiRTnJ`TdJ@h_d&IHqXF zcvtDCP(C!H9R)+VMgzh^byy!~sKT;%LE>Y7R*hz(l#p?ZGM2kR4>81CUF7}twi>Om zv*l>CrNyW=*g{1pQ{jEC_7;|C%~vr`m2sGBwRcf#p&G2rx2(oI+gXA(ne)-6s8?&v zJM#nbXE`|+kVA5VT!cCH%5F@~XR{CSGsAKU@nOueJ8@mMRFKF!UI!N>W2MOko|{>1n5TTxgd z&e4r0^QK%({xkjck?G$pNrjua-5pCp<1oe(GdOeZuBwM z5opn(3T=AxVF1%WEWdQbWILhL+lpGc9$gq{bv}iG7Ue9W(}reZ--`FYe?|J=W}GGH zXJkH(W`m7Z)DaF*WMyYk7D63;%F>K21KA=Q|fa z$PN7(cV%PViSfC_VYoh@&+D)8f^H+g!N^Jr{We%Ud^ zu5nJBf)7((fE76)E+GGqQ}f%zp1DJujrKewe~k{GixU)?4TDjyIbT4%`LQ!GCLh;^ zx8o)aSe{l67ey2en9H6fj?5s^cVso9^ahL%`Yl%TR&}m5Y zN4+8WE0hh_f#nqo*F0@HiM{mbkH7jrC;3oJ(;rLoEFIXz9G!S7#v6#8bB#g7&YBG- zcZvNC$Iv_?2BO9kjaXJNvf90*FQuBeF;1p-pM1Qki;WePT>JjO}dfPs9$! z`xEvzA+EzNCdM}8NZ)ae=j7=1b|Cv~;CpN?yp-^CEOjda2 z&%mb`OD@GoEWj`w*uDcTK+`*KyK&bmsAq8*`) zJ-5I9{Y`6@9y{hfAPNk%}pV6hwsX5fxQqkV0B!`>W4$`f16?vg zm7>AIr%u)0izl=*` zsM|2Z2-{(%9b-FUx)By)BO@(UNI4(Iorc96j4;Gv3^k+}SfjtuXjU+ssjp|KJG*83 zepe8e#ldkfd3fHFpODK9i*dx4-0hFVpDpn&?vJN5qc$FmO>tB_>^8g++nJ0*;+Mt} z2SgDv=gIapbaoM8a1PD6m1%Hu~Z|g~_=#wo|c>jy~xKZY##lLI++OinL8nO+kHwz$lc@~zFrMVJ|yp$i| zeUIhqc+K6uh3E6z9>bI7u~CgkTb zF)_Ewrh@6q7KE;BBSzR%e{#FnIR{`y>}@D9JG+e`?<{Sh38nJ6>M)^nu93)GY7RPc zczg~Ul<&)m4evO##oyZWQYVvWQ;6)*pFsx%OXRV4dg(WD=7pcWn2 zRezm0Mt=h^Ee0Bdkq$Hn?FJi4)EjCrx!!Ptu{0WE5Z=$#c`IJ73~)m%Fo@XKG=s@I z%`%ACGiJwN@~qq@4QTD-u9~pEo#lV|VEd)ppS42^!a#t5CUvqD*R7A6@OpT|^# zeFqyGrk2>$us9j}8s!hz%4nMs+bI+P2O1mCVP_LOUr_=)kNu2~8x;)yGm5N$KVp01 z{1Mw48{fi~F*feQCdRs*m|;X*j2VWFYDDQ2L4*bz+a;P0%{b4f2~6DUq`;LORa+GZ%`-;`X~nf zW-O>E4;CtGRR9$I6^wsh1>w<-S~WV^>_;cvG^xDwfNk#Er)ly|t#_^4&=Wmt>H3Mk z_5P5qo4nM!8{PH!R_|%_&hn4)DfB+eqdhLPllBdOo(&7iu@sM0K|PcXLXYgC^Uqe_pc zte!oK{d-ORFV<%Oz-Q~T_5Wkd?!5A~ny=6Q^qKFUF}nL(v){1PIQ}}T=KiYl9Wi2| z=GsMZCwXiX`Xr9y!<^H^Bh6$Uswdd+`nO4yC-8I6-wBN8X~wAmVJb z`8d86Gvjdb>inmXbd6u%u=)u8dF87wJ$MBF2YdGwCUt%P4gd9-?FE*))LnY-C`CX~ zY%x)zv3HZ$d+)tQqp?@)#zbR_vG?AMiijv62!bF@I&7QobMPCJ|8+kH&wX&;&yk;# z?=`b??d&zz%+BxqdAC^|zAb&@BKq9Z`=u>6(B;T31BabXRp089I&M$t)};@`OQc*J zt%jE8Qki<1A8UEuOj>SC-=$fklU-*GsbjpWjCe8kb@U+DyQoSpayu4{GLTde$D2>O zA`WvN%`2MwH9p9a_1`c4YB&onnf1e4^I7?)rH{>d7!SH9?TX9Qcn*7M6%P{o$XkUb zY(mOXZj~s>YU-3K!ygO4zkPxE@1k`<>R;n(^wr)#^wrT&VvtU;Ifm(C97gGGCa#X| z{)(}B#1vwz9jw4uy{*G|eLRo*^va~R`a%rTOCCd`xA)OoU5rK#bsEv$5muo_XFnln zrxi8p>h|nC5;w%NT9YQ{#Q;pQ+V(gzX2vcUA4|+6PL5BNVQRc*8F8#PjldaknZd+i zUeFhRh<)_L_OV`9VtmZi8ACm&Blgy=AhpdH=vIXbn<6Vzp~*a@SQl3aEQt>^<2x@X zAto50i1bOEk};I71mDMz^62XjEvUDRO+;7yY#^Pk%xY9wWVwu4Scbe=zDI+enz2ep z#iXSwZA6W{#aL#oc_>aln1Lc4e1*0)_yXfhN%M(bQIXb@8Dsq7~^(@;f!oZR*9!*qa^DFu54WQwY<_FB9`L%op_mE%fu=F=lvW>5C zW7XfxV{FvHDcjE6Z(*|Fp z+XOZ^IGu zZ=Di!RGnT$Z49$DI?J*tI$2r>3;fmwbTp(O{U2bc%=`Z%COX3cnfE_y%cxN+lm53V z{9VP^Q1HsGHQx&S;3M-eH@)Bs%y3G2hxDPJ(zCcdo#kB8GbyF1I4#XiiF8jYbqMxK z-P5_4n@ZEJr1R67^ag2Cnv=G|*z|E4hq3t=(rD5F>Gx?kX>$JVG#ERi3(^46p82Wz zk#YUad_$**P^F!=~jH2 z{u#GntJL6eEOSD74vp!$xEAG3u^rLTk7i(;k-kGO<$gpfbFIc&^K2w`E!1efWlcev z+^nhq6v}XM_U#(SX>B7;)ZS|Bq@(V{B%PxULv%^6p@%N&(N1@DI7Sck#8FYN4$JgX zk9pBw4}9)B^`tk_`|43`v^rvM(ZF`hzk(@f-c3H^#&t(@>%=ouE5P zZB?j&tCT0bWvRo-wb3q0DBdA1&Glr>Ybz&i*aD-p%M>#hg&M^+N4Kb!ErK)Ye;*y} zNc7jy?$}BvKVpQgaR@fk-IKEP|9Wf@J>pDkY4dnb#@v4*WAZ=8lq{srl;{_qVT#f5 z9=6fX`^47zco{qD9hYN5^o++b-T;pgqjZaNF;X{E(O+kQemceRs8j1A8RP$Z)au|I zqP=$ZN0(^rQ`Bhdcc@bBU0E&cE+|(SJ7=d;P^>a`Lb37!&@Z#S4ES#givDwMgQUo0 zne}p#%vyPx|1$p@<(-Cgnqst!=|4>-@gIhtFo?8V-T*9?^pyeYo>;6U>ST=ni88?7 zSq8{E%9#FkGRD3Q={pT-Fw;g=GN4~AtMAKhH#jZ3t@3}I^EId}0RL)ytwFVH(eSgy zpp6Xp|C~M00m};3gf()}K?eLUM5*HFO0N^Qf2&K+bxZcjowsm8o3@WME%9akP8zTB ze)Cr}?&s5%i8OtizG^v}=I4EqKbPifEzEZ#zrW@AOUU17Ra!~9%f^DWt;r3fv*gV{ zjiRW; z|7AYSuB3hYNa2p1|JO(SvHcY@EpCh1OB#$|i1Jezp z+0i9VLeBZ=BGUHp_c#JO`*WH~W&84xC4cky2^Z|M{=_RQ@3`UYhY!7B`nq=)#g7Mk zy?^yY`9c;zla|;U&GPAanY8TZJ!CbCh+WY@dewjH0{@31ps*;2^ond#U8oa`gHS5# z6 ztCuYt-XJcD&GjN(YkGQzxGYVN-_vky!x!uR&R0uku7B<@X1q1?gXb!!x~1x#_T#AC zuhWi$)=|5l(}#mzqxI!&?(2FNB@@e@tbUr}`(koTA?+QnDJC_^EF@>dZHCf#SL4L> zgIK#{&9aq~Sk?BY=?mUrY3D_^O#guuS1i40b~X9CT6SrA8sB=wG~8^xAF;pw&cRZR zzL$mdEXM}PDzqs2Pt8pW$_v21O1UbuSEarGF#l1nt)4Qee?M7x-%t$J$tYZ+tGh5V zx|v9f)g!jSINMr+iF$h+x9S_e!6bb&6O%IF>+2Zc4a_zmevNTEz;-K|4J2kofYyvH4Vo<=Lz_zU@o`B!|6PxZDf@p62Zc~Il~*a45lOs^1U z#}^jj%J?`&6Q{)c&d13yGWrw0^NRg2CAQWbN5%I#VNld*hx%x(9ohvosEoB*K`c>; z4KZ68e)6dnEcCV(%!$jCpmY39DY2y+WiYC zZ>=Kjl3ukGIbUjrMtwA3U7^Q&xoWFO3+2ow729YQso8R$k*d=2^f``qVwyqj)HvL9 ziY_Yp)L=@lDQ)in${Wg7yMvai8{0K@;CruM^ZFtdyzt7tFBH)@drirj_t8VnTpXDx zZ5(5yd+>!1bYuAqpZC!X#ZG_rd&>5#*{03k==J(;Gj`9>=}%kC-Qs0RZZEz$?xkVB z=6WM(ytCyNuhKj%Ep#twjG5+<4)v&Ve3CBHn>atsNr#}tcw>>$N49{JrHD7DZpL7f zBaFZ*ucoW9I6bK=X1S#hGI*CU=xmB6l%EJUZB9H*_ep2Gq)B82~ZNWYl_D13H}$wCV8}2Zyi>eWift8AEmGHtt-;Un4TVV4L&l~<9J=GbPt}f z#3^_r#L0n_f&qNzbHJsS|po(WyVtBaL+?ak$ZOE4gp%XMc*`i<4py zxzYN@ZNwRNu?sOEc6B&uYRpRKkvhw#qe#zrUPn@GIzH8r&PY$EJ+UN}co+XjIoDxb zs!SK7B6W)^@lqO^ZpWDPQ{02E?B?%Sl-@{Bk=CTPaW`?QE4_y{={D0)=V)^<#1e}z zLYK4>oh*wzk$f-V2P>2if6$Vp#jFZU?ihl7J#UG^CbJ%dmih1jQi`J2a zzg9>wN>-cs8(9X-mZ;Y@wk3M&=pu|&8@tJB|L)6PSBV&{YaER|qDQ=n2{!W~F-b3< z;9z~?Vr;EXd`V1+zA+8^8RK(oqpuH$$@;{fWx)RhOpM-M#033~B*vxRB4u_&D2*MESL|fumBbX}6%}ratz?V;lVr^Q7P9Ms@mMEs3|1F109IuRfblQ#aVFj%BHv-oLJTlhP>V!0IEu?#n$S&^=?P{cag z{I>sN)%P?m|Kpi=AG+OHRez6%s%a+%A!zH{kwdAehiyTrv zQ+0HW2XMZ71Zt&pHK{Dli8HZTLfe!{`wz~aCx~gXI>5`* z6IPP;QltS>-D5pzk2Km6;&rbUlmyx3ZVwNqH%ZG|s$y%>XR==Tmt%Ok9-q5CP9klH z)6>NmZ$%tV+Al6nQ>jR09~5)s2~+p`^u!+3SKR!kM?Syt)eYBtf8ye+XC6@b^`yr7sO7U8|K5#9!3-i1pX4pT1&q=G`*)>37a%-c56_e)|;CUFnRb_|L=*|>2-*PlM8d8g*74NLH-#U|ptv^@Qbw9Inz zG2L*LsJ6Jpm~o9XU*Y;KBWxK(fe!UVnJSHxEOgW39f1#@Fi?25k| zq=uMiKW(tcARl3#!LdE&8>}s!jsD8ePk+;i4tmBwbjTR)T=(LSO6qAfu}lBDLujsP zI?8(TXXINOgzpTpBmR_y_<1$H*O9n1=Eat{%`A5l7sO3Q;yj-jOdJ}A>xVO=lb+bx z8@gh242aG||AO?tOJreMZK65z62^+c`*xu!to4;rER8#}nxlAGBF*%qa?}~F6s_Yj zWyBO0C?Wkl4l8&X-%*5-jujZ9lZ1ZOYeK!5nN&P>&U)t4P1c|)wp1(&<7z@O)Jin! zZ80{gH6Nl)nn&#I52oR%xW^`nM-^=}g7Wrdr^MBiK2TDc&Sp)w1$QnukLerkc=qNK z*!bHOb61>%BCT8@1Dri&-pY)LF3A9aQuVTQ>cZ{lAyb~z(KvVYtkrdVv*7;9`?~VO zX>X2r;}H7XwRiV@mQeFa-&uVYQ!=C^?8L@7P3v7k)8PE*bT8@GruqRx9I6w(PInuI zl-kD@$l2E<6#F`zkMOmL$fvJj95$rMhLTpgPH(J84_Z$QbF$7@o1RjFnl#F4l~<8%Kej#chc>=W;KoA@DKwvad=ayAi*V>|O{?&=@; zM`@Z6@8!>;=~ySXY)8w#T$I0n{7r7r9$%zm({cFB@4Zf%pI-J);^H*Uv!r!t`}8_- zwHxDMqP5q3jtkQhX%+^!F>b?{^g*GWVHayLDN8qx-qy(x<<_deD$QA_rJPy}RvsOQ zbG252tFehVRfn_$mlr}?ch*+^M;^4PS+D< z=|b(XMi0fr%c=|~w>Tz7HU1pi#g=TSU$M{nGcjFlj6#LH?a{8#S6`<(wnPtYorvCQ z<2N!3Kqj^C9Q(*D08hviXmv6rz~BNx$z z^S>oF%Ev^R)c<&_Edc-37?lBkKLh_EGKv3SEYTAEWfK2mWdOXFjOouTR1NB6fWNa0 zxOXPawMngvvG0IyZO}o+?6;Ev|7;=f^SM#mf>NNp%rej(-)M}ESg6qjSfnX_&K$TL zOXLfHKkr(sKyR#+(@)0y_oD2&cB4xh($vF?AK7Nd*)g|O_sWmTC&!f=FZEQ@4>auN z-R5m*T(=1!$#Z$pY6 zBTg*T%z}^RWWfLRCH_C=mj%Et^UM5Sm@SI0TvKw&_ou%9*x7xXt{Z)v75ee-+0&B%{QFOhEblpr0L zCJJJ@e+yhHTLiSt(t^_-=^pE`mwA?u_DmND?47?vpjTX$-oz?b#@6^U4uSNpf2He5 zuSHdyjAdC^U>Xt=<8T}nXQruCw3O{!oSbvahkO6=_#u^lxMkQQ_uq8#`pXx7xMbqY zi>uC+3gLgvqEI843SoiyLTf%hSv8wbQ>82PTM-W%18HST?Qj8JEM zrdt(vlfJdiXP9T5FR(wt%Vdj@H8Kxeqbwu{s#T~&8RrE&i&*krB`hrU1)Rt zu{gb$&O}KXY%7$f`LQiZGlp7`Gt!^YY=+H<$J57#pxVt|Absfpy@-B=|0|uW(S#Cp z7GtfAuE7RBdJRq1r$<4!L&o4ef=cc6Mr&Qu(`c)k-=n>vg0iXBYp7MK7@Z2CSGAUC zMy)LMzoQ{W5$&VD<(T9w-^-K*b*R!_2N`gvmMu~Pjn-L@wHEmiE7DA}u*fw&#+>v_ zdIi&sO_TAVRxZQq7N%?QOnS{}s7Nj8bkYN9qQgkz)4+5XMyA@dH~ObyyQ5E9pAIFR zofg}X)F;hJ$C3u6E@>P5F8^iv9J{8gTtsZ2#@L3UbJD%hj^ZzLh!-hu8^12vm(s=Y z@7!tVnzBBM9n!$`EUWIFyU&u@EZg|SQQzIlrgwkZcHQ%oz1zCBVt>kd*L+^NfU>&Q zyH}h_n~uFZ*6vEH^SV9QZVI`Rb7du$vT3&%+x!WQZH;K@OLIFPK3AG37>*F|?5G0+KqLKk&09F1JZqtl%RkuHs`4J7`Uerqu45BeB_Us-Pm zu~{5r2w5RZ*4D>Yvm1;sj@ptRTQp|GN50iJThLl+_6YX{aOb6pO*pu?U5A6 zc1XerXb~})Lc>TV^`C%^h1Yeh7GtqmOKeVBDLEF)i5lCj;``NZ;D1D`wjion=7(SQ+Er0ds6rTX=lO!r4id4bTa*3RZ&o8g!Kb z|14u5%N$swS-osQ(Ob5#=!@m(FUu?#h;@n#q@=2PpW<5k95V5titTRR)UWQVy4%}N ztGIGwPtP`Wpy66?HBX{(fe)L{rfGGW)^Y>Q-}|EFIGPt)kY7y8JWKKikpIBS^d9MP z>ry|`SsK!gq#Na3g;k1`;x9_%T%a`K9s-{1e7 z{hF@Ztb>*MiO<>;zfpXtgL|A<-W0X*0M3a_5s=)Tt|E1g6W-G zcEsl{PA`%UDFFHd^5+WjOJpH{7p2GjgclWSAPp{L1l*KHSwen=7X@k0{1`}mJ(%7k zt!pWYtw~cOTReOc6Vml~FN))2(p*QRi_jysk0VGYc{WX@;_>p`O73&daUJ%bbHZ~~ zx8FSZZ^zwu`TEHVpIh?j%>Am~)LdZvn+l46CS+lO`Sf#WNhOM;|6~DVihxu|Wlbtj zqNorm80keM3SL&RpKPO>WdxL@N71IB2;@ULRr?^=gUyyFs-WS9z z+M7nYODEHa+hVe5cp$rAOb@2nJ|=Fpz=wF;cityGZ((|uxWNx`AKtRWt9Z}S^eXAo zm=G`G3#+_ATw|?&W3Kh-bNpzNZ?RNU{8P5+Ux5a!k}>~HG6e?I6ndcBsM1>I_3tL@ zTYe6=nQR~i>Kub*z<(JrTAkJSQg5$frrxnF?vMUTiOB}4!a~3E4t_8^cE`hpt0lHG z(g#=?o5k)}Zgbu7FGu>67;CfmH3k`>26JPS8gw;C$WqY1q2|&0IlW&aDq_9EX*s32 zt%aoBQXd0}1LCii5bs1LS$rur#&*OVvCc!dEmru1xF~)!0yoAY1Brv;JAH9o%qpaa zS4DRWi*-g4J)&BBbckl{P!VgimWBSc!rJ&ctKEx_6_Mt9T@${Jze)IaJfMO&*i;pG zAudr#dc<`qusZftiC_6h1>Tn_AtpIfIWaa)Q9=5R_myHudRPhi#qX7%OFCQ;s$+)& z2pl1>G3_W3bw+`1(N$}T9xQq?`cQsZ`4ksZ`tQHJ!Nel-H8fCdZJpXzNl6n{V+_qfwBeD)@Y-`V6;_bFj}iH5Uo|}j|yecJ3IA6 zq^osd=?@`UHlzJ?YvLj-_gw^1&rTSifb%hi<0n=F~1dLw<7FpcDt% zFGk@f9~FAD7pH4ck(SsV<>|-R2GTqe(CF?$nB_m5i&p6|gGlonr;}{aQcP4ER8Ulu zn2k+Vr1y~5;BMJs^FjZ$090z{0$ECb52B5pp2x*erY+IIy7UT;S7s-4ETmm`vRE@Z z#t<9P!5!%hwAHupy4G8OYMpgOg|^zs07N;G7A(bpUJpb*M zFQ@Q*n>V-n@>G^@KI7H-d(xO&`_ZO@&?(v*f;MXX4)^G45IXAVP@=1uu?10=dp4e; z+xrvx4BU&Mk00>xq=~fKqyMVTJ5cd?uBiA7HXIq7nqH=%drW9~m;4^K&%c9hy_PN@ zx~I7@75Asb7Gjj?mSK`P*5X$dYr>vc>p~n{s9!uUY zI;bOV)>$=f%j(YJOg~wUJELoi#?J0ZGl&W5<Qsdf^EWFBd~baaa> zRex8azfPXT?uFXJA-Wn!jLNxNcQgDK*pJpd-o@zX z?rmbYfd*hJUE%}`)Y;v#g?I(}6d3=WI`}iuRfjkT-L!Q++C@$5hc?Dc-CkcAFz+Q}{Cmn6{d&^3HmQ>V{H~aj zEd+dPL*d*QT?>ytQ3m|GlIGc{8)j?Nz3@EU{a0bINTXh|1we1vf})=c_z#fP5Du0B z|6!z6az;|}L-p^9Yy9ruagY3A+dI}hJoK?Kow`SL>85?W-1Hs|$9k=Kdm6U(Uh^(A zE=ixZyh+ni(^|HqX|8YcFVXU%Md<+Y|F9zUCV#25X(H)To6`RHqZTLPc{!_z6BQdv zj8Wl2;tbW%lU!SE;~a9&>6F`r;=^^%HB$0o^v+G9BuptfjMD84Evlq+sNqH3iN}>1 zt1z#Bo}FKJ^uIa3EC7C)U*`XZ+2U_k{JrGlpDud8;@rPCUBBhkR=S+Gw<~$Q_)-V= zc*Tfp5s;4eIHct@OyIDSR?AU`R+h^~d&xc$>sf|%(u zK|0^#ej@!&M-8|w-EA#tck>1DM!Kl52)Hy0sfo+eo1}&ou{CL~y&+AvRl1(^lyz|u zzKBE8MWi-n#Nnhr#^Gry73Y=hU(8WQ58vblC= z@{(4ve%UN5AjKZY7bN})wJbSV7C^BqBcOzzGXe^XYA*KhU+X|#Q9%)qrMs3YjX$8Z zTzU#k#0j!Z_}^sN0Cyu4#pMM>zzw8Nl*IKiYr(y!R2G*I2giW)1nH?XKh8#t^7ImM zmg+c_be~p65|^v-A?dD|U<>s1wat9zDwCcU|~$f3l@2g^p+pf+j!g3c#^ouN^jsZtGtRi)~1h1bFGV)utbB| zSgyf5*%$tD|6KqSDJd|Rr2=gWwNmX=X(Q`d?nczf`qKv%q~e2h@ft?z9%FI09vX-V z(MQ58{k?(j>>9tpUk%X~+ZwI|vAvP;0anCjy5dn|^dzSEV|h$khy?;om3uf<SXaVVt*=V?-RIg6N$tQihIkh!T{h{S~9RkhzdKHl@4cQDx-X=01*LlpRx6 zoGzxkS9y8d&dRA@ul)KMzPj##%kI4xbxw62QLB}D*`ls1Di!O49!m5@J7qCQ2KdLJ zqe>&uNwpEOY>APmEu8ID$MC}YZzx)+bg0al(y#D7?Ji5z@5-viuiyBl4Kqq^n|)^= zhSnTE>FBOheNetv(IKoqsPQ<2W9bOAbDL49N-yexoCCBXE!R@u=-XOAthB;yXt7adValb4WEOy%P@#Q# zLdMjefi}@Iy@2-mIS}n*U_l8mKfQrkjkZNci{(-4%fbR+U?Kg#Z+ZhYdZu2ej?Oxx z++;Nu4u? z|I`|d+ANGTx~Mk+-Smnrv4#HjAoa?ZrbR6L`kl5jXR;{0yU*9}(&d~nkMwC|^NWvv zbjNDiEbj4E`|DYMU=%m~g-zR=()<=J+oZeF3)ssChGK-Je#92$r`5y`7Q~CVEiILZ z1Kkv5I7N$_hy&$xB(_nZJJCnAoq?-FgL zgFQrS8(Wz|>8Pq{r61uIos315CgX?>l8NZ2%GNR_eOvU_(N5^2)(Z4j8+&44)W(|_ zVl&fZnE+#C;e2y3TDLes2Jq*}0RMNyX6k%}ebxCg`?(2)1;VM=O1F4I_WhTgS)E^h zo!o(5I#@;Y)K13uw>1ZyqK%&!|9_*kRt3htb?j02zi=E%RK@Nx;D0nigj%iXqf_FM>Hw2qfF|b0e>-B=Jh{DX8qezb{#MgYlU%Gr6tCaR!EMJG3g_* zRFmN{#(xMFX^cVm-X?#PrTO=h0rx&KAm1}%{G%u4*x+z{YomJE0-z3aZP2}Ne_i4J z`i$|99x~vcS&AA8)`CX8WvTvs@k44ZWDw*H!ZIy}V!4Q+GT?s$X|-HzMrp3qzQwoO z|IihW&fDRh4HpeNVock*gR4s$dU>m5D;l= zX1Cl-%jp*8Z=&TdmgO%X|A^J;Po#%zG?_GBv;9fe2(yVTvPF2TQRW!pa+UD{xhJ)W zRuny?Hg^=oi*$<*DDI_K?qG`ls(;Znl-zAdZUH5a8Bw$=rJZ6#QCEe@@0*>c?^N*o zAL@T|epvwgGQZ6KkF&+b%axq;{Tc7AJhyMt^@H!TQh&a@v*h)n%N^Y72#=Juj|XvR z+@CRXF*RL98WM-bnfPs#r$3>GEWB?`Tp2rJrc2XHqyr0pexLjW5Hn>8fparOK&n)1 z6KQvMS&QGM5rVXv=LCl4M`zXod!;wgY-LQw_p!Uctk^zXPx>mF;$+f4W1n;(X_we5 z4kw)wFQlnd98|Vn$zAR|>X7~Zar^_7SKQL@(04a(x$co4VoCkXgR1w@Qm_Uz%7A^d zme^ee%riwm`i~-DSLEcqj8aAEb(BcTWf=jx$-K2MA!Jp7`K)G86f6Q|QhE|K1&csu z;*`SkJt(s%Tuf}PINeVAL{a=j=0(0!=25OA4v2yNL3%pP*B>2JdyzO@E5k{DQG-!5rjb7PQ<{cvt&IgkGEVapOEYsv$|Fr;Yk|p~7KkVIC zw3X%AKKkFiIZ0W%V~LsMIFPQKMXy zi~#6@JJec+0owbX7^tJo_$<1bgD>=oak#^tiZNb)t%#ir_Bxgs5_@BLj8KO=jrImH z&0%pM))=Qd?zMwAF*PQ|EMkHQ+F_Y9bi`M7j1Tdzn4kh(jgcs7DBH93Vbs3VZ}{LZ zXl`!oY$LgK(p)D103ZNKL_t(fTMBf>N@b=Jk3@0oOMDnjUMHT9a+~pRw2E=WpQG4i zcqk%<5+_BIqY$x0FWeU!qdT#EtThJxBd0w&M4@_A#71o)maE2=_);a-_&_O^#%u+o zFXB~=c;B-UUXQ=oO#CVyQ9*h@Zxv|c4i&^Pv8PJXMLt%A19ei3r}8Ps)6G|d$zIl$ z7?y6)mNYKTQ;Vwf>-_s-qE=|oR|5veZaPqKV=T~`;xmfJm`dv#TkjEb**bR3eyiW) zll3=TblpPItIc0DKP}6I=qYRW=z>;~?x<9(zYO>fLOZQYKnLZvLnjrsM;DdGqO+>V zyuDS%5*<`J9_>{cg|=E7f-0?JVE+BsT}A|Sm2I}#vTo}q{(TZ(e)9JPw>{0EV^8|` z&pOlkz|xNj_F>EK8~5^an$AqOdY`n_KYfMgJe6)mxs9<0s?t_#NYA7VR-$WKlujh} z_K0cd=DFAb?M&1UH<;d-^x& zzO-FBjnpL%u&E@I z)br8w+d4mgAqzLYuR=4fUMD-Eo9-rKoL(^n zz4UiD`Wj*n3^XG4!q6CFUtD4b`?8|$y@~Vp=cNv37TkRyyIu0vO-I*Iadn5=tA-G( zW0k&adm&D4Ih@=Gzs_wWPO`?+__IPqh)huxCq|VTT<32(5Wk6by5Lfi^}!5P`V)g} zi=jlNUUnzG)6*z?te3vTJ-VqO9?&e%PooWZKu=4F-E~)k`FbcrFFj1aKlQK?ExK7n z+^AcO!$LjMt@u%X{Hgx zRPACrjIcb7LAN+Zf0+WHGq%Kj%5i3l^bP6tbY0p7zl%p>7wVe&H@E9YbTq(~D7P)P zM<>Z)=&Vgllo0?IqpP~uRkm?|K-LP-3jMXy9{qKQLoratm?yIcEFuQ$XbFzh!D0;2 z!6I3f&B^)ma}10Q-oQXZ4Msop+M%!d__-_#=WpnqN&KUmT9;?9FAhVysB#W!)Wkuk zQ0`P&$lv}lZ~sZM&Hui#P5;yW&%l2#8Sp<*Ch^}LP5CpY&G^0ry6&g$N(u)0yUUR@PGahW2R=V|_A&-o?qubf7?`eF|+szx; zHr;bg53+5J+075rus*%hd>)PO__*Z{G(Bcs?w2%=vpDyATAs8rcQm;VtWUoocaANo zKj}5iX$I*$+(T3f&y6r7?iW>WZ-PH_r_FX~ZHOVL-+x8OpG ztL;*F3MI!GQ8bv+p+*&EeL(+hp3m$)J{`BS@G=LIc;{3BRwu-rvJMl;74mf$f=OE0%TTzTwWPalwOiajVn@Rq%i#xMM~mQVyqH3l0H?I{)Se{;u=(H zmF^>shyigi=~+wD(`cU-$N9uLs_lRWRC|kbuNsqxYt{LLbXQEs*FSx0E^%}E#@D3# zboUi;dv^L)*1w(}vDnwdU6y7*C8nmYFx%?5k2J?BU*dzbHhn?dZGF6mul?Xtd~37M zuu6luSgj#_OWGp4ST@M{4h4$ieHr0_s8;Sh)T+o!?Az*$o>3QFWwk~dai>nUU^}~O zAx7)v9ek^QOv3!UEPH;w%PB5dyMlYF+C>7Ay{Lgu2|<3 z@8Tg-Vn1S1?4mp7+sVgR=;GJ~-Az_T{VjujHzG&DYel~)>PW+zo60OAH$8V#^g~7b z#x2C{ajy=d+F4v(#(fWIe?3rc!(x9|_`5@s38k={4|DykslsU!GDyoE4|3Al;BB zVz#YD+p?qZ-UebEB3R=h?I`Y3I0DJ=&)W)T4FG%4c$nvlZL))l9sk zSR;jP3;XCw*^Q-_$L++pcr5M1yp0dcxOX;9!#D4}c^R5*)&t2lbtsd|*Mx;Ws8bw6 zQLmM;=%U9O|RONLdZ#d9u*Y-ud6x z?y^>oS{b2L#p<>1Hotc%g=e(7yHy_soO|r1!@r`eTk+I*h1|&Ie|VCXTMhGXEK7%4 zhD~W%`X}1DJ@%2+H?GBw7I+q4nQagBNzbOU(8Zs!O>_D+|HWwMg}nEFP#QzjMSsIF z!Fg85Qr^4B>i=p{rc$f?&u>AK%{E}0?=8kgi+zUge4bt>E%iov9Dhwuxqw+ z(L}xWc|xg!Nf@E44Dk1g-O%3vhhd;0_QkN+E)F0@80*&|j^dv!&~r z-8LS^OCOwh$vqpH(dCA}9(@;;?b{A({V6VwjSc71FfjHpob;ZX(xJqW)vMo&AE-jB{c#LLQK3~{N(bOq^7D`OOD z(?{tn;w;OJAe|Xg49D^5SB4Wa%`lwwUwRsjVSX@D7HT>WrE!Ta*pmLB8gCfqMRH?f zztozROVg9NPSn5J|M%_wiB3AiOK5GgebG*f9nnP_6J(q9ozYFL6+}-pu{&l)ojK@b zgf2w?%*x5 zK}G<~K%>&gfPb+QWs8pKGT?s#S}+xj#4a+g|8dwNOaI@9pJBb6ais6H#8|A<>{zVU zY&#k7&j^4{jXV1F!1*||icL0IAkLu69_;WC0^ge=@|luYWsy=>#ZL$>iZf#S6l2NX{*>!@2E zIAhn|o1YyrWK6GaJGI%=xYaw&FR^W%=bDeC;byNk??&U6^mg;nG`;V`mOs+;hedVXIYjz&lGu!<8 zga3!~69Mqk{51bR&W^WU`bhEVt9E#^`Zo_Y{ds&VYxU!e5ydYTUGB&pADbBu#+{^B zlFPK=+E zu9ec?DVtb!M$tWH9bb6pk*7{-bLP!Q-}~bA{kDu-dc*R)<{r{IX_3-nXw;Hd2Bg>@ zN%~JlK-Qz4emrAO6w9SoQT(6Kz^vc8K*WA1%o71Wh6aX$I0B_g(qpL11BZ4ZPL~k@ z_sTA$?T8VI;tJAdiqc=uMoCaZjx zsl#oKj#sdYsp^T{Ow$41#%}Q%{^=}r7-cuDiS1&#chTG1F@|=-hpidah4?Va<3yT! zH_f*d?b14vh`zDFzQpIzM+bZotp)LXtcgj)>(SNk@p^QK5yWq!&d)I<+UiHV8kNxt zMNw)T)<>o+SP;$Hqc$=?TpSzJpdr?1i|;JYdW&PB63mTx3h*r4V@?uu?`tX$xUc*Cb{2VPx)S^&k zeJGdH8`TP906Hi!&i?}bJyn{FzN%tpVxSt+WH-`BV?VW~VL;T!bo5bUXY^F-x9DV; zu|#{7Fk_$dn~+)eXRuV_cZ z>Xs>?y?Ov z`leUWR!_ZA8C`Wo8#}8;sR|`B0-zBMn(`SS>n)I306vms0NjaXYCVhv=A~OO*CJQr zBhRL5&@KHTUqg0Ex`gziw;h7s>3E-@ed?OdK~1Vjd!u`5Z5DA$+9ka}!GV^zm$LH; zwk!Mtkj# zfT23&0e^S9qMyF87X}*O7z{VmEMi2AibFBh1Q+2r6XO_6Fx5s(jA{Ns>}2ma5j#0r z6&rV1JZ9a=ymZQ0i|_k_-M+s5jw5DLen;yEOV*G($@+#fXgWRm0YyJw_WuTx(1W$1O(rGjXuW7(;xm!ihwO7-0gj#n~~5_|gyq@oaRnGjUTiC?cNo zSxiMo^DRTtCw+m(^~^rMqo)ECJ0zxHlOE{-EYaN}d>&n6JaL^K>3Te?%A2GIGcR~t zqSh$VwK3c%75l~r>|{vVg%~22CjdS&1g+A|x?)S55;df~ zVtkrJx-R`b^`^Np&d&XX@?86p^4lmot9oXuN7?r9`e(Opk2=k<1KMllx9FfcCdvrv z`_Ng9jp(i}W}t@}ALP%0=%vP(sBf2Iuhzl<`i?Jo=U+efzX&wzfJ zy=B1vN#vB+6HSU^PqZj>V zEmH`N!AcD=N(TIo#4-(e0$`gVvd#Y>EVMPUER@ZTmI3=bArSp#68~fKXFnO>?F< z^IE3RywIZDWwbnFRc-{i?|h#-mE3S!)92)F)szmxMEst#LXi!`sfz7Lbko}96#PM@ zD=2(kZJbBp6FL<9jiR@8D?rhS`WBu{aeIRbms0$J;e|6P9cy&qL`sWdhoTEWnF-ba zXFi$%|23oiznq^4fS=~4`5!PluD`Uf_!rBz&RP50hnoJ}_j79v;Onu)uNGeEh#v2_ zx1=)e#JIRC3xSH;(p9ACv1gn^8WA1Sm83#v3epq4iRoCHn~|O;9qd&>nw6#r;tQV% z{K|vYkxH_RfV4;YyB|n#hK8tJ%}=Ezc^rwG#haZvg*>FTJC z(@Dj#Q@ViECw>{nlWvJA>1N7aEIXm-9@m^O@9>|Ux_j$!H*~yXr|bW*sbs~sD_@y= zSb3wIlpaNkq#R9hc}0M{A|T~~e5MR2&bJ2?lU_jy`PP7f|2%&H3KgUm^Z(cxaEUku zZ4}B{2@2E0GA2DsF)wnjj3~H^7^Nhx!I#R?&7@DXitA+-fV*WW`6G$rW3VSlFIa9z z3`k48MqHrY+jv3;ACex|*}sYFbTJoC=L_(sZkpLx61&Ag_=ml9A*MLf>sTE7$1Hs1J=J)?wQ49DS^8qxWa_#N=sM(lTDmp= z(KY05Z+S@-X|@tm(KEWn0a#ivEM^n$7cB7z@qWDN8{)(0XDmJ{=xZeLQJiTgK8!B< zzZzwZG2JDrm*Mkn7X zmEEAWL$QYFjJA@2s8e7Jx+=2+dMUS42KZwd25DpjLO81)xwz)N7k& z%LstoP%rzgbp~cNkNR4RwtA)6sMIBOL>u*~Ql>a4L`Z21S~R8A*k*nD3O`ulK75zH z@En#Kk?zHO;cuAhvvfT^PWL+t9bAyEfb=^@Vs)C7en)yb4M_WvYEzBfN!40=6Q}8% zZXj-QUyPyTgjgASQNCBfX~nnE@Xh*nHeJQ5ADn&rZO^me#)ZrOv+>U){%!O`l}ZDN z`lvDj9r7Fhf!f;%y>*G{7^sK+FhpO6VLOB4aANz|&RaOxIKRbYlj3AdGS&6OuCZtQ z0@EC%0v9{LXUsi%#o@d^`>+3gX#wL$oi%61X_PI9-HYxc*Te6c z_9C~&q_hnes*IkPWRtguKgJk~aFf%GBz|LiV{mU?Au~y(@kCeO#3Z7@a693f7-|Ud ztijq5FW9OCFGhcxh@(AaDMFv@`meXQ(O%D#DdT$BjCFcs#B6j+x8Z$5%*AXy>_~b% zQ?kXC!Wh!uGOJ4bR-x@k=Nn@raf&YuBmE+##c*P;ba>hu6YO9l=>%m)po_0#7|P;8 zJ+L+XK5dU8(^Qe06VuZEH2*7I({c@^n__a|3hJW&uJzlqwEa8n78lAaF=Npo-xktQ zwRedQs!hQjs%=3RZB-N9)WlvmR&6Yj0r%zT8Fkj6rwP`fcm5sqBQelNJM)O1>dnOr zwQ;nJkbGGN{7Z>0`AvU+ZC#I!S(>|cQSBnMS8JZE-tRzED3ASR>Hh!S>o2Q=Lo53r zrzG~4N&SC@CMB_lOabt7G$}FzDZlw|k!|!F^OFAtIXlbJ{WIX7i^^E2SzXp}57Xe5?vFdU0*jiFd%ndhql0G7LpXRh1hD$CZeX7WE;!s6~6FFth zBW_R;A5d_y+JeI<_*jSdmZCFtD>#j!YxONSn&L-eP~iiVylbbz>6G4POwlEjd}&(2SpU$pOW`Xl|Z!{O=BGx^DchGDX0gkBiXdRwatO2{{6#@Oz5NP?@qk?osuD>7+^|SOEX^1s3nRH%E6r_JTBK?VU zf%fqW(t$oo7m`NAxp6${{urD7PT8>1V~g%K^Q1*b9enD;(YDORrsa z{uhU}>7YgA6#?1izd?%qkhG+Hn3mSE)bUIaP!M~f^uJ{U{1_UTDFaeu-nxSH3X)Kn zKdb$((7+0j9+xrcnWy(hLLjRXEK?Gf5j!Yz3+W53(oJZqP27NXD${+$QL5q(crAve zr${fS<#8>Bs!wkc7wZsLlb+Gp$JjNiC5js@H4iUX;ak%4v5SSoy;i2Bc*UytC*DeH zEG4~TO3mh>p4u!=hFvtdC|LP##U{(Hwn6)e#q(tsUuNqqHZEi|#s-eyLbz>}HK_#F+G+9_SzU=z|XK z(w8(#T?|5V`ezzO>=mP8IKGb;Bnq1f|CUaq^ox?u<0-c8w|3&1>1?Q8uylSenWVcv zwkpsHp)9g+xow7{Rx%u&6d8>^dEnn)MeK@6>ggMOV zLt0=WHoL|UY_l%igl60F8`tCx6e;mJN|l?9)@swEsMOKTsMak#gSL7*54C!wmr$p~ z(Wq0W7WG>4(*LC<6LtFAf~ophE3*(}^?#kTlUV>NP^uEN*kTisCd<%hgD>!d<=!N% z^Hq8he@h?u2NtDG{*JHG9Gyv@yT$d`Io)V8n3;Z$@65I@I_lvwRB4kg!Ju?zI+%DX z9qbc|UXPQbBdyMlnT2QI^YmqE;Ei<`ym056Y#6$5`KtD4mH&TjqqUx>Q(+)c8`Uua zeYH0Q-L$t0`sot8W3ZkM#Blu_j_nPOV~8D%bT$q()~T3kQk+3dv#Xo2d+Zfw<9819 zIQDUHoI~szhuZ<)IxRlI?M^a}*e6bk#mp~%_UKQKXY_+7{dvkCXz7YS@1qK}dc>}1);;|TD|NL7U+QKu>CJpC-t)T0 zj>I3;rYlIdD2nZgOZo_h(qjR80kcN8jA6;lVKR3E=HhZ zTAoIrweEUib6lbxYvNNCq;}~V7t!)U{3fj=o=gu#BXv*p|DxSUmd<-;(2{3RW1FK; zpOB&7001BWNklhJ$Eb-_=%UJ3+4JVf6mad(QFR=Ij;h?BJ=Tc!D&qjuN0mRLPHh~3w#xkum0HLC z`QPVX%80;yQKU@PX7nTQFO7X<3ajH}Z6bRj<8jFSQFBd}Uyo&eYuqh!GUFxkdG1LRwbkV*eD;J?LSd~LI# zSdf3bz?K*+1OCV4&p|TaKa8{>Z8a40{Sd=stpTTFsfHf`zcDiF!45LuzhnNKD68|E zjP=6Kqz%Hy6t`3yRD6U(j(+yRtEc^K^KL^w8uL!KS#5sac!f_}wy^C<&o!OLwnNQs zeuajT^iK0Q8s~Yh`L8s6n%*bfqbco#_whT@ z8;X2G%uuW!u|g|n5Pwk_Z&27;P3%nJ813Q$ioVmeU=>Bv^es4;;_4Vwcn&4+nN&E8 zk~54cypq!WjVtOx@sQ|MaD)6bW)zXbWt;z|Tl{a%PXxeE^V9qfobfkbe1Gw2%MY5< z_JV&kUAKK_YhBITR~ElrbhRUT9q#UUH10(AI6`1>9Gb2q&59{;E~!)WO;?a^a&lgN zx;&w2&yh~@rXbBsy9?rDb0MATBHxpC&$k9#mu|8Nmu0EOxp^6Zkf!9Wf;2Kt zNUxHHv=qc-(j_rgkp3RWrRzw)if-`>(u^39E+kEMNgPkQN9S}iWnIc<7Ts;%<8p_c zbIQ4G&b{TDyM|srbMv-kColi^+(X;^QFC4q(5ywd%o=b2l4j3?NH563wJNa9kL>}; zD<~tqDpLkjp+v+?S!m$PGNnMRj7X?MYlU$yExJ^n&^$qHX$ww1mn zZnM$?S=iqq(i>J2 zrUXceYy>5?$Zk{$P!Sc1P^YabbX2FF=&qBs=w)~7Fi=lFU`Rgo{{{nNEXKxm8i*Z? z&JzLqdk%lHQ_R40)07gs#vY!;SAG_UN$e4acn)7VHx9+Oag27j)dH^*6I~bk z(r(tUhEW^wV3hbZ&A)4!>q%1kO#1Jp_%040zK99lB)*F#g~T_}Ii?Wv3&y${3*%m+ ziG?xJFf5Fr1``Vl24*S8F+opsjh?#T>nPWe=os6yL#t@f4h_*Js!3~PM8G%EqLg?& zwkXCM)=GFL%kfSR$NZ=!ZuezcL%Pi;HW8P`0cuEpRH2SI*#_-M7kWlVVlQ;UGgd@b z(#i2~`XzdJP#@H$dt(5K;U@mW$t+?!o#H_BQ|BY}R_jOQ%}AW9O$^kt6MI-_XKc1IMxen4U1h2NrTNdDEM&9P*C>t3^ak3v-yGxS&?qRah`QPK1+9}>qs9uAsvMw=?`XMWg3vKL~UxH z_CQT4HUmfKtBiudPKdu!`cRB4m`2Gdv8kYr`E8!<^8S3*?mPF5CGAkAR7X^3rJD>u z^&{G=ilOMP#$=gfdm{SkU@C^`Y9EZ$)1kzU`p3^P!4SW|fw7%mVK+O+xx}8P_zO;t z8F3-@cYp_ogB%=J;mbJ6tGM3DaV2q(ljC!2jngf_11^h|v>euOZgVwXk9^{)kMCjl zSx25Su8h)mV?<$FTJH9zG?;X`)@fJV7W-)=?slW`#DlS?Re02Hz9ddkYAVqjJI6Gl zkJ}wYFwRtBzA-V3n4?|?yk>(^;ylfMK)2|hGNst{x)FV&w`x@QxdLp~1Kr zLps1v4A9k3bcp{l9Hm~-8{5*Kwa2nJOdFKMC+Rp^=B4v;SK!?EhZ5>v7*N&j6Bz+E z3bnS%tN=xk)dE)L0e1%M>s7=A)T{Eb45)X`pDWQpwKe(2-{;Rw=%}sLGSB{Z=oHm) zAUYWp^JGB(HJP-(9nnEm%*1vo-6ta&R-sNC2gtHYK9z;&&Xg(Y=Ae}}vaF)A*iYv5 z&%*kC1pdW&;9v41@Q=OGtjIB-DDV9*aI7r+bXVDZ(KMN*?`Rppzmp93PsDo7CX&`^ zG+qY$Gl1P_dzs`v1K!*I1N^tcQd{Cj;D2Zy_z%O^w!}~@wAm5(@#_ongup_Zv+HNt z8Y4)HvdzEwHX4N`8jO-z3&voXrr1HYpg0e!HOEAm)c-=PlVWGtXX(}1#y_T0GOJ=} z@g`G_ZTC>sZvD2NIk<3iN!R1s?9q6c4_dJ83C}g1!?u}bH@`+hoAh?`7#hFwPRk86 zz2>XjQ#Aj>;+C6eKHI8X6D`B6&z(SSSswV`s40yj4aIq+#}xa5n5;+-;tOR?A%)TeMiic4Z(;a-%yYeL~rO3pK;@RyV(cF|NdnxLKguU{{b<#Lv@Bq}N-rzSxasrsqf_(-nfWU+z*t zyf3Q*{I7Je?@39C2GVpl*+kkc4YZ1Q-ot{leXf@vjc{^$jWp1oV>0P#kAb`5MQdimJp zRbL!dewLQ}X1*!k4$z2lOc5RYR!Ghzo^VXU>7V3Hq*$+5Es@UA^$A6)8i&Dg_1 z?jvTz;c*DAa)xs37RPyn*uw>J6h3jHG79FEj4#=hx(fzgJ+y?}4$YIbrR9R=Q&ixe zKD9HkIR+{v3Zg^oMXZZv4-$)GzHhKNnru%zTrk#1VtHI{2v)?%=u51K5vE{y4Auk7 zqnoa%jtsCYjt$zQC>o;{o1`^?BaoxSxad zWo1kDOfs|gf1Prj=eW0yc=MMyUT^nvi1zHx#~1bmF|>FF18XxBUpiB8^^+W{uX zHGx?NDc4;-5OxH*dVjoLTvuGeNB_F{q1%4T_AmaV^7}7SwW<71u`9*j zHVt(x#aCiiFXQKqbtN|W(iUv?dAbH$d=&?w-Z~$m(bn__G;7eW^f{8V0IF=3f$gWL z(a~Kp@Xw)tz0(6|XMn5FAqJ;M96?Q$1cNX9!bB)hwgDQ z+NZzSOnSrg^czwS{mexN?R!*vU7_R%*z(4lJ1pORKOg1=lFC7rX0&1rCZkhy@O`Gp>vy;z{BNCwL9pd@nx0U*r3h634nQu0c(l z7hCa|Yc=!!y?69`btT*Fb9m=TpVQ)R<;RzwMMFcp-25s{`}s-pJH)Lz+lP26j*OYO z&++adeq~2Xu~>>dh&}Xj8nG&NHJ>OrDP|F$+Sz>kTRYu}8>~@DJflG^dix;eqRC)y z;UR<4zfqM3fNaz+b|r4rJN+3i>FpyttXE7W-KI~vjJQIVm_)iti*zn=o~9T}I$KRT zl{iI{(WLKLX*BjR#VBHiSw@kLHZMkCu;FPZbSu@fRTyOuww3AtSEh@#A+@!O+iBV@ z4sM=C@qlz|^J@A{ow~OFSwwB&xwwcluz6}4gZ9dem7PH2(JoqAhIabffOcBOPH3+t zK9cK4R}&r8Scem}v`KcBY(YmYtwCq4ttUEb?GqfJjluc;m@g9z`Tf?)*8KHjZ*)-O zn$qokO5fK7vhu%=JgRhN{v3$CQKrU;pxWMOROwzcsIoVb@|Z6x{>OqcbLA|fJxY6? z^%uL#;ipH**Ylk5P>h+f;(s{SYcd6EG{nwWt-)dPNMH{C%f!k`+ltH zAvlM0y9#d-J1W-`FRFGtagtW?0EJo|qaS5|(Y0^}<{R|7)!&P0`1D=lZ*UGPSc>nV1x8v}*nbc@~`YmS0$#F7is<+ZPq$^@< zoQThSC*6n_tdH5G1R6? z^b3LZ%?s00q<4$0Vmhf`j25H|otCa9wKOVDBW-V0x`1?8ToosfE{!qicT_i3Us?WF z2Oj&#k$Ww?rqwlneDg0)Upr*W`zs#*{FHYOYdK7FDJ-y2vo@3hyJ)5~OPA84C|CSu z1R&4AHpgD5Bt3;{*%44BTZStX;=sJ8Cs05a*)eb&S}S+IoC)v{TByolTUx|o5%S|9h~Lz^ri{+4I|EwR;7EVac_|H%OOQln2%t617oT25+I zZmsMDSWi@{(ST~LO0|IPOaE7$w3K_bJEM=@(Gvq=pil8D!!5xGds;zkZ@fD(!DLgh zbIgpHxY&Vev8%oOgV^1{aUkA^BUIvYr?`vQH%^M9@vfg}jcekJcmTWFstp}a9`fd> zV<=x#_3NrdYw&K#bVo5(M^{AD>x2!lTswRkIeq=jQoq7W zQB)xP+h#%B7VFhxkyR>4cgAO$iHp4#9ZA3UM*4s_DJJTIbyn+6x>y@Mi0SI|!Z@Gm zhe7EXgGmeHww$`{ZrhvsaB8BtH?oN*oN5ogQv8~_Q7uIwp z`srmfX@hzN)JG3((K4;qpJ-p2;T%QmgrTa<#>8l64@}lO_915J;k%flkMCelec}Y{ zX`s`IJ@t?8;Wz`FhiUpc4pX8}98GMetE(`=1oO~Mo7ho4u#ZEO4rEQaPu-UqG-HK2 zb$HWyOG&TBM4zyJ!aF{Afz=nj@WWRJ(ew{!RQd`+6Q4cCDm23ZALL1%F{j&0Z3(>&<58+P+ z*%_S-mDB%Qms%Azq$kliMw*Y#9w-rW!)!x`QU*Xf{l3Wn=ztoXQzgokrFzs`lU5@s zx=(HccnMp5=^<>iB;6(-z^}tfuctp_NqWqg_{fjb4RXuDVW>}k@C8<-?&%uR3#lf} z#_#lZ9%b9djd3qkUA$hnl|nBM>BGDI{_?=%XHoaYy1O^;BrE=-iMDF=MOQ6j2zo?Y zGvtw@9pzfLDcD~3*hB7TKbDwgU>t$jhWP>ZjP0C(z0HW95(k(ZSK=fy+&~;@cKinK z+soa!I1Y;IiIXgh=Wx9fy-qBQGvXsu`iWJzD=v(U#PNP%8~Vqu;x}wP^~)=3PiNV` zo_^`0)(m)MmqEjBreaw<+_Wdnuf(0rm*Ahy_A;KcSru`Y!(w;hp&0AGh!N4`4Lt2R z^N8V=#9X3ahWYr+jkuQ*9OdqMh!pl52GLK^ryBM+-Hv zH>y-S1EJbJ@~rYsG&SHkSZ%t>&14jT$ZR zALW0o`q&A}G&n>)4@|@=+hU@u_#Y$#|E$n&j&bri;t(v$^^ca=5@Y3a#e#g>I4nt} zJp=u;)aIBV&-kApj}T183JrFW>jbCbON}u@ZXNhB)+xqr@@)S_*htJR0skwgJfiiO ziYLta&UOD>y~k}^_Z$7w9m@K=+;XpmUwWxnPu;&f(0Dd=2c{>Qo~FLq6HVLG@S!(~ zH`4f!4~pGsy56$l&uKcz>gI;5_@`58Ua7A6UD6er3?}sz&L;gsrB{ft%5=rksvS*X zFRkM~3ROBpC(7>EB^FZl7ro=Vly5Moa6T1HMwC5FT-tWB2r1(%d*bP9}|w(disgW9%3wlHQKP(~YEiij6TFjjmFjf2d;rP)=QVK!FzpK9<;y*ie`f!>}n%FdLg%z+Jv(5niWrDx z=>e^XJLBmzhr-&zQ*k7Yb7wk-#?vRSvVt1VDBGykSFHLi`{*X*{C4=U8^%eOEku8(l7* z+-v-YR31_GlbA%))HFm-nh)}^b!hZzx(Q9{;=8iapMiaIx)}v!rpVUbVW`wv2H+hu zqDf1iqqVy9D%wQfQt00`9!7^4lpaMV^IeQiMx-auMO!DJi}iY-i{|tc(J6+Tf%$%y zTL5CP)oAYsz0pQ5?PUi*KIJvphOJsyB?F5OQB?1FISlZAY_`&0u-<0>z*pWhg0#Z( zZpI9cmq^vB^m`O7w-46jbah=+Sx!8bKC_Xs-@7x~(PC!di?Ts%ethNL>k520>R(qs zyBEsx`y$$B>-R97?3{o8F&pEvgCwRH;4tiNh&M4OMmhugnil5~ zhuAT$!HK52o;Wski$CB;4tF~i+CTnEEc94BgWovTE5vDWa{LvyxG+8^PK$G`L0`X$ zEqEk;EfHtL4N-;XjgGdw|I!`HU%Q@xf1GvPu)k6Hbj&MUK;we=eQcxo)L3b6Vv)-{ zN<0#WE8=m7#~k8^wz!<=5PQcwJnL7EKv~SREAecMGluxLNzt8nCdR42M2piVJnk(^ ziSaQ+Pjqoc>AILR09NXg{!V&LZ=Vu3>1i5n*TZw9oAinuiA$nuI*)XrHpUTWi5&Kq z)*3||B^g0F&XOU!X}L+<-Ex>(%O>cf0+hTWZ=IOR@r6}R@qvz;BT`7KDWgLS@|DNT2=!1 zAJ`n@Wg_4(nE)7vrDaj$}I$YU_%b| zvq>_aRIA`UDrdJIUh#-&$6Rs8$UTQ|yL{xOI4&%9Ag)ZOKQ#vf64P+Hvd z4E0qOH*HVD`(7{JNaG_uDt0R={+InX;6KX7w2ZU3n}}B-i3E4A7)V5$tB=lOXbTkw(O@=US(R@jZ{xI zrTjvwx0+rtjq;nKSK)SqVEH}3ymzwlzpa$||G$5IO8|VkzFq$V*OblYudFz=_Qa>x z{%mODwG)?GV;C=ATk(F`FD>Z5Fuhb+6*rTm`%Fab?IT!6mJUB{Istip7Mzx9qtnANmF%Ek4sY~0xq#OhYF@l1RRjAgfyyn zL3)yOiFacL=`S$=(rHdfSCd-A&T$&4U$jjZkj{!*<9M1w=X4X*9jgCa{ugH)bInl` z7WQcM&p*C@^YrWP+FZZ#sn1V)=g3x7nl;CvXrweE(EPtR0;1#yC@T>GrJitA#9pW` zIRd^W0-6+bC_TPEs)T1yrI4OPnSx$up*)V4odmh3xH3JAwpzrG&{3mB=%#hLAHCI^ zM9fqd*J71z=`Jj{HEtm-wZ&cdRBgHkpV|_4;S(D@O8i)@r{vlG=kQ5P@}gYx_X<9< zHN7q~^iN==I&Tu!+v-hxnd-fXHJZ|!*r+L9l}78KJNOjCm{`f86}2I_`$jr2DRHO^3sj-8Apwl_B>VY1zQinHzOkJvR1jlGGvj!N>5BAzeI@_k}+{Fl+h=6KR@ zY>Ax>!j_nL2((?zRizi|zTBb#5 zAyWLp_NYzwPatkw=6##eSDE{N^)C4L!qnN8!4TmQD@Uex%mQP?DB7c^?p z5$#mPaCB2=B1Q^RFiwpbn5bjyfvLJV8ME|>5&_^GVlRW@yEw=YKgIqrG|nXUHNrVK z)*z>0H~nKFCg|zs#P+(!0dmUs)cpRkqdb!H8`;6q)qgSoK3Aun^t$!&8@ya107mNO z-_+jo$zw|nX4|Z<_F3JHnyf2H}Ubh0vx^5-}RMfK^ANQ!YJLY_-4 zQ(-wOv`DwgGxuH4LPyO=+FFHeiWZ?=DdoSt0qJ3MFvNA}WSGa$*>DrkS-bQky4dJ2 zbe6P3XG_yF@~A)#LmOg^JOa=YZS~d;Ep=7!pYXp5HCCb^yoaJ~=^4~p?{3-Qa3|JV zot*&bP3K{SU#7ocSz7EnXlbU+Xz)q;J=$uWc9&>rztwdueSh254`h+y*fnqjh&Iv7?^$#Vq~f2<&RG zi!tA*_&#xvajwJBChAFiH>SpQIKvTc!>Q)RKZvtpzjz3jJJZv|xp7R~hu^s{-Y3qB zA6Smt{VM)WoEw+M77TNPL_A}3RAF5FDO$63{QI-NoSU5leoe*C;F9Sl;1I{j_KXZplU;tzVI8*#s$u`}tfdZwQfmq!mfkS^25Sb3&- z4C(tS()WmCV~`P~6D&0hbJDYh5VL#~gE1i{r+K9P)2RlbuUQ77qaHCBW!9w}CfZY9 ztZ=3d_#_U{f^uBXWz1A_Q>PMU)=lHmXjOK_hPY@y1U%pcn)<3 zr^QYG{(k}g-_iJJN%6nI^5SJQE%enl!2fibm#S}mvjqGHk@|^r{(l4hW0dKFM^sxt zVNb2&4hpq8L<;$TAFtz*-RR7tO@)M|j z%gpjQR1{)ZA^+Ti%`<@QfBb(M_=u%->(0`YwEo7$5kv`*Z1iK7hcqO?Ubjj zi4o%I>nc7f|D^>3PVrp)C~ihyHR-pcW8!CVGO2s?N#~GO#;$Q9X|X?~8}Lzf1f+^M zHa(0PIrU%b=F0?Wx@`GBG-V=SOHPeX7iUL6x-1P6#2wAQ5Tw1W5Tp@tc6yR@WwBdK zC*7{MAbmHEOIMSoni;2)Mo8&=(nWD*98Wqd_D(lZ{ZRGq%5QPtQT>lxa>|vhetPpm zx14d^D_gp)*m?P{-Z`x0$%>ld5E;;CYio7{G)pB%K=K&MHJ9oFb7)|-ikK?{`KPgs z^bBfB2kjOm2SBxm{ZOqeJ&lTz)wo6KCU;Wc`SA(K8a;s#^C&Zl2-2JMPC#BD)F8e0-lH_> zN&*VfLhlM9gdRxfBwzj)zwcsava=W2ne6PzInO!g>33|S2HlgQ3CSnLqyI6{C_cM* z^+P(Z&V?n$5gnj#)?F%1oJB}1)QqGw7jMG zq&Z;LrUW+CSSf61Z1aSe`L77$kYdtgyhX~SdJZ1e)ul2yNPX<#(Ci)uB3dY?LCIxU zTXpES7%3}ik=pC*#GyS&FP#ZaR0z>JXlHTan_d0;?Cmo92lh^j+++3TDVvx;1-&lE z)UM+HOl-A)DhDXf6^e=a($LROZwP(cMK&s$MiPBVOomBnvx;@YyOS}A#+$i`wifMPm zeQjIcP`ilU%niEj>ybt6(ko~Qmx>&3M*F~Ti z1?8x{c>*3)5GlM;LHUueC7X!k;!J6ZFBYI|BDp+YTE9Y{s-%1a==MA{^JrwDxc*KA zC~;62c8P}`yIM71R~aI`pv3>o!3R960 zvR9V9X^a3SP79eO_|Y;s5tqUb=E^zz9(h+MhE+DuJXov9@`As~rvH0z%e8FLW*fxv zYjWy5ag9z)gNsc_$%%KaRVRD&Z`+{txfz{5)fh``mA$?NvXqkmD{sjC_Bl|Eq|M^S z5-E(2rmqJfnMFL!?U3m&ywysgSf5VPKc0ptUNt>$n@mQXcXSW-O@H(EGyFWHE7 zRN%QNLlBK8<>o{KsbQZas*PN2YcR|(;IsO{&ly{G2wp&Yl20_Y{N-<;Y+T2RhIm|I zU~(p}V%5Vtm)!671*rmkYCEzboe=`rl7*HSHWO_f!OevI&DH9yXM7|$gk!9pZ@1Z? zxZ*iE5IV!o-sIq21x5B^gw2hxH?!w_bS*_U?a4I~wvz2@Oq^H0*OI2jfJbfk%yP^W z(1|Nu&)leFLf_D$6p0zZ;#i7bshOJCG-(78oD4>D{qe$vBC{q;xL>J9jP(VN&==L; zuN1r3E)OyiqXS|s;liF{qLb3h&nCQ%3li}Z_4Gfrpxt#w?`Le%S4IsH?(n50Q9W+m zl>Gn&#G>!ph+Hvla$QNoNGo*F*9u^r zH^H<`DW-{t5{uk^#N(R!2Jz8I&r+baU^gYYD%G>Ou=El|%A}nZT?2ol^yS|YyZ9LV z%V%^~tq#xM&gjolmN=hBcRu(K*W z^_!V*0js9AiHo+2Wfix=2QQt`Pz4>IZRyJb4fOnxOxGZ2M4_;B^Nh{i%OLKb^={Rd zw~o_`rqr)=;9&%}wlR*N z-9I9EO=bUCVe;EU`7MJAV1O94Jg(iDZ!l4M{A5&EJmc{zbjZ4l_A80zYH;+%>nv}b%}azoQQK&mK>7_LN}kmCWXst zmSc{$`>_?AFW4a#$dn|$p^JSM`Nu@VzRVzjYaDJj!zu4Z4jFN?G`M)T#x7saLQ_j0 z|D|SEHdh!i{;$cCF8^W-AJHQPpT~=g+?rfyhg6yvJ z`c&6kGlO68L{Uyr1J6gz;~x7OdcHUY16Shfj}ukpSb?A?hfgm(hHF?&*ZTiR^xQZ4 zcjX-L79EB$gDy5e_r7Kv#aOK(P@_^pgqwu6*ru`<-!geaXnrThlq7pBCXB4KI=C}Tw{cjdA5PfJYbQBx+} z*n6-vu$xvh2Xgc?k7lzNZ8z6kV**0LKsSxLd56yB)0Wi^)_GIQ8Fxoy6%IuwOi)>usL^OW_ek{a_Yg5J^&yvcsj_#V$LmM>dG zg$wUkx!RrCX=qR*Y7JfYZl@Z4DNawz=Q!Ybq{ymujiWLlV*Fofu{_vRsR!tnJ3tR! z(i25fESDWWM)W-lDB~9R_zLJ;NPzqN8EcL)i*TIS1U$`I1^^yko$&BuWC8gT)%^jo zJG;VN(tgQ9rWzThOU`@>CiJ53|NK#ns22*#JTTWEWzzlkV+hOWIp=AY|09Ii>c80f zytOcc>j+uabJ&sKrCji;9w$};v)G^8B~|r@*p^4a;8Hl5X{!2o8nuLdaHq(_i%I^n zwHe}QdY24)*S*QpfuJeKt?5D0A)ZXAf4bq--p$0&Ss-ycAh8li3P5U_q1qpc9vCu< z-Sh!Qmf1K@MeQ5t`?hfu!}b`r0lKMpyrlLY>qQ|C2|G!+rb7Dib>e0203V~O-B z!#$Fi+f4gw+w>g6$Q$RwbJ|TE*92F4KqKQUX@xGnSM`zb4&IC#>)Al;@Rs>1sM7jR*xj?qH17fIi47vJO+3PS@*~w|FYl-2 zqE4uB@};vfZMnKiR(CJ8_FFvTT49s?09FV$2sZQ0Rz2tc%BIVGKO5;P zvQL@lH^oy{(gkW8vQ)n;M8*PTali$e!siu$1;Q?+MZK0gv1R0nuq9ap;T9l1!n@}l zi%~VJoE4Q?s&%f&{Iee+`T*A%-gdm_+?z4X+ZCF{yA*F}=9pN@;2jRX?qSr8(C^CF+T5@Q#L$W0 z1=s6qe7Z2J5f1892&3L6dmHYU_owHR^GHxng};osWV?jkBAnYwa&Etl*%TIG1Nw@%{kt1(qsfj`eG))I+f<8K!6c&nXi;piSYjY z_d(uGc7kbDPj$oROrKO~lvFJ+;fB99?Zlq{*YSwZZtOp0@>v1gkW|Em3v==*2E)x` zX86RW=|iIDbt%6r)ucebUS~vZ_HR4#Ykr($R4Sa7WtHYUE~vhIws~kE-4{&nIcM+= z!_4x6`ql`UErXkrVbM*%l5d+&GvEI%$;P(gM-BkLPLTiT(!N|Ac_E-3`VWhBI2phm zw(Q@}C~nTa7XA6FYEmbR0wEuhPF_w=Mfr&Jp&Q=^8bZTUPz8^<5~ZpXov{+6vMzdV zy802^<4H_fuAoxt7Oa z8$+vmvi$pGV2jpK8#v;4iQ*toma;HlRO9{VAGIa*2vV=_i**X^4%TRroFenURf>yS z*af>zkJrNQa$5P?z^I=_)#B|~2p&eGR<@VDqO!KP$7SX^qxS9MESf?%n`j2D;*%{@ ziF$l6W|QSyeMyT&2_URi;8|~wieMB8t|YUjK>`nGbWryOx%v3+psvrmov^Bc6 z(EtyuGmBFx4!<6Ti<>{q9kEzq3q(Z7QEH4Js(`SJ09ISx`6qDF(zMVj=^cLfaFvZe ztNk5pjDz#{Yi6zU&zwWK_d^m9+>x`EZ%?Ym32A$CZfVZHU_Tvmt~=`pa21#08@Pj1 zW-m2kS!j3D$vYHmba2Lr6eF$Kt-}C$dA%dw(0N^ju)}t0a+!nW67Z%NhTFxuKjL;; z+=PEoVR72O`rRk0X|0<|oYm!kz+YC$X@$QaLDT@v*U!7!&E-=)eKp@Vvq0@C8^yggx0Rr`aZ43-O{ zr18J`gTxSU#1?X6K%C|CC zwK!9rFuwdc?&%C1#{*WPCp_dm) zu`K^(MsTH#e8R<3K-4x=F55*{?QBxR;BECS(%268B5sv%YSqo*O{hFpZ}g0HT-=b& zHf%)+P=kihf2tTaf_CS%#xPL!u4)>O+Mk2=T_uy3SnhRSb{Zc#KZU$(5v5z@`^TWw z4l@*CSp_s8p`8qXXA&A+baQIW7@ko}vJhFKy5WPcdRVey^OGqarya$WhsPbZ7N(fq zSA043T*Xg*IJ$R;`-g+ z{Y$J#rz=0zY^yFW0ccT5mvQ0SB3)&H2;P8pZA(Ig<#W6GHtu*gdXJWcSJSvW=3@jc z-)DF{Rj0e3ax}?Le9ZJ~m}WwxG_M?H_8~)=ZM2WURZ&1}xzQW|8V1ReetF8x$TkYF zmd>SlK|`zi3%EGZn`#^MX@sc9#;RRL|M?GX<~yz!8x)><)+Y< zO{tZk^!Rz2rZDrE?5M)4I|#AszHCp_Qmpa3G25d`#}m3P^cr9!WyvQ* zOn{`PtL+a6bog#&My<^5?g?f_Pb=bIO-UK?HI3c(#j`QVCGyF`Hg~6KFxINXr^MLE|4O33(SB;rNrup zMN9pfyLvDwDSOPJ*WO6m{eliA4y}z8D*4t0z=kAsPdgzw2ADNMm}0HzFDcESyhxfT zhOQA3=;b{Z_#u?OUp;~wLw89z7zp$nY9UYYf^KeW^dLmq{&35;e7D}g8kFalYzi+Cu@oJtDkL2;$Rt&=6D3p9_a}|!$Ob0I6hLaOq{F0*g#qdU z@01G|Q={}Qrz%?R$X1A};%ta#{WV&J=kALC)CzV-Lc{z1wTzglKYPr%bFgd0qosD` z{EpM^B)H7@E>}4UgJpv)X6n57Yr-g+EKiMZQX+V9v*qv1%x5zC&SH5`Wb>loP+-Xl zS6}v1^^M+nYf9vfzse+D9q9h=xhfIHDZElw`rD^_fC^31u##4pZiMPHyoqV?sI<sijUNmwh1 zY8S0sZNk&mF!rlWeVt@Jf)BMDX%zrqs;`;JeC+?c0NVI>9wfhkud_#)O_49=GgF(F z686A9y9ENSCDnt7h%1w{x0=X!r_IoKHKQ&A8 zYtyp_EgfW=x}43`eFJx1%9ii_XUIPU_2i<);s2Sxk&%7#5vG4cvP`N+ zozxua4oQDNm7G{o*&5z;J~7RE!On1|YOWUXP9aM!rRAgf-n&kvP9lY*qqoSH(qva6 zoOJ?=-|;@p>)n6(BDz(&Qm)-10HQtow~nKjD#jsh4^e zUPXveB;i)kRgnks`pU-o&B2|8)ozKhK#H);#}@V=wv)$jZuk*7bg^>dHH)>cjsPcl zs^gj#D%oP=1@w2G+MGrLNuuryKNsc6h~;=9naG5zXG(oM6|-Z;wp%%H?`5hJ#@Ue+DIKMed%XeQeMW`1C(?QgPon5n;hFp2h?*Ii-ph%!YxdifjUgGT z%bz{GDM)>ML;pO6K;T)8Yg(L?QL=}~>d+E#FVkN5()_b2$C4QG@5e;p56{W`K-T;r zJn9F#uSgdcpH`SsI64i8arHL>3P)ex;)+}e{@ zw2>)>;J1d+#Tlupt*%1=wr)j*uNc+;?X(&mxuT3prP$sV7&f{a%nanMp3 z#Y$fx<_#Yr;2A{31!&yd_PWL((i=9HS+dsW4?}d)KaS@qM3i(Md;lT=(-dkfd+1-N;~Kt4GS>2b&1^EHwK#StIda zBC!@}hnx>7xeAhF^7(Cs)Nibd>_)(Es;frtWo=UJ@_Y#O3{P})Qou{uS26bha^GZk zNop0xPwO9InGf}Jz+Xj5>t0R0?rSRLm710>#_VN%RU zFCHLYHjB_oYWUx9A^2tia=tYf1WA=Y8EA&_s#a7>W8a5?+|-5gV!AmyuN9652QZ_D zAF7~XKPn;tWri~#F2`B9x%?`sYS(Gd1R{{k^{jxa%DfK0aJFQbxB7X%R%o|gX_