codex 1.6.0a4__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 -3
- 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 +65 -1
- 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-CN6Sn_k7.9bf27129091c.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-CN6Sn_k7.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-CN1RPZPQ.b763c6a7abaf.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-CN1RPZPQ.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-DC3KGsMT.71e2a460e44b.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-DC3KGsMT.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-DJQYXCn2.f0d471a1b80a.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-DJQYXCn2.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-CgodTThy.b20290d57146.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-CgodTThy.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-zPp4DXWq.635b7f5aa882.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-zPp4DXWq.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-pK9Xompk.67014b86fc74.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-pK9Xompk.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-CMi6sDtP.8445f91d9023.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-CMi6sDtP.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-DNsRQx9l.c42f636c386e.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-DNsRQx9l.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-BHTkpC6Z.2d5d5dd57ad3.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-BHTkpC6Z.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-DmXGDg4z.284812bd376d.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-DmXGDg4z.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-DKaH5S3z.5c6042016200.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-DKaH5S3z.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-DUVATQW3.e1e7aa6d0b18.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-DUVATQW3.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-D3CyU-Il.fbf9ce5ef593.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-D3CyU-Il.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-0ef6-Ajc.e2879bcd242f.js → http-error-HFeYnF0n.a3c578a0265e.js} +1 -1
- 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-0ef6-Ajc.js → http-error-HFeYnF0n.js} +1 -1
- 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-CuzJev9T.js → main-gSW7TFDY.329223b148d4.js} +6 -6
- 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-CuzJev9T.f58771cc37e2.js → main-gSW7TFDY.js} +6 -6
- 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-BlU5mm5n.0431bcdf48d1.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-BlU5mm5n.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-s7jttB_F.f7b43b04961a.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-s7jttB_F.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-BiKxuxj7.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-BiKxuxj7.dabe592f46f1.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-DbJuixzh.352aaaab5925.js → settings-drawer-xSctoxZn.4ce8afcb879a.js} +2 -2
- 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-DbJuixzh.js → settings-drawer-xSctoxZn.js} +2 -2
- 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-BhRLh7XB.7be95b5021c3.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-BhRLh7XB.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-BgXQnNpn.f63b36d526b5.js → unauthorized-kEuFL5br.475729603aeb.js} +1 -1
- 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-BgXQnNpn.js → unauthorized-kEuFL5br.js} +1 -1
- 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-cZoJDlb-.2a497e8b2b24.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-cZoJDlb-.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 +254 -254
- 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.0a4.dist-info → codex-1.6.0a5.dist-info}/METADATA +3 -2
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/RECORD +340 -339
- codex/static_root/assets/VCheckbox-CN6Sn_k7.9bf27129091c.js.br +0 -0
- codex/static_root/assets/VCheckbox-CN6Sn_k7.9bf27129091c.js.gz +0 -0
- codex/static_root/assets/VCheckbox-CN6Sn_k7.js.br +0 -0
- codex/static_root/assets/VCheckbox-CN6Sn_k7.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.b763c6a7abaf.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.b763c6a7abaf.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CN1RPZPQ.js.gz +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.6e49097cea2c.js +0 -1
- codex/static_root/assets/VCombobox-CtLUGYtD.6e49097cea2c.js.br +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.6e49097cea2c.js.gz +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.js +0 -1
- codex/static_root/assets/VCombobox-CtLUGYtD.js.br +0 -0
- codex/static_root/assets/VCombobox-CtLUGYtD.js.gz +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.71e2a460e44b.js.br +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.71e2a460e44b.js.gz +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.js.br +0 -0
- codex/static_root/assets/VDialog-DC3KGsMT.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.f0d471a1b80a.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.f0d471a1b80a.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DJQYXCn2.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.b20290d57146.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.b20290d57146.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.js.br +0 -0
- codex/static_root/assets/VRadioGroup-CgodTThy.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-B6s-x36h.de1030c82785.js +0 -1
- codex/static_root/assets/VSelect-B6s-x36h.de1030c82785.js.br +0 -0
- codex/static_root/assets/VSelect-B6s-x36h.de1030c82785.js.gz +0 -0
- codex/static_root/assets/VSelect-B6s-x36h.js +0 -1
- codex/static_root/assets/VSelect-B6s-x36h.js.br +0 -0
- codex/static_root/assets/VSelect-B6s-x36h.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.635b7f5aa882.js.br +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.635b7f5aa882.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.js.br +0 -0
- codex/static_root/assets/VSelectionControl-zPp4DXWq.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.67014b86fc74.js.br +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.67014b86fc74.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.js.br +0 -0
- codex/static_root/assets/VSlideGroup-pK9Xompk.js.gz +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.8445f91d9023.js.br +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.8445f91d9023.js.gz +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.js.br +0 -0
- codex/static_root/assets/VTable-CMi6sDtP.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/VTextField-dMTizQEN.e93eefec0fdf.js +0 -1
- codex/static_root/assets/VTextField-dMTizQEN.e93eefec0fdf.js.br +0 -0
- codex/static_root/assets/VTextField-dMTizQEN.e93eefec0fdf.js.gz +0 -0
- codex/static_root/assets/VTextField-dMTizQEN.js +0 -1
- codex/static_root/assets/VTextField-dMTizQEN.js.br +0 -0
- codex/static_root/assets/VTextField-dMTizQEN.js.gz +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.c42f636c386e.js.br +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.c42f636c386e.js.gz +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.js.br +0 -0
- codex/static_root/assets/VWindowItem-DNsRQx9l.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-CApphpty.5a41cbf0eefc.js +0 -1
- codex/static_root/assets/admin-CApphpty.5a41cbf0eefc.js.br +0 -0
- codex/static_root/assets/admin-CApphpty.5a41cbf0eefc.js.gz +0 -0
- codex/static_root/assets/admin-CApphpty.js +0 -1
- codex/static_root/assets/admin-CApphpty.js.br +0 -0
- codex/static_root/assets/admin-CApphpty.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/admin-drawer-panel-Dp8KD9D7.a79eda3055a7.js +0 -30
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.a79eda3055a7.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.a79eda3055a7.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.js +0 -30
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Dp8KD9D7.js.gz +0 -0
- codex/static_root/assets/browser-C_PHQbyN.bb065e2ce21a.js +0 -1
- codex/static_root/assets/browser-C_PHQbyN.bb065e2ce21a.js.br +0 -0
- codex/static_root/assets/browser-C_PHQbyN.bb065e2ce21a.js.gz +0 -0
- codex/static_root/assets/browser-C_PHQbyN.js +0 -1
- codex/static_root/assets/browser-C_PHQbyN.js.br +0 -0
- codex/static_root/assets/browser-C_PHQbyN.js.gz +0 -0
- codex/static_root/assets/browser-DVI4LUNv.css +0 -1
- codex/static_root/assets/browser-DVI4LUNv.css.br +0 -0
- codex/static_root/assets/browser-DVI4LUNv.css.gz +0 -0
- codex/static_root/assets/browser-DVI4LUNv.fef2b997145a.css +0 -1
- codex/static_root/assets/browser-DVI4LUNv.fef2b997145a.css.br +0 -0
- codex/static_root/assets/browser-DVI4LUNv.fef2b997145a.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.2d5d5dd57ad3.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.2d5d5dd57ad3.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BHTkpC6Z.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.284812bd376d.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.284812bd376d.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DmXGDg4z.js.gz +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.5c6042016200.js.br +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.5c6042016200.js.gz +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.js.br +0 -0
- codex/static_root/assets/datetime-column-DKaH5S3z.js.gz +0 -0
- codex/static_root/assets/filter-DUVATQW3.e1e7aa6d0b18.js.br +0 -0
- codex/static_root/assets/filter-DUVATQW3.e1e7aa6d0b18.js.gz +0 -0
- codex/static_root/assets/filter-DUVATQW3.js.br +0 -0
- codex/static_root/assets/filter-DUVATQW3.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/flag-tab-CSKMJEn-.5d5c0604541b.js +0 -1
- codex/static_root/assets/flag-tab-CSKMJEn-.5d5c0604541b.js.br +0 -0
- codex/static_root/assets/flag-tab-CSKMJEn-.5d5c0604541b.js.gz +0 -0
- codex/static_root/assets/flag-tab-CSKMJEn-.js +0 -1
- codex/static_root/assets/flag-tab-CSKMJEn-.js.br +0 -0
- codex/static_root/assets/flag-tab-CSKMJEn-.js.gz +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.fbf9ce5ef593.js.br +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.fbf9ce5ef593.js.gz +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.js.br +0 -0
- codex/static_root/assets/group-tab-D3CyU-Il.js.gz +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.e2879bcd242f.js.br +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.e2879bcd242f.js.gz +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.js.br +0 -0
- codex/static_root/assets/http-error-0ef6-Ajc.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/library-tab-IdDfLn4k.70b55460b8e1.js +0 -1
- codex/static_root/assets/library-tab-IdDfLn4k.70b55460b8e1.js.br +0 -0
- codex/static_root/assets/library-tab-IdDfLn4k.70b55460b8e1.js.gz +0 -0
- codex/static_root/assets/library-tab-IdDfLn4k.js +0 -1
- codex/static_root/assets/library-tab-IdDfLn4k.js.br +0 -0
- codex/static_root/assets/library-tab-IdDfLn4k.js.gz +0 -0
- codex/static_root/assets/main-CuzJev9T.f58771cc37e2.js.br +0 -0
- codex/static_root/assets/main-CuzJev9T.f58771cc37e2.js.gz +0 -0
- codex/static_root/assets/main-CuzJev9T.js.br +0 -0
- codex/static_root/assets/main-CuzJev9T.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-CLr9RVHd.66d3b756d974.js +0 -1
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.66d3b756d974.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.66d3b756d974.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.js +0 -1
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CLr9RVHd.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.0431bcdf48d1.js.br +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.0431bcdf48d1.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.js.br +0 -0
- codex/static_root/assets/pdf-doc-BlU5mm5n.js.gz +0 -0
- codex/static_root/assets/reader-BiKxuxj7.css.br +0 -0
- codex/static_root/assets/reader-BiKxuxj7.css.gz +0 -0
- codex/static_root/assets/reader-BiKxuxj7.dabe592f46f1.css.br +0 -0
- codex/static_root/assets/reader-BiKxuxj7.dabe592f46f1.css.gz +0 -0
- codex/static_root/assets/reader-s7jttB_F.f7b43b04961a.js.br +0 -0
- codex/static_root/assets/reader-s7jttB_F.f7b43b04961a.js.gz +0 -0
- codex/static_root/assets/reader-s7jttB_F.js.br +0 -0
- codex/static_root/assets/reader-s7jttB_F.js.gz +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.27eca8616ba4.js +0 -1
- codex/static_root/assets/relation-chips-BiVsYCd9.27eca8616ba4.js.br +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.27eca8616ba4.js.gz +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.js +0 -1
- codex/static_root/assets/relation-chips-BiVsYCd9.js.br +0 -0
- codex/static_root/assets/relation-chips-BiVsYCd9.js.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/settings-drawer-DbJuixzh.352aaaab5925.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbJuixzh.352aaaab5925.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DbJuixzh.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbJuixzh.js.gz +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.7be95b5021c3.js.br +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.7be95b5021c3.js.gz +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.js.br +0 -0
- codex/static_root/assets/stats-tab-BhRLh7XB.js.gz +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.c574111658b9.js +0 -1
- codex/static_root/assets/task-tab-DLrd54z2.c574111658b9.js.br +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.c574111658b9.js.gz +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.js +0 -1
- codex/static_root/assets/task-tab-DLrd54z2.js.br +0 -0
- codex/static_root/assets/task-tab-DLrd54z2.js.gz +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.f63b36d526b5.js.br +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.f63b36d526b5.js.gz +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.js.br +0 -0
- codex/static_root/assets/unauthorized-BgXQnNpn.js.gz +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.2a497e8b2b24.js.br +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.2a497e8b2b24.js.gz +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.js.br +0 -0
- codex/static_root/assets/user-tab-cZoJDlb-.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.3c2f342d5f5a.json +0 -642
- codex/static_root/manifest.3c2f342d5f5a.json.br +0 -0
- codex/static_root/manifest.3c2f342d5f5a.json.gz +0 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/LICENSE +0 -0
- {codex-1.6.0a4.dist-info → codex-1.6.0a5.dist-info}/WHEEL +0 -0
- {codex-1.6.0a4.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 #
|
|
@@ -139,9 +144,15 @@ BULK_UPDATE_COMIC_FIELDS_WITH_VALUES = tuple(
|
|
|
139
144
|
- frozenset(BULK_UPDATE_FOLDER_MODIFIED_FIELDS)
|
|
140
145
|
)
|
|
141
146
|
)
|
|
142
|
-
MOVED_BULK_COMIC_UPDATE_FIELDS = ("path", "parent_folder")
|
|
143
|
-
MOVED_BULK_FOLDER_UPDATE_FIELDS = (
|
|
144
|
-
|
|
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")
|
|
145
156
|
|
|
146
157
|
#########
|
|
147
158
|
# OTHER #
|
|
@@ -184,6 +195,15 @@ GROUP_COMPARE_FIELDS = MappingProxyType(
|
|
|
184
195
|
Volume: ("publisher__name", "imprint__name", "series__name", "name"),
|
|
185
196
|
}
|
|
186
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
|
+
)
|
|
187
207
|
|
|
188
208
|
COMIC_FK_FIELD_NAMES = frozenset(
|
|
189
209
|
field.name
|