codex 1.6.3__py3-none-any.whl → 1.6.5__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/librarian/importer/importer.py +1 -1
- codex/librarian/janitor/cleanup.py +2 -4
- codex/librarian/janitor/update.py +10 -9
- codex/librarian/search/merge.py +37 -40
- codex/librarian/search/remove.py +30 -26
- codex/librarian/search/update.py +47 -38
- codex/librarian/stats.py +177 -0
- codex/librarian/watchdog/events.py +2 -2
- codex/serializers/admin/stats.py +77 -65
- codex/serializers/fields.py +23 -0
- codex/settings/settings.py +3 -3
- codex/static_root/assets/{VCheckbox-CXQlndFT.c17c006a62e6.js → VCheckbox-OPrrn8D4.95d8259619aa.js} +1 -1
- codex/static_root/assets/VCheckbox-OPrrn8D4.95d8259619aa.js.br +0 -0
- codex/static_root/assets/VCheckbox-OPrrn8D4.95d8259619aa.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-CXQlndFT.js → VCheckbox-OPrrn8D4.js} +1 -1
- codex/static_root/assets/VCheckbox-OPrrn8D4.js.br +0 -0
- codex/static_root/assets/VCheckbox-OPrrn8D4.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-DB72Bclg.ab414c6ae2b8.js → VCheckboxBtn-BLEc6BrH.e8ae3d2738e2.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-BLEc6BrH.e8ae3d2738e2.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BLEc6BrH.e8ae3d2738e2.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-DB72Bclg.js → VCheckboxBtn-BLEc6BrH.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-BLEc6BrH.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BLEc6BrH.js.gz +0 -0
- codex/static_root/assets/{VCombobox-BDcFPzYf.0b80447cd638.js → VCombobox-C18LIWGs.2a41fab61075.js} +1 -1
- codex/static_root/assets/VCombobox-C18LIWGs.2a41fab61075.js.br +0 -0
- codex/static_root/assets/VCombobox-C18LIWGs.2a41fab61075.js.gz +0 -0
- codex/static_root/assets/{VCombobox-BDcFPzYf.js → VCombobox-C18LIWGs.js} +1 -1
- codex/static_root/assets/VCombobox-C18LIWGs.js.br +0 -0
- codex/static_root/assets/VCombobox-C18LIWGs.js.gz +0 -0
- codex/static_root/assets/{VDialog-BB4zqrGw.2decc14ea597.js → VDialog-DPlcZ188.9398019bd69c.js} +1 -1
- codex/static_root/assets/VDialog-DPlcZ188.9398019bd69c.js.br +0 -0
- codex/static_root/assets/VDialog-DPlcZ188.9398019bd69c.js.gz +0 -0
- codex/static_root/assets/{VDialog-BB4zqrGw.js → VDialog-DPlcZ188.js} +1 -1
- codex/static_root/assets/VDialog-DPlcZ188.js.br +0 -0
- codex/static_root/assets/VDialog-DPlcZ188.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-SZc735F5.19004a0eba16.js → VExpansionPanels-BrAdxiR-.f5e7507958b2.js} +1 -1
- codex/static_root/assets/VExpansionPanels-BrAdxiR-.f5e7507958b2.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-BrAdxiR-.f5e7507958b2.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-SZc735F5.js → VExpansionPanels-BrAdxiR-.js} +1 -1
- codex/static_root/assets/VExpansionPanels-BrAdxiR-.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-BrAdxiR-.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-tFEuhFRq.774e14a4945a.js → VRadioGroup-BWf1qZO1.ee8a1b8d589d.js} +1 -1
- codex/static_root/assets/VRadioGroup-BWf1qZO1.ee8a1b8d589d.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BWf1qZO1.ee8a1b8d589d.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-tFEuhFRq.js → VRadioGroup-BWf1qZO1.js} +1 -1
- codex/static_root/assets/VRadioGroup-BWf1qZO1.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BWf1qZO1.js.gz +0 -0
- codex/static_root/assets/{VSelect-Dd5Zy8Uy.f225abf824ff.js → VSelect-BEOlJiRW.2d4996f0d6a3.js} +1 -1
- codex/static_root/assets/VSelect-BEOlJiRW.2d4996f0d6a3.js.br +0 -0
- codex/static_root/assets/VSelect-BEOlJiRW.2d4996f0d6a3.js.gz +0 -0
- codex/static_root/assets/{VSelect-Dd5Zy8Uy.js → VSelect-BEOlJiRW.js} +1 -1
- codex/static_root/assets/VSelect-BEOlJiRW.js.br +0 -0
- codex/static_root/assets/VSelect-BEOlJiRW.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-DImIQPJV.1f14d2113063.js → VSelectionControl-BpvvZpBd.d83b413a38d1.js} +1 -1
- codex/static_root/assets/VSelectionControl-BpvvZpBd.d83b413a38d1.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BpvvZpBd.d83b413a38d1.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-DImIQPJV.js → VSelectionControl-BpvvZpBd.js} +1 -1
- codex/static_root/assets/VSelectionControl-BpvvZpBd.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BpvvZpBd.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-CDaAHRAK.783800d70a8a.js → VSlideGroup-Bv0lRnhB.e0b8f680bcbf.js} +1 -1
- codex/static_root/assets/VSlideGroup-Bv0lRnhB.e0b8f680bcbf.js.br +0 -0
- codex/static_root/assets/VSlideGroup-Bv0lRnhB.e0b8f680bcbf.js.gz +0 -0
- codex/static_root/assets/{VSlideGroup-CDaAHRAK.js → VSlideGroup-Bv0lRnhB.js} +1 -1
- codex/static_root/assets/VSlideGroup-Bv0lRnhB.js.br +0 -0
- codex/static_root/assets/VSlideGroup-Bv0lRnhB.js.gz +0 -0
- codex/static_root/assets/{VTable-CkCZonsu.81e34381271f.js → VTable-Dw1ASmYk.4e3817348a64.js} +1 -1
- codex/static_root/assets/VTable-Dw1ASmYk.4e3817348a64.js.br +0 -0
- codex/static_root/assets/VTable-Dw1ASmYk.4e3817348a64.js.gz +0 -0
- codex/static_root/assets/{VTable-CkCZonsu.js → VTable-Dw1ASmYk.js} +1 -1
- codex/static_root/assets/VTable-Dw1ASmYk.js.br +0 -0
- codex/static_root/assets/VTable-Dw1ASmYk.js.gz +0 -0
- codex/static_root/assets/{VTextField-fNAzFu1M.00dec8623f18.js → VTextField-DhIkxARQ.e688815dc516.js} +1 -1
- codex/static_root/assets/VTextField-DhIkxARQ.e688815dc516.js.br +0 -0
- codex/static_root/assets/VTextField-DhIkxARQ.e688815dc516.js.gz +0 -0
- codex/static_root/assets/{VTextField-fNAzFu1M.js → VTextField-DhIkxARQ.js} +1 -1
- codex/static_root/assets/VTextField-DhIkxARQ.js.br +0 -0
- codex/static_root/assets/VTextField-DhIkxARQ.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-_YBMW1SU.0836924f8c12.js → VWindowItem-IDCZgODl.8c4527097642.js} +1 -1
- codex/static_root/assets/VWindowItem-IDCZgODl.8c4527097642.js.br +0 -0
- codex/static_root/assets/VWindowItem-IDCZgODl.8c4527097642.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-_YBMW1SU.js → VWindowItem-IDCZgODl.js} +1 -1
- codex/static_root/assets/VWindowItem-IDCZgODl.js.br +0 -0
- codex/static_root/assets/VWindowItem-IDCZgODl.js.gz +0 -0
- codex/static_root/assets/{admin-DYmGyJVg.262e6032ee86.js → admin-DQMEj7xy.f249ade8981c.js} +1 -1
- codex/static_root/assets/admin-DQMEj7xy.f249ade8981c.js.br +0 -0
- codex/static_root/assets/admin-DQMEj7xy.f249ade8981c.js.gz +0 -0
- codex/static_root/assets/{admin-DYmGyJVg.js → admin-DQMEj7xy.js} +1 -1
- codex/static_root/assets/admin-DQMEj7xy.js.br +0 -0
- codex/static_root/assets/admin-DQMEj7xy.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-c3f3H29x.fa2b12c75b06.js → admin-drawer-panel-Can9sQIk.7021726ce8b9.js} +20 -20
- codex/static_root/assets/admin-drawer-panel-Can9sQIk.7021726ce8b9.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Can9sQIk.7021726ce8b9.js.gz +0 -0
- codex/static_root/assets/{admin-drawer-panel-c3f3H29x.js → admin-drawer-panel-Can9sQIk.js} +20 -20
- codex/static_root/assets/admin-drawer-panel-Can9sQIk.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Can9sQIk.js.gz +0 -0
- codex/static_root/assets/{browser-Ck3zWCOk.d98c77747e6d.js → browser-BOPyA0wB.b69c491ad88e.js} +1 -1
- codex/static_root/assets/browser-BOPyA0wB.b69c491ad88e.js.br +0 -0
- codex/static_root/assets/browser-BOPyA0wB.b69c491ad88e.js.gz +0 -0
- codex/static_root/assets/{browser-Ck3zWCOk.js → browser-BOPyA0wB.js} +1 -1
- codex/static_root/assets/browser-BOPyA0wB.js.br +0 -0
- codex/static_root/assets/browser-BOPyA0wB.js.gz +0 -0
- codex/static_root/assets/{browser-DgawKPSA.c7cf59dc4b21.css → browser-Cy0OgQJC.css} +1 -1
- codex/static_root/assets/browser-Cy0OgQJC.css.br +0 -0
- codex/static_root/assets/browser-Cy0OgQJC.css.gz +0 -0
- codex/static_root/assets/{browser-DgawKPSA.css → browser-Cy0OgQJC.fabbd1aaffcf.css} +1 -1
- codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.br +0 -0
- codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.gz +0 -0
- codex/static_root/assets/{change-password-dialog-BbIO0YI5.2bb0a5dfb190.js → change-password-dialog-BKyyB0i6.81abe7a243de.js} +1 -1
- codex/static_root/assets/change-password-dialog-BKyyB0i6.81abe7a243de.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BKyyB0i6.81abe7a243de.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-BbIO0YI5.js → change-password-dialog-BKyyB0i6.js} +1 -1
- codex/static_root/assets/change-password-dialog-BKyyB0i6.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BKyyB0i6.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-BrzuoYAT.a0a1b6364708.js → confirm-dialog-CbDA0K1C.6fca78c3b9bf.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbDA0K1C.6fca78c3b9bf.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbDA0K1C.6fca78c3b9bf.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-BrzuoYAT.js → confirm-dialog-CbDA0K1C.js} +1 -1
- codex/static_root/assets/confirm-dialog-CbDA0K1C.js.br +0 -0
- codex/static_root/assets/confirm-dialog-CbDA0K1C.js.gz +0 -0
- codex/static_root/assets/{datetime-column-D8-UeX_p.e33b1556805b.js → datetime-column-lzxYDDvK.055ffa7f271c.js} +1 -1
- codex/static_root/assets/datetime-column-lzxYDDvK.055ffa7f271c.js.br +0 -0
- codex/static_root/assets/datetime-column-lzxYDDvK.055ffa7f271c.js.gz +0 -0
- codex/static_root/assets/{datetime-column-D8-UeX_p.js → datetime-column-lzxYDDvK.js} +1 -1
- codex/static_root/assets/datetime-column-lzxYDDvK.js.br +0 -0
- codex/static_root/assets/datetime-column-lzxYDDvK.js.gz +0 -0
- codex/static_root/assets/{filter-XMAX5Nmg.c6ed87350f7d.js → filter-SD2wRta_.2db402d82ae5.js} +1 -1
- codex/static_root/assets/filter-SD2wRta_.2db402d82ae5.js.br +0 -0
- codex/static_root/assets/filter-SD2wRta_.2db402d82ae5.js.gz +0 -0
- codex/static_root/assets/{filter-XMAX5Nmg.js → filter-SD2wRta_.js} +1 -1
- codex/static_root/assets/filter-SD2wRta_.js.br +0 -0
- codex/static_root/assets/filter-SD2wRta_.js.gz +0 -0
- codex/static_root/assets/flag-tab-Caak9VvH.643528c944e7.js +1 -0
- codex/static_root/assets/flag-tab-Caak9VvH.643528c944e7.js.br +0 -0
- codex/static_root/assets/flag-tab-Caak9VvH.643528c944e7.js.gz +0 -0
- codex/static_root/assets/flag-tab-Caak9VvH.js +1 -0
- codex/static_root/assets/flag-tab-Caak9VvH.js.br +0 -0
- codex/static_root/assets/flag-tab-Caak9VvH.js.gz +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css +1 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +1 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
- codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
- codex/static_root/assets/{group-tab-D9n5gZCA.634223001c44.js → group-tab-DI7cBTNB.8042c34ec394.js} +1 -1
- codex/static_root/assets/group-tab-DI7cBTNB.8042c34ec394.js.br +0 -0
- codex/static_root/assets/group-tab-DI7cBTNB.8042c34ec394.js.gz +0 -0
- codex/static_root/assets/{group-tab-D9n5gZCA.js → group-tab-DI7cBTNB.js} +1 -1
- codex/static_root/assets/group-tab-DI7cBTNB.js.br +0 -0
- codex/static_root/assets/group-tab-DI7cBTNB.js.gz +0 -0
- codex/static_root/assets/{http-error-C_EFLi0c.c7e906206b62.js → http-error-BpbtQXBu.5ac5d9dfb219.js} +1 -1
- codex/static_root/assets/http-error-BpbtQXBu.5ac5d9dfb219.js.br +0 -0
- codex/static_root/assets/http-error-BpbtQXBu.5ac5d9dfb219.js.gz +0 -0
- codex/static_root/assets/{http-error-C_EFLi0c.js → http-error-BpbtQXBu.js} +1 -1
- codex/static_root/assets/http-error-BpbtQXBu.js.br +0 -0
- codex/static_root/assets/http-error-BpbtQXBu.js.gz +0 -0
- codex/static_root/assets/{library-tab-fDxuhPBs.285b36d9cec0.js → library-tab-DFEha1Cd.e0c6cde24a27.js} +1 -1
- codex/static_root/assets/library-tab-DFEha1Cd.e0c6cde24a27.js.br +0 -0
- codex/static_root/assets/library-tab-DFEha1Cd.e0c6cde24a27.js.gz +0 -0
- codex/static_root/assets/{library-tab-fDxuhPBs.js → library-tab-DFEha1Cd.js} +1 -1
- codex/static_root/assets/library-tab-DFEha1Cd.js.br +0 -0
- codex/static_root/assets/library-tab-DFEha1Cd.js.gz +0 -0
- codex/static_root/assets/main-hKTXzzX1.6888db202b7a.js +6 -0
- codex/static_root/assets/main-hKTXzzX1.6888db202b7a.js.br +0 -0
- codex/static_root/assets/main-hKTXzzX1.6888db202b7a.js.gz +0 -0
- codex/static_root/assets/main-hKTXzzX1.js +6 -0
- codex/static_root/assets/main-hKTXzzX1.js.br +0 -0
- codex/static_root/assets/main-hKTXzzX1.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-BD6nzIKF.f3ca4874c203.js → pagination-toolbar-nUqE7Y6_.573598728e43.js} +1 -1
- codex/static_root/assets/pagination-toolbar-nUqE7Y6_.573598728e43.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-nUqE7Y6_.573598728e43.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-BD6nzIKF.js → pagination-toolbar-nUqE7Y6_.js} +1 -1
- codex/static_root/assets/pagination-toolbar-nUqE7Y6_.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-nUqE7Y6_.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-iF15lKRp.de9a9d394438.js → pdf-doc-LW8r9w5L.0fc8e96441a3.js} +1 -1
- codex/static_root/assets/pdf-doc-LW8r9w5L.0fc8e96441a3.js.br +0 -0
- codex/static_root/assets/pdf-doc-LW8r9w5L.0fc8e96441a3.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-iF15lKRp.js → pdf-doc-LW8r9w5L.js} +1 -1
- codex/static_root/assets/pdf-doc-LW8r9w5L.js.br +0 -0
- codex/static_root/assets/pdf-doc-LW8r9w5L.js.gz +0 -0
- codex/static_root/assets/{reader-5Ja_bAaT.2fe33a3891f6.js → reader-DfCdn4RV.e821068881fd.js} +2 -2
- codex/static_root/assets/reader-DfCdn4RV.e821068881fd.js.br +0 -0
- codex/static_root/assets/reader-DfCdn4RV.e821068881fd.js.gz +0 -0
- codex/static_root/assets/{reader-5Ja_bAaT.js → reader-DfCdn4RV.js} +2 -2
- codex/static_root/assets/reader-DfCdn4RV.js.br +0 -0
- codex/static_root/assets/reader-DfCdn4RV.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CUWkvLNo.3d18d0c09d39.js → relation-chips-iwVvmmvT.9e8ef037d134.js} +1 -1
- codex/static_root/assets/relation-chips-iwVvmmvT.9e8ef037d134.js.br +0 -0
- codex/static_root/assets/relation-chips-iwVvmmvT.9e8ef037d134.js.gz +0 -0
- codex/static_root/assets/{relation-chips-CUWkvLNo.js → relation-chips-iwVvmmvT.js} +1 -1
- codex/static_root/assets/relation-chips-iwVvmmvT.js.br +0 -0
- codex/static_root/assets/relation-chips-iwVvmmvT.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-rn3JdMxA.js → settings-drawer-D_S8wFKC.d68db990933d.js} +2 -2
- codex/static_root/assets/settings-drawer-D_S8wFKC.d68db990933d.js.br +0 -0
- codex/static_root/assets/settings-drawer-D_S8wFKC.d68db990933d.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-rn3JdMxA.a9d7b45c2138.js → settings-drawer-D_S8wFKC.js} +2 -2
- codex/static_root/assets/settings-drawer-D_S8wFKC.js.br +0 -0
- codex/static_root/assets/settings-drawer-D_S8wFKC.js.gz +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css +1 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.br +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.gz +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.css +1 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.css.br +0 -0
- codex/static_root/assets/stats-tab-CI0u-X7Y.css.gz +0 -0
- codex/static_root/assets/stats-tab-DD5sxuw1.29a98d090716.js +1 -0
- codex/static_root/assets/stats-tab-DD5sxuw1.29a98d090716.js.br +0 -0
- codex/static_root/assets/stats-tab-DD5sxuw1.29a98d090716.js.gz +0 -0
- codex/static_root/assets/stats-tab-DD5sxuw1.js +1 -0
- codex/static_root/assets/stats-tab-DD5sxuw1.js.br +0 -0
- codex/static_root/assets/stats-tab-DD5sxuw1.js.gz +0 -0
- codex/static_root/assets/{task-tab-CbzDX0IR.7b43bdf9cf42.js → task-tab-DDrdraG5.ddc487971339.js} +1 -1
- codex/static_root/assets/task-tab-DDrdraG5.ddc487971339.js.br +0 -0
- codex/static_root/assets/task-tab-DDrdraG5.ddc487971339.js.gz +0 -0
- codex/static_root/assets/{task-tab-CbzDX0IR.js → task-tab-DDrdraG5.js} +1 -1
- codex/static_root/assets/task-tab-DDrdraG5.js.br +0 -0
- codex/static_root/assets/task-tab-DDrdraG5.js.gz +0 -0
- codex/static_root/assets/{unauthorized-CllrtnFV.2a18fab3dd41.js → unauthorized-BMsnj48f.512d881936ab.js} +1 -1
- codex/static_root/assets/unauthorized-BMsnj48f.512d881936ab.js.br +0 -0
- codex/static_root/assets/unauthorized-BMsnj48f.512d881936ab.js.gz +0 -0
- codex/static_root/assets/{unauthorized-CllrtnFV.js → unauthorized-BMsnj48f.js} +1 -1
- codex/static_root/assets/unauthorized-BMsnj48f.js.br +0 -0
- codex/static_root/assets/unauthorized-BMsnj48f.js.gz +0 -0
- codex/static_root/assets/{user-tab-FY1xALpi.05a88f51df4f.js → user-tab-DSq5ht4e.5b70e9642a13.js} +1 -1
- codex/static_root/assets/user-tab-DSq5ht4e.5b70e9642a13.js.br +0 -0
- codex/static_root/assets/user-tab-DSq5ht4e.5b70e9642a13.js.gz +0 -0
- codex/static_root/assets/{user-tab-FY1xALpi.js → user-tab-DSq5ht4e.js} +1 -1
- codex/static_root/assets/user-tab-DSq5ht4e.js.br +0 -0
- codex/static_root/assets/user-tab-DSq5ht4e.js.gz +0 -0
- codex/static_root/{manifest.166aa32817b1.json → manifest.342051b44ea0.json} +216 -216
- codex/static_root/manifest.342051b44ea0.json.br +0 -0
- codex/static_root/manifest.342051b44ea0.json.gz +0 -0
- codex/static_root/manifest.json +216 -216
- codex/static_root/manifest.json.br +0 -0
- codex/static_root/manifest.json.gz +0 -0
- codex/static_root/staticfiles.json +1 -1
- codex/urls/root.py +12 -2
- codex/views/admin/stats.py +38 -186
- codex/views/opds/v1/entry/entry.py +14 -18
- codex/views/opds/v1/feed.py +5 -1
- codex/views/opds/v1/links.py +1 -0
- codex/views/reader/arcs.py +5 -4
- {codex-1.6.3.dist-info → codex-1.6.5.dist-info}/METADATA +18 -7
- {codex-1.6.3.dist-info → codex-1.6.5.dist-info}/RECORD +245 -245
- codex/settings/logging.py +0 -20
- codex/static_root/assets/VCheckbox-CXQlndFT.c17c006a62e6.js.br +0 -0
- codex/static_root/assets/VCheckbox-CXQlndFT.c17c006a62e6.js.gz +0 -0
- codex/static_root/assets/VCheckbox-CXQlndFT.js.br +0 -0
- codex/static_root/assets/VCheckbox-CXQlndFT.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-DB72Bclg.ab414c6ae2b8.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DB72Bclg.ab414c6ae2b8.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-DB72Bclg.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DB72Bclg.js.gz +0 -0
- codex/static_root/assets/VCombobox-BDcFPzYf.0b80447cd638.js.br +0 -0
- codex/static_root/assets/VCombobox-BDcFPzYf.0b80447cd638.js.gz +0 -0
- codex/static_root/assets/VCombobox-BDcFPzYf.js.br +0 -0
- codex/static_root/assets/VCombobox-BDcFPzYf.js.gz +0 -0
- codex/static_root/assets/VDialog-BB4zqrGw.2decc14ea597.js.br +0 -0
- codex/static_root/assets/VDialog-BB4zqrGw.2decc14ea597.js.gz +0 -0
- codex/static_root/assets/VDialog-BB4zqrGw.js.br +0 -0
- codex/static_root/assets/VDialog-BB4zqrGw.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-SZc735F5.19004a0eba16.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-SZc735F5.19004a0eba16.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-SZc735F5.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-SZc735F5.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-tFEuhFRq.774e14a4945a.js.br +0 -0
- codex/static_root/assets/VRadioGroup-tFEuhFRq.774e14a4945a.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-tFEuhFRq.js.br +0 -0
- codex/static_root/assets/VRadioGroup-tFEuhFRq.js.gz +0 -0
- codex/static_root/assets/VSelect-Dd5Zy8Uy.f225abf824ff.js.br +0 -0
- codex/static_root/assets/VSelect-Dd5Zy8Uy.f225abf824ff.js.gz +0 -0
- codex/static_root/assets/VSelect-Dd5Zy8Uy.js.br +0 -0
- codex/static_root/assets/VSelect-Dd5Zy8Uy.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DImIQPJV.1f14d2113063.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DImIQPJV.1f14d2113063.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-DImIQPJV.js.br +0 -0
- codex/static_root/assets/VSelectionControl-DImIQPJV.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-CDaAHRAK.783800d70a8a.js.br +0 -0
- codex/static_root/assets/VSlideGroup-CDaAHRAK.783800d70a8a.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-CDaAHRAK.js.br +0 -0
- codex/static_root/assets/VSlideGroup-CDaAHRAK.js.gz +0 -0
- codex/static_root/assets/VTable-CkCZonsu.81e34381271f.js.br +0 -0
- codex/static_root/assets/VTable-CkCZonsu.81e34381271f.js.gz +0 -0
- codex/static_root/assets/VTable-CkCZonsu.js.br +0 -0
- codex/static_root/assets/VTable-CkCZonsu.js.gz +0 -0
- codex/static_root/assets/VTextField-fNAzFu1M.00dec8623f18.js.br +0 -0
- codex/static_root/assets/VTextField-fNAzFu1M.00dec8623f18.js.gz +0 -0
- codex/static_root/assets/VTextField-fNAzFu1M.js.br +0 -0
- codex/static_root/assets/VTextField-fNAzFu1M.js.gz +0 -0
- codex/static_root/assets/VWindowItem-_YBMW1SU.0836924f8c12.js.br +0 -0
- codex/static_root/assets/VWindowItem-_YBMW1SU.0836924f8c12.js.gz +0 -0
- codex/static_root/assets/VWindowItem-_YBMW1SU.js.br +0 -0
- codex/static_root/assets/VWindowItem-_YBMW1SU.js.gz +0 -0
- codex/static_root/assets/admin-DYmGyJVg.262e6032ee86.js.br +0 -0
- codex/static_root/assets/admin-DYmGyJVg.262e6032ee86.js.gz +0 -0
- codex/static_root/assets/admin-DYmGyJVg.js.br +0 -0
- codex/static_root/assets/admin-DYmGyJVg.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-c3f3H29x.fa2b12c75b06.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-c3f3H29x.fa2b12c75b06.js.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-c3f3H29x.js.br +0 -0
- codex/static_root/assets/admin-drawer-panel-c3f3H29x.js.gz +0 -0
- codex/static_root/assets/browser-Ck3zWCOk.d98c77747e6d.js.br +0 -0
- codex/static_root/assets/browser-Ck3zWCOk.d98c77747e6d.js.gz +0 -0
- codex/static_root/assets/browser-Ck3zWCOk.js.br +0 -0
- codex/static_root/assets/browser-Ck3zWCOk.js.gz +0 -0
- codex/static_root/assets/browser-DgawKPSA.c7cf59dc4b21.css.br +0 -0
- codex/static_root/assets/browser-DgawKPSA.c7cf59dc4b21.css.gz +0 -0
- codex/static_root/assets/browser-DgawKPSA.css.br +0 -0
- codex/static_root/assets/browser-DgawKPSA.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-BbIO0YI5.2bb0a5dfb190.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BbIO0YI5.2bb0a5dfb190.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-BbIO0YI5.js.br +0 -0
- codex/static_root/assets/change-password-dialog-BbIO0YI5.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-BrzuoYAT.a0a1b6364708.js.br +0 -0
- codex/static_root/assets/confirm-dialog-BrzuoYAT.a0a1b6364708.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-BrzuoYAT.js.br +0 -0
- codex/static_root/assets/confirm-dialog-BrzuoYAT.js.gz +0 -0
- codex/static_root/assets/datetime-column-D8-UeX_p.e33b1556805b.js.br +0 -0
- codex/static_root/assets/datetime-column-D8-UeX_p.e33b1556805b.js.gz +0 -0
- codex/static_root/assets/datetime-column-D8-UeX_p.js.br +0 -0
- codex/static_root/assets/datetime-column-D8-UeX_p.js.gz +0 -0
- codex/static_root/assets/filter-XMAX5Nmg.c6ed87350f7d.js.br +0 -0
- codex/static_root/assets/filter-XMAX5Nmg.c6ed87350f7d.js.gz +0 -0
- codex/static_root/assets/filter-XMAX5Nmg.js.br +0 -0
- codex/static_root/assets/filter-XMAX5Nmg.js.gz +0 -0
- codex/static_root/assets/flag-tab-BLEUVMot.c14936c90346.js +0 -1
- codex/static_root/assets/flag-tab-BLEUVMot.c14936c90346.js.br +0 -0
- codex/static_root/assets/flag-tab-BLEUVMot.c14936c90346.js.gz +0 -0
- codex/static_root/assets/flag-tab-BLEUVMot.js +0 -1
- codex/static_root/assets/flag-tab-BLEUVMot.js.br +0 -0
- codex/static_root/assets/flag-tab-BLEUVMot.js.gz +0 -0
- codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css +0 -1
- 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 +0 -1
- 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/group-tab-D9n5gZCA.634223001c44.js.br +0 -0
- codex/static_root/assets/group-tab-D9n5gZCA.634223001c44.js.gz +0 -0
- codex/static_root/assets/group-tab-D9n5gZCA.js.br +0 -0
- codex/static_root/assets/group-tab-D9n5gZCA.js.gz +0 -0
- codex/static_root/assets/http-error-C_EFLi0c.c7e906206b62.js.br +0 -0
- codex/static_root/assets/http-error-C_EFLi0c.c7e906206b62.js.gz +0 -0
- codex/static_root/assets/http-error-C_EFLi0c.js.br +0 -0
- codex/static_root/assets/http-error-C_EFLi0c.js.gz +0 -0
- codex/static_root/assets/library-tab-fDxuhPBs.285b36d9cec0.js.br +0 -0
- codex/static_root/assets/library-tab-fDxuhPBs.285b36d9cec0.js.gz +0 -0
- codex/static_root/assets/library-tab-fDxuhPBs.js.br +0 -0
- codex/static_root/assets/library-tab-fDxuhPBs.js.gz +0 -0
- codex/static_root/assets/main-B3MfqYNN.30d1310fc082.js +0 -6
- codex/static_root/assets/main-B3MfqYNN.30d1310fc082.js.br +0 -0
- codex/static_root/assets/main-B3MfqYNN.30d1310fc082.js.gz +0 -0
- codex/static_root/assets/main-B3MfqYNN.js +0 -6
- codex/static_root/assets/main-B3MfqYNN.js.br +0 -0
- codex/static_root/assets/main-B3MfqYNN.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-BD6nzIKF.f3ca4874c203.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-BD6nzIKF.f3ca4874c203.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-BD6nzIKF.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-BD6nzIKF.js.gz +0 -0
- codex/static_root/assets/pdf-doc-iF15lKRp.de9a9d394438.js.br +0 -0
- codex/static_root/assets/pdf-doc-iF15lKRp.de9a9d394438.js.gz +0 -0
- codex/static_root/assets/pdf-doc-iF15lKRp.js.br +0 -0
- codex/static_root/assets/pdf-doc-iF15lKRp.js.gz +0 -0
- codex/static_root/assets/reader-5Ja_bAaT.2fe33a3891f6.js.br +0 -0
- codex/static_root/assets/reader-5Ja_bAaT.2fe33a3891f6.js.gz +0 -0
- codex/static_root/assets/reader-5Ja_bAaT.js.br +0 -0
- codex/static_root/assets/reader-5Ja_bAaT.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUWkvLNo.3d18d0c09d39.js.br +0 -0
- codex/static_root/assets/relation-chips-CUWkvLNo.3d18d0c09d39.js.gz +0 -0
- codex/static_root/assets/relation-chips-CUWkvLNo.js.br +0 -0
- codex/static_root/assets/relation-chips-CUWkvLNo.js.gz +0 -0
- codex/static_root/assets/settings-drawer-rn3JdMxA.a9d7b45c2138.js.br +0 -0
- codex/static_root/assets/settings-drawer-rn3JdMxA.a9d7b45c2138.js.gz +0 -0
- codex/static_root/assets/settings-drawer-rn3JdMxA.js.br +0 -0
- codex/static_root/assets/settings-drawer-rn3JdMxA.js.gz +0 -0
- codex/static_root/assets/stats-tab-B6L-YFRA.1c90593f7b5c.css +0 -1
- codex/static_root/assets/stats-tab-B6L-YFRA.1c90593f7b5c.css.br +0 -0
- codex/static_root/assets/stats-tab-B6L-YFRA.1c90593f7b5c.css.gz +0 -0
- codex/static_root/assets/stats-tab-B6L-YFRA.css +0 -1
- codex/static_root/assets/stats-tab-B6L-YFRA.css.br +0 -0
- codex/static_root/assets/stats-tab-B6L-YFRA.css.gz +0 -0
- codex/static_root/assets/stats-tab-zts2Xsmg.9fb959ccf4da.js +0 -1
- codex/static_root/assets/stats-tab-zts2Xsmg.9fb959ccf4da.js.br +0 -0
- codex/static_root/assets/stats-tab-zts2Xsmg.9fb959ccf4da.js.gz +0 -0
- codex/static_root/assets/stats-tab-zts2Xsmg.js +0 -1
- codex/static_root/assets/stats-tab-zts2Xsmg.js.br +0 -0
- codex/static_root/assets/stats-tab-zts2Xsmg.js.gz +0 -0
- codex/static_root/assets/task-tab-CbzDX0IR.7b43bdf9cf42.js.br +0 -0
- codex/static_root/assets/task-tab-CbzDX0IR.7b43bdf9cf42.js.gz +0 -0
- codex/static_root/assets/task-tab-CbzDX0IR.js.br +0 -0
- codex/static_root/assets/task-tab-CbzDX0IR.js.gz +0 -0
- codex/static_root/assets/unauthorized-CllrtnFV.2a18fab3dd41.js.br +0 -0
- codex/static_root/assets/unauthorized-CllrtnFV.2a18fab3dd41.js.gz +0 -0
- codex/static_root/assets/unauthorized-CllrtnFV.js.br +0 -0
- codex/static_root/assets/unauthorized-CllrtnFV.js.gz +0 -0
- codex/static_root/assets/user-tab-FY1xALpi.05a88f51df4f.js.br +0 -0
- codex/static_root/assets/user-tab-FY1xALpi.05a88f51df4f.js.gz +0 -0
- codex/static_root/assets/user-tab-FY1xALpi.js.br +0 -0
- codex/static_root/assets/user-tab-FY1xALpi.js.gz +0 -0
- codex/static_root/manifest.166aa32817b1.json.br +0 -0
- codex/static_root/manifest.166aa32817b1.json.gz +0 -0
- {codex-1.6.3.dist-info → codex-1.6.5.dist-info}/LICENSE +0 -0
- {codex-1.6.3.dist-info → codex-1.6.5.dist-info}/WHEEL +0 -0
- {codex-1.6.3.dist-info → codex-1.6.5.dist-info}/entry_points.txt +0 -0
|
@@ -42,7 +42,7 @@ class ComicImporter(MovedImporter):
|
|
|
42
42
|
self.librarian_queue.put(LIBRARY_CHANGED_TASK)
|
|
43
43
|
|
|
44
44
|
# Wait to start the search index update in case more updates are incoming.
|
|
45
|
-
until = time() +
|
|
45
|
+
until = time() + 1
|
|
46
46
|
delayed_search_task = DelayedTasks(until, (SearchIndexUpdateTask(False),))
|
|
47
47
|
self.librarian_queue.put(delayed_search_task)
|
|
48
48
|
else:
|
|
@@ -111,7 +111,6 @@ class CleanupMixin(WorkerBaseMixin):
|
|
|
111
111
|
|
|
112
112
|
def cleanup_custom_covers(self):
|
|
113
113
|
"""Clean up unused custom covers."""
|
|
114
|
-
start = time()
|
|
115
114
|
covers = CustomCover.objects.only("path")
|
|
116
115
|
status = Status(JanitorStatusTypes.CLEANUP_COVERS, 0, covers.count())
|
|
117
116
|
delete_pks = []
|
|
@@ -128,12 +127,11 @@ class CleanupMixin(WorkerBaseMixin):
|
|
|
128
127
|
level = logging.INFO if status.complete else logging.DEBUG
|
|
129
128
|
self.log.log(level, f"Deleted {count} CustomCovers without source images.")
|
|
130
129
|
finally:
|
|
131
|
-
until =
|
|
130
|
+
until = time() + 1
|
|
132
131
|
self.status_controller.finish(status, until=until)
|
|
133
132
|
|
|
134
133
|
def cleanup_sessions(self):
|
|
135
134
|
"""Delete corrupt sessions."""
|
|
136
|
-
start = time()
|
|
137
135
|
status = Status(JanitorStatusTypes.CLEANUP_SESSIONS)
|
|
138
136
|
try:
|
|
139
137
|
self.status_controller.start(status)
|
|
@@ -152,5 +150,5 @@ class CleanupMixin(WorkerBaseMixin):
|
|
|
152
150
|
count, _ = bad_sessions.delete()
|
|
153
151
|
self.log.info(f"Deleted {count} corrupt sessions.")
|
|
154
152
|
finally:
|
|
155
|
-
until =
|
|
153
|
+
until = time() + 1
|
|
156
154
|
self.status_controller.finish(status, until=until)
|
|
@@ -33,15 +33,16 @@ class UpdateMixin(WorkerBaseMixin):
|
|
|
33
33
|
|
|
34
34
|
self.log.info("Codex seems outdated. Trying to update.")
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
args = (
|
|
37
|
+
sys.executable,
|
|
38
|
+
"-m",
|
|
39
|
+
"pip",
|
|
40
|
+
"install",
|
|
41
|
+
"--upgrade",
|
|
42
|
+
"codex",
|
|
43
|
+
)
|
|
44
|
+
subprocess.run( # noqa: S603
|
|
45
|
+
args,
|
|
45
46
|
check=True,
|
|
46
47
|
)
|
|
47
48
|
except Exception:
|
codex/librarian/search/merge.py
CHANGED
|
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
from humanize import naturaldelta, naturalsize
|
|
8
8
|
|
|
9
9
|
from codex.librarian.search.status import SearchIndexStatusTypes
|
|
10
|
-
from codex.librarian.search.tasks import SearchIndexRemoveStaleTask
|
|
11
10
|
from codex.librarian.search.version import VersionMixin
|
|
12
11
|
from codex.settings.settings import SEARCH_INDEX_PATH
|
|
13
12
|
from codex.status import Status
|
|
@@ -39,50 +38,48 @@ class MergeMixin(VersionMixin):
|
|
|
39
38
|
size += Path(segment).stat().st_size
|
|
40
39
|
return size
|
|
41
40
|
|
|
42
|
-
def
|
|
41
|
+
def _merge_search_index(self, optimize, status, name):
|
|
43
42
|
"""Optimize search index."""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE, subtitle=name)
|
|
47
|
-
try:
|
|
48
|
-
statii = (
|
|
49
|
-
status,
|
|
50
|
-
Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
|
|
51
|
-
)
|
|
52
|
-
self.status_controller.start_many(statii)
|
|
53
|
-
start = time()
|
|
43
|
+
self.status_controller.start(status)
|
|
44
|
+
start = time()
|
|
54
45
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
46
|
+
segments, old_num_segments = self._get_segments_and_len()
|
|
47
|
+
if self._is_index_optimized(old_num_segments):
|
|
48
|
+
return
|
|
49
|
+
self.status_controller.start(status)
|
|
50
|
+
old_size = self._get_segments_size(segments)
|
|
51
|
+
# Optimize
|
|
52
|
+
self.log.info(
|
|
53
|
+
f"Search index found in {old_num_segments} segments," f" merging {name}..."
|
|
54
|
+
)
|
|
55
|
+
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
56
|
+
if optimize:
|
|
57
|
+
backend.optimize()
|
|
58
|
+
else:
|
|
59
|
+
backend.merge_small()
|
|
60
|
+
|
|
61
|
+
# Finish
|
|
62
|
+
segments, new_num_segments = self._get_segments_and_len()
|
|
63
|
+
new_size = self._get_segments_size(segments)
|
|
64
|
+
saved = naturalsize(old_size - new_size)
|
|
65
|
+
num_segments_diff = old_num_segments - new_num_segments
|
|
66
|
+
elapsed_time = time() - start
|
|
67
|
+
elapsed = naturaldelta(elapsed_time)
|
|
68
|
+
if num_segments_diff:
|
|
61
69
|
self.log.info(
|
|
62
|
-
f"
|
|
63
|
-
f"
|
|
70
|
+
f"Merged {num_segments_diff} search index segments in {elapsed}."
|
|
71
|
+
f"Saved {saved}."
|
|
64
72
|
)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
backend.optimize()
|
|
68
|
-
else:
|
|
69
|
-
backend.merge_small()
|
|
73
|
+
else:
|
|
74
|
+
self.log.info("No small search index segments found.")
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if num_segments_diff:
|
|
79
|
-
self.log.info(
|
|
80
|
-
f"Merged {num_segments_diff} search index segments in {elapsed}."
|
|
81
|
-
f"Saved {saved}."
|
|
82
|
-
)
|
|
83
|
-
else:
|
|
84
|
-
self.log.info("No small search index segments found.")
|
|
85
|
-
self.librarian_queue.put(SearchIndexRemoveStaleTask())
|
|
76
|
+
def merge_search_index(self, optimize=False):
|
|
77
|
+
"""Optimize search index, trapping exceptions."""
|
|
78
|
+
verb = "All" if optimize else "Small"
|
|
79
|
+
name = f"Merge {verb} Segments"
|
|
80
|
+
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE, subtitle=name)
|
|
81
|
+
try:
|
|
82
|
+
self._merge_search_index(optimize, status, name)
|
|
86
83
|
except Exception:
|
|
87
84
|
self.log.exception("Search index merge.")
|
|
88
85
|
finally:
|
codex/librarian/search/remove.py
CHANGED
|
@@ -38,39 +38,43 @@ class RemoveMixin(VersionMixin):
|
|
|
38
38
|
return delete_docnums
|
|
39
39
|
return delete_docnums
|
|
40
40
|
|
|
41
|
+
def _remove_stale_records(self, backend: CodexSearchBackend | None, status): # type: ignore
|
|
42
|
+
"""Remove records not in the database from the index."""
|
|
43
|
+
start_time = time()
|
|
44
|
+
if not backend:
|
|
45
|
+
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
46
|
+
if not backend.setup_complete:
|
|
47
|
+
backend.setup(False)
|
|
48
|
+
|
|
49
|
+
delete_docnums = self._get_delete_docnums(backend)
|
|
50
|
+
num_delete_docnums = len(delete_docnums)
|
|
51
|
+
count = 0
|
|
52
|
+
if num_delete_docnums:
|
|
53
|
+
status.total = num_delete_docnums
|
|
54
|
+
self.status_controller.start(status)
|
|
55
|
+
count = backend.remove_docnums(delete_docnums)
|
|
56
|
+
|
|
57
|
+
# Finish
|
|
58
|
+
if count:
|
|
59
|
+
elapsed_time = time() - start_time
|
|
60
|
+
elapsed = naturaldelta(elapsed_time)
|
|
61
|
+
cps = int(count / elapsed_time)
|
|
62
|
+
self.log.info(
|
|
63
|
+
f"Removed {count} stale records from the search index"
|
|
64
|
+
f" in {elapsed} at {cps} per second."
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
self.log.debug("No stale records to remove from the search index.")
|
|
68
|
+
|
|
41
69
|
def remove_stale_records(
|
|
42
70
|
self,
|
|
43
71
|
backend: CodexSearchBackend | None = None, # type: ignore
|
|
44
72
|
):
|
|
45
|
-
"""Remove records not in the database from the index."""
|
|
73
|
+
"""Remove records not in the database from the index, trapping exceptions."""
|
|
46
74
|
self.abort_event.clear()
|
|
47
75
|
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE)
|
|
48
76
|
try:
|
|
49
|
-
|
|
50
|
-
if not backend:
|
|
51
|
-
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
52
|
-
if not backend.setup_complete:
|
|
53
|
-
backend.setup(False)
|
|
54
|
-
|
|
55
|
-
delete_docnums = self._get_delete_docnums(backend)
|
|
56
|
-
num_delete_docnums = len(delete_docnums)
|
|
57
|
-
count = 0
|
|
58
|
-
if num_delete_docnums:
|
|
59
|
-
status.total = num_delete_docnums
|
|
60
|
-
self.status_controller.start(status)
|
|
61
|
-
count = backend.remove_docnums(delete_docnums)
|
|
62
|
-
|
|
63
|
-
# Finish
|
|
64
|
-
if count:
|
|
65
|
-
elapsed_time = time() - start_time
|
|
66
|
-
elapsed = naturaldelta(elapsed_time)
|
|
67
|
-
cps = int(count / elapsed_time)
|
|
68
|
-
self.log.info(
|
|
69
|
-
f"Removed {count} stale records from the search index"
|
|
70
|
-
f" in {elapsed} at {cps} per second."
|
|
71
|
-
)
|
|
72
|
-
else:
|
|
73
|
-
self.log.debug("No stale records to remove from the search index.")
|
|
77
|
+
self._remove_stale_records(backend, status)
|
|
74
78
|
except Exception:
|
|
75
79
|
self.log.exception("Removing stale records:")
|
|
76
80
|
finally:
|
codex/librarian/search/update.py
CHANGED
|
@@ -301,7 +301,7 @@ class UpdateMixin(RemoveMixin):
|
|
|
301
301
|
except Exception:
|
|
302
302
|
self.log.exception("Update search index with multiprocessing")
|
|
303
303
|
finally:
|
|
304
|
-
until =
|
|
304
|
+
until = time() + 1
|
|
305
305
|
self.status_controller.finish(status, until=until)
|
|
306
306
|
|
|
307
307
|
def clear_search_index(self):
|
|
@@ -313,50 +313,59 @@ class UpdateMixin(RemoveMixin):
|
|
|
313
313
|
self.status_controller.finish(clear_status)
|
|
314
314
|
self.log.info("Old search index cleared.")
|
|
315
315
|
|
|
316
|
-
def
|
|
316
|
+
def _update_search_index(self, start_time, rebuild):
|
|
317
317
|
"""Update or Rebuild the search index."""
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
)
|
|
328
|
-
return
|
|
318
|
+
remove_stale = False
|
|
319
|
+
any_update_in_progress = Library.objects.filter(
|
|
320
|
+
covers_only=False, update_in_progress=True
|
|
321
|
+
).exists()
|
|
322
|
+
if any_update_in_progress:
|
|
323
|
+
self.log.debug(
|
|
324
|
+
"Database update in progress, not updating search index yet."
|
|
325
|
+
)
|
|
326
|
+
return remove_stale
|
|
329
327
|
|
|
330
|
-
|
|
331
|
-
|
|
328
|
+
if not rebuild and not self.is_search_index_uuid_match():
|
|
329
|
+
rebuild = True
|
|
332
330
|
|
|
333
|
-
|
|
331
|
+
self._init_statuses(rebuild)
|
|
334
332
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
333
|
+
# Clear
|
|
334
|
+
if rebuild:
|
|
335
|
+
self.log.info("Rebuilding search index...")
|
|
336
|
+
self.clear_search_index()
|
|
339
337
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
338
|
+
# Update
|
|
339
|
+
backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
|
|
340
|
+
backend.setup(False)
|
|
341
|
+
if self.abort_event.is_set():
|
|
342
|
+
self.log.debug("Abort update search index.")
|
|
343
|
+
return remove_stale
|
|
344
|
+
qs = self._get_queryset(backend, rebuild)
|
|
345
|
+
self._mp_update(backend, qs)
|
|
347
346
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
self.librarian_queue.put(task)
|
|
347
|
+
# Finish
|
|
348
|
+
if rebuild:
|
|
349
|
+
self.set_search_index_version()
|
|
350
|
+
else:
|
|
351
|
+
remove_stale = True
|
|
354
352
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
353
|
+
elapsed_time = time() - start_time
|
|
354
|
+
elapsed = naturaldelta(elapsed_time)
|
|
355
|
+
self.log.info(f"Search index updated in {elapsed}.")
|
|
356
|
+
return remove_stale
|
|
357
|
+
|
|
358
|
+
def update_search_index(self, rebuild=False):
|
|
359
|
+
"""Update or Rebuild the search index."""
|
|
360
|
+
start_time = time()
|
|
361
|
+
self.abort_event.clear()
|
|
362
|
+
remove_stale = False
|
|
363
|
+
try:
|
|
364
|
+
remove_stale = self._update_search_index(start_time, rebuild)
|
|
358
365
|
except Exception:
|
|
359
366
|
self.log.exception("Update search index")
|
|
360
367
|
finally:
|
|
361
|
-
|
|
362
|
-
|
|
368
|
+
self.status_controller.finish_many(self._STATUS_FINISH_TYPES)
|
|
369
|
+
if remove_stale:
|
|
370
|
+
task = SearchIndexRemoveStaleTask()
|
|
371
|
+
self.librarian_queue.put(task)
|
codex/librarian/stats.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Admin Flag View."""
|
|
2
|
+
|
|
3
|
+
from multiprocessing import cpu_count
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from platform import machine, python_version, release, system
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
|
|
8
|
+
from caseconverter import snakecase
|
|
9
|
+
from django.contrib.sessions.models import Session
|
|
10
|
+
from django.db.models import Count
|
|
11
|
+
|
|
12
|
+
from codex.logger.logging import get_logger
|
|
13
|
+
from codex.models import (
|
|
14
|
+
Comic,
|
|
15
|
+
Library,
|
|
16
|
+
)
|
|
17
|
+
from codex.version import VERSION
|
|
18
|
+
from codex.views.const import CONFIG_MODELS, METADATA_MODELS, STATS_GROUP_MODELS
|
|
19
|
+
from codex.views.session import SessionView
|
|
20
|
+
|
|
21
|
+
LOG = get_logger(__name__)
|
|
22
|
+
_KEY_MODELS_MAP = MappingProxyType(
|
|
23
|
+
{
|
|
24
|
+
"config": CONFIG_MODELS,
|
|
25
|
+
"groups": STATS_GROUP_MODELS,
|
|
26
|
+
"metadata": METADATA_MODELS,
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
_DOCKERENV_PATH = Path("/.dockerenv")
|
|
30
|
+
_CGROUP_PATH = Path("/proc/self/cgroup")
|
|
31
|
+
_USER_STATS = MappingProxyType(
|
|
32
|
+
{
|
|
33
|
+
SessionView.BROWSER_SESSION_KEY: ("top_group", "order_by", "dynamic_covers"),
|
|
34
|
+
SessionView.READER_SESSION_KEY: (
|
|
35
|
+
"finish_on_last_page",
|
|
36
|
+
"fit_to",
|
|
37
|
+
"reading_direction",
|
|
38
|
+
),
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class CodexStats:
|
|
44
|
+
"""Collect codex stats."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, params=None):
|
|
47
|
+
"""Specify which stats to collect. Default to all."""
|
|
48
|
+
if not params:
|
|
49
|
+
params = {}
|
|
50
|
+
self.params = params
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def _is_docker(cls):
|
|
54
|
+
"""Test if we're in a docker container."""
|
|
55
|
+
try:
|
|
56
|
+
return _DOCKERENV_PATH.is_file() or "docker" in _CGROUP_PATH.read_text()
|
|
57
|
+
except Exception:
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
def _get_models(self, key):
|
|
61
|
+
"""Get models from request params."""
|
|
62
|
+
request_model_set = self.params.get(key, {})
|
|
63
|
+
all_models = _KEY_MODELS_MAP[key]
|
|
64
|
+
if request_model_set:
|
|
65
|
+
models = []
|
|
66
|
+
for model_name in request_model_set:
|
|
67
|
+
for model in all_models:
|
|
68
|
+
if model.__name__.lower() == model_name.lower():
|
|
69
|
+
models.append(model)
|
|
70
|
+
else:
|
|
71
|
+
models = all_models
|
|
72
|
+
return tuple(models)
|
|
73
|
+
|
|
74
|
+
def _get_model_counts(self, key):
|
|
75
|
+
"""Get database counts of each model group."""
|
|
76
|
+
models = self._get_models(key)
|
|
77
|
+
obj = {}
|
|
78
|
+
for model in models:
|
|
79
|
+
name = snakecase(model.__name__) + "_count"
|
|
80
|
+
qs = model.objects
|
|
81
|
+
if model == Library:
|
|
82
|
+
qs = qs.filter(covers_only=False)
|
|
83
|
+
obj[name] = qs.count()
|
|
84
|
+
return obj
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _aggregate_session_key(session, session_key, session_subkeys, user_stats):
|
|
88
|
+
session_dict = session.get(session_key, {})
|
|
89
|
+
for key in session_subkeys:
|
|
90
|
+
value = session_dict.get(key)
|
|
91
|
+
if value is None:
|
|
92
|
+
continue
|
|
93
|
+
if key not in user_stats:
|
|
94
|
+
user_stats[key] = {}
|
|
95
|
+
if value not in user_stats[key]:
|
|
96
|
+
user_stats[key][value] = 0
|
|
97
|
+
user_stats[key][value] += 1
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def _get_session_stats(cls):
|
|
101
|
+
"""Return the number of anonymous sessions."""
|
|
102
|
+
sessions = Session.objects.all()
|
|
103
|
+
anon_session_count = 0
|
|
104
|
+
user_stats = {}
|
|
105
|
+
for encoded_session in sessions:
|
|
106
|
+
session = encoded_session.get_decoded()
|
|
107
|
+
if not session.get("_auth_user_id"):
|
|
108
|
+
anon_session_count += 1
|
|
109
|
+
for session_key, subkeys in _USER_STATS.items():
|
|
110
|
+
cls._aggregate_session_key(session, session_key, subkeys, user_stats)
|
|
111
|
+
|
|
112
|
+
return user_stats, anon_session_count
|
|
113
|
+
|
|
114
|
+
def _get_platform(self, obj):
|
|
115
|
+
"""Add dict of platform information to object."""
|
|
116
|
+
platform = {
|
|
117
|
+
"docker": self._is_docker(),
|
|
118
|
+
"machine": machine(),
|
|
119
|
+
"cores": cpu_count(),
|
|
120
|
+
"system": {
|
|
121
|
+
"name": system(),
|
|
122
|
+
"release": release(),
|
|
123
|
+
},
|
|
124
|
+
"python_version": python_version(),
|
|
125
|
+
"codex_version": VERSION,
|
|
126
|
+
}
|
|
127
|
+
obj["platform"] = platform
|
|
128
|
+
|
|
129
|
+
def _get_config(self, obj):
|
|
130
|
+
"""Add dict of config informaation to object."""
|
|
131
|
+
config = self._get_model_counts("config")
|
|
132
|
+
sessions, config["user_anonymous_count"] = self._get_session_stats()
|
|
133
|
+
config["user_registered_count"] = config.pop("users_count", 0)
|
|
134
|
+
config["auth_group_count"] = config.pop("groups_count", 0)
|
|
135
|
+
obj["config"] = config
|
|
136
|
+
obj["sessions"] = sessions
|
|
137
|
+
|
|
138
|
+
def _get_groups(self, obj):
|
|
139
|
+
"""Add dict of groups information to object."""
|
|
140
|
+
groups = self._get_model_counts("groups")
|
|
141
|
+
obj["groups"] = groups
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
def _get_file_types(obj):
|
|
145
|
+
"""Query for file types."""
|
|
146
|
+
file_types = {}
|
|
147
|
+
qs = (
|
|
148
|
+
Comic.objects.values("file_type")
|
|
149
|
+
.annotate(count=Count("file_type"))
|
|
150
|
+
.order_by()
|
|
151
|
+
)
|
|
152
|
+
for query_group in qs:
|
|
153
|
+
value = query_group["file_type"]
|
|
154
|
+
name = value.lower() if value else "unknown"
|
|
155
|
+
file_types[name] = query_group["count"]
|
|
156
|
+
sorted_fts = dict(sorted(file_types.items()))
|
|
157
|
+
obj["file_types"] = sorted_fts
|
|
158
|
+
|
|
159
|
+
def _get_metadata(self, obj):
|
|
160
|
+
"""Add dict of metadata counts to object."""
|
|
161
|
+
metadata = self._get_model_counts("metadata")
|
|
162
|
+
obj["metadata"] = metadata
|
|
163
|
+
|
|
164
|
+
def get(self):
|
|
165
|
+
"""Construct the stats object."""
|
|
166
|
+
obj = {}
|
|
167
|
+
if not self.params or "platform" in self.params:
|
|
168
|
+
self._get_platform(obj)
|
|
169
|
+
if not self.params or "config" in self.params:
|
|
170
|
+
self._get_config(obj)
|
|
171
|
+
if not self.params or "groups" in self.params:
|
|
172
|
+
self._get_groups(obj)
|
|
173
|
+
if not self.params or "fileTypes" in self.params:
|
|
174
|
+
self._get_file_types(obj)
|
|
175
|
+
if not self.params or "metadata" in self.params:
|
|
176
|
+
self._get_metadata(obj)
|
|
177
|
+
return obj
|
|
@@ -191,8 +191,8 @@ class CodexLibraryEventHandler(CodexEventHandlerBase):
|
|
|
191
191
|
events = self._transform_file_event(event)
|
|
192
192
|
|
|
193
193
|
# Send it to the EventBatcher
|
|
194
|
-
for
|
|
195
|
-
task = WatchdogEventTask(self.library_pk,
|
|
194
|
+
for sub_event in events:
|
|
195
|
+
task = WatchdogEventTask(self.library_pk, sub_event)
|
|
196
196
|
self.librarian_queue.put(task)
|
|
197
197
|
|
|
198
198
|
# Calls stub event dispatchers
|