codex 1.6.0a3__py3-none-any.whl → 1.6.0a5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of codex might be problematic. Click here for more details.
- codex/exceptions.py +17 -6
- codex/integrity.py +67 -3
- codex/librarian/covers/coverd.py +2 -2
- codex/librarian/covers/create.py +29 -13
- codex/librarian/covers/path.py +8 -6
- codex/librarian/covers/purge.py +27 -16
- codex/librarian/covers/tasks.py +3 -2
- codex/librarian/importer/aggregate_metadata.py +2 -2
- codex/librarian/importer/const.py +23 -7
- codex/librarian/importer/create_fks.py +88 -28
- codex/librarian/importer/deleted.py +26 -2
- codex/librarian/importer/importerd.py +124 -27
- codex/librarian/importer/link_comics.py +55 -0
- codex/librarian/importer/moved.py +78 -3
- codex/librarian/importer/query_fks.py +68 -4
- codex/librarian/importer/status.py +6 -0
- codex/librarian/importer/tasks.py +11 -3
- codex/librarian/importer/update_comics.py +1 -3
- codex/librarian/janitor/cleanup.py +24 -0
- codex/librarian/janitor/failed_imports.py +1 -1
- codex/librarian/janitor/janitor.py +5 -0
- codex/librarian/janitor/janitord.py +1 -1
- codex/librarian/janitor/status.py +1 -0
- codex/librarian/janitor/tasks.py +5 -0
- codex/librarian/librariand.py +3 -3
- codex/librarian/search/update.py +1 -1
- codex/librarian/watchdog/db_snapshot.py +9 -6
- codex/librarian/watchdog/emitter.py +3 -0
- codex/librarian/watchdog/event_batcherd.py +19 -3
- codex/librarian/watchdog/events.py +176 -36
- codex/librarian/watchdog/observers.py +13 -3
- codex/migrations/0027_sort_name.py +2 -12
- codex/migrations/0028_custom_covers.py +166 -0
- codex/models/admin.py +0 -1
- codex/models/base.py +3 -0
- codex/models/comic.py +15 -9
- codex/models/groups.py +30 -14
- codex/models/library.py +11 -3
- codex/models/paths.py +62 -14
- codex/models/{const.py → util.py} +15 -2
- codex/serializers/admin.py +11 -2
- codex/serializers/browser/mixins.py +1 -0
- codex/serializers/browser/settings.py +3 -0
- codex/settings/settings.py +11 -5
- codex/startup.py +77 -3
- codex/static_root/assets/{VCheckbox-CRu3BJ5D.48cbd6b412f4.js → VCheckbox-BF_99tgB.7fe59e56d9d4.js} +1 -1
- codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.br +0 -0
- codex/static_root/assets/VCheckbox-BF_99tgB.7fe59e56d9d4.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-CRu3BJ5D.js → VCheckbox-BF_99tgB.js} +1 -1
- codex/static_root/assets/VCheckbox-BF_99tgB.js.br +0 -0
- codex/static_root/assets/VCheckbox-BF_99tgB.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CAUrQoP4.b162429d9a68.js → VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.31e1e6aa44c9.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CAUrQoP4.js → VCheckboxBtn-Coc7g8m7.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Coc7g8m7.js.gz +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js +1 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.br +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.0f00903c2ebc.js.gz +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js +1 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js.br +0 -0
- codex/static_root/assets/VCombobox-CTRWnOuM.js.gz +0 -0
- codex/static_root/assets/{VDialog-BcnzrzZO.7fa7ec337394.js → VDialog-pzwzInEh.51acf566a069.js} +1 -1
- codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.br +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.51acf566a069.js.gz +0 -0
- codex/static_root/assets/{VDialog-BcnzrzZO.js → VDialog-pzwzInEh.js} +1 -1
- codex/static_root/assets/VDialog-pzwzInEh.js.br +0 -0
- codex/static_root/assets/VDialog-pzwzInEh.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DP3HSBJj.a5600e541dc2.js → VExpansionPanels-CKSq1GpG.0ecf702c5b67.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.0ecf702c5b67.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DP3HSBJj.js → VExpansionPanels-CKSq1GpG.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CKSq1GpG.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-B9OZxVQw.28f10e381149.js → VRadioGroup-CW5GobGU.b72925bca3de.js} +1 -1
- codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.b72925bca3de.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-B9OZxVQw.js → VRadioGroup-CW5GobGU.js} +1 -1
- codex/static_root/assets/VRadioGroup-CW5GobGU.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CW5GobGU.js.gz +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js +1 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.br +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.00b11751b25e.js.gz +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js +1 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js.br +0 -0
- codex/static_root/assets/VSelect-ChOlLRyH.js.gz +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css +1 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.br +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.7cabd30bc5e4.css.gz +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css +1 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css.br +0 -0
- codex/static_root/assets/VSelect-MGVSeLgr.css.gz +0 -0
- codex/static_root/assets/{VSelectionControl-gDbXN6J_.js → VSelectionControl-DKOhc2zB.061dca094303.js} +1 -1
- codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.061dca094303.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-gDbXN6J_.137a5bce3954.js → VSelectionControl-DKOhc2zB.js} +1 -1
- codex/static_root/assets/VSelectionControl-DKOhc2zB.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DKOhc2zB.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-D8FKczaP.3c2d0e591ae3.js → VSlideGroup-zFz5_byd.e0e246b7acbb.js} +1 -1
- codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.br +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.e0e246b7acbb.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-D8FKczaP.js → VSlideGroup-zFz5_byd.js} +1 -1
- codex/static_root/assets/VSlideGroup-zFz5_byd.js.br +0 -0
- codex/static_root/assets/VSlideGroup-zFz5_byd.js.gz +0 -0
- codex/static_root/assets/{VTable-D86x2F7y.60c6356a0b73.js → VTable-FKMTcdkF.6c1eed84d25e.js} +1 -1
- codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.br +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.6c1eed84d25e.js.gz +0 -0
- codex/static_root/assets/{VTable-D86x2F7y.js → VTable-FKMTcdkF.js} +1 -1
- codex/static_root/assets/VTable-FKMTcdkF.js.br +0 -0
- codex/static_root/assets/VTable-FKMTcdkF.js.gz +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css +1 -0
- codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css.br +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.4cc388b4aa3c.css.gz +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.css +1 -0
- codex/static_root/assets/VTextField-BssVoDhB.css.br +0 -0
- codex/static_root/assets/VTextField-BssVoDhB.css.gz +0 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js +1 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.br +0 -0
- codex/static_root/assets/VTextField-Dftcik85.cb2f1dda87e1.js.gz +0 -0
- codex/static_root/assets/VTextField-Dftcik85.js +1 -0
- codex/static_root/assets/VTextField-Dftcik85.js.br +0 -0
- codex/static_root/assets/VTextField-Dftcik85.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-aBb7Akq5.f0d7de98e392.js → VWindowItem-CYCwnt3s.28025dc36c6a.js} +1 -1
- codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.br +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.28025dc36c6a.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-aBb7Akq5.js → VWindowItem-CYCwnt3s.js} +1 -1
- codex/static_root/assets/VWindowItem-CYCwnt3s.js.br +0 -0
- codex/static_root/assets/VWindowItem-CYCwnt3s.js.gz +0 -0
- codex/static_root/assets/{admin-BbrOU9Y9.29936748af9e.css → admin-B8Bp2Uih.82c98714bcf1.css} +1 -1
- codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.br +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.82c98714bcf1.css.gz +0 -0
- codex/static_root/assets/{admin-BbrOU9Y9.css → admin-B8Bp2Uih.css} +1 -1
- codex/static_root/assets/admin-B8Bp2Uih.css.br +0 -0
- codex/static_root/assets/admin-B8Bp2Uih.css.gz +0 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js +1 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.br +0 -0
- codex/static_root/assets/admin-DH_DrB0F.dc45fb726e07.js.gz +0 -0
- codex/static_root/assets/admin-DH_DrB0F.js +1 -0
- codex/static_root/assets/admin-DH_DrB0F.js.br +0 -0
- codex/static_root/assets/admin-DH_DrB0F.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js +30 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.a9e72fbfdadc.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js +30 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B2rmpAVA.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css +1 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.bd2c1d6148bb.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css +1 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BHdNPwzF.css.gz +0 -0
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css +1 -0
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.br +0 -0
- codex/static_root/assets/browser-CpppBvim.a1b5d4fe97b1.css.gz +0 -0
- codex/static_root/assets/browser-CpppBvim.css +1 -0
- codex/static_root/assets/browser-CpppBvim.css.br +0 -0
- codex/static_root/assets/browser-CpppBvim.css.gz +0 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js +1 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.br +0 -0
- codex/static_root/assets/browser-lKU7rqLd.cdb86e625ed9.js.gz +0 -0
- codex/static_root/assets/browser-lKU7rqLd.js +1 -0
- codex/static_root/assets/browser-lKU7rqLd.js.br +0 -0
- codex/static_root/assets/browser-lKU7rqLd.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-DywVK04u.eabe6b4c3e27.js → change-password-dialog-BIBG6Bk8.3583021c124a.js} +1 -1
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.3583021c124a.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-DywVK04u.js → change-password-dialog-BIBG6Bk8.js} +1 -1
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BIBG6Bk8.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-UnMzYsqN.d732afc0dc59.js → confirm-dialog-DARLmXRf.53fc6938fb8c.js} +1 -1
- codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.53fc6938fb8c.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-UnMzYsqN.js → confirm-dialog-DARLmXRf.js} +1 -1
- codex/static_root/assets/confirm-dialog-DARLmXRf.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DARLmXRf.js.gz +0 -0
- codex/static_root/assets/{datetime-column-IeiZdinf.e2807c847ddc.js → datetime-column-LpLgWpkf.16ce3324970f.js} +1 -1
- codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.br +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.16ce3324970f.js.gz +0 -0
- codex/static_root/assets/{datetime-column-IeiZdinf.js → datetime-column-LpLgWpkf.js} +1 -1
- codex/static_root/assets/datetime-column-LpLgWpkf.js.br +0 -0
- codex/static_root/assets/datetime-column-LpLgWpkf.js.gz +0 -0
- codex/static_root/assets/{filter-BT3OCu_E.39135c83c357.js → filter-jRB1Ob6r.6a559a53e805.js} +1 -1
- codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.br +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.6a559a53e805.js.gz +0 -0
- codex/static_root/assets/{filter-BT3OCu_E.js → filter-jRB1Ob6r.js} +1 -1
- codex/static_root/assets/filter-jRB1Ob6r.js.br +0 -0
- codex/static_root/assets/filter-jRB1Ob6r.js.gz +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css +1 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.br +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.gz +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css +1 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css.br +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.css.gz +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js +1 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.br +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.17829c46cae9.js.gz +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js +1 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js.br +0 -0
- codex/static_root/assets/flag-tab-mYS2wg5e.js.gz +0 -0
- codex/static_root/assets/{group-tab-Bt_AURnL.59aa6b13b14b.js → group-tab-MK1lcjUU.98406d5babdc.js} +1 -1
- codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.br +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.98406d5babdc.js.gz +0 -0
- codex/static_root/assets/{group-tab-Bt_AURnL.js → group-tab-MK1lcjUU.js} +1 -1
- codex/static_root/assets/group-tab-MK1lcjUU.js.br +0 -0
- codex/static_root/assets/group-tab-MK1lcjUU.js.gz +0 -0
- codex/static_root/assets/http-error-DKulXuTl.95e42988d5d5.css +1 -0
- codex/static_root/assets/{http-error-_wlDqEcp.5b78d3e332c9.css.br → http-error-DKulXuTl.95e42988d5d5.css.br} +0 -0
- codex/static_root/assets/http-error-DKulXuTl.95e42988d5d5.css.gz +0 -0
- codex/static_root/assets/http-error-DKulXuTl.css +1 -0
- codex/static_root/assets/{http-error-_wlDqEcp.css.br → http-error-DKulXuTl.css.br} +0 -0
- codex/static_root/assets/http-error-DKulXuTl.css.gz +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js +1 -0
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.br +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.a3c578a0265e.js.gz +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.js +1 -0
- codex/static_root/assets/http-error-HFeYnF0n.js.br +0 -0
- codex/static_root/assets/http-error-HFeYnF0n.js.gz +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css +1 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.br +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.913e236f625e.css.gz +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css +1 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css.br +0 -0
- codex/static_root/assets/library-tab-BN0SfPcH.css.gz +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js +1 -0
- codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.br +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.a2a2ef088d1c.js.gz +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js +1 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js.br +0 -0
- codex/static_root/assets/library-tab-DYeEBYlT.js.gz +0 -0
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js +32 -0
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.br +0 -0
- codex/static_root/assets/main-gSW7TFDY.329223b148d4.js.gz +0 -0
- codex/static_root/assets/main-gSW7TFDY.js +32 -0
- codex/static_root/assets/main-gSW7TFDY.js.br +0 -0
- codex/static_root/assets/main-gSW7TFDY.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-B3wS4_7p.7695fafdefce.css → pagination-toolbar-ClZui9Zf.697d7ae11c21.css} +1 -1
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.697d7ae11c21.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-B3wS4_7p.css → pagination-toolbar-ClZui9Zf.css} +1 -1
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-ClZui9Zf.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js +1 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.8f23f8bd9a05.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js +1 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dlm70Bhs.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-DNwjWxO_.70b904e8e4ff.js → pdf-doc-BTdS9hUz.7c966524280a.js} +1 -1
- codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.br +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.7c966524280a.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-DNwjWxO_.js → pdf-doc-BTdS9hUz.js} +1 -1
- codex/static_root/assets/pdf-doc-BTdS9hUz.js.br +0 -0
- codex/static_root/assets/pdf-doc-BTdS9hUz.js.gz +0 -0
- codex/static_root/assets/{reader-CScIyWY8.0f2b4c8ea2bf.js → reader-BtSTIETq.f940f66c0e58.js} +2 -2
- codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.br +0 -0
- codex/static_root/assets/reader-BtSTIETq.f940f66c0e58.js.gz +0 -0
- codex/static_root/assets/{reader-CScIyWY8.js → reader-BtSTIETq.js} +2 -2
- codex/static_root/assets/reader-BtSTIETq.js.br +0 -0
- codex/static_root/assets/reader-BtSTIETq.js.gz +0 -0
- codex/static_root/assets/{reader-pqgXBr7J.92a23ab06864.css → reader-CF3xt6JG.19fad7f62450.css} +1 -1
- codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.br +0 -0
- codex/static_root/assets/reader-CF3xt6JG.19fad7f62450.css.gz +0 -0
- codex/static_root/assets/{reader-pqgXBr7J.css → reader-CF3xt6JG.css} +1 -1
- codex/static_root/assets/reader-CF3xt6JG.css.br +0 -0
- codex/static_root/assets/reader-CF3xt6JG.css.gz +0 -0
- codex/static_root/assets/{relation-chips-D6T6FX7G.css → relation-chips-Bsc-gUxE.98e65384c3c9.css} +1 -1
- codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.br +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.98e65384c3c9.css.gz +0 -0
- codex/static_root/assets/{relation-chips-D6T6FX7G.db095b2497e4.css → relation-chips-Bsc-gUxE.css} +1 -1
- codex/static_root/assets/relation-chips-Bsc-gUxE.css.br +0 -0
- codex/static_root/assets/relation-chips-Bsc-gUxE.css.gz +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js +1 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.br +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.95855a010f4a.js.gz +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js +1 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js.br +0 -0
- codex/static_root/assets/relation-chips-C4Ob7ySi.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css +1 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.br +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.6471a50fc05b.css.gz +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.css +1 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.css.br +0 -0
- codex/static_root/assets/settings-drawer-BJOfMtqA.css.gz +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js +2 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.br +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.4ce8afcb879a.js.gz +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.js +2 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.js.br +0 -0
- codex/static_root/assets/settings-drawer-xSctoxZn.js.gz +0 -0
- codex/static_root/assets/{stats-tab-Bn9lHtZb.b2969fd36ae9.js → stats-tab-C_uC_U7o.e6b014b6e8c6.js} +1 -1
- codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.br +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.e6b014b6e8c6.js.gz +0 -0
- codex/static_root/assets/{stats-tab-Bn9lHtZb.js → stats-tab-C_uC_U7o.js} +1 -1
- codex/static_root/assets/stats-tab-C_uC_U7o.js.br +0 -0
- codex/static_root/assets/stats-tab-C_uC_U7o.js.gz +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js +1 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.br +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.c07877250c88.js.gz +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.js +1 -0
- codex/static_root/assets/task-tab-VIpy1Ula.js.br +0 -0
- codex/static_root/assets/task-tab-VIpy1Ula.js.gz +0 -0
- codex/static_root/assets/{unauthorized-DWgytUow.ca2f13dbdbf5.css → unauthorized-DHV6Q1n-.b7847b2872a1.css} +1 -1
- codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.br +0 -0
- codex/static_root/assets/unauthorized-DHV6Q1n-.b7847b2872a1.css.gz +0 -0
- codex/static_root/assets/{unauthorized-DWgytUow.css → unauthorized-DHV6Q1n-.css} +1 -1
- codex/static_root/assets/unauthorized-DHV6Q1n-.css.br +0 -0
- codex/static_root/assets/unauthorized-DHV6Q1n-.css.gz +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js +1 -0
- codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.br +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.475729603aeb.js.gz +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.js +1 -0
- codex/static_root/assets/unauthorized-kEuFL5br.js.br +0 -0
- codex/static_root/assets/unauthorized-kEuFL5br.js.gz +0 -0
- codex/static_root/assets/{user-tab-DDViQ8Q6.c258185d0ebe.js → user-tab-CZNFTTEI.f3940a75daae.js} +1 -1
- codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.br +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.f3940a75daae.js.gz +0 -0
- codex/static_root/assets/{user-tab-DDViQ8Q6.js → user-tab-CZNFTTEI.js} +1 -1
- codex/static_root/assets/user-tab-CZNFTTEI.js.br +0 -0
- codex/static_root/assets/user-tab-CZNFTTEI.js.gz +0 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json +1 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json.br +0 -0
- codex/static_root/js/choices-admin.ef1ff3a8b9da.json.gz +0 -0
- codex/static_root/js/choices-admin.json +1 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/js/{choices.b60817ba0a93.json → choices.23fedac2b9ba.json} +1 -1
- codex/static_root/js/choices.23fedac2b9ba.json.br +0 -0
- codex/static_root/js/choices.23fedac2b9ba.json.gz +0 -0
- codex/static_root/js/choices.json +1 -1
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.json.gz +0 -0
- codex/static_root/manifest.6ef112683dae.json +642 -0
- codex/static_root/manifest.6ef112683dae.json.br +0 -0
- codex/static_root/manifest.6ef112683dae.json.gz +0 -0
- codex/static_root/manifest.json +259 -258
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/status_controller.py +11 -2
- codex/views/admin/library.py +47 -27
- codex/views/admin/stats.py +4 -1
- codex/views/admin/tasks.py +6 -1
- codex/views/browser/base.py +2 -2
- codex/views/browser/browser.py +1 -2
- codex/views/browser/browser_annotations.py +87 -17
- codex/views/browser/browser_breadcrumbs.py +2 -2
- codex/views/browser/browser_order_by.py +31 -20
- codex/views/browser/filters/search.py +1 -1
- codex/views/browser/metadata.py +0 -1
- codex/views/cover.py +3 -2
- codex/views/session.py +1 -0
- {codex-1.6.0a3.dist-info → codex-1.6.0a5.dist-info}/METADATA +3 -2
- {codex-1.6.0a3.dist-info → codex-1.6.0a5.dist-info}/RECORD +358 -357
- codex/static_root/assets/VCheckbox-CRu3BJ5D.48cbd6b412f4.js.br +0 -0
- codex/static_root/assets/VCheckbox-CRu3BJ5D.48cbd6b412f4.js.gz +0 -0
- codex/static_root/assets/VCheckbox-CRu3BJ5D.js.br +0 -0
- codex/static_root/assets/VCheckbox-CRu3BJ5D.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CAUrQoP4.b162429d9a68.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CAUrQoP4.b162429d9a68.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CAUrQoP4.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CAUrQoP4.js.gz +0 -0
- codex/static_root/assets/VCombobox-DJFO04Gk.b335ed051992.js +0 -1
- codex/static_root/assets/VCombobox-DJFO04Gk.b335ed051992.js.br +0 -0
- codex/static_root/assets/VCombobox-DJFO04Gk.b335ed051992.js.gz +0 -0
- codex/static_root/assets/VCombobox-DJFO04Gk.js +0 -1
- codex/static_root/assets/VCombobox-DJFO04Gk.js.br +0 -0
- codex/static_root/assets/VCombobox-DJFO04Gk.js.gz +0 -0
- codex/static_root/assets/VDialog-BcnzrzZO.7fa7ec337394.js.br +0 -0
- codex/static_root/assets/VDialog-BcnzrzZO.7fa7ec337394.js.gz +0 -0
- codex/static_root/assets/VDialog-BcnzrzZO.js.br +0 -0
- codex/static_root/assets/VDialog-BcnzrzZO.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DP3HSBJj.a5600e541dc2.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DP3HSBJj.a5600e541dc2.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DP3HSBJj.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DP3HSBJj.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-B9OZxVQw.28f10e381149.js.br +0 -5
- codex/static_root/assets/VRadioGroup-B9OZxVQw.28f10e381149.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-B9OZxVQw.js.br +0 -5
- codex/static_root/assets/VRadioGroup-B9OZxVQw.js.gz +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +0 -1
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.css +0 -1
- codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
- codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
- codex/static_root/assets/VSelect-BIXGglsb.018666f134d5.js +0 -1
- codex/static_root/assets/VSelect-BIXGglsb.018666f134d5.js.br +0 -0
- codex/static_root/assets/VSelect-BIXGglsb.018666f134d5.js.gz +0 -0
- codex/static_root/assets/VSelect-BIXGglsb.js +0 -1
- codex/static_root/assets/VSelect-BIXGglsb.js.br +0 -0
- codex/static_root/assets/VSelect-BIXGglsb.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-gDbXN6J_.137a5bce3954.js.br +0 -0
- codex/static_root/assets/VSelectionControl-gDbXN6J_.137a5bce3954.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-gDbXN6J_.js.br +0 -0
- codex/static_root/assets/VSelectionControl-gDbXN6J_.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-D8FKczaP.3c2d0e591ae3.js.br +0 -0
- codex/static_root/assets/VSlideGroup-D8FKczaP.3c2d0e591ae3.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-D8FKczaP.js.br +0 -0
- codex/static_root/assets/VSlideGroup-D8FKczaP.js.gz +0 -0
- codex/static_root/assets/VTable-D86x2F7y.60c6356a0b73.js.br +0 -0
- codex/static_root/assets/VTable-D86x2F7y.60c6356a0b73.js.gz +0 -0
- codex/static_root/assets/VTable-D86x2F7y.js.br +0 -0
- codex/static_root/assets/VTable-D86x2F7y.js.gz +0 -0
- codex/static_root/assets/VTextField-BEgIi5E-.6a5cf9192004.js +0 -1
- codex/static_root/assets/VTextField-BEgIi5E-.6a5cf9192004.js.br +0 -0
- codex/static_root/assets/VTextField-BEgIi5E-.6a5cf9192004.js.gz +0 -0
- codex/static_root/assets/VTextField-BEgIi5E-.js +0 -1
- codex/static_root/assets/VTextField-BEgIi5E-.js.br +0 -0
- codex/static_root/assets/VTextField-BEgIi5E-.js.gz +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css +0 -1
- codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css.br +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.580110ad24dc.css.gz +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.css +0 -1
- codex/static_root/assets/VTextField-DuIIp-VY.css.br +0 -0
- codex/static_root/assets/VTextField-DuIIp-VY.css.gz +0 -0
- codex/static_root/assets/VWindowItem-aBb7Akq5.f0d7de98e392.js.br +0 -0
- codex/static_root/assets/VWindowItem-aBb7Akq5.f0d7de98e392.js.gz +0 -0
- codex/static_root/assets/VWindowItem-aBb7Akq5.js.br +0 -0
- codex/static_root/assets/VWindowItem-aBb7Akq5.js.gz +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.29936748af9e.css.br +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.29936748af9e.css.gz +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.css.br +0 -0
- codex/static_root/assets/admin-BbrOU9Y9.css.gz +0 -0
- codex/static_root/assets/admin-CUlXuIkb.2c5ca314c698.js +0 -1
- codex/static_root/assets/admin-CUlXuIkb.2c5ca314c698.js.br +0 -0
- codex/static_root/assets/admin-CUlXuIkb.2c5ca314c698.js.gz +0 -0
- codex/static_root/assets/admin-CUlXuIkb.js +0 -1
- codex/static_root/assets/admin-CUlXuIkb.js.br +0 -0
- codex/static_root/assets/admin-CUlXuIkb.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B98ueC5c.3fb257a9e9e8.js +0 -30
- codex/static_root/assets/admin-drawer-panel-B98ueC5c.3fb257a9e9e8.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B98ueC5c.3fb257a9e9e8.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-B98ueC5c.js +0 -30
- codex/static_root/assets/admin-drawer-panel-B98ueC5c.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-B98ueC5c.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css +0 -1
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.1514926c78e8.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css +0 -1
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BF0dMvEE.css.gz +0 -0
- codex/static_root/assets/browser-C3YcP-vH.31dac9c003c4.js +0 -1
- codex/static_root/assets/browser-C3YcP-vH.31dac9c003c4.js.br +0 -0
- codex/static_root/assets/browser-C3YcP-vH.31dac9c003c4.js.gz +0 -0
- codex/static_root/assets/browser-C3YcP-vH.js +0 -1
- codex/static_root/assets/browser-C3YcP-vH.js.br +0 -0
- codex/static_root/assets/browser-C3YcP-vH.js.gz +0 -0
- codex/static_root/assets/browser-CwzOGMJs.css +0 -1
- codex/static_root/assets/browser-CwzOGMJs.css.br +0 -0
- codex/static_root/assets/browser-CwzOGMJs.css.gz +0 -0
- codex/static_root/assets/browser-CwzOGMJs.e53bf262a521.css +0 -1
- codex/static_root/assets/browser-CwzOGMJs.e53bf262a521.css.br +0 -0
- codex/static_root/assets/browser-CwzOGMJs.e53bf262a521.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DywVK04u.eabe6b4c3e27.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DywVK04u.eabe6b4c3e27.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DywVK04u.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DywVK04u.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-UnMzYsqN.d732afc0dc59.js.br +0 -0
- codex/static_root/assets/confirm-dialog-UnMzYsqN.d732afc0dc59.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-UnMzYsqN.js.br +0 -0
- codex/static_root/assets/confirm-dialog-UnMzYsqN.js.gz +0 -0
- codex/static_root/assets/datetime-column-IeiZdinf.e2807c847ddc.js.br +0 -0
- codex/static_root/assets/datetime-column-IeiZdinf.e2807c847ddc.js.gz +0 -0
- codex/static_root/assets/datetime-column-IeiZdinf.js.br +0 -0
- codex/static_root/assets/datetime-column-IeiZdinf.js.gz +0 -0
- codex/static_root/assets/filter-BT3OCu_E.39135c83c357.js.br +0 -0
- codex/static_root/assets/filter-BT3OCu_E.39135c83c357.js.gz +0 -0
- codex/static_root/assets/filter-BT3OCu_E.js.br +0 -0
- codex/static_root/assets/filter-BT3OCu_E.js.gz +0 -0
- codex/static_root/assets/flag-tab-BQmB4JNc.29dd38ce57d1.js +0 -1
- codex/static_root/assets/flag-tab-BQmB4JNc.29dd38ce57d1.js.br +0 -0
- codex/static_root/assets/flag-tab-BQmB4JNc.29dd38ce57d1.js.gz +0 -0
- codex/static_root/assets/flag-tab-BQmB4JNc.js +0 -1
- codex/static_root/assets/flag-tab-BQmB4JNc.js.br +0 -0
- codex/static_root/assets/flag-tab-BQmB4JNc.js.gz +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css +0 -1
- codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css.br +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.8deba6a5370d.css.gz +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.css +0 -1
- codex/static_root/assets/flag-tab-BacZLlVi.css.br +0 -0
- codex/static_root/assets/flag-tab-BacZLlVi.css.gz +0 -0
- codex/static_root/assets/group-tab-Bt_AURnL.59aa6b13b14b.js.br +0 -0
- codex/static_root/assets/group-tab-Bt_AURnL.59aa6b13b14b.js.gz +0 -0
- codex/static_root/assets/group-tab-Bt_AURnL.js.br +0 -0
- codex/static_root/assets/group-tab-Bt_AURnL.js.gz +0 -0
- codex/static_root/assets/http-error-Cy29flzW.26884b2f42da.js +0 -1
- codex/static_root/assets/http-error-Cy29flzW.26884b2f42da.js.br +0 -4
- codex/static_root/assets/http-error-Cy29flzW.26884b2f42da.js.gz +0 -0
- codex/static_root/assets/http-error-Cy29flzW.js +0 -1
- codex/static_root/assets/http-error-Cy29flzW.js.br +0 -4
- codex/static_root/assets/http-error-Cy29flzW.js.gz +0 -0
- codex/static_root/assets/http-error-_wlDqEcp.5b78d3e332c9.css +0 -1
- codex/static_root/assets/http-error-_wlDqEcp.5b78d3e332c9.css.gz +0 -0
- codex/static_root/assets/http-error-_wlDqEcp.css +0 -1
- codex/static_root/assets/http-error-_wlDqEcp.css.gz +0 -0
- codex/static_root/assets/library-tab-0LLO3xlP.3636f75510b6.js +0 -1
- codex/static_root/assets/library-tab-0LLO3xlP.3636f75510b6.js.br +0 -0
- codex/static_root/assets/library-tab-0LLO3xlP.3636f75510b6.js.gz +0 -0
- codex/static_root/assets/library-tab-0LLO3xlP.js +0 -1
- codex/static_root/assets/library-tab-0LLO3xlP.js.br +0 -0
- codex/static_root/assets/library-tab-0LLO3xlP.js.gz +0 -0
- codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css +0 -1
- codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css.br +0 -2
- codex/static_root/assets/library-tab-CXbkxNZt.954f2850a439.css.gz +0 -0
- codex/static_root/assets/library-tab-CXbkxNZt.css +0 -1
- codex/static_root/assets/library-tab-CXbkxNZt.css.br +0 -2
- codex/static_root/assets/library-tab-CXbkxNZt.css.gz +0 -0
- codex/static_root/assets/main-BCYKxiL-.67d660e491ad.js +0 -32
- codex/static_root/assets/main-BCYKxiL-.67d660e491ad.js.br +0 -0
- codex/static_root/assets/main-BCYKxiL-.67d660e491ad.js.gz +0 -0
- codex/static_root/assets/main-BCYKxiL-.js +0 -32
- codex/static_root/assets/main-BCYKxiL-.js.br +0 -0
- codex/static_root/assets/main-BCYKxiL-.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.7695fafdefce.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.7695fafdefce.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-B3wS4_7p.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DQ8B0BSx.845b2f091ae7.js +0 -1
- codex/static_root/assets/pagination-toolbar-DQ8B0BSx.845b2f091ae7.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DQ8B0BSx.845b2f091ae7.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-DQ8B0BSx.js +0 -1
- codex/static_root/assets/pagination-toolbar-DQ8B0BSx.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-DQ8B0BSx.js.gz +0 -0
- codex/static_root/assets/pdf-doc-DNwjWxO_.70b904e8e4ff.js.br +0 -0
- codex/static_root/assets/pdf-doc-DNwjWxO_.70b904e8e4ff.js.gz +0 -0
- codex/static_root/assets/pdf-doc-DNwjWxO_.js.br +0 -0
- codex/static_root/assets/pdf-doc-DNwjWxO_.js.gz +0 -0
- codex/static_root/assets/reader-CScIyWY8.0f2b4c8ea2bf.js.br +0 -0
- codex/static_root/assets/reader-CScIyWY8.0f2b4c8ea2bf.js.gz +0 -0
- codex/static_root/assets/reader-CScIyWY8.js.br +0 -0
- codex/static_root/assets/reader-CScIyWY8.js.gz +0 -0
- codex/static_root/assets/reader-pqgXBr7J.92a23ab06864.css.br +0 -0
- codex/static_root/assets/reader-pqgXBr7J.92a23ab06864.css.gz +0 -0
- codex/static_root/assets/reader-pqgXBr7J.css.br +0 -0
- codex/static_root/assets/reader-pqgXBr7J.css.gz +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.css.br +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.css.gz +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.db095b2497e4.css.br +0 -0
- codex/static_root/assets/relation-chips-D6T6FX7G.db095b2497e4.css.gz +0 -0
- codex/static_root/assets/relation-chips-LT1N5iCN.37fceb98c9dd.js +0 -1
- codex/static_root/assets/relation-chips-LT1N5iCN.37fceb98c9dd.js.br +0 -0
- codex/static_root/assets/relation-chips-LT1N5iCN.37fceb98c9dd.js.gz +0 -0
- codex/static_root/assets/relation-chips-LT1N5iCN.js +0 -1
- codex/static_root/assets/relation-chips-LT1N5iCN.js.br +0 -0
- codex/static_root/assets/relation-chips-LT1N5iCN.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BgUazs7O.7bbeb1fd98f1.css +0 -1
- codex/static_root/assets/settings-drawer-BgUazs7O.7bbeb1fd98f1.css.br +0 -0
- codex/static_root/assets/settings-drawer-BgUazs7O.7bbeb1fd98f1.css.gz +0 -0
- codex/static_root/assets/settings-drawer-BgUazs7O.css +0 -1
- codex/static_root/assets/settings-drawer-BgUazs7O.css.br +0 -0
- codex/static_root/assets/settings-drawer-BgUazs7O.css.gz +0 -0
- codex/static_root/assets/settings-drawer-DwqbWCCT.c6f1febbec8d.js +0 -2
- codex/static_root/assets/settings-drawer-DwqbWCCT.c6f1febbec8d.js.br +0 -0
- codex/static_root/assets/settings-drawer-DwqbWCCT.c6f1febbec8d.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DwqbWCCT.js +0 -2
- codex/static_root/assets/settings-drawer-DwqbWCCT.js.br +0 -0
- codex/static_root/assets/settings-drawer-DwqbWCCT.js.gz +0 -0
- codex/static_root/assets/stats-tab-Bn9lHtZb.b2969fd36ae9.js.br +0 -0
- codex/static_root/assets/stats-tab-Bn9lHtZb.b2969fd36ae9.js.gz +0 -0
- codex/static_root/assets/stats-tab-Bn9lHtZb.js.br +0 -0
- codex/static_root/assets/stats-tab-Bn9lHtZb.js.gz +0 -0
- codex/static_root/assets/task-tab-BBfwR3Q4.d0de374d7ec0.js +0 -1
- codex/static_root/assets/task-tab-BBfwR3Q4.d0de374d7ec0.js.br +0 -0
- codex/static_root/assets/task-tab-BBfwR3Q4.d0de374d7ec0.js.gz +0 -0
- codex/static_root/assets/task-tab-BBfwR3Q4.js +0 -1
- codex/static_root/assets/task-tab-BBfwR3Q4.js.br +0 -0
- codex/static_root/assets/task-tab-BBfwR3Q4.js.gz +0 -0
- codex/static_root/assets/unauthorized-BWF4C258.3da18d521795.js +0 -1
- codex/static_root/assets/unauthorized-BWF4C258.3da18d521795.js.br +0 -0
- codex/static_root/assets/unauthorized-BWF4C258.3da18d521795.js.gz +0 -0
- codex/static_root/assets/unauthorized-BWF4C258.js +0 -1
- codex/static_root/assets/unauthorized-BWF4C258.js.br +0 -0
- codex/static_root/assets/unauthorized-BWF4C258.js.gz +0 -0
- codex/static_root/assets/unauthorized-DWgytUow.ca2f13dbdbf5.css.br +0 -0
- codex/static_root/assets/unauthorized-DWgytUow.ca2f13dbdbf5.css.gz +0 -0
- codex/static_root/assets/unauthorized-DWgytUow.css.br +0 -0
- codex/static_root/assets/unauthorized-DWgytUow.css.gz +0 -0
- codex/static_root/assets/user-tab-DDViQ8Q6.c258185d0ebe.js.br +0 -0
- codex/static_root/assets/user-tab-DDViQ8Q6.c258185d0ebe.js.gz +0 -0
- codex/static_root/assets/user-tab-DDViQ8Q6.js.br +0 -0
- codex/static_root/assets/user-tab-DDViQ8Q6.js.gz +0 -0
- codex/static_root/js/choices-admin.248384ece3f6.json +0 -1
- codex/static_root/js/choices-admin.248384ece3f6.json.br +0 -0
- codex/static_root/js/choices-admin.248384ece3f6.json.gz +0 -0
- codex/static_root/js/choices.b60817ba0a93.json.br +0 -0
- codex/static_root/js/choices.b60817ba0a93.json.gz +0 -0
- codex/static_root/manifest.776c8d59c3e4.json +0 -641
- codex/static_root/manifest.776c8d59c3e4.json.br +0 -0
- codex/static_root/manifest.776c8d59c3e4.json.gz +0 -0
- {codex-1.6.0a3.dist-info → codex-1.6.0a5.dist-info}/LICENSE +0 -0
- {codex-1.6.0a3.dist-info → codex-1.6.0a5.dist-info}/WHEEL +0 -0
- {codex-1.6.0a3.dist-info → codex-1.6.0a5.dist-info}/entry_points.txt +0 -0
codex/exceptions.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"""Special Redirect Error."""
|
|
2
2
|
|
|
3
3
|
from collections.abc import Mapping
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
from pprint import pformat
|
|
4
6
|
|
|
5
7
|
from django.shortcuts import redirect
|
|
6
8
|
from django.urls import reverse
|
|
@@ -10,6 +12,7 @@ from rest_framework.status import HTTP_303_SEE_OTHER
|
|
|
10
12
|
|
|
11
13
|
from codex.logger.logging import get_logger
|
|
12
14
|
from codex.serializers.choices import DEFAULTS
|
|
15
|
+
from codex.serializers.route import RouteSerializer
|
|
13
16
|
|
|
14
17
|
LOG = get_logger(__name__)
|
|
15
18
|
|
|
@@ -23,10 +26,20 @@ class SeeOtherRedirectError(APIException):
|
|
|
23
26
|
|
|
24
27
|
def __init__(self, detail: Mapping[str, str | dict]):
|
|
25
28
|
"""Create a response to pass to the exception handler."""
|
|
29
|
+
detail = dict(detail)
|
|
30
|
+
route: dict = detail.get("route", {}) # type: ignore
|
|
31
|
+
params: dict = route.get("params", DEFAULTS["breadcrumbs"][0])
|
|
32
|
+
# Save route in server format for redirect reverse
|
|
33
|
+
self.route_kwargs = params
|
|
34
|
+
# Serialize route for detail
|
|
35
|
+
serializer = RouteSerializer(params)
|
|
36
|
+
route = deepcopy(route)
|
|
37
|
+
route["params"] = serializer.data
|
|
38
|
+
detail["route"] = route
|
|
39
|
+
# Pop uneccissary breadcrumbs.
|
|
40
|
+
detail.get("settings", {}).pop("breadcrumbs", None) # type: ignore
|
|
26
41
|
self.detail: Mapping[str, str | Mapping] = detail # type: ignore
|
|
27
|
-
LOG.debug(
|
|
28
|
-
f"redirect {self.detail.get('reason')} to {self.detail.get('route')} {self.detail.get('settings')}"
|
|
29
|
-
)
|
|
42
|
+
LOG.debug(f"redirect {pformat(detail)}")
|
|
30
43
|
# super().__init__ converts every type into strings! do not use it.
|
|
31
44
|
|
|
32
45
|
def _add_query_params(self, url):
|
|
@@ -44,9 +57,7 @@ class SeeOtherRedirectError(APIException):
|
|
|
44
57
|
def get_response(self, url_name):
|
|
45
58
|
"""Return a Django Redirect Response."""
|
|
46
59
|
# only used in codex_exception_handler for opds stuff
|
|
47
|
-
|
|
48
|
-
params: dict = route.get("params", DEFAULTS["breadcrumbs"][0])
|
|
49
|
-
url = reverse(url_name, kwargs=params)
|
|
60
|
+
url = reverse(url_name, kwargs=self.route_kwargs)
|
|
50
61
|
url = self._add_query_params(url)
|
|
51
62
|
|
|
52
63
|
return redirect(url, permanent=False)
|
codex/integrity.py
CHANGED
|
@@ -12,12 +12,14 @@ from django.db import DEFAULT_DB_ALIAS, connection, connections
|
|
|
12
12
|
from django.db.migrations.executor import MigrationExecutor
|
|
13
13
|
from django.db.migrations.recorder import MigrationRecorder
|
|
14
14
|
from django.db.models.functions import Now
|
|
15
|
+
from django.db.models.query import Q
|
|
15
16
|
from django.db.utils import OperationalError
|
|
16
17
|
|
|
17
18
|
from codex.logger.logging import get_logger
|
|
18
19
|
from codex.settings.settings import (
|
|
19
20
|
CODEX_PATH,
|
|
20
21
|
CONFIG_PATH,
|
|
22
|
+
CUSTOM_COVERS_DIR,
|
|
21
23
|
DB_PATH,
|
|
22
24
|
SKIP_INTEGRITY_CHECK,
|
|
23
25
|
)
|
|
@@ -34,6 +36,7 @@ MIGRATION_0011 = "0010_library_groups_and_metadata_changes"
|
|
|
34
36
|
MIGRATION_0018 = "0018_rename_userbookmark_bookmark"
|
|
35
37
|
MIGRATION_0025 = "0025_add_story_arc_number"
|
|
36
38
|
MIGRATION_0026 = "0026_comicbox_1"
|
|
39
|
+
MIGRATION_0028 = "0028_custom_covers" # TODO merge with 27
|
|
37
40
|
M2M_NAMES = MappingProxyType(
|
|
38
41
|
{
|
|
39
42
|
"Character": "characters",
|
|
@@ -51,7 +54,7 @@ SKIP_M2M_CHECKS = MappingProxyType(
|
|
|
51
54
|
{"Contributor": MIGRATION_0026, "StoryArcNumber": MIGRATION_0025}
|
|
52
55
|
)
|
|
53
56
|
NULL_SET = frozenset({None})
|
|
54
|
-
HAVE_LIBRARY_FKS = ("FailedImport", "Folder", "Comic")
|
|
57
|
+
HAVE_LIBRARY_FKS = ("FailedImport", "Folder", "Comic", "CustomCover")
|
|
55
58
|
GROUP_HOSTS = MappingProxyType(
|
|
56
59
|
{
|
|
57
60
|
"Imprint": ("Publisher",),
|
|
@@ -60,7 +63,6 @@ GROUP_HOSTS = MappingProxyType(
|
|
|
60
63
|
"Comic": ("Publisher", "Imprint", "Series", "Volume"),
|
|
61
64
|
}
|
|
62
65
|
)
|
|
63
|
-
|
|
64
66
|
COMIC_FK_FIELDS = MappingProxyType(
|
|
65
67
|
{
|
|
66
68
|
"AgeRating": "age_rating",
|
|
@@ -71,7 +73,6 @@ COMIC_FK_FIELDS = MappingProxyType(
|
|
|
71
73
|
"Tagger": "tagger",
|
|
72
74
|
}
|
|
73
75
|
)
|
|
74
|
-
|
|
75
76
|
WATCHED_PATHS = ("Comic", "Folder")
|
|
76
77
|
CONTRIBUTOR_FIELDS = MappingProxyType(
|
|
77
78
|
{"ContributorRole": "role", "ContributorPerson": "person"}
|
|
@@ -92,6 +93,9 @@ DELETE_BAD_COMIC_FOLDER_RELATIONS_SQL = (
|
|
|
92
93
|
'IN (SELECT "codex_folder"."id" FROM "codex_folder")))'
|
|
93
94
|
)
|
|
94
95
|
MIGRATION_DIR = CODEX_PATH / "migrations"
|
|
96
|
+
CUSTOM_COVER_MODEL_NAMES = MappingProxyType(
|
|
97
|
+
{"publisher": "p", "imprint": "i", "series": "s", "storyarc": "a", "folder": "f"}
|
|
98
|
+
)
|
|
95
99
|
|
|
96
100
|
LOG = get_logger(__name__)
|
|
97
101
|
|
|
@@ -303,9 +307,68 @@ def _repair_library_groups(apps):
|
|
|
303
307
|
bad_relations.delete()
|
|
304
308
|
|
|
305
309
|
|
|
310
|
+
def _delete_extra_custom_cover_libraries(apps):
|
|
311
|
+
if not has_applied_migration(MIGRATION_0028):
|
|
312
|
+
return
|
|
313
|
+
library_model = apps.get_model("codex", "library")
|
|
314
|
+
custom_cover_libraries = library_model.objects.filter(covers_only=True)
|
|
315
|
+
if custom_cover_libraries.count() <= 1:
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
# Attempt to remove the bad ones, probably futile
|
|
319
|
+
bad_lib_pks = []
|
|
320
|
+
for library in custom_cover_libraries:
|
|
321
|
+
if library.path != CUSTOM_COVERS_DIR:
|
|
322
|
+
bad_lib_pks.append(library.pk)
|
|
323
|
+
delete_libs = library_model.object.filter(pk__in=bad_lib_pks)
|
|
324
|
+
count = delete_libs.count()
|
|
325
|
+
if count:
|
|
326
|
+
delete_libs.delete()
|
|
327
|
+
LOG.warn(
|
|
328
|
+
f"Removed {count} duplicate custom cover libraries pointing to unused custom cover dirs."
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
custom_cover_libraries = library_model.object.filter(covers_only=True)
|
|
332
|
+
count = custom_cover_libraries.count()
|
|
333
|
+
if count <= 1:
|
|
334
|
+
return
|
|
335
|
+
custom_cover_libraries.delete()
|
|
336
|
+
LOG.warn(
|
|
337
|
+
f"Removed all ({count}) custom cover libraries, Unable to determine valid one. Will recreate upon startup."
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _repair_groups_with_custom_covers(apps):
|
|
342
|
+
if not has_applied_migration(MIGRATION_0028):
|
|
343
|
+
return
|
|
344
|
+
custom_cover_model = apps.get_model("codex", "customcover")
|
|
345
|
+
now = Now()
|
|
346
|
+
for model_name, group_letter in CUSTOM_COVER_MODEL_NAMES.items():
|
|
347
|
+
valid_covers = custom_cover_model.objects.filter(group=group_letter)
|
|
348
|
+
group_model = apps.get_model("codex", model_name)
|
|
349
|
+
objs = group_model.objects.exclude(
|
|
350
|
+
Q(custom_cover__isnull=True) | Q(custom_cover__in=valid_covers)
|
|
351
|
+
)
|
|
352
|
+
update_groups = []
|
|
353
|
+
for group in objs:
|
|
354
|
+
group.custom_cover = None
|
|
355
|
+
group.updated_at = now
|
|
356
|
+
update_groups.append(group)
|
|
357
|
+
group_model.objects.bulk_update(update_groups, ["custom_cover"])
|
|
358
|
+
count = len(update_groups)
|
|
359
|
+
if count:
|
|
360
|
+
LOG.warn(f"Removed illegal custom cover links from {count} {model_name}s")
|
|
361
|
+
|
|
362
|
+
|
|
306
363
|
def _delete_errors():
|
|
307
364
|
"""DELETE things we can't fix."""
|
|
365
|
+
_delete_extra_custom_cover_libraries(apps)
|
|
366
|
+
|
|
308
367
|
for host_model_name in HAVE_LIBRARY_FKS:
|
|
368
|
+
if host_model_name == "CustomCover" and not has_applied_migration(
|
|
369
|
+
MIGRATION_0028
|
|
370
|
+
):
|
|
371
|
+
continue
|
|
309
372
|
_delete_fk_integrity_errors(apps, host_model_name, "Library", "library")
|
|
310
373
|
|
|
311
374
|
for host_model_name in WATCHED_PATHS:
|
|
@@ -378,6 +441,7 @@ def _repair_integrity():
|
|
|
378
441
|
_mark_comics_with_bad_rels_for_update(comic_model, bad_comic_ids)
|
|
379
442
|
|
|
380
443
|
_repair_library_groups(apps)
|
|
444
|
+
_repair_groups_with_custom_covers(apps)
|
|
381
445
|
|
|
382
446
|
|
|
383
447
|
def repair_db():
|
codex/librarian/covers/coverd.py
CHANGED
|
@@ -11,7 +11,7 @@ from codex.librarian.covers.tasks import (
|
|
|
11
11
|
)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class CoverThread(CoverCreateMixin, CoverPurgeMixin):
|
|
15
15
|
"""Create comic covers in it's own thread."""
|
|
16
16
|
|
|
17
17
|
def process_item(self, item):
|
|
@@ -22,7 +22,7 @@ class CoverContributorThread(CoverCreateMixin, CoverPurgeMixin):
|
|
|
22
22
|
elif isinstance(task, CoverRemoveAllTask):
|
|
23
23
|
self.purge_all_comic_covers(self.librarian_queue)
|
|
24
24
|
elif isinstance(task, CoverRemoveTask):
|
|
25
|
-
self.purge_comic_covers(task.
|
|
25
|
+
self.purge_comic_covers(task.pks, task.custom)
|
|
26
26
|
elif isinstance(task, CoverRemoveOrphansTask):
|
|
27
27
|
self.cleanup_orphan_covers()
|
|
28
28
|
elif isinstance(task, CoverCreateAllTask):
|
codex/librarian/covers/create.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Create comic cover paths."""
|
|
2
2
|
|
|
3
3
|
from io import BytesIO
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
from time import time
|
|
5
6
|
|
|
6
7
|
from comicbox.box import Comicbox
|
|
@@ -10,7 +11,7 @@ from PIL import Image
|
|
|
10
11
|
from codex.librarian.covers.path import CoverPathMixin
|
|
11
12
|
from codex.librarian.covers.status import CoverStatusTypes
|
|
12
13
|
from codex.librarian.covers.tasks import CoverSaveToCache
|
|
13
|
-
from codex.models import Comic
|
|
14
|
+
from codex.models import Comic, CustomCover
|
|
14
15
|
from codex.status import Status
|
|
15
16
|
|
|
16
17
|
|
|
@@ -51,20 +52,30 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
51
52
|
return image_data
|
|
52
53
|
|
|
53
54
|
@classmethod
|
|
54
|
-
def
|
|
55
|
+
def _get_custom_cover_image(cls, cover_path):
|
|
56
|
+
"""Get cover image from image file."""
|
|
57
|
+
with Path(cover_path).open("rb") as f:
|
|
58
|
+
return f.read()
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def create_cover_from_path(cls, pk, cover_path, log, librarian_queue, custom=False): # noqa: PLR0913
|
|
55
62
|
"""Create cover for path.
|
|
56
63
|
|
|
57
64
|
Called from views/cover.
|
|
58
65
|
"""
|
|
59
|
-
|
|
66
|
+
db_path = None
|
|
60
67
|
try:
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
model = CustomCover if custom else Comic
|
|
69
|
+
db_path = model.objects.only("path").get(pk=pk).path
|
|
70
|
+
if custom:
|
|
71
|
+
cover_image = cls._get_custom_cover_image(db_path)
|
|
72
|
+
else:
|
|
73
|
+
cover_image = cls._get_comic_cover_image(db_path)
|
|
63
74
|
data = cls._create_cover_thumbnail(cover_image)
|
|
64
75
|
except Exception as exc:
|
|
65
76
|
data = b""
|
|
66
|
-
|
|
67
|
-
log.warning(f"Could not create cover thumbnail for {
|
|
77
|
+
cover_str = db_path if db_path else f"{pk=}"
|
|
78
|
+
log.warning(f"Could not create cover thumbnail for {cover_str}: {exc}")
|
|
68
79
|
|
|
69
80
|
task = CoverSaveToCache(cover_path, data)
|
|
70
81
|
librarian_queue.put(task)
|
|
@@ -80,9 +91,9 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
80
91
|
# zero length file is code for missing.
|
|
81
92
|
cover_path.touch()
|
|
82
93
|
|
|
83
|
-
def _bulk_create_comic_covers(self,
|
|
94
|
+
def _bulk_create_comic_covers(self, pks, custom=False):
|
|
84
95
|
"""Create bulk comic covers."""
|
|
85
|
-
num_comics = len(
|
|
96
|
+
num_comics = len(pks)
|
|
86
97
|
if not num_comics:
|
|
87
98
|
return None
|
|
88
99
|
status = Status(CoverStatusTypes.CREATE_COVERS, 0, num_comics)
|
|
@@ -92,9 +103,9 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
92
103
|
self.status_controller.start(status)
|
|
93
104
|
|
|
94
105
|
# Get comic objects
|
|
95
|
-
for pk in
|
|
106
|
+
for pk in pks:
|
|
96
107
|
# Create all covers.
|
|
97
|
-
cover_path = self.get_cover_path(pk)
|
|
108
|
+
cover_path = self.get_cover_path(pk, custom)
|
|
98
109
|
if cover_path.exists():
|
|
99
110
|
status.decrement_total()
|
|
100
111
|
else:
|
|
@@ -106,12 +117,17 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
106
117
|
self.status_controller.update(status)
|
|
107
118
|
|
|
108
119
|
total_elapsed = naturaldelta(time() - start_time)
|
|
109
|
-
|
|
120
|
+
desc = "custom" if custom else "comic"
|
|
121
|
+
self.log.info(
|
|
122
|
+
f"Created {status.complete} {desc} covers in {total_elapsed}."
|
|
123
|
+
)
|
|
110
124
|
finally:
|
|
111
125
|
self.status_controller.finish(status)
|
|
112
126
|
return status.complete
|
|
113
127
|
|
|
114
128
|
def create_all_covers(self):
|
|
115
129
|
"""Create all covers for all libraries."""
|
|
130
|
+
pks = CustomCover.objects.values_list("pk", flat=True)
|
|
131
|
+
self._bulk_create_comic_covers(pks, True)
|
|
116
132
|
pks = Comic.objects.values_list("pk", flat=True)
|
|
117
|
-
self._bulk_create_comic_covers(pks)
|
|
133
|
+
self._bulk_create_comic_covers(pks, False)
|
codex/librarian/covers/path.py
CHANGED
|
@@ -11,7 +11,8 @@ from codex.threads import QueuedThread
|
|
|
11
11
|
class CoverPathMixin(QueuedThread):
|
|
12
12
|
"""Path methods for covers."""
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
COVERS_ROOT = ROOT_CACHE_PATH / "covers"
|
|
15
|
+
CUSTOM_COVERS_ROOT = ROOT_CACHE_PATH / "custom-covers"
|
|
15
16
|
_HEX_FILL = 8
|
|
16
17
|
_PATH_STEP = 2
|
|
17
18
|
_ZFILL = 12
|
|
@@ -27,16 +28,17 @@ class CoverPathMixin(QueuedThread):
|
|
|
27
28
|
return Path("/".join(parts))
|
|
28
29
|
|
|
29
30
|
@classmethod
|
|
30
|
-
def get_cover_path(cls, pk):
|
|
31
|
+
def get_cover_path(cls, pk, custom=False):
|
|
31
32
|
"""Get cover path for comic pk."""
|
|
32
33
|
cover_path = cls._hex_path(pk)
|
|
33
|
-
|
|
34
|
+
root = cls.CUSTOM_COVERS_ROOT if custom else cls.COVERS_ROOT
|
|
35
|
+
return root / cover_path.with_suffix(".webp")
|
|
34
36
|
|
|
35
37
|
@classmethod
|
|
36
|
-
def get_cover_paths(cls,
|
|
38
|
+
def get_cover_paths(cls, pks, custom=False):
|
|
37
39
|
"""Get cover paths for many comic pks."""
|
|
38
40
|
cover_paths = set()
|
|
39
|
-
for
|
|
40
|
-
cover_path = cls.get_cover_path(
|
|
41
|
+
for pk in pks:
|
|
42
|
+
cover_path = cls.get_cover_path(pk, custom)
|
|
41
43
|
cover_paths.add(cover_path)
|
|
42
44
|
return cover_paths
|
codex/librarian/covers/purge.py
CHANGED
|
@@ -8,6 +8,7 @@ from codex.librarian.covers.path import CoverPathMixin
|
|
|
8
8
|
from codex.librarian.covers.status import CoverStatusTypes
|
|
9
9
|
from codex.librarian.notifier.tasks import LIBRARY_CHANGED_TASK
|
|
10
10
|
from codex.models import Comic, Timestamp
|
|
11
|
+
from codex.models.paths import CustomCover
|
|
11
12
|
from codex.status import Status
|
|
12
13
|
|
|
13
14
|
|
|
@@ -20,17 +21,17 @@ class CoverPurgeMixin(CoverPathMixin):
|
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
@classmethod
|
|
23
|
-
def _cleanup_cover_dirs(cls, path):
|
|
24
|
+
def _cleanup_cover_dirs(cls, path, cover_root):
|
|
24
25
|
"""Recursively remove empty cover directories."""
|
|
25
|
-
if not path or
|
|
26
|
+
if not path or cover_root not in path.parents:
|
|
26
27
|
return
|
|
27
28
|
try:
|
|
28
29
|
path.rmdir()
|
|
29
|
-
cls._cleanup_cover_dirs(path.parent)
|
|
30
|
+
cls._cleanup_cover_dirs(path.parent, cover_root)
|
|
30
31
|
except OSError:
|
|
31
32
|
pass
|
|
32
33
|
|
|
33
|
-
def purge_cover_paths(self, cover_paths):
|
|
34
|
+
def purge_cover_paths(self, cover_paths, cover_root):
|
|
34
35
|
"""Purge a set a cover paths."""
|
|
35
36
|
self.log.debug(f"Removing {len(cover_paths)} possible cover thumbnails...")
|
|
36
37
|
status = Status(CoverStatusTypes.PURGE_COVERS, 0, len(cover_paths))
|
|
@@ -46,38 +47,41 @@ class CoverPurgeMixin(CoverPathMixin):
|
|
|
46
47
|
cover_dirs.add(cover_path.parent)
|
|
47
48
|
self.status_controller.update(status)
|
|
48
49
|
for cover_dir in cover_dirs:
|
|
49
|
-
self._cleanup_cover_dirs(cover_dir)
|
|
50
|
+
self._cleanup_cover_dirs(cover_dir, cover_root)
|
|
50
51
|
self.log.info(f"Removed {status.complete} cover thumbnails.")
|
|
51
52
|
finally:
|
|
52
53
|
self.status_controller.finish(status)
|
|
53
54
|
return status.complete
|
|
54
55
|
|
|
55
|
-
def purge_comic_covers(self,
|
|
56
|
+
def purge_comic_covers(self, pks: frozenset[int], custom: bool):
|
|
56
57
|
"""Purge a set a cover paths."""
|
|
57
|
-
cover_paths = self.get_cover_paths(
|
|
58
|
-
|
|
58
|
+
cover_paths = self.get_cover_paths(pks, custom)
|
|
59
|
+
cover_root = self.CUSTOM_COVERS_ROOT if custom else self.COVERS_ROOT
|
|
60
|
+
return self.purge_cover_paths(cover_paths, cover_root)
|
|
59
61
|
|
|
60
62
|
def purge_all_comic_covers(self, librarian_queue):
|
|
61
63
|
"""Purge every comic cover."""
|
|
62
64
|
self.log.debug("Removing entire comic cover cache.")
|
|
63
65
|
try:
|
|
64
|
-
shutil.rmtree(self.
|
|
66
|
+
shutil.rmtree(self.COVERS_ROOT)
|
|
65
67
|
self.log.info("Removed entire comic cover cache.")
|
|
68
|
+
shutil.rmtree(self.CUSTOM_COVERS_ROOT)
|
|
69
|
+
self.log.info("Removed entire custom cover cache.")
|
|
66
70
|
except Exception as exc:
|
|
67
71
|
self.log.warning(exc)
|
|
68
72
|
Timestamp.touch(Timestamp.TimestampChoices.COVERS)
|
|
69
73
|
librarian_queue.put(LIBRARY_CHANGED_TASK)
|
|
70
74
|
|
|
71
|
-
def
|
|
75
|
+
def _cleanup_orphan_covers(self, cover_class, cover_root, name):
|
|
72
76
|
"""Remove all orphan cover thumbs."""
|
|
73
77
|
try:
|
|
74
|
-
self.log.debug("Removing covers from missing
|
|
78
|
+
self.log.debug(f"Removing covers from missing {name}.")
|
|
75
79
|
self.status_controller.start_many(self._CLEANUP_STATUS_MAP)
|
|
76
|
-
|
|
77
|
-
db_cover_paths = self.get_cover_paths(
|
|
80
|
+
pks = cover_class.objects.all().values_list("pk", flat=True)
|
|
81
|
+
db_cover_paths = self.get_cover_paths(pks)
|
|
78
82
|
|
|
79
83
|
orphan_cover_paths = set()
|
|
80
|
-
for root, _, filenames in os.walk(
|
|
84
|
+
for root, _, filenames in os.walk(cover_root):
|
|
81
85
|
root_path = Path(root)
|
|
82
86
|
for fn in filenames:
|
|
83
87
|
fs_cover_path = root_path / fn
|
|
@@ -86,6 +90,13 @@ class CoverPurgeMixin(CoverPathMixin):
|
|
|
86
90
|
finally:
|
|
87
91
|
self.status_controller.finish(CoverStatusTypes.FIND_ORPHAN)
|
|
88
92
|
|
|
89
|
-
count = self.purge_cover_paths(orphan_cover_paths)
|
|
90
|
-
self.log.info(f"Removed {count} covers for missing
|
|
93
|
+
count = self.purge_cover_paths(orphan_cover_paths, cover_root)
|
|
94
|
+
self.log.info(f"Removed {count} covers for missing {name}.")
|
|
91
95
|
return count
|
|
96
|
+
|
|
97
|
+
def cleanup_orphan_covers(self):
|
|
98
|
+
"""Cleanup both comic and custom covers."""
|
|
99
|
+
self._cleanup_orphan_covers(Comic, self.COVERS_ROOT, "comics")
|
|
100
|
+
self._cleanup_orphan_covers(
|
|
101
|
+
CustomCover, self.CUSTOM_COVERS_ROOT, "custom covers"
|
|
102
|
+
)
|
codex/librarian/covers/tasks.py
CHANGED
|
@@ -5,7 +5,7 @@ from dataclasses import dataclass
|
|
|
5
5
|
|
|
6
6
|
@dataclass
|
|
7
7
|
class CoverTask:
|
|
8
|
-
"""Handle with the
|
|
8
|
+
"""Handle with the CoverThread."""
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
@@ -29,7 +29,8 @@ class LibrariesTask:
|
|
|
29
29
|
class CoverRemoveTask(CoverTask):
|
|
30
30
|
"""Purge a set of comic covers."""
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
pks: frozenset
|
|
33
|
+
custom: bool
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
@dataclass
|
|
@@ -87,7 +87,7 @@ class AggregateMetadataMixin(CleanMetadataMixin):
|
|
|
87
87
|
return m2m_md
|
|
88
88
|
|
|
89
89
|
def _get_path_metadata(self, path, import_metadata):
|
|
90
|
-
"""Get the
|
|
90
|
+
"""Get the metadata from comicbox and munge it a little."""
|
|
91
91
|
md = {}
|
|
92
92
|
fk_md = {}
|
|
93
93
|
group_tree_md = {}
|
|
@@ -251,7 +251,7 @@ class AggregateMetadataMixin(CleanMetadataMixin):
|
|
|
251
251
|
force_import_metadata,
|
|
252
252
|
status=None,
|
|
253
253
|
):
|
|
254
|
-
"""Get aggregated
|
|
254
|
+
"""Get aggregated metadata for the paths given."""
|
|
255
255
|
total_paths = len(all_paths)
|
|
256
256
|
if not total_paths:
|
|
257
257
|
return 0
|
|
@@ -2,17 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from types import MappingProxyType
|
|
4
4
|
|
|
5
|
+
from bidict import bidict
|
|
6
|
+
|
|
5
7
|
from codex.models import (
|
|
6
8
|
Comic,
|
|
7
9
|
Contributor,
|
|
10
|
+
Folder,
|
|
8
11
|
Imprint,
|
|
9
12
|
Publisher,
|
|
10
13
|
Series,
|
|
14
|
+
StoryArc,
|
|
11
15
|
StoryArcNumber,
|
|
12
16
|
Volume,
|
|
13
17
|
)
|
|
14
18
|
from codex.models.groups import BrowserGroupModel
|
|
15
19
|
from codex.models.named import Identifier
|
|
20
|
+
from codex.models.paths import CustomCover
|
|
16
21
|
|
|
17
22
|
#################
|
|
18
23
|
# DICT METADATA #
|
|
@@ -66,10 +71,6 @@ DICT_MODEL_REL_MAP = MappingProxyType(
|
|
|
66
71
|
f"{_STORY_ARC_FIELD_NAME}__name",
|
|
67
72
|
_NUMBER_FIELD_NAME,
|
|
68
73
|
),
|
|
69
|
-
}
|
|
70
|
-
)
|
|
71
|
-
IDENTIFIERS_MODEL_REL_MAP = MappingProxyType(
|
|
72
|
-
{
|
|
73
74
|
IDENTIFIERS_FIELD_NAME: (
|
|
74
75
|
f"{_IDENTIFIER_TYPE_FIELD_NAME}__name",
|
|
75
76
|
{
|
|
@@ -143,9 +144,15 @@ BULK_UPDATE_COMIC_FIELDS_WITH_VALUES = tuple(
|
|
|
143
144
|
- frozenset(BULK_UPDATE_FOLDER_MODIFIED_FIELDS)
|
|
144
145
|
)
|
|
145
146
|
)
|
|
146
|
-
MOVED_BULK_COMIC_UPDATE_FIELDS = ("path", "parent_folder")
|
|
147
|
-
MOVED_BULK_FOLDER_UPDATE_FIELDS = (
|
|
148
|
-
|
|
147
|
+
MOVED_BULK_COMIC_UPDATE_FIELDS = ("path", "parent_folder", "stat", "updated_at")
|
|
148
|
+
MOVED_BULK_FOLDER_UPDATE_FIELDS = (
|
|
149
|
+
"path",
|
|
150
|
+
"parent_folder",
|
|
151
|
+
*GROUP_BASE_FIELDS,
|
|
152
|
+
"stat",
|
|
153
|
+
"updated_at",
|
|
154
|
+
)
|
|
155
|
+
CUSTOM_COVER_UPDATE_FIELDS = ("path", "stat", "updated_at", "sort_name", "group")
|
|
149
156
|
|
|
150
157
|
#########
|
|
151
158
|
# OTHER #
|
|
@@ -188,6 +195,15 @@ GROUP_COMPARE_FIELDS = MappingProxyType(
|
|
|
188
195
|
Volume: ("publisher__name", "imprint__name", "series__name", "name"),
|
|
189
196
|
}
|
|
190
197
|
)
|
|
198
|
+
CLASS_CUSTOM_COVER_GROUP_MAP = bidict(
|
|
199
|
+
{
|
|
200
|
+
Publisher: CustomCover.GroupChoice.P.value,
|
|
201
|
+
Imprint: CustomCover.GroupChoice.I.value,
|
|
202
|
+
Series: CustomCover.GroupChoice.S.value,
|
|
203
|
+
StoryArc: CustomCover.GroupChoice.A.value,
|
|
204
|
+
Folder: CustomCover.GroupChoice.F.value,
|
|
205
|
+
}
|
|
206
|
+
)
|
|
191
207
|
|
|
192
208
|
COMIC_FK_FIELD_NAMES = frozenset(
|
|
193
209
|
field.name
|