codex 1.6.19__py3-none-any.whl → 1.7.0a0__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/db.py +119 -0
- codex/librarian/covers/purge.py +1 -1
- codex/librarian/importer/const.py +7 -1
- codex/librarian/importer/create_fks.py +5 -2
- codex/librarian/importer/importerd.py +17 -12
- codex/librarian/importer/moved.py +44 -13
- codex/librarian/importer/tasks.py +2 -0
- codex/librarian/janitor/cleanup.py +21 -8
- codex/librarian/janitor/integrity.py +369 -0
- codex/librarian/janitor/janitor.py +50 -15
- codex/librarian/janitor/latest_version.py +2 -1
- codex/librarian/janitor/status.py +5 -0
- codex/librarian/janitor/tasks.py +37 -0
- codex/librarian/janitor/update.py +1 -1
- codex/librarian/librariand.py +32 -24
- codex/librarian/search/optimize.py +43 -0
- codex/librarian/search/remove.py +18 -46
- codex/librarian/search/searchd.py +14 -17
- codex/librarian/search/status.py +2 -1
- codex/librarian/search/tasks.py +4 -9
- codex/librarian/search/update.py +223 -301
- codex/librarian/watchdog/observers.py +2 -1
- codex/migrations/0029_comicfts.py +58 -0
- codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +198 -0
- codex/models/admin.py +15 -6
- codex/models/base.py +9 -0
- codex/models/bookmark.py +3 -3
- codex/models/comic.py +68 -16
- codex/models/functions.py +59 -2
- codex/models/groups.py +6 -1
- codex/models/library.py +1 -3
- codex/models/paths.py +9 -3
- codex/serializers/browser/page.py +2 -0
- codex/settings/settings.py +16 -17
- codex/signals/django_signals.py +4 -10
- codex/startup.py +1 -35
- codex/static_root/assets/{VCheckbox-Cko1sQK-.170726b3b8a1.js → VCheckbox-CWiqcZ7a.3feebd47daab.js} +1 -1
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-Cko1sQK-.js → VCheckbox-CWiqcZ7a.js} +1 -1
- codex/static_root/assets/VCheckbox-CWiqcZ7a.js.br +0 -0
- codex/static_root/assets/VCheckbox-CWiqcZ7a.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js → VCheckboxBtn-Dk3iB62X.bc66d5351baa.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CO3-aqQL.js → VCheckboxBtn-Dk3iB62X.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.gz +0 -0
- codex/static_root/assets/{VCombobox-Bjp6lffE.js → VCombobox-D1_KU27A.b1d30587a973.js} +1 -1
- codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.gz +0 -0
- codex/static_root/assets/{VCombobox-Bjp6lffE.caf59fba486e.js → VCombobox-D1_KU27A.js} +1 -1
- codex/static_root/assets/VCombobox-D1_KU27A.js.br +0 -0
- codex/static_root/assets/VCombobox-D1_KU27A.js.gz +0 -0
- codex/static_root/assets/{VDialog-BkpVGB70.6eea3ca762a5.js → VDialog-GbxvFMMN.594fb3a39ced.js} +1 -1
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.br +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.gz +0 -0
- codex/static_root/assets/{VDialog-BkpVGB70.js → VDialog-GbxvFMMN.js} +1 -1
- codex/static_root/assets/VDialog-GbxvFMMN.js.br +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js → VExpansionPanels-Cywv_bEj.a762e63858e5.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-Ci2-j8XX.js → VExpansionPanels-Cywv_bEj.js} +1 -1
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-D3Py5BfQ.a4f25edb86d3.js → VRadioGroup-C0etmxpZ.0eb3009a6300.js} +1 -1
- codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-D3Py5BfQ.js → VRadioGroup-C0etmxpZ.js} +1 -1
- codex/static_root/assets/VRadioGroup-C0etmxpZ.js.br +0 -0
- codex/static_root/assets/VRadioGroup-C0etmxpZ.js.gz +0 -0
- codex/static_root/assets/{VSelect-DtvZsYZz.e98745d858eb.js → VSelect-txrnRiNJ.a5625760018c.js} +1 -1
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.br +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.gz +0 -0
- codex/static_root/assets/{VSelect-DtvZsYZz.js → VSelect-txrnRiNJ.js} +1 -1
- codex/static_root/assets/VSelect-txrnRiNJ.js.br +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-D6kmykQW.1e1fda62ceba.js → VSelectionControl-CY6RmcvW.7e8824224f6c.js} +1 -1
- codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-D6kmykQW.js → VSelectionControl-CY6RmcvW.js} +1 -1
- codex/static_root/assets/VSelectionControl-CY6RmcvW.js.br +0 -0
- codex/static_root/assets/VSelectionControl-CY6RmcvW.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-UGZWxpF5.360bdefcd215.js → VSlideGroup-DtEQMwY1.6c72325635ed.js} +1 -1
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-UGZWxpF5.js → VSlideGroup-DtEQMwY1.js} +1 -1
- codex/static_root/assets/VSlideGroup-DtEQMwY1.js.br +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.js.gz +0 -0
- codex/static_root/assets/{VTable-DXH_yQ0o.1149f56ad566.js → VTable-2af995xo.6c5bf36631b2.js} +1 -1
- codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.br +0 -0
- codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.gz +0 -0
- codex/static_root/assets/{VTable-DXH_yQ0o.js → VTable-2af995xo.js} +1 -1
- codex/static_root/assets/VTable-2af995xo.js.br +0 -0
- codex/static_root/assets/VTable-2af995xo.js.gz +0 -0
- codex/static_root/assets/{VTextField-Blqy56_L.26d8761e0155.js → VTextField-G6CMj7yO.657a130dd302.js} +1 -1
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.br +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.gz +0 -0
- codex/static_root/assets/{VTextField-Blqy56_L.js → VTextField-G6CMj7yO.js} +1 -1
- codex/static_root/assets/VTextField-G6CMj7yO.js.br +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-C8_ovV7e.afbc08414765.js → VWindowItem-CzoGd6Ul.721e5da11ad4.js} +1 -1
- codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.br +0 -0
- codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-C8_ovV7e.js → VWindowItem-CzoGd6Ul.js} +1 -1
- codex/static_root/assets/VWindowItem-CzoGd6Ul.js.br +0 -0
- codex/static_root/assets/VWindowItem-CzoGd6Ul.js.gz +0 -0
- codex/static_root/assets/{admin-DTRn8bBs.d031ea42d8d0.js → admin-CsPAIQeG.5175467148a3.js} +1 -1
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.br +0 -0
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.gz +0 -0
- codex/static_root/assets/{admin-DTRn8bBs.js → admin-CsPAIQeG.js} +1 -1
- codex/static_root/assets/admin-CsPAIQeG.js.br +0 -0
- codex/static_root/assets/admin-CsPAIQeG.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BaXFOcru.b1a44b05f482.js → admin-drawer-panel-BAT7GA64.73a064628c19.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-BaXFOcru.js → admin-drawer-panel-BAT7GA64.js} +11 -11
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js +1 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js.br +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.js +1 -0
- codex/static_root/assets/browser-C18l93yc.js.br +0 -0
- codex/static_root/assets/browser-C18l93yc.js.gz +0 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css +1 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.br +0 -0
- codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.gz +0 -0
- codex/static_root/assets/browser-DaAqlBYO.css +1 -0
- codex/static_root/assets/browser-DaAqlBYO.css.br +0 -0
- codex/static_root/assets/browser-DaAqlBYO.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js +1 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js +1 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BazJdNs9.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css +1 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css +1 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.gz +0 -0
- codex/static_root/assets/{confirm-dialog-D3_s2DQy.9dde66036c72.js → confirm-dialog-CbW3hlOp.9dd39b443c31.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-D3_s2DQy.js → confirm-dialog-CbW3hlOp.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbW3hlOp.js.gz +0 -0
- codex/static_root/assets/{datetime-column-BxC1Li9e.3dc314b3295d.js → datetime-column-uSqKZU37.b157b0743db5.js} +1 -1
- codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.br +0 -0
- codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.gz +0 -0
- codex/static_root/assets/{datetime-column-BxC1Li9e.js → datetime-column-uSqKZU37.js} +1 -1
- codex/static_root/assets/datetime-column-uSqKZU37.js.br +0 -0
- codex/static_root/assets/datetime-column-uSqKZU37.js.gz +0 -0
- codex/static_root/assets/{filter-Bzk7x841.d0c3b602e62d.js → filter-CBl5KEsB.845a81573c2e.js} +1 -1
- codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.br +0 -0
- codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.gz +0 -0
- codex/static_root/assets/{filter-Bzk7x841.js → filter-CBl5KEsB.js} +1 -1
- codex/static_root/assets/filter-CBl5KEsB.js.br +0 -0
- codex/static_root/assets/filter-CBl5KEsB.js.gz +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js +1 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.br +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.gz +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js +1 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js.br +0 -0
- codex/static_root/assets/flag-tab-CT8pqM4r.js.gz +0 -0
- codex/static_root/assets/{group-tab-5esH4lwX.a81fdd258117.js → group-tab-BOW9kigw.7e5007596367.js} +1 -1
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.br +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.gz +0 -0
- codex/static_root/assets/{group-tab-5esH4lwX.js → group-tab-BOW9kigw.js} +1 -1
- codex/static_root/assets/group-tab-BOW9kigw.js.br +0 -0
- codex/static_root/assets/group-tab-BOW9kigw.js.gz +0 -0
- codex/static_root/assets/{http-error-BTOBfcGY.52ced6f28420.js → http-error-C6yuLApo.45cf99eb7a70.js} +1 -1
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +4 -0
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
- codex/static_root/assets/{http-error-BTOBfcGY.js → http-error-C6yuLApo.js} +1 -1
- codex/static_root/assets/http-error-C6yuLApo.js.br +4 -0
- codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
- codex/static_root/assets/{library-tab-DiPEdOF7.ece2d642a9dc.js → library-tab-D3WOtpE0.9af34e6e7235.js} +1 -1
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.br +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.gz +0 -0
- codex/static_root/assets/{library-tab-DiPEdOF7.js → library-tab-D3WOtpE0.js} +1 -1
- codex/static_root/assets/library-tab-D3WOtpE0.js.br +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.js.gz +0 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js +6 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.br +0 -0
- codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.gz +0 -0
- codex/static_root/assets/main-CmjU2oTp.js +6 -0
- codex/static_root/assets/main-CmjU2oTp.js.br +0 -0
- codex/static_root/assets/main-CmjU2oTp.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js +1 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js +1 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-BH54_5f6.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-C45-onwe.4722195bb60b.css → pagination-toolbar-DGrxp2OD.css} +1 -1
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-C45-onwe.css → pagination-toolbar-DGrxp2OD.f960a624f04f.css} +1 -1
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-CkIzuyx1.00c354a82e6e.js → pagination-toolbar-JH3Kbffr.451171f7ac33.js} +1 -1
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-CkIzuyx1.js → pagination-toolbar-JH3Kbffr.js} +1 -1
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BjlRPuf4.0f056fa321b6.js → pdf-doc-BcRNpoaW.882f6e967869.js} +1 -1
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BjlRPuf4.js → pdf-doc-BcRNpoaW.js} +1 -1
- codex/static_root/assets/pdf-doc-BcRNpoaW.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.js.gz +0 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js +2 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.br +0 -0
- codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.gz +0 -0
- codex/static_root/assets/reader-DIPRg2VB.js +2 -0
- codex/static_root/assets/reader-DIPRg2VB.js.br +0 -0
- codex/static_root/assets/reader-DIPRg2VB.js.gz +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css +1 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -0
- codex/static_root/assets/reader-DveZZr9x.css +1 -0
- codex/static_root/assets/reader-DveZZr9x.css.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.css.gz +0 -0
- codex/static_root/assets/{relation-chips-CUSatZET.js → relation-chips-3tTlR5QH.71f18bc92adc.js} +1 -1
- codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.br +0 -0
- codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CUSatZET.a58031ff8141.js → relation-chips-3tTlR5QH.js} +1 -1
- codex/static_root/assets/relation-chips-3tTlR5QH.js.br +0 -0
- codex/static_root/assets/relation-chips-3tTlR5QH.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-BSHTAsJ9.js → settings-drawer-BdyFhA_r.151f4c3061ed.js} +2 -2
- codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.br +0 -0
- codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-BSHTAsJ9.1d07ce084fc9.js → settings-drawer-BdyFhA_r.js} +2 -2
- codex/static_root/assets/settings-drawer-BdyFhA_r.js.br +0 -0
- codex/static_root/assets/settings-drawer-BdyFhA_r.js.gz +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js +1 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.br +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.gz +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js +1 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js.br +0 -0
- codex/static_root/assets/stats-tab-DeKjwvmh.js.gz +0 -0
- codex/static_root/assets/{stats-tab-BXGisn5N.9b7a71e7fe28.css → stats-tab-DpfGsVX3.1cb66f2649a9.css} +1 -1
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
- codex/static_root/assets/{stats-tab-BXGisn5N.css → stats-tab-DpfGsVX3.css} +1 -1
- codex/static_root/assets/stats-tab-DpfGsVX3.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.css.gz +0 -0
- codex/static_root/assets/{task-tab-NORFAa9p.97e07e366ddd.js → task-tab-C8D0qRP_.d4ab174c69a3.js} +1 -1
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.br +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.gz +0 -0
- codex/static_root/assets/{task-tab-NORFAa9p.js → task-tab-C8D0qRP_.js} +1 -1
- codex/static_root/assets/task-tab-C8D0qRP_.js.br +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.js.gz +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js +1 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.gz +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js +1 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
- codex/static_root/assets/{unauthorized-muDsv-rC.css → unauthorized-BjhIc97q.728511c92cd0.css} +1 -1
- codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.br +0 -0
- codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.gz +0 -0
- codex/static_root/assets/{unauthorized-muDsv-rC.2166ec8b3a32.css → unauthorized-BjhIc97q.css} +1 -1
- codex/static_root/assets/unauthorized-BjhIc97q.css.br +0 -0
- codex/static_root/assets/unauthorized-BjhIc97q.css.gz +0 -0
- codex/static_root/assets/{user-tab-CHbyN1EW.e7206db08680.js → user-tab-BU31Z92y.35613c713faf.js} +1 -1
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.br +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.gz +0 -0
- codex/static_root/assets/{user-tab-CHbyN1EW.js → user-tab-BU31Z92y.js} +1 -1
- codex/static_root/assets/user-tab-BU31Z92y.js.br +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.js.gz +0 -0
- codex/static_root/js/choices-admin.8aa64c911203.json +1 -0
- codex/static_root/js/choices-admin.8aa64c911203.json.br +0 -0
- codex/static_root/js/choices-admin.8aa64c911203.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/manifest.45cb46e19686.json +635 -0
- codex/static_root/manifest.45cb46e19686.json.br +0 -0
- codex/static_root/manifest.45cb46e19686.json.gz +0 -0
- codex/static_root/manifest.json +248 -248
- 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 +10 -4
- codex/views/admin/tasks.py +46 -40
- codex/views/auth.py +15 -4
- codex/views/browser/annotations.py +94 -51
- codex/views/browser/base.py +1 -2
- codex/views/browser/browser.py +52 -21
- codex/views/browser/cover.py +8 -4
- codex/views/browser/filters/annotations.py +28 -6
- codex/views/browser/filters/search/__init__.py +1 -0
- codex/views/browser/filters/search/aliases.py +81 -0
- codex/views/browser/filters/search/field/__init__.py +1 -0
- codex/views/browser/filters/search/field/column.py +53 -0
- codex/views/browser/filters/search/field/expression.py +177 -0
- codex/views/browser/filters/search/field/filter.py +70 -0
- codex/views/browser/filters/search/field/optimize.py +71 -0
- codex/views/browser/filters/search/field/parse.py +187 -0
- codex/views/browser/filters/search/fts.py +38 -0
- codex/views/browser/filters/search/parse.py +220 -0
- codex/views/browser/order_by.py +8 -2
- codex/views/browser/page_in_bounds.py +88 -0
- codex/views/browser/paginate.py +28 -113
- codex/views/const.py +0 -1
- codex/views/opds/v1/feed.py +2 -1
- codex/views/opds/v2/feed.py +2 -1
- codex/views/opds/v2/links.py +1 -1
- codex/views/reader/books.py +1 -1
- codex/views/reader/page.py +3 -3
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/METADATA +18 -10
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/RECORD +321 -372
- codex/_vendor/__init__.py +0 -0
- codex/_vendor/django_haystack-3.2.1.dist-info/AUTHORS +0 -127
- codex/_vendor/django_haystack-3.2.1.dist-info/INSTALLER +0 -1
- codex/_vendor/django_haystack-3.2.1.dist-info/LICENSE +0 -31
- codex/_vendor/django_haystack-3.2.1.dist-info/METADATA +0 -98
- codex/_vendor/django_haystack-3.2.1.dist-info/RECORD +0 -97
- codex/_vendor/django_haystack-3.2.1.dist-info/REQUESTED +0 -0
- codex/_vendor/django_haystack-3.2.1.dist-info/WHEEL +0 -5
- codex/_vendor/django_haystack-3.2.1.dist-info/top_level.txt +0 -1
- codex/_vendor/haystack/__init__.py +0 -80
- codex/_vendor/haystack/admin.py +0 -163
- codex/_vendor/haystack/apps.py +0 -31
- codex/_vendor/haystack/backends/__init__.py +0 -1109
- codex/_vendor/haystack/backends/elasticsearch2_backend.py +0 -390
- codex/_vendor/haystack/backends/elasticsearch5_backend.py +0 -483
- codex/_vendor/haystack/backends/elasticsearch7_backend.py +0 -586
- codex/_vendor/haystack/backends/elasticsearch_backend.py +0 -1141
- codex/_vendor/haystack/backends/simple_backend.py +0 -132
- codex/_vendor/haystack/backends/solr_backend.py +0 -991
- codex/_vendor/haystack/backends/whoosh_backend.py +0 -1104
- codex/_vendor/haystack/constants.py +0 -57
- codex/_vendor/haystack/exceptions.py +0 -57
- codex/_vendor/haystack/fields.py +0 -562
- codex/_vendor/haystack/forms.py +0 -135
- codex/_vendor/haystack/generic_views.py +0 -141
- codex/_vendor/haystack/indexes.py +0 -548
- codex/_vendor/haystack/inputs.py +0 -169
- codex/_vendor/haystack/management/__init__.py +0 -0
- codex/_vendor/haystack/management/commands/__init__.py +0 -0
- codex/_vendor/haystack/management/commands/build_solr_schema.py +0 -187
- codex/_vendor/haystack/management/commands/clear_index.py +0 -67
- codex/_vendor/haystack/management/commands/haystack_info.py +0 -22
- codex/_vendor/haystack/management/commands/rebuild_index.py +0 -65
- codex/_vendor/haystack/management/commands/update_index.py +0 -438
- codex/_vendor/haystack/manager.py +0 -105
- codex/_vendor/haystack/models.py +0 -257
- codex/_vendor/haystack/panels.py +0 -89
- codex/_vendor/haystack/query.py +0 -771
- codex/_vendor/haystack/routers.py +0 -14
- codex/_vendor/haystack/signals.py +0 -88
- codex/_vendor/haystack/templates/panels/haystack.html +0 -33
- codex/_vendor/haystack/templates/search_configuration/schema.xml +0 -1056
- codex/_vendor/haystack/templates/search_configuration/solrconfig.xml +0 -1446
- codex/_vendor/haystack/templatetags/__init__.py +0 -0
- codex/_vendor/haystack/templatetags/highlight.py +0 -131
- codex/_vendor/haystack/templatetags/more_like_this.py +0 -119
- codex/_vendor/haystack/urls.py +0 -5
- codex/_vendor/haystack/utils/__init__.py +0 -84
- codex/_vendor/haystack/utils/app_loading.py +0 -34
- codex/_vendor/haystack/utils/geo.py +0 -71
- codex/_vendor/haystack/utils/highlighting.py +0 -165
- codex/_vendor/haystack/utils/loading.py +0 -374
- codex/_vendor/haystack/utils/log.py +0 -21
- codex/_vendor/haystack/version.py +0 -16
- codex/_vendor/haystack/views.py +0 -253
- codex/integrity.py +0 -492
- codex/librarian/search/merge.py +0 -86
- codex/librarian/search/version.py +0 -63
- codex/search/__init__.py +0 -1
- codex/search/backend.py +0 -524
- codex/search/backend_search.py +0 -229
- codex/search/engine.py +0 -33
- codex/search/indexes.py +0 -79
- codex/search/query.py +0 -67
- codex/search/writing.py +0 -180
- codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.br +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.gz +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.js.br +0 -0
- codex/static_root/assets/VCheckbox-Cko1sQK-.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.br +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.gz +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.js.br +0 -0
- codex/static_root/assets/VCombobox-Bjp6lffE.js.gz +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.br +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.gz +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.js.br +0 -0
- codex/static_root/assets/VDialog-BkpVGB70.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.gz +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.br +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.gz +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.js.br +0 -0
- codex/static_root/assets/VSelect-DtvZsYZz.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.js.br +0 -0
- codex/static_root/assets/VSelectionControl-D6kmykQW.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.br +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.js.br +0 -0
- codex/static_root/assets/VSlideGroup-UGZWxpF5.js.gz +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.br +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.gz +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.js.br +0 -0
- codex/static_root/assets/VTable-DXH_yQ0o.js.gz +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.br +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.gz +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.js.br +0 -0
- codex/static_root/assets/VTextField-Blqy56_L.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.br +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.gz +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.js.br +0 -0
- codex/static_root/assets/VWindowItem-C8_ovV7e.js.gz +0 -0
- codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.br +0 -0
- codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.gz +0 -0
- codex/static_root/assets/admin-DTRn8bBs.js.br +0 -0
- codex/static_root/assets/admin-DTRn8bBs.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.gz +0 -0
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js +0 -1
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.br +0 -0
- codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.gz +0 -0
- codex/static_root/assets/browser-BBpUyGmz.js +0 -1
- codex/static_root/assets/browser-BBpUyGmz.js.br +0 -0
- codex/static_root/assets/browser-BBpUyGmz.js.gz +0 -0
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css +0 -1
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.br +0 -0
- codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.gz +0 -0
- codex/static_root/assets/browser-Binc3n9H.css +0 -1
- codex/static_root/assets/browser-Binc3n9H.css.br +0 -0
- codex/static_root/assets/browser-Binc3n9H.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css +0 -1
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.css +0 -1
- codex/static_root/assets/change-password-dialog-Cebek1-c.css.br +0 -0
- codex/static_root/assets/change-password-dialog-Cebek1-c.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js +0 -1
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js +0 -1
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DlHoLhDF.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.br +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.js.br +0 -0
- codex/static_root/assets/confirm-dialog-D3_s2DQy.js.gz +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.br +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.gz +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.js.br +0 -0
- codex/static_root/assets/datetime-column-BxC1Li9e.js.gz +0 -0
- codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.br +0 -0
- codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.gz +0 -0
- codex/static_root/assets/filter-Bzk7x841.js.br +0 -0
- codex/static_root/assets/filter-Bzk7x841.js.gz +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js +0 -1
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.br +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.gz +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.js +0 -1
- codex/static_root/assets/flag-tab-C2bI5zq3.js.br +0 -0
- codex/static_root/assets/flag-tab-C2bI5zq3.js.gz +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.br +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.gz +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.js.br +0 -0
- codex/static_root/assets/group-tab-5esH4lwX.js.gz +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.br +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.gz +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.js.br +0 -0
- codex/static_root/assets/http-error-BTOBfcGY.js.gz +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.br +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.gz +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.js.br +0 -0
- codex/static_root/assets/library-tab-DiPEdOF7.js.gz +0 -0
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js +0 -6
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.br +0 -0
- codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.gz +0 -0
- codex/static_root/assets/main-BPIPjzsc.js +0 -6
- codex/static_root/assets/main-BPIPjzsc.js.br +0 -0
- codex/static_root/assets/main-BPIPjzsc.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js +0 -1
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js +0 -1
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.css.br +0 -0
- codex/static_root/assets/pagination-toolbar-C45-onwe.css.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.br +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.gz +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.js.br +0 -0
- codex/static_root/assets/pdf-doc-BjlRPuf4.js.gz +0 -0
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css +0 -1
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.br +0 -0
- codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.gz +0 -0
- codex/static_root/assets/reader-_stAWL-4.css +0 -1
- codex/static_root/assets/reader-_stAWL-4.css.br +0 -0
- codex/static_root/assets/reader-_stAWL-4.css.gz +0 -0
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js +0 -2
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.br +0 -0
- codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.gz +0 -0
- codex/static_root/assets/reader-ucxnLi2K.js +0 -2
- codex/static_root/assets/reader-ucxnLi2K.js.br +0 -0
- codex/static_root/assets/reader-ucxnLi2K.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.br +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.js.br +0 -0
- codex/static_root/assets/relation-chips-CUSatZET.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.br +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.gz +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.js.br +0 -0
- codex/static_root/assets/settings-drawer-BSHTAsJ9.js.gz +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.br +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.gz +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.css.br +0 -0
- codex/static_root/assets/stats-tab-BXGisn5N.css.gz +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js +0 -1
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.br +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.gz +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.js +0 -1
- codex/static_root/assets/stats-tab-DKXVB2Cc.js.br +0 -0
- codex/static_root/assets/stats-tab-DKXVB2Cc.js.gz +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.br +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.gz +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.js.br +0 -0
- codex/static_root/assets/task-tab-NORFAa9p.js.gz +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js +0 -1
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.br +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.gz +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.js +0 -1
- codex/static_root/assets/unauthorized-RHaQZRfr.js.br +0 -0
- codex/static_root/assets/unauthorized-RHaQZRfr.js.gz +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.br +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.gz +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.css.br +0 -0
- codex/static_root/assets/unauthorized-muDsv-rC.css.gz +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.br +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.gz +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.js.br +0 -0
- codex/static_root/assets/user-tab-CHbyN1EW.js.gz +0 -0
- codex/static_root/js/choices-admin.1a20a5648f20.json +0 -1
- codex/static_root/js/choices-admin.1a20a5648f20.json.br +0 -0
- codex/static_root/js/choices-admin.1a20a5648f20.json.gz +0 -0
- codex/static_root/manifest.0fe31a6cb99f.json +0 -635
- codex/static_root/manifest.0fe31a6cb99f.json.br +0 -0
- codex/static_root/manifest.0fe31a6cb99f.json.gz +0 -0
- codex/templates/search/indexes/codex/comic_text.txt +0 -47
- codex/views/browser/filters/search.py +0 -196
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/LICENSE +0 -0
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/WHEEL +0 -0
- {codex-1.6.19.dist-info → codex-1.7.0a0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""Database integrity checks and remedies."""
|
|
2
|
+
# Uses app.get_model() because functions may also be called before the models are ready on startup.
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from django.apps import apps
|
|
7
|
+
from django.db import DEFAULT_DB_ALIAS, connections
|
|
8
|
+
from django.db.models.functions import Now
|
|
9
|
+
from django.db.utils import OperationalError
|
|
10
|
+
|
|
11
|
+
from codex.librarian.janitor.status import JanitorStatusTypes
|
|
12
|
+
from codex.librarian.janitor.tasks import JanitorFTSRebuildTask
|
|
13
|
+
from codex.logger.logging import get_logger
|
|
14
|
+
from codex.settings.settings import (
|
|
15
|
+
CONFIG_PATH,
|
|
16
|
+
CUSTOM_COVERS_DIR,
|
|
17
|
+
DB_PATH,
|
|
18
|
+
)
|
|
19
|
+
from codex.status import Status
|
|
20
|
+
from codex.worker_base import WorkerBaseMixin
|
|
21
|
+
|
|
22
|
+
REPAIR_FLAG_PATH = CONFIG_PATH / "rebuild_db"
|
|
23
|
+
REBUILT_DB_PATH = DB_PATH.parent / (DB_PATH.name + ".rebuilt")
|
|
24
|
+
BACKUP_DB_PATH = DB_PATH.parent / (DB_PATH.name + ".bak")
|
|
25
|
+
DUMP_LINE_MATCHER = re.compile("TRANSACTION|ROLLBACK|COMMIT")
|
|
26
|
+
|
|
27
|
+
LOG = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
_FTS_INSERT_TMPL = "INSERT INTO codex_comicfts (codex_comicfts) VALUES('%s');"
|
|
30
|
+
_PRAGMA_TMPL = "PRAGMA %s;"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _exec_sql(sql):
|
|
34
|
+
"""Run sql on an potentially unready database.."""
|
|
35
|
+
connection = connections[DEFAULT_DB_ALIAS]
|
|
36
|
+
connection.prepare_database()
|
|
37
|
+
with connection.cursor() as cursor:
|
|
38
|
+
cursor.execute(sql)
|
|
39
|
+
return cursor.fetchall()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _foreign_key_check(log):
|
|
43
|
+
"""Get table and row ids from foreign_key_check."""
|
|
44
|
+
sql = _PRAGMA_TMPL % "foreign_key_check"
|
|
45
|
+
if results := _exec_sql(sql):
|
|
46
|
+
log.warning(f"Found {len(results)} database rows with illegal foreign keys.")
|
|
47
|
+
log.debug(results)
|
|
48
|
+
else:
|
|
49
|
+
log.info("Database passed foreign key check.")
|
|
50
|
+
return results
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _compile_foreign_key_results(results, log):
|
|
54
|
+
bad_fk_rels = {}
|
|
55
|
+
bad_m2m_rows = {}
|
|
56
|
+
|
|
57
|
+
for row in results:
|
|
58
|
+
table_name, rowid, _parent, fkid = row
|
|
59
|
+
if fkid:
|
|
60
|
+
if table_name not in bad_fk_rels:
|
|
61
|
+
bad_fk_rels[table_name] = {}
|
|
62
|
+
if rowid not in bad_fk_rels[table_name]:
|
|
63
|
+
bad_fk_rels[table_name][rowid] = set()
|
|
64
|
+
bad_fk_rels[table_name][rowid].add(fkid)
|
|
65
|
+
else:
|
|
66
|
+
if table_name not in bad_m2m_rows:
|
|
67
|
+
bad_m2m_rows[table_name] = set()
|
|
68
|
+
bad_m2m_rows[table_name].add(rowid)
|
|
69
|
+
|
|
70
|
+
if bad_fk_rels:
|
|
71
|
+
log.debug(f"Found {len(bad_fk_rels)} tables with illegal foreign keys.")
|
|
72
|
+
if bad_m2m_rows:
|
|
73
|
+
log.debug(
|
|
74
|
+
f"Found {len(bad_m2m_rows)} many to many through tables with illegal relations."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return bad_fk_rels, bad_m2m_rows
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _get_model(table_name):
|
|
81
|
+
"""Get a django model from the table name."""
|
|
82
|
+
app_name, model_name = table_name.split("_", 1)
|
|
83
|
+
app_config = apps.get_app_config(app_name)
|
|
84
|
+
try:
|
|
85
|
+
from_rel_model_name, to_rel_model_name = model_name.split("_", 1)
|
|
86
|
+
except ValueError:
|
|
87
|
+
from_rel_model_name = to_rel_model_name = ""
|
|
88
|
+
if from_rel_model_name in ("comic", "library"):
|
|
89
|
+
# Use the from model to create an m2m through model because I don't know a simpler way.
|
|
90
|
+
from_model = app_config.get_model(from_rel_model_name)
|
|
91
|
+
field_name = to_rel_model_name.replace("_", "")
|
|
92
|
+
model = getattr(from_model, field_name).through
|
|
93
|
+
else:
|
|
94
|
+
# Not an m2m through model.
|
|
95
|
+
model_name = model_name.replace("_", "")
|
|
96
|
+
model = app_config.get_model(model_name)
|
|
97
|
+
return model
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _null_bad_fk_rels_table(table_name, bad_rows, log):
|
|
101
|
+
"""Null bad foreign key relations by table."""
|
|
102
|
+
fix_comic_pks = set()
|
|
103
|
+
model = _get_model(table_name)
|
|
104
|
+
fields = model._meta.fields
|
|
105
|
+
field_names = {}
|
|
106
|
+
update_objs = []
|
|
107
|
+
objs = model.objects.filter(pk__in=bad_rows.keys())
|
|
108
|
+
update_fields = {"updated_at"}
|
|
109
|
+
now = Now()
|
|
110
|
+
for obj in objs:
|
|
111
|
+
try:
|
|
112
|
+
fkids = bad_rows.get(obj.pk)
|
|
113
|
+
if not fkids:
|
|
114
|
+
continue
|
|
115
|
+
for fkid in fkids:
|
|
116
|
+
if fkid not in field_names:
|
|
117
|
+
field_names[fkid] = fields[fkid].name
|
|
118
|
+
field_name = field_names[fkid]
|
|
119
|
+
setattr(obj, field_name, None)
|
|
120
|
+
update_fields.add(field_name)
|
|
121
|
+
obj.updated_at = now # type: ignore
|
|
122
|
+
update_objs.append(obj)
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
log.warning(
|
|
125
|
+
f"Unable to null bad foreign keys in {table_name}:{obj.pk} - {exc}"
|
|
126
|
+
)
|
|
127
|
+
if table_name == "comic":
|
|
128
|
+
fix_comic_pks.add(obj.pk)
|
|
129
|
+
|
|
130
|
+
if update_objs:
|
|
131
|
+
model.objects.bulk_update(update_objs, sorted(update_fields))
|
|
132
|
+
log.info(
|
|
133
|
+
f"Removed illegal foreign key relations from {len(update_objs)} {table_name}s"
|
|
134
|
+
)
|
|
135
|
+
return fix_comic_pks
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _null_bad_fk_rels(bad_fk_rels, log):
|
|
139
|
+
"""Null bad foreign key relations."""
|
|
140
|
+
fix_comic_pks = set()
|
|
141
|
+
for table_name, bad_rows in bad_fk_rels.items():
|
|
142
|
+
try:
|
|
143
|
+
fix_comic_pks |= _null_bad_fk_rels_table(table_name, bad_rows, log)
|
|
144
|
+
except Exception as exc:
|
|
145
|
+
pks = sorted(bad_rows.keys())
|
|
146
|
+
log.error( # noqa: TRY400
|
|
147
|
+
f"Unable to null {len(pks)} {table_name} rows with bad foreign keys - {exc}"
|
|
148
|
+
)
|
|
149
|
+
log.error(f"{table_name}: {pks}") # noqa: TRY400
|
|
150
|
+
return fix_comic_pks
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _delete_bad_m2m_rows_table(table_name, ids, fix_comic_pks, log):
|
|
154
|
+
"""Delete illegal foreign keys for one table."""
|
|
155
|
+
model = _get_model(table_name)
|
|
156
|
+
|
|
157
|
+
count = len(ids)
|
|
158
|
+
qs = model.objects.filter(id__in=ids)
|
|
159
|
+
try:
|
|
160
|
+
comic_ids = qs.values_list("comic_id", flat=True)
|
|
161
|
+
fix_comic_pks |= set(comic_ids)
|
|
162
|
+
except (OperationalError, AttributeError):
|
|
163
|
+
log.exception("Add comic ids to fix list.")
|
|
164
|
+
|
|
165
|
+
qs.delete()
|
|
166
|
+
log.info(f"Deleted {count} {table_name} rows that failed integrity check.")
|
|
167
|
+
return count
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _delete_bad_m2m_rows(bad_m2m_rows, log):
|
|
171
|
+
"""Delete illegal foreign keys for all tables."""
|
|
172
|
+
fix_comic_pks = set()
|
|
173
|
+
count = 0
|
|
174
|
+
for table_name, ids in bad_m2m_rows.items():
|
|
175
|
+
try:
|
|
176
|
+
count += _delete_bad_m2m_rows_table(table_name, ids, fix_comic_pks, log)
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
log.error( # noqa: TRY400
|
|
179
|
+
f"Could not delete bad foreign keys in table {table_name} - {exc}"
|
|
180
|
+
)
|
|
181
|
+
if count:
|
|
182
|
+
log.info(f"Removed {count} bad foreign key relations.")
|
|
183
|
+
return fix_comic_pks
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _mark_comics_for_update(fix_comic_pks, log):
|
|
187
|
+
"""Mark comics with altered foreign keys for update."""
|
|
188
|
+
if not fix_comic_pks:
|
|
189
|
+
return
|
|
190
|
+
comic_model = apps.get_model(app_label="codex", model_name="comic")
|
|
191
|
+
outdated_comics = comic_model.objects.filter(pk__in=fix_comic_pks).only(
|
|
192
|
+
"stat", "updated_at"
|
|
193
|
+
)
|
|
194
|
+
if not outdated_comics:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
update_comics = []
|
|
198
|
+
now = Now()
|
|
199
|
+
for comic in outdated_comics:
|
|
200
|
+
stat_list = comic.stat # type: ignore
|
|
201
|
+
if not stat_list:
|
|
202
|
+
continue
|
|
203
|
+
stat_list[8] = 0.0
|
|
204
|
+
comic.stat = stat_list # type: ignore
|
|
205
|
+
comic.updated_at = now # type: ignore
|
|
206
|
+
update_comics.append(comic)
|
|
207
|
+
|
|
208
|
+
if update_comics:
|
|
209
|
+
count = comic_model.objects.bulk_update(
|
|
210
|
+
update_comics, fields=["stat", "updated_at"]
|
|
211
|
+
)
|
|
212
|
+
log.info(f"Marked {count} comics with bad relations for update by poller.")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def fix_foreign_keys(log=None):
|
|
216
|
+
"""Foreign Key Check."""
|
|
217
|
+
if not log:
|
|
218
|
+
log = LOG
|
|
219
|
+
try:
|
|
220
|
+
results = _foreign_key_check(log)
|
|
221
|
+
bad_fk_rels, bad_m2m_rows = _compile_foreign_key_results(results, log)
|
|
222
|
+
except Exception:
|
|
223
|
+
log.exception("Integrity: foreign_key_check")
|
|
224
|
+
return
|
|
225
|
+
fix_comic_pks = _null_bad_fk_rels(bad_fk_rels, log)
|
|
226
|
+
fix_comic_pks |= _delete_bad_m2m_rows(bad_m2m_rows, log)
|
|
227
|
+
try:
|
|
228
|
+
_mark_comics_for_update(fix_comic_pks, log)
|
|
229
|
+
except Exception as exc:
|
|
230
|
+
LOG.warning(f"Could not mark comics with bad relations for update: {exc}")
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _repair_extra_custom_cover_libraries(library_model, log):
|
|
234
|
+
"""Attempt to remove the bad ones, probably futile."""
|
|
235
|
+
delete_libs = library_model.objects.filter(covers_only=True).exclude(
|
|
236
|
+
path=CUSTOM_COVERS_DIR
|
|
237
|
+
)
|
|
238
|
+
count = delete_libs.count()
|
|
239
|
+
if count:
|
|
240
|
+
delete_libs.delete()
|
|
241
|
+
log.warning(
|
|
242
|
+
f"Removed {count} duplicate custom cover libraries pointing to unused custom cover dirs."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def cleanup_custom_cover_libraries(log=None):
|
|
247
|
+
"""Cleanup extra custom cover libraries."""
|
|
248
|
+
if not log:
|
|
249
|
+
log = LOG
|
|
250
|
+
try:
|
|
251
|
+
try:
|
|
252
|
+
library_model = apps.get_model("codex", "library")
|
|
253
|
+
except LookupError:
|
|
254
|
+
log.debug("Library model doesn't exist yet.")
|
|
255
|
+
return
|
|
256
|
+
if not library_model or not hasattr(library_model, "covers_only"):
|
|
257
|
+
log.debug("Library model doesn't support custom cover library yet.")
|
|
258
|
+
return
|
|
259
|
+
_repair_extra_custom_cover_libraries(library_model, log)
|
|
260
|
+
|
|
261
|
+
custom_cover_libraries = library_model.objects.filter(covers_only=True)
|
|
262
|
+
count = custom_cover_libraries.count()
|
|
263
|
+
if count <= 1:
|
|
264
|
+
return
|
|
265
|
+
custom_cover_libraries.delete()
|
|
266
|
+
log.warning(
|
|
267
|
+
f"Removed all ({count}) custom cover libraries, Unable to determine valid one. Will recreate upon startup."
|
|
268
|
+
)
|
|
269
|
+
except Exception as exc:
|
|
270
|
+
log.warning(f"Failed to check custom cover library for integrity - {exc}")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _is_integrity_ok(results):
|
|
274
|
+
return (
|
|
275
|
+
results and len(results) == 1 and len(results[0]) == 1 and results[0][0] == "ok"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def integrity_check(long=False, log=None):
|
|
280
|
+
"""Run sqlite3 integrity check."""
|
|
281
|
+
pragma = "integrity_check" if long else "quick_check"
|
|
282
|
+
sql = _PRAGMA_TMPL % pragma
|
|
283
|
+
results = _exec_sql(sql)
|
|
284
|
+
|
|
285
|
+
if not log:
|
|
286
|
+
log = LOG
|
|
287
|
+
|
|
288
|
+
if _is_integrity_ok(results):
|
|
289
|
+
length = "thorough" if long else "quick"
|
|
290
|
+
log.info(f"Database passed {length} integrity check.")
|
|
291
|
+
else:
|
|
292
|
+
log.error(
|
|
293
|
+
"Database integrity compromised. See the README for database rebuild instructions."
|
|
294
|
+
)
|
|
295
|
+
log.error(results)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def fts_rebuild(log=None):
|
|
299
|
+
"""FTS Rebuild."""
|
|
300
|
+
sql = _FTS_INSERT_TMPL % "rebuild"
|
|
301
|
+
if not log:
|
|
302
|
+
log = LOG
|
|
303
|
+
_exec_sql(sql)
|
|
304
|
+
log.info("Rebuilt FTS Virtual Table.")
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def fts_integrity_check(log=None):
|
|
308
|
+
"""Run sqlite3 fts integrity check."""
|
|
309
|
+
if not log:
|
|
310
|
+
log = LOG
|
|
311
|
+
results = []
|
|
312
|
+
sql = _FTS_INSERT_TMPL % "integrity-check"
|
|
313
|
+
success = False
|
|
314
|
+
try:
|
|
315
|
+
results = _exec_sql(sql)
|
|
316
|
+
if results:
|
|
317
|
+
# I'm not sure if this raises or puts the error in the results.
|
|
318
|
+
raise ValueError(results) # noqa: TRY301
|
|
319
|
+
except Exception as exc:
|
|
320
|
+
log.warning("Full Text Search Index failed integrity check.")
|
|
321
|
+
log.warning(exc)
|
|
322
|
+
else:
|
|
323
|
+
log.info("Full Text Search Index passed integrity check.")
|
|
324
|
+
success = True
|
|
325
|
+
return success
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class IntegrityMixin(WorkerBaseMixin):
|
|
329
|
+
"""Integrity Check Mixin."""
|
|
330
|
+
|
|
331
|
+
def foreign_key_check(self):
|
|
332
|
+
"""Foreign Key Check task."""
|
|
333
|
+
status = Status(JanitorStatusTypes.INTEGRITY_FK)
|
|
334
|
+
try:
|
|
335
|
+
self.status_controller.start(status)
|
|
336
|
+
fix_foreign_keys(self.log)
|
|
337
|
+
|
|
338
|
+
finally:
|
|
339
|
+
self.status_controller.finish(status)
|
|
340
|
+
|
|
341
|
+
def integrity_check(self, long=False):
|
|
342
|
+
"""Integrity check task."""
|
|
343
|
+
subtitle = "Thorough" if long else "Quick"
|
|
344
|
+
status = Status(JanitorStatusTypes.INTEGRITY_CHECK, subtitle=subtitle)
|
|
345
|
+
try:
|
|
346
|
+
self.status_controller.start(status)
|
|
347
|
+
integrity_check(long, self.log)
|
|
348
|
+
finally:
|
|
349
|
+
self.status_controller.finish(status)
|
|
350
|
+
|
|
351
|
+
def fts_rebuild(self):
|
|
352
|
+
"""FTS rebuild task."""
|
|
353
|
+
status = Status(JanitorStatusTypes.FTS_REBUILD)
|
|
354
|
+
try:
|
|
355
|
+
self.status_controller.start(status)
|
|
356
|
+
fts_rebuild(self.log)
|
|
357
|
+
finally:
|
|
358
|
+
self.status_controller.finish(status)
|
|
359
|
+
|
|
360
|
+
def fts_integrity_check(self):
|
|
361
|
+
"""FTS integrity check task."""
|
|
362
|
+
status = Status(JanitorStatusTypes.FTS_INTEGRITY_CHECK)
|
|
363
|
+
try:
|
|
364
|
+
self.status_controller.start(status)
|
|
365
|
+
success = fts_integrity_check(self.log)
|
|
366
|
+
if not success:
|
|
367
|
+
self.librarian_queue.put(JanitorFTSRebuildTask())
|
|
368
|
+
finally:
|
|
369
|
+
self.status_controller.finish(status)
|
|
@@ -8,18 +8,26 @@ from codex.librarian.importer.tasks import (
|
|
|
8
8
|
)
|
|
9
9
|
from codex.librarian.janitor.cleanup import TOTAL_NUM_FK_CLASSES, CleanupMixin
|
|
10
10
|
from codex.librarian.janitor.failed_imports import UpdateFailedImportsMixin
|
|
11
|
+
from codex.librarian.janitor.integrity import IntegrityMixin
|
|
11
12
|
from codex.librarian.janitor.latest_version import LatestVersionMixin
|
|
12
13
|
from codex.librarian.janitor.status import JanitorStatusTypes
|
|
13
14
|
from codex.librarian.janitor.tasks import (
|
|
14
15
|
ForceUpdateAllFailedImportsTask,
|
|
16
|
+
JanitorAdoptOrphanFoldersFinishedTask,
|
|
15
17
|
JanitorBackupTask,
|
|
16
18
|
JanitorCleanCoversTask,
|
|
17
19
|
JanitorCleanFKsTask,
|
|
20
|
+
JanitorCleanupBookmarksTask,
|
|
18
21
|
JanitorCleanupSessionsTask,
|
|
19
22
|
JanitorClearStatusTask,
|
|
23
|
+
JanitorForeignKeyCheck,
|
|
24
|
+
JanitorFTSIntegrityCheck,
|
|
25
|
+
JanitorFTSRebuildTask,
|
|
26
|
+
JanitorIntegrityCheck,
|
|
20
27
|
JanitorLatestVersionTask,
|
|
21
28
|
JanitorNightlyTask,
|
|
22
29
|
JanitorRestartTask,
|
|
30
|
+
JanitorSearchOptimizeFinishedTask,
|
|
23
31
|
JanitorShutdownTask,
|
|
24
32
|
JanitorUpdateTask,
|
|
25
33
|
JanitorVacuumTask,
|
|
@@ -29,16 +37,20 @@ from codex.librarian.janitor.vacuum import VacuumMixin
|
|
|
29
37
|
from codex.librarian.search.status import SearchIndexStatusTypes
|
|
30
38
|
from codex.librarian.search.tasks import (
|
|
31
39
|
SearchIndexAbortTask,
|
|
32
|
-
|
|
40
|
+
SearchIndexOptimizeTask,
|
|
33
41
|
SearchIndexUpdateTask,
|
|
34
42
|
)
|
|
35
|
-
from codex.models import
|
|
43
|
+
from codex.models import Timestamp
|
|
36
44
|
from codex.status import Status
|
|
37
45
|
|
|
38
46
|
_JANITOR_STATII = (
|
|
47
|
+
Status(JanitorStatusTypes.CODEX_LATEST_VERSION),
|
|
48
|
+
Status(JanitorStatusTypes.INTEGRITY_FK),
|
|
49
|
+
Status(JanitorStatusTypes.INTEGRITY_CHECK),
|
|
39
50
|
Status(JanitorStatusTypes.CLEANUP_FK, 0, TOTAL_NUM_FK_CLASSES),
|
|
40
51
|
Status(JanitorStatusTypes.CLEANUP_COVERS),
|
|
41
52
|
Status(JanitorStatusTypes.CLEANUP_SESSIONS),
|
|
53
|
+
Status(JanitorStatusTypes.CLEANUP_BOOKMARKS),
|
|
42
54
|
Status(JanitorStatusTypes.DB_OPTIMIZE),
|
|
43
55
|
Status(JanitorStatusTypes.DB_BACKUP),
|
|
44
56
|
Status(JanitorStatusTypes.CODEX_UPDATE),
|
|
@@ -48,13 +60,17 @@ _JANITOR_STATII = (
|
|
|
48
60
|
Status(ImportStatusTypes.DIRS_MOVED),
|
|
49
61
|
Status(SearchIndexStatusTypes.SEARCH_INDEX_UPDATE),
|
|
50
62
|
Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
|
|
51
|
-
Status(SearchIndexStatusTypes.
|
|
52
|
-
Status(JanitorStatusTypes.CODEX_LATEST_VERSION),
|
|
63
|
+
Status(SearchIndexStatusTypes.SEARCH_INDEX_OPTIMIZE),
|
|
53
64
|
)
|
|
54
65
|
|
|
55
66
|
|
|
56
67
|
class Janitor(
|
|
57
|
-
CleanupMixin,
|
|
68
|
+
CleanupMixin,
|
|
69
|
+
LatestVersionMixin,
|
|
70
|
+
UpdateMixin,
|
|
71
|
+
UpdateFailedImportsMixin,
|
|
72
|
+
VacuumMixin,
|
|
73
|
+
IntegrityMixin,
|
|
58
74
|
):
|
|
59
75
|
"""Janitor inline task runner."""
|
|
60
76
|
|
|
@@ -65,23 +81,20 @@ class Janitor(
|
|
|
65
81
|
def queue_tasks(self):
|
|
66
82
|
"""Queue all the janitor tasks."""
|
|
67
83
|
try:
|
|
68
|
-
optimize = AdminFlag.objects.get(
|
|
69
|
-
key=AdminFlag.FlagChoices.SEARCH_INDEX_OPTIMIZE.value
|
|
70
|
-
).on
|
|
71
84
|
self.status_controller.start_many(_JANITOR_STATII)
|
|
72
85
|
tasks = (
|
|
73
|
-
JanitorLatestVersionTask(),
|
|
74
|
-
JanitorUpdateTask(force=False),
|
|
75
86
|
SearchIndexAbortTask(),
|
|
87
|
+
JanitorLatestVersionTask(),
|
|
88
|
+
JanitorUpdateTask(),
|
|
89
|
+
JanitorForeignKeyCheck(),
|
|
90
|
+
JanitorIntegrityCheck(),
|
|
91
|
+
JanitorFTSIntegrityCheck(),
|
|
76
92
|
JanitorCleanFKsTask(),
|
|
77
93
|
JanitorCleanCoversTask(),
|
|
78
94
|
JanitorCleanupSessionsTask(),
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
JanitorBackupTask(),
|
|
95
|
+
JanitorCleanupBookmarksTask(),
|
|
96
|
+
AdoptOrphanFoldersTask(janitor=True),
|
|
82
97
|
CoverRemoveOrphansTask(),
|
|
83
|
-
SearchIndexUpdateTask(False),
|
|
84
|
-
SearchIndexMergeTask(optimize),
|
|
85
98
|
)
|
|
86
99
|
for task in tasks:
|
|
87
100
|
self.librarian_queue.put(task)
|
|
@@ -111,12 +124,34 @@ class Janitor(
|
|
|
111
124
|
self.cleanup_custom_covers()
|
|
112
125
|
case JanitorCleanupSessionsTask():
|
|
113
126
|
self.cleanup_sessions()
|
|
127
|
+
case JanitorCleanupBookmarksTask():
|
|
128
|
+
self.cleanup_orphan_bookmarks()
|
|
114
129
|
case JanitorClearStatusTask():
|
|
115
130
|
self.status_controller.finish_many([])
|
|
116
131
|
case ForceUpdateAllFailedImportsTask():
|
|
117
132
|
self.force_update_all_failed_imports()
|
|
133
|
+
case JanitorForeignKeyCheck():
|
|
134
|
+
self.foreign_key_check()
|
|
135
|
+
case JanitorIntegrityCheck():
|
|
136
|
+
self.integrity_check(task.long)
|
|
137
|
+
case JanitorFTSIntegrityCheck():
|
|
138
|
+
self.fts_integrity_check()
|
|
139
|
+
case JanitorFTSRebuildTask():
|
|
140
|
+
self.fts_rebuild()
|
|
118
141
|
case JanitorNightlyTask():
|
|
119
142
|
self.queue_tasks()
|
|
143
|
+
case JanitorAdoptOrphanFoldersFinishedTask():
|
|
144
|
+
next_tasks = (
|
|
145
|
+
SearchIndexAbortTask(),
|
|
146
|
+
SearchIndexUpdateTask(),
|
|
147
|
+
SearchIndexOptimizeTask(janitor=True),
|
|
148
|
+
)
|
|
149
|
+
for next_task in next_tasks:
|
|
150
|
+
self.librarian_queue.put(next_task)
|
|
151
|
+
case JanitorSearchOptimizeFinishedTask():
|
|
152
|
+
next_tasks = (JanitorVacuumTask(), JanitorBackupTask())
|
|
153
|
+
for next_task in next_tasks:
|
|
154
|
+
self.librarian_queue.put(next_task)
|
|
120
155
|
case _:
|
|
121
156
|
self.log.warning(f"Janitor received unknown task {task}")
|
|
122
157
|
except Exception:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from datetime import timedelta
|
|
5
|
+
from time import time
|
|
5
6
|
|
|
6
7
|
import requests
|
|
7
8
|
from django.utils import timezone
|
|
@@ -51,4 +52,4 @@ class LatestVersionMixin(WorkerBaseMixin):
|
|
|
51
52
|
else:
|
|
52
53
|
self.log.debug("Not fetching new latest version, not expired.")
|
|
53
54
|
finally:
|
|
54
|
-
self.status_controller.finish(status)
|
|
55
|
+
self.status_controller.finish(status, until=time() + 2)
|
codex/librarian/janitor/tasks.py
CHANGED
|
@@ -62,11 +62,48 @@ class JanitorCleanupSessionsTask(JanitorTask):
|
|
|
62
62
|
"""Cleanup Session table."""
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
@dataclass
|
|
66
|
+
class JanitorCleanupBookmarksTask(JanitorTask):
|
|
67
|
+
"""Clean unused bookmarks."""
|
|
68
|
+
|
|
69
|
+
|
|
65
70
|
@dataclass
|
|
66
71
|
class ForceUpdateAllFailedImportsTask(JanitorTask):
|
|
67
72
|
"""Force update for failed imports in every library."""
|
|
68
73
|
|
|
69
74
|
|
|
75
|
+
@dataclass
|
|
76
|
+
class JanitorForeignKeyCheck(JanitorTask):
|
|
77
|
+
"""Check and repair foreign keys integrity."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class JanitorIntegrityCheck(JanitorTask):
|
|
82
|
+
"""Check integrity and warn."""
|
|
83
|
+
|
|
84
|
+
long: bool = False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class JanitorFTSIntegrityCheck(JanitorTask):
|
|
89
|
+
"""Check fts integrity."""
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class JanitorFTSRebuildTask(JanitorTask):
|
|
94
|
+
"""Rebuild fts table in place."""
|
|
95
|
+
|
|
96
|
+
|
|
70
97
|
@dataclass
|
|
71
98
|
class JanitorNightlyTask(JanitorTask):
|
|
72
99
|
"""Submit all janitor nightly tasks to the queue."""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class JanitorAdoptOrphanFoldersFinishedTask(JanitorTask):
|
|
104
|
+
"""Signals to the Janitor that the Adopt Orphan Folders task is finished."""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class JanitorSearchOptimizeFinishedTask(JanitorTask):
|
|
109
|
+
"""Signals to the Janitor that the Search Optimize task is finished."""
|
|
@@ -32,7 +32,7 @@ class UpdateMixin(WorkerBaseMixin):
|
|
|
32
32
|
else:
|
|
33
33
|
result = versio_latest_version > installed_versio_version
|
|
34
34
|
pre_blurb = ""
|
|
35
|
-
self.log.
|
|
35
|
+
self.log.debug(f"{latest_version=} > {VERSION=} = {result}{pre_blurb}")
|
|
36
36
|
return result
|
|
37
37
|
|
|
38
38
|
def update_codex(self, force=False):
|
codex/librarian/librariand.py
CHANGED
|
@@ -24,7 +24,7 @@ from codex.librarian.search.searchd import SearchIndexerThread
|
|
|
24
24
|
from codex.librarian.search.tasks import (
|
|
25
25
|
SearchIndexAbortTask,
|
|
26
26
|
SearchIndexerTask,
|
|
27
|
-
|
|
27
|
+
SearchIndexUpdateTask,
|
|
28
28
|
)
|
|
29
29
|
from codex.librarian.tasks import DelayedTasks, LibrarianShutdownTask, WakeCronTask
|
|
30
30
|
from codex.librarian.telemeter.tasks import TelemeterTask
|
|
@@ -76,7 +76,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
76
76
|
startup_tasks = (
|
|
77
77
|
AdoptOrphanFoldersTask(),
|
|
78
78
|
WatchdogSyncTask(),
|
|
79
|
-
|
|
79
|
+
SearchIndexUpdateTask(False),
|
|
80
80
|
)
|
|
81
81
|
|
|
82
82
|
for task in startup_tasks:
|
|
@@ -101,12 +101,12 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
101
101
|
self._threads.library_polling_observer.poll(
|
|
102
102
|
task.library_ids, task.force
|
|
103
103
|
)
|
|
104
|
+
case SearchIndexAbortTask():
|
|
105
|
+
# Must come before the generic SearchIndexerTask below
|
|
106
|
+
self.search_indexer_abort_event.set()
|
|
107
|
+
self.log.debug("Told search indexers to stop for db updates.")
|
|
104
108
|
case SearchIndexerTask():
|
|
105
|
-
|
|
106
|
-
self.search_indexer_abort_event.set()
|
|
107
|
-
self.log.debug("Told search indexers to stop for db updates.")
|
|
108
|
-
else:
|
|
109
|
-
self._threads.search_indexer_thread.queue.put(task)
|
|
109
|
+
self._threads.search_indexer_thread.queue.put(task)
|
|
110
110
|
case WakeCronTask():
|
|
111
111
|
self._threads.cron_thread.end_timeout()
|
|
112
112
|
case TelemeterTask():
|
|
@@ -150,6 +150,16 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
150
150
|
thread.start()
|
|
151
151
|
self.log.info(f"{self.__class__.__name__} started all threads.")
|
|
152
152
|
|
|
153
|
+
def _startup(self):
|
|
154
|
+
"""Initialize threads."""
|
|
155
|
+
self.init_logger(self.log_queue)
|
|
156
|
+
self.log.debug(f"Started {self.__class__.__name__}.")
|
|
157
|
+
self.janitor = Janitor(self.log_queue, self.queue)
|
|
158
|
+
self._create_threads() # can't do this in init.
|
|
159
|
+
self._start_threads()
|
|
160
|
+
self.run_loop = True
|
|
161
|
+
self.log.info(f"{self.__class__.__name__} ready for tasks.")
|
|
162
|
+
|
|
153
163
|
def _stop_threads(self):
|
|
154
164
|
"""Stop all librarian's threads."""
|
|
155
165
|
self.log.debug(f"{self.__class__.__name__} stopping all threads...")
|
|
@@ -164,19 +174,26 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
164
174
|
thread.join()
|
|
165
175
|
self.log.info(f"{self.__class__.__name__} joined all threads.")
|
|
166
176
|
|
|
177
|
+
def _shutdown(self):
|
|
178
|
+
"""Shutdown threads and queues."""
|
|
179
|
+
self._reversed_threads = reversed(self._threads)
|
|
180
|
+
self._stop_threads()
|
|
181
|
+
self._join_threads()
|
|
182
|
+
while not self.queue.empty():
|
|
183
|
+
self.queue.get_nowait()
|
|
184
|
+
self.queue.close()
|
|
185
|
+
self.queue.join_thread()
|
|
186
|
+
self.log.info(f"{self.__class__.__name__} finished.")
|
|
187
|
+
self.log_queue.close()
|
|
188
|
+
self.log_queue.join_thread()
|
|
189
|
+
|
|
167
190
|
def run(self):
|
|
168
191
|
"""Process tasks from the queue.
|
|
169
192
|
|
|
170
193
|
This process also runs the crond thread and the Watchdog Observer
|
|
171
194
|
threads.
|
|
172
195
|
"""
|
|
173
|
-
self.
|
|
174
|
-
self.log.debug(f"Started {self.__class__.__name__}.")
|
|
175
|
-
self.janitor = Janitor(self.log_queue, self.queue)
|
|
176
|
-
self._create_threads() # can't do this in init.
|
|
177
|
-
self._start_threads()
|
|
178
|
-
self.run_loop = True
|
|
179
|
-
self.log.info(f"{self.__class__.__name__} ready for tasks.")
|
|
196
|
+
self._startup()
|
|
180
197
|
try:
|
|
181
198
|
while self.run_loop:
|
|
182
199
|
try:
|
|
@@ -189,16 +206,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
189
206
|
except KeyboardInterrupt:
|
|
190
207
|
self.log.debug(f"{self.__class__.__name__} Keyboard interrupt")
|
|
191
208
|
finally:
|
|
192
|
-
self.
|
|
193
|
-
self._stop_threads()
|
|
194
|
-
self._join_threads()
|
|
195
|
-
while not self.queue.empty():
|
|
196
|
-
self.queue.get_nowait()
|
|
197
|
-
self.queue.close()
|
|
198
|
-
self.queue.join_thread()
|
|
199
|
-
self.log.info(f"{self.__class__.__name__} finished.")
|
|
200
|
-
self.log_queue.close()
|
|
201
|
-
self.log_queue.join_thread()
|
|
209
|
+
self._shutdown()
|
|
202
210
|
|
|
203
211
|
def stop(self):
|
|
204
212
|
"""Close up the librarian process."""
|