codex 1.7.0a0__py3-none-any.whl → 1.7.0a1__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/choices.py +404 -0
- codex/choices_to_json.py +122 -0
- codex/db.py +31 -15
- codex/exceptions.py +2 -3
- codex/librarian/bookmark/__init__.py +1 -0
- codex/librarian/bookmark/bookmarkd.py +38 -0
- codex/librarian/bookmark/tasks.py +19 -0
- codex/librarian/bookmark/update.py +154 -0
- codex/librarian/covers/coverd.py +3 -4
- codex/librarian/covers/create.py +8 -7
- codex/librarian/covers/path.py +1 -2
- codex/librarian/covers/purge.py +2 -2
- codex/librarian/janitor/integrity.py +1 -0
- codex/librarian/librariand.py +5 -0
- codex/librarian/watchdog/emitter.py +21 -19
- codex/librarian/watchdog/event_batcherd.py +0 -1
- codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +4 -0
- codex/models/admin.py +0 -5
- codex/models/functions.py +6 -5
- codex/models/query.py +6 -0
- codex/run.py +11 -9
- codex/serializers/admin/tasks.py +2 -2
- codex/serializers/browser/mixins.py +18 -14
- codex/serializers/browser/settings.py +4 -4
- codex/serializers/fields.py +9 -5
- codex/startup.py +6 -4
- codex/static_root/assets/VCheckbox-B38iL9f_.81d9c00753e7.js +1 -0
- codex/static_root/assets/VCheckbox-B38iL9f_.81d9c00753e7.js.br +0 -0
- codex/static_root/assets/VCheckbox-B38iL9f_.81d9c00753e7.js.gz +0 -0
- codex/static_root/assets/VCheckbox-B38iL9f_.js +1 -0
- codex/static_root/assets/VCheckbox-B38iL9f_.js.br +0 -0
- codex/static_root/assets/VCheckbox-B38iL9f_.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BbNnqvzj.aa7fa886f86a.js +1 -0
- codex/static_root/assets/VCheckboxBtn-BbNnqvzj.aa7fa886f86a.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BbNnqvzj.aa7fa886f86a.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-BbNnqvzj.js +1 -0
- codex/static_root/assets/VCheckboxBtn-BbNnqvzj.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-BbNnqvzj.js.gz +0 -0
- codex/static_root/assets/VCombobox-BaV3XhIr.bf0c945d80a9.js +1 -0
- codex/static_root/assets/VCombobox-BaV3XhIr.bf0c945d80a9.js.br +0 -0
- codex/static_root/assets/VCombobox-BaV3XhIr.bf0c945d80a9.js.gz +0 -0
- codex/static_root/assets/VCombobox-BaV3XhIr.js +1 -0
- codex/static_root/assets/VCombobox-BaV3XhIr.js.br +0 -0
- codex/static_root/assets/VCombobox-BaV3XhIr.js.gz +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css +1 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css.br +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.css.gz +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css +1 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.br +0 -0
- codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.gz +0 -0
- codex/static_root/assets/VDialog-DJR54d5g.9d6a52f62db3.js +1 -0
- codex/static_root/assets/VDialog-DJR54d5g.9d6a52f62db3.js.br +0 -0
- codex/static_root/assets/VDialog-DJR54d5g.9d6a52f62db3.js.gz +0 -0
- codex/static_root/assets/VDialog-DJR54d5g.js +1 -0
- codex/static_root/assets/VDialog-DJR54d5g.js.br +0 -0
- codex/static_root/assets/VDialog-DJR54d5g.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CJWqDA9d.acf5acdb3a81.js +1 -0
- codex/static_root/assets/VExpansionPanels-CJWqDA9d.acf5acdb3a81.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CJWqDA9d.acf5acdb3a81.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-CJWqDA9d.js +1 -0
- codex/static_root/assets/VExpansionPanels-CJWqDA9d.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CJWqDA9d.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BFLE3yCT.8b78cefe4206.js +1 -0
- codex/static_root/assets/VRadioGroup-BFLE3yCT.8b78cefe4206.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BFLE3yCT.8b78cefe4206.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-BFLE3yCT.js +1 -0
- codex/static_root/assets/VRadioGroup-BFLE3yCT.js.br +0 -0
- codex/static_root/assets/VRadioGroup-BFLE3yCT.js.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +1 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css +1 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
- codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
- codex/static_root/assets/VSelect-Dvsi19pi.6d38dc99e0a8.js +1 -0
- codex/static_root/assets/VSelect-Dvsi19pi.6d38dc99e0a8.js.br +0 -0
- codex/static_root/assets/VSelect-Dvsi19pi.6d38dc99e0a8.js.gz +0 -0
- codex/static_root/assets/VSelect-Dvsi19pi.js +1 -0
- codex/static_root/assets/VSelect-Dvsi19pi.js.br +0 -0
- codex/static_root/assets/VSelect-Dvsi19pi.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-BioGLzkQ.106c3be4b6d4.js +1 -0
- codex/static_root/assets/VSelectionControl-BioGLzkQ.106c3be4b6d4.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BioGLzkQ.106c3be4b6d4.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-BioGLzkQ.js +1 -0
- codex/static_root/assets/VSelectionControl-BioGLzkQ.js.br +0 -0
- codex/static_root/assets/VSelectionControl-BioGLzkQ.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +1 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css +1 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
- codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-CFkaPBuS.1d168fc87687.js +1 -0
- codex/static_root/assets/VSlideGroup-CFkaPBuS.1d168fc87687.js.br +0 -0
- codex/static_root/assets/VSlideGroup-CFkaPBuS.1d168fc87687.js.gz +0 -0
- codex/static_root/assets/VSlideGroup-CFkaPBuS.js +1 -0
- codex/static_root/assets/VSlideGroup-CFkaPBuS.js.br +0 -0
- codex/static_root/assets/VSlideGroup-CFkaPBuS.js.gz +0 -0
- codex/static_root/assets/VTable-B7x3Oih2.d0010dab39ce.js +1 -0
- codex/static_root/assets/VTable-B7x3Oih2.d0010dab39ce.js.br +0 -0
- codex/static_root/assets/VTable-B7x3Oih2.d0010dab39ce.js.gz +0 -0
- codex/static_root/assets/VTable-B7x3Oih2.js +1 -0
- codex/static_root/assets/VTable-B7x3Oih2.js.br +0 -0
- codex/static_root/assets/VTable-B7x3Oih2.js.gz +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css +1 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.br +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.gz +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css +1 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css.br +0 -0
- codex/static_root/assets/VTextField-BnCEPZIq.css.gz +0 -0
- codex/static_root/assets/VTextField-rsRdtAxl.488a06f530fe.js +1 -0
- codex/static_root/assets/VTextField-rsRdtAxl.488a06f530fe.js.br +0 -0
- codex/static_root/assets/VTextField-rsRdtAxl.488a06f530fe.js.gz +0 -0
- codex/static_root/assets/VTextField-rsRdtAxl.js +1 -0
- codex/static_root/assets/VTextField-rsRdtAxl.js.br +0 -0
- codex/static_root/assets/VTextField-rsRdtAxl.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CibnlNHs.75a48ded87d5.js +1 -0
- codex/static_root/assets/VWindowItem-CibnlNHs.75a48ded87d5.js.br +0 -0
- codex/static_root/assets/VWindowItem-CibnlNHs.75a48ded87d5.js.gz +0 -0
- codex/static_root/assets/VWindowItem-CibnlNHs.js +1 -0
- codex/static_root/assets/VWindowItem-CibnlNHs.js.br +0 -0
- codex/static_root/assets/VWindowItem-CibnlNHs.js.gz +0 -0
- codex/static_root/assets/admin-CaVmJ-fa.251f5d3cc755.js +1 -0
- codex/static_root/assets/admin-CaVmJ-fa.251f5d3cc755.js.br +0 -0
- codex/static_root/assets/admin-CaVmJ-fa.251f5d3cc755.js.gz +0 -0
- codex/static_root/assets/admin-CaVmJ-fa.js +1 -0
- codex/static_root/assets/admin-CaVmJ-fa.js.br +0 -0
- codex/static_root/assets/admin-CaVmJ-fa.js.gz +0 -0
- codex/static_root/assets/{admin-CWEjaFr5.7d4523387384.css → admin-DJ7pIXDQ.css} +1 -1
- codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
- codex/static_root/assets/{admin-CWEjaFr5.css → admin-DJ7pIXDQ.e2a77af73ef2.css} +1 -1
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
- codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css +1 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.br +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.gz +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css +1 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css.br +0 -0
- codex/static_root/assets/admin-menu-B9bzD6hV.css.gz +0 -0
- codex/static_root/assets/admin-menu-Cb7A0Ilo.ddf1e9339257.js +1 -0
- codex/static_root/assets/admin-menu-Cb7A0Ilo.ddf1e9339257.js.br +0 -0
- codex/static_root/assets/admin-menu-Cb7A0Ilo.ddf1e9339257.js.gz +0 -0
- codex/static_root/assets/admin-menu-Cb7A0Ilo.js +1 -0
- codex/static_root/assets/admin-menu-Cb7A0Ilo.js.br +0 -0
- codex/static_root/assets/admin-menu-Cb7A0Ilo.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DID-drll.979420762aea.js +1 -0
- codex/static_root/assets/admin-settings-button-progress-DID-drll.979420762aea.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-DID-drll.979420762aea.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DID-drll.js +1 -0
- codex/static_root/assets/admin-settings-button-progress-DID-drll.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-DID-drll.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.2548c49a853e.css +1 -0
- codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.css +1 -0
- codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css +1 -0
- codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.br +0 -0
- codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.gz +0 -0
- codex/static_root/assets/browser-Br5CorL4.css +1 -0
- codex/static_root/assets/browser-Br5CorL4.css.br +0 -0
- codex/static_root/assets/browser-Br5CorL4.css.gz +0 -0
- codex/static_root/assets/browser-xxQTUEuu.1686c20dc492.js +1 -0
- codex/static_root/assets/browser-xxQTUEuu.1686c20dc492.js.br +0 -0
- codex/static_root/assets/browser-xxQTUEuu.1686c20dc492.js.gz +0 -0
- codex/static_root/assets/browser-xxQTUEuu.js +1 -0
- codex/static_root/assets/browser-xxQTUEuu.js.br +0 -0
- codex/static_root/assets/browser-xxQTUEuu.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DXY2Xg9k.4a7ca6108810.js +1 -0
- codex/static_root/assets/change-password-dialog-DXY2Xg9k.4a7ca6108810.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DXY2Xg9k.4a7ca6108810.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DXY2Xg9k.js +1 -0
- codex/static_root/assets/change-password-dialog-DXY2Xg9k.js.br +0 -0
- codex/static_root/assets/change-password-dialog-DXY2Xg9k.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css +1 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.br +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css +1 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css.br +0 -0
- codex/static_root/assets/change-password-dialog-DrIXdKUC.css.gz +0 -0
- codex/static_root/assets/confirm-dialog-B7w6z2rf.472011c06253.css +1 -0
- codex/static_root/assets/confirm-dialog-B7w6z2rf.css +1 -0
- codex/static_root/assets/confirm-dialog-BEVEU-kA.281b6e877fb5.js +1 -0
- codex/static_root/assets/confirm-dialog-BEVEU-kA.281b6e877fb5.js.br +0 -0
- codex/static_root/assets/confirm-dialog-BEVEU-kA.281b6e877fb5.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-BEVEU-kA.js +1 -0
- codex/static_root/assets/confirm-dialog-BEVEU-kA.js.br +0 -0
- codex/static_root/assets/confirm-dialog-BEVEU-kA.js.gz +0 -0
- codex/static_root/assets/datetime-column-DhrZEHwa.80e95aef7bdb.js +1 -0
- codex/static_root/assets/datetime-column-DhrZEHwa.80e95aef7bdb.js.br +1 -0
- codex/static_root/assets/datetime-column-DhrZEHwa.80e95aef7bdb.js.gz +0 -0
- codex/static_root/assets/datetime-column-DhrZEHwa.js +1 -0
- codex/static_root/assets/datetime-column-DhrZEHwa.js.br +1 -0
- codex/static_root/assets/datetime-column-DhrZEHwa.js.gz +0 -0
- codex/static_root/assets/filter-BJ7TmFiU.3cefe2d609c7.js +1 -0
- codex/static_root/assets/filter-BJ7TmFiU.3cefe2d609c7.js.br +0 -0
- codex/static_root/assets/filter-BJ7TmFiU.3cefe2d609c7.js.gz +0 -0
- codex/static_root/assets/filter-BJ7TmFiU.js +1 -0
- codex/static_root/assets/filter-BJ7TmFiU.js.br +0 -0
- codex/static_root/assets/filter-BJ7TmFiU.js.gz +0 -0
- codex/static_root/assets/flag-tab-AJj0sesB.2ab1be15d10f.js +1 -0
- codex/static_root/assets/flag-tab-AJj0sesB.2ab1be15d10f.js.br +0 -0
- codex/static_root/assets/flag-tab-AJj0sesB.2ab1be15d10f.js.gz +0 -0
- codex/static_root/assets/flag-tab-AJj0sesB.js +1 -0
- codex/static_root/assets/flag-tab-AJj0sesB.js.br +0 -0
- codex/static_root/assets/flag-tab-AJj0sesB.js.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +1 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css +1 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
- codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css +1 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.br +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.gz +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css +1 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css.br +0 -0
- codex/static_root/assets/group-tab-DkIN1Heu.css.gz +0 -0
- codex/static_root/assets/group-tab-DuxOH4Hn.8ac96cfe7c43.js +1 -0
- codex/static_root/assets/group-tab-DuxOH4Hn.8ac96cfe7c43.js.br +0 -0
- codex/static_root/assets/group-tab-DuxOH4Hn.8ac96cfe7c43.js.gz +0 -0
- codex/static_root/assets/group-tab-DuxOH4Hn.js +1 -0
- codex/static_root/assets/group-tab-DuxOH4Hn.js.br +0 -0
- codex/static_root/assets/group-tab-DuxOH4Hn.js.gz +0 -0
- codex/static_root/assets/http-error-DIeFpbQV.086e1dd09555.js +1 -0
- codex/static_root/assets/http-error-DIeFpbQV.086e1dd09555.js.br +0 -0
- codex/static_root/assets/http-error-DIeFpbQV.086e1dd09555.js.gz +0 -0
- codex/static_root/assets/http-error-DIeFpbQV.js +1 -0
- codex/static_root/assets/http-error-DIeFpbQV.js.br +0 -0
- codex/static_root/assets/http-error-DIeFpbQV.js.gz +0 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css +1 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.br +0 -0
- codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.gz +0 -0
- codex/static_root/assets/index-CL_6g4tJ.css +1 -0
- codex/static_root/assets/index-CL_6g4tJ.css.br +0 -0
- codex/static_root/assets/index-CL_6g4tJ.css.gz +0 -0
- codex/static_root/assets/index-MN9BB_He.201e30aa00ef.js +1 -0
- codex/static_root/assets/index-MN9BB_He.201e30aa00ef.js.br +0 -0
- codex/static_root/assets/index-MN9BB_He.201e30aa00ef.js.gz +0 -0
- codex/static_root/assets/index-MN9BB_He.js +1 -0
- codex/static_root/assets/index-MN9BB_He.js.br +0 -0
- codex/static_root/assets/index-MN9BB_He.js.gz +0 -0
- codex/static_root/assets/{library-tab-Bh7ErAk7.7c44200ee727.css → library-tab-Wew8YOLv.0c1780a6b68e.css} +1 -1
- codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.br +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.gz +0 -0
- codex/static_root/assets/{library-tab-Bh7ErAk7.css → library-tab-Wew8YOLv.css} +1 -1
- codex/static_root/assets/library-tab-Wew8YOLv.css.br +0 -0
- codex/static_root/assets/library-tab-Wew8YOLv.css.gz +0 -0
- codex/static_root/assets/library-tab-jpeUUWzV.70dae13f6dab.js +1 -0
- codex/static_root/assets/library-tab-jpeUUWzV.70dae13f6dab.js.br +0 -0
- codex/static_root/assets/library-tab-jpeUUWzV.70dae13f6dab.js.gz +0 -0
- codex/static_root/assets/library-tab-jpeUUWzV.js +1 -0
- codex/static_root/assets/library-tab-jpeUUWzV.js.br +0 -0
- codex/static_root/assets/library-tab-jpeUUWzV.js.gz +0 -0
- codex/static_root/assets/main-Cs7q8tob.86ef2567544d.js +35 -0
- codex/static_root/assets/main-Cs7q8tob.86ef2567544d.js.br +0 -0
- codex/static_root/assets/main-Cs7q8tob.86ef2567544d.js.gz +0 -0
- codex/static_root/assets/main-Cs7q8tob.js +35 -0
- codex/static_root/assets/main-Cs7q8tob.js.br +0 -0
- codex/static_root/assets/main-Cs7q8tob.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-D_ecZhu-.ff93ca6c55e0.js +1 -0
- codex/static_root/assets/pager-full-pdf-D_ecZhu-.ff93ca6c55e0.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-D_ecZhu-.ff93ca6c55e0.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-D_ecZhu-.js +1 -0
- codex/static_root/assets/pager-full-pdf-D_ecZhu-.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-D_ecZhu-.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-yeU7sPiY.ce0751d6009d.js +1 -0
- codex/static_root/assets/pagination-toolbar-yeU7sPiY.ce0751d6009d.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-yeU7sPiY.ce0751d6009d.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-yeU7sPiY.js +1 -0
- codex/static_root/assets/pagination-toolbar-yeU7sPiY.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-yeU7sPiY.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BcRNpoaW.882f6e967869.js → pdf-doc-Czi9tnhD.4d4b8b7ff5d7.js} +3 -3
- codex/static_root/assets/pdf-doc-Czi9tnhD.4d4b8b7ff5d7.js.br +0 -0
- codex/static_root/assets/pdf-doc-Czi9tnhD.4d4b8b7ff5d7.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-BcRNpoaW.js → pdf-doc-Czi9tnhD.js} +3 -3
- codex/static_root/assets/pdf-doc-Czi9tnhD.js.br +0 -0
- codex/static_root/assets/pdf-doc-Czi9tnhD.js.gz +0 -0
- codex/static_root/assets/reader-DQKMY8MS.d9d3451f2cff.js +2 -0
- codex/static_root/assets/reader-DQKMY8MS.d9d3451f2cff.js.br +0 -0
- codex/static_root/assets/reader-DQKMY8MS.d9d3451f2cff.js.gz +0 -0
- codex/static_root/assets/reader-DQKMY8MS.js +2 -0
- codex/static_root/assets/reader-DQKMY8MS.js.br +0 -0
- codex/static_root/assets/reader-DQKMY8MS.js.gz +0 -0
- codex/static_root/assets/{reader-DveZZr9x.7b21b9f8f628.css → reader-OpKdTZ_B.css} +1 -1
- codex/static_root/assets/reader-OpKdTZ_B.css.br +0 -0
- codex/static_root/assets/reader-OpKdTZ_B.css.gz +0 -0
- codex/static_root/assets/{reader-DveZZr9x.css → reader-OpKdTZ_B.ebd1512b7014.css} +1 -1
- codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.br +0 -0
- codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.gz +0 -0
- codex/static_root/assets/relation-chips-CFH0Xf78.62ded2739a22.js +1 -0
- codex/static_root/assets/relation-chips-CFH0Xf78.62ded2739a22.js.br +0 -0
- codex/static_root/assets/relation-chips-CFH0Xf78.62ded2739a22.js.gz +0 -0
- codex/static_root/assets/relation-chips-CFH0Xf78.js +1 -0
- codex/static_root/assets/relation-chips-CFH0Xf78.js.br +0 -0
- codex/static_root/assets/relation-chips-CFH0Xf78.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DbOztp2R.b6f981108782.js +2 -0
- codex/static_root/assets/settings-drawer-DbOztp2R.b6f981108782.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbOztp2R.b6f981108782.js.gz +0 -0
- codex/static_root/assets/settings-drawer-DbOztp2R.js +2 -0
- codex/static_root/assets/settings-drawer-DbOztp2R.js.br +0 -0
- codex/static_root/assets/settings-drawer-DbOztp2R.js.gz +0 -0
- codex/static_root/assets/{stats-tab-DpfGsVX3.1cb66f2649a9.css → stats-tab-BAYTqQNg.5f715428c394.css} +1 -1
- codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.br +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.gz +0 -0
- codex/static_root/assets/{stats-tab-DpfGsVX3.css → stats-tab-BAYTqQNg.css} +1 -1
- codex/static_root/assets/stats-tab-BAYTqQNg.css.br +0 -0
- codex/static_root/assets/stats-tab-BAYTqQNg.css.gz +0 -0
- codex/static_root/assets/stats-tab-DWktIwEj.42666baa0d93.js +1 -0
- codex/static_root/assets/stats-tab-DWktIwEj.42666baa0d93.js.br +0 -0
- codex/static_root/assets/stats-tab-DWktIwEj.42666baa0d93.js.gz +0 -0
- codex/static_root/assets/stats-tab-DWktIwEj.js +1 -0
- codex/static_root/assets/stats-tab-DWktIwEj.js.br +0 -0
- codex/static_root/assets/stats-tab-DWktIwEj.js.gz +0 -0
- codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css +1 -0
- codex/static_root/assets/{task-tab-1XiWn9xP.06cb8e1b794d.css.br → task-tab--cXi48kg.5dfd6890a1c2.css.br} +1 -1
- codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css.gz +0 -0
- codex/static_root/assets/task-tab--cXi48kg.css +1 -0
- codex/static_root/assets/{task-tab-1XiWn9xP.css.br → task-tab--cXi48kg.css.br} +1 -1
- codex/static_root/assets/task-tab--cXi48kg.css.gz +0 -0
- codex/static_root/assets/task-tab-BtTtKJP6.421692e51e82.js +1 -0
- codex/static_root/assets/task-tab-BtTtKJP6.421692e51e82.js.br +0 -0
- codex/static_root/assets/task-tab-BtTtKJP6.421692e51e82.js.gz +0 -0
- codex/static_root/assets/task-tab-BtTtKJP6.js +1 -0
- codex/static_root/assets/task-tab-BtTtKJP6.js.br +0 -0
- codex/static_root/assets/task-tab-BtTtKJP6.js.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +1 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +4 -0
- codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
- codex/static_root/assets/transition-5LRFTPo5.css +1 -0
- codex/static_root/assets/transition-5LRFTPo5.css.br +4 -0
- codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
- codex/static_root/assets/transition-A__CJ90w.970355913cb9.js +1 -0
- codex/static_root/assets/transition-A__CJ90w.970355913cb9.js.br +0 -0
- codex/static_root/assets/transition-A__CJ90w.970355913cb9.js.gz +0 -0
- codex/static_root/assets/transition-A__CJ90w.js +1 -0
- codex/static_root/assets/transition-A__CJ90w.js.br +0 -0
- codex/static_root/assets/transition-A__CJ90w.js.gz +0 -0
- codex/static_root/assets/unauthorized-FF1nwDKt.b3d6255ddb7f.js +1 -0
- codex/static_root/assets/unauthorized-FF1nwDKt.b3d6255ddb7f.js.br +0 -0
- codex/static_root/assets/unauthorized-FF1nwDKt.b3d6255ddb7f.js.gz +0 -0
- codex/static_root/assets/unauthorized-FF1nwDKt.js +1 -0
- codex/static_root/assets/unauthorized-FF1nwDKt.js.br +0 -0
- codex/static_root/assets/unauthorized-FF1nwDKt.js.gz +0 -0
- codex/static_root/assets/user-tab-RwMqZeSN.d4ed24177855.js +1 -0
- codex/static_root/assets/user-tab-RwMqZeSN.d4ed24177855.js.br +0 -0
- codex/static_root/assets/user-tab-RwMqZeSN.d4ed24177855.js.gz +0 -0
- codex/static_root/assets/user-tab-RwMqZeSN.js +1 -0
- codex/static_root/assets/user-tab-RwMqZeSN.js.br +0 -0
- codex/static_root/assets/user-tab-RwMqZeSN.js.gz +0 -0
- codex/static_root/manifest.8dff5f2fb15e.json +712 -0
- codex/static_root/manifest.8dff5f2fb15e.json.br +0 -0
- codex/static_root/manifest.8dff5f2fb15e.json.gz +0 -0
- codex/static_root/manifest.json +377 -300
- 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 +5 -6
- codex/threads.py +1 -1
- codex/urls/api/auth.py +1 -1
- codex/urls/api/browser.py +1 -1
- codex/views/admin/library.py +18 -13
- codex/views/admin/stats.py +23 -16
- codex/views/auth.py +42 -77
- codex/views/bookmark.py +16 -153
- codex/views/browser/annotate/__init__.py +1 -0
- codex/views/browser/annotate/bookmark.py +112 -0
- codex/views/browser/annotate/card.py +61 -0
- codex/views/browser/annotate/order.py +247 -0
- codex/views/browser/breadcrumbs.py +90 -11
- codex/views/browser/browser.py +44 -110
- codex/views/browser/choices.py +15 -18
- codex/views/browser/cover.py +10 -20
- codex/views/browser/filters/bookmark.py +18 -19
- codex/views/browser/filters/field.py +8 -3
- codex/views/browser/filters/filter.py +69 -0
- codex/views/browser/filters/group.py +9 -6
- codex/views/browser/filters/search/fts.py +5 -19
- codex/views/browser/filters/search/parse.py +54 -24
- codex/views/browser/group_mtime.py +83 -0
- codex/views/browser/metadata/__init__.py +1 -0
- codex/views/browser/metadata/annotate.py +109 -0
- codex/views/browser/metadata/copy_intersections.py +90 -0
- codex/views/browser/metadata/metadata.py +83 -0
- codex/views/browser/metadata/query_intersections.py +114 -0
- codex/views/browser/mtime.py +2 -10
- codex/views/browser/order_by.py +22 -22
- codex/views/browser/page_in_bounds.py +2 -2
- codex/views/browser/paginate.py +9 -15
- codex/views/browser/params.py +64 -0
- codex/views/browser/title.py +1 -6
- codex/views/browser/validate.py +68 -33
- codex/views/download.py +1 -1
- codex/views/mixins.py +27 -15
- codex/views/opds/const.py +2 -3
- codex/views/opds/urls.py +2 -2
- codex/views/opds/util.py +2 -3
- codex/views/opds/v1/entry/links.py +3 -1
- codex/views/opds/v1/facets.py +65 -12
- codex/views/opds/v1/feed.py +5 -64
- codex/views/opds/v1/links.py +5 -4
- codex/views/opds/v2/feed.py +1 -28
- codex/views/opds/v2/links.py +26 -1
- codex/views/opds/v2/publications.py +5 -6
- codex/views/opds/v2/top_links.py +3 -6
- codex/views/public.py +2 -2
- codex/views/reader/arcs.py +0 -3
- codex/views/reader/books.py +57 -41
- codex/views/reader/page.py +10 -4
- codex/views/reader/{init.py → params.py} +37 -31
- codex/views/reader/reader.py +1 -2
- codex/views/session.py +43 -54
- codex/views/timezone.py +54 -0
- codex/views/version.py +1 -1
- {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/METADATA +1 -1
- {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/RECORD +417 -383
- codex/serializers/choices.py +0 -196
- codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js +0 -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-CWiqcZ7a.js +0 -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-Dk3iB62X.bc66d5351baa.js +0 -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-Dk3iB62X.js +0 -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-D1_KU27A.b1d30587a973.js +0 -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-D1_KU27A.js +0 -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-38np3TSA.5a0cb7a9e56d.css +0 -1
- codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.br +0 -0
- codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.gz +0 -0
- codex/static_root/assets/VDialog-38np3TSA.css +0 -1
- codex/static_root/assets/VDialog-38np3TSA.css.br +0 -0
- codex/static_root/assets/VDialog-38np3TSA.css.gz +0 -0
- codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js +0 -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-GbxvFMMN.js +0 -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-Cywv_bEj.a762e63858e5.js +0 -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-Cywv_bEj.js +0 -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-C0etmxpZ.0eb3009a6300.js +0 -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-C0etmxpZ.js +0 -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-Ca2bIOso.6613989e4c45.css +0 -1
- codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.br +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.gz +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.css +0 -1
- codex/static_root/assets/VSelect-Ca2bIOso.css.br +0 -0
- codex/static_root/assets/VSelect-Ca2bIOso.css.gz +0 -0
- codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js +0 -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-txrnRiNJ.js +0 -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-CY6RmcvW.7e8824224f6c.js +0 -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-CY6RmcvW.js +0 -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-Dk_1upHt.2c836070ec75.css +0 -1
- codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.br +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css +0 -1
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css.br +0 -0
- codex/static_root/assets/VSlideGroup-Dk_1upHt.css.gz +0 -0
- codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js +0 -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-DtEQMwY1.js +0 -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-2af995xo.6c5bf36631b2.js +0 -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-2af995xo.js +0 -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-Crc3gdNh.39995ccf4d52.css +0 -1
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css +0 -1
- codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
- codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
- codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js +0 -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-G6CMj7yO.js +0 -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-CzoGd6Ul.721e5da11ad4.js +0 -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-CzoGd6Ul.js +0 -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-CWEjaFr5.7d4523387384.css.br +0 -0
- codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.gz +0 -0
- codex/static_root/assets/admin-CWEjaFr5.css.br +0 -0
- codex/static_root/assets/admin-CWEjaFr5.css.gz +0 -0
- codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js +0 -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-CsPAIQeG.js +0 -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-BAT7GA64.73a064628c19.js +0 -30
- 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-BAT7GA64.js +0 -30
- 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/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css +0 -1
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.gz +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css +0 -1
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.br +0 -0
- codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.gz +0 -0
- codex/static_root/assets/browser-C18l93yc.923860009ef4.js +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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 +0 -1
- 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-BmhedLCx.7c2103d319d4.css +0 -1
- codex/static_root/assets/confirm-dialog-BmhedLCx.css +0 -1
- codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js +0 -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-CbW3hlOp.js +0 -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-uSqKZU37.b157b0743db5.js +0 -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-uSqKZU37.js +0 -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-CBl5KEsB.845a81573c2e.js +0 -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-CBl5KEsB.js +0 -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 +0 -1
- 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 +0 -1
- 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/flag-tab-DBqpLKi7.css +0 -1
- 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 +0 -1
- 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-BOW9kigw.7e5007596367.js +0 -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-BOW9kigw.js +0 -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/group-tab-CBKuS3Iv.7e4746634782.css +0 -1
- codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.br +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.gz +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.css +0 -1
- codex/static_root/assets/group-tab-CBKuS3Iv.css.br +0 -0
- codex/static_root/assets/group-tab-CBKuS3Iv.css.gz +0 -0
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js +0 -1
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +0 -4
- codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
- codex/static_root/assets/http-error-C6yuLApo.js +0 -1
- codex/static_root/assets/http-error-C6yuLApo.js.br +0 -4
- codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
- codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.br +0 -2
- codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.gz +0 -0
- codex/static_root/assets/library-tab-Bh7ErAk7.css.br +0 -2
- codex/static_root/assets/library-tab-Bh7ErAk7.css.gz +0 -0
- codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js +0 -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-D3WOtpE0.js +0 -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 +0 -6
- 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 +0 -6
- 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 +0 -1
- 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 +0 -1
- 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-JH3Kbffr.451171f7ac33.js +0 -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-JH3Kbffr.js +0 -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-BcRNpoaW.882f6e967869.js.br +0 -0
- codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
- 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 +0 -2
- 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 +0 -2
- 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.br +0 -0
- codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -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-3tTlR5QH.71f18bc92adc.js +0 -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-3tTlR5QH.js +0 -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-BdyFhA_r.151f4c3061ed.js +0 -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-BdyFhA_r.js +0 -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 +0 -1
- 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 +0 -1
- 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-DpfGsVX3.1cb66f2649a9.css.br +0 -0
- codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
- 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-1XiWn9xP.06cb8e1b794d.css +0 -1
- codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css.gz +0 -0
- codex/static_root/assets/task-tab-1XiWn9xP.css +0 -1
- codex/static_root/assets/task-tab-1XiWn9xP.css.gz +0 -0
- codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js +0 -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-C8D0qRP_.js +0 -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 +0 -1
- 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 +0 -1
- codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
- codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
- codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js +0 -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-BU31Z92y.js +0 -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 +0 -1
- 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 +0 -1
- codex/static_root/js/choices-admin.json.br +0 -0
- codex/static_root/js/choices-admin.json.gz +0 -0
- codex/static_root/js/choices.2f6b0359e1ef.json +0 -1
- codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
- codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
- codex/static_root/js/choices.json +0 -1
- codex/static_root/js/choices.json.br +0 -0
- codex/static_root/js/choices.json.gz +0 -0
- codex/static_root/manifest.45cb46e19686.json +0 -635
- codex/static_root/manifest.45cb46e19686.json.br +0 -0
- codex/static_root/manifest.45cb46e19686.json.gz +0 -0
- codex/views/browser/annotations.py +0 -339
- codex/views/browser/base.py +0 -139
- codex/views/browser/filters/annotations.py +0 -125
- codex/views/browser/metadata.py +0 -332
- {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/LICENSE +0 -0
- {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/WHEEL +0 -0
- {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Bookmark Tasks."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class BookmarkTask:
|
|
10
|
+
"""Bookmark Base Class."""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class BookmarkUpdateTask(BookmarkTask):
|
|
15
|
+
"""Bookmark a page."""
|
|
16
|
+
|
|
17
|
+
auth_filter: Mapping[str, int | str]
|
|
18
|
+
comic_filter: Mapping[str, Any]
|
|
19
|
+
updates: Mapping[str, Any]
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""Sends notifications to connections, reading from a queue."""
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth.models import User
|
|
4
|
+
from django.db.models.expressions import F
|
|
5
|
+
from django.db.models.functions import Now
|
|
6
|
+
from django.db.models.query import Q
|
|
7
|
+
|
|
8
|
+
from codex.librarian.mp_queue import LIBRARIAN_QUEUE
|
|
9
|
+
from codex.librarian.notifier.tasks import NotifierTask
|
|
10
|
+
from codex.models import Bookmark, Comic
|
|
11
|
+
from codex.views.auth import GroupACLMixin
|
|
12
|
+
from codex.views.mixins import BookmarkSearchMixin
|
|
13
|
+
|
|
14
|
+
VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
|
|
15
|
+
_BOOKMARK_UPDATE_FIELDS = frozenset(
|
|
16
|
+
{
|
|
17
|
+
"page",
|
|
18
|
+
"finished",
|
|
19
|
+
"fit_to",
|
|
20
|
+
"two_pages",
|
|
21
|
+
"reading_direction",
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
_COMIC_ONLY_FIELDS = ("pk", "page_count")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
|
|
28
|
+
"""Update Bookmarks."""
|
|
29
|
+
|
|
30
|
+
# Used by Bookmarkd and view.bookmark.
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def _update_bookmarks_validate_page(bm, updates):
|
|
34
|
+
"""Force bookmark page into valid range."""
|
|
35
|
+
page = updates.get("page")
|
|
36
|
+
if page is None:
|
|
37
|
+
return
|
|
38
|
+
max_page = bm.page_count + 1
|
|
39
|
+
page = max(min(page, max_page), 0)
|
|
40
|
+
if page == max_page:
|
|
41
|
+
# Auto finish on bookmark last page
|
|
42
|
+
bm.finished = True
|
|
43
|
+
updates["page"] = page
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _update_bookmarks_validate_two_pages(bm, updates):
|
|
47
|
+
"""Force vertical view to not use two pages."""
|
|
48
|
+
rd = updates.get("reading_direction")
|
|
49
|
+
if (
|
|
50
|
+
bm.two_pages
|
|
51
|
+
and rd in VERTICAL_READING_DIRECTIONS
|
|
52
|
+
or bm.reading_direction in VERTICAL_READING_DIRECTIONS
|
|
53
|
+
):
|
|
54
|
+
updates["two_pages"] = None
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _notify_library_changed(uid):
|
|
58
|
+
"""Notify one user that their library changed."""
|
|
59
|
+
group = f"user_{uid}"
|
|
60
|
+
task = NotifierTask("LIBRARY_CHANGED", group)
|
|
61
|
+
LIBRARIAN_QUEUE.put(task)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _update_bookmarks(cls, search_kwargs, updates, user) -> int:
|
|
65
|
+
"""Update existing bookmarks."""
|
|
66
|
+
count = 0
|
|
67
|
+
if not updates:
|
|
68
|
+
return count
|
|
69
|
+
group_acl_filter = cls.get_group_acl_filter(Bookmark, user)
|
|
70
|
+
query_filter = group_acl_filter & Q(**search_kwargs)
|
|
71
|
+
existing_bookmarks = Bookmark.objects.filter(query_filter)
|
|
72
|
+
if updates.get("page") is not None:
|
|
73
|
+
existing_bookmarks = existing_bookmarks.annotate(
|
|
74
|
+
page_count=F("comic__page_count")
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
update_fields = set(updates.keys()) & _BOOKMARK_UPDATE_FIELDS
|
|
78
|
+
update_fields.add("updated_at")
|
|
79
|
+
only_fields = (*update_fields, "pk")
|
|
80
|
+
|
|
81
|
+
existing_bookmarks = existing_bookmarks.only(*only_fields)
|
|
82
|
+
update_bookmarks = []
|
|
83
|
+
for bm in existing_bookmarks:
|
|
84
|
+
cls._update_bookmarks_validate_page(bm, updates)
|
|
85
|
+
cls._update_bookmarks_validate_two_pages(bm, updates)
|
|
86
|
+
for key, value in updates.items():
|
|
87
|
+
setattr(bm, key, value)
|
|
88
|
+
bm.updated_at = Now()
|
|
89
|
+
update_bookmarks.append(bm)
|
|
90
|
+
count = len(update_bookmarks)
|
|
91
|
+
if count:
|
|
92
|
+
Bookmark.objects.bulk_update(update_bookmarks, tuple(update_fields))
|
|
93
|
+
return count
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _create_bookmarks_validate_two_pages(updates):
|
|
97
|
+
"""Force vertical view to not use two pages."""
|
|
98
|
+
if (
|
|
99
|
+
updates.get("two_pages")
|
|
100
|
+
and updates.get("reading_direction") in VERTICAL_READING_DIRECTIONS
|
|
101
|
+
):
|
|
102
|
+
updates.pop("two_pages", None)
|
|
103
|
+
|
|
104
|
+
@classmethod
|
|
105
|
+
def _create_bookmarks(cls, comic_filter, search_kwargs, updates, user) -> int:
|
|
106
|
+
"""Create new bookmarks for comics that don't exist yet."""
|
|
107
|
+
count = 0
|
|
108
|
+
cls._create_bookmarks_validate_two_pages(updates)
|
|
109
|
+
if not updates:
|
|
110
|
+
return count
|
|
111
|
+
create_bookmarks = []
|
|
112
|
+
group_acl_filter = cls.get_group_acl_filter(Comic, user)
|
|
113
|
+
exclude = {}
|
|
114
|
+
for key, value in search_kwargs.items():
|
|
115
|
+
exclude["bookmark__" + key] = value
|
|
116
|
+
query_filter = group_acl_filter & Q(**comic_filter) & ~Q(**exclude)
|
|
117
|
+
create_bookmark_comics = Comic.objects.filter(query_filter).only(
|
|
118
|
+
*_COMIC_ONLY_FIELDS
|
|
119
|
+
)
|
|
120
|
+
for comic in create_bookmark_comics:
|
|
121
|
+
defaults = {"comic": comic}
|
|
122
|
+
defaults.update(updates)
|
|
123
|
+
for key, value in search_kwargs.items():
|
|
124
|
+
if not key.startswith("comic"):
|
|
125
|
+
defaults[key] = value
|
|
126
|
+
|
|
127
|
+
bm = Bookmark(**defaults)
|
|
128
|
+
create_bookmarks.append(bm)
|
|
129
|
+
count = len(create_bookmarks)
|
|
130
|
+
if count:
|
|
131
|
+
Bookmark.objects.bulk_create(
|
|
132
|
+
create_bookmarks,
|
|
133
|
+
update_fields=tuple(_BOOKMARK_UPDATE_FIELDS),
|
|
134
|
+
unique_fields=Bookmark._meta.unique_together[0], # type: ignore
|
|
135
|
+
)
|
|
136
|
+
return count
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def update_bookmarks(cls, auth_filter, comic_filter, updates):
|
|
140
|
+
"""Update a user bookmark."""
|
|
141
|
+
search_kwargs = cls.get_bookmark_search_kwargs(auth_filter, comic_filter)
|
|
142
|
+
|
|
143
|
+
user = None
|
|
144
|
+
for key, value in auth_filter.items():
|
|
145
|
+
if key == "user":
|
|
146
|
+
user = User.objects.get(pk=value)
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
count = cls._update_bookmarks(search_kwargs, updates, user)
|
|
150
|
+
count += cls._create_bookmarks(comic_filter, search_kwargs, updates, user)
|
|
151
|
+
if count:
|
|
152
|
+
uid = next(iter(auth_filter.values()))
|
|
153
|
+
cls._notify_library_changed(uid)
|
|
154
|
+
return count
|
codex/librarian/covers/coverd.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Functions for dealing with comic cover thumbnails."""
|
|
2
2
|
|
|
3
|
-
from codex.librarian.covers.
|
|
4
|
-
from codex.librarian.covers.purge import CoverPurgeMixin
|
|
3
|
+
from codex.librarian.covers.purge import CoverPurgeThread
|
|
5
4
|
from codex.librarian.covers.tasks import (
|
|
6
5
|
CoverCreateAllTask,
|
|
7
6
|
CoverRemoveAllTask,
|
|
@@ -11,11 +10,11 @@ from codex.librarian.covers.tasks import (
|
|
|
11
10
|
)
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
class CoverThread(
|
|
13
|
+
class CoverThread(CoverPurgeThread):
|
|
15
14
|
"""Create comic covers in it's own thread."""
|
|
16
15
|
|
|
17
16
|
def process_item(self, item):
|
|
18
|
-
"""Run the
|
|
17
|
+
"""Run the task method."""
|
|
19
18
|
task = item
|
|
20
19
|
if isinstance(task, CoverSaveToCache):
|
|
21
20
|
self.save_cover_to_cache(task.cover_path, task.data)
|
codex/librarian/covers/create.py
CHANGED
|
@@ -13,15 +13,16 @@ from codex.librarian.covers.status import CoverStatusTypes
|
|
|
13
13
|
from codex.librarian.covers.tasks import CoverSaveToCache
|
|
14
14
|
from codex.models import Comic, CustomCover
|
|
15
15
|
from codex.status import Status
|
|
16
|
+
from codex.threads import QueuedThread
|
|
16
17
|
|
|
18
|
+
_COVER_RATIO = 1.5372233400402415 # modal cover ratio
|
|
19
|
+
THUMBNAIL_WIDTH = 165
|
|
20
|
+
THUMBNAIL_HEIGHT = round(THUMBNAIL_WIDTH * _COVER_RATIO)
|
|
21
|
+
_THUMBNAIL_SIZE = (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
|
|
17
22
|
|
|
18
|
-
class CoverCreateMixin(CoverPathMixin):
|
|
19
|
-
"""Create methods for covers."""
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
THUMBNAIL_HEIGHT = round(THUMBNAIL_WIDTH * _COVER_RATIO)
|
|
24
|
-
_THUMBNAIL_SIZE = (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
|
|
24
|
+
class CoverCreateThread(QueuedThread, CoverPathMixin):
|
|
25
|
+
"""Create methods for covers."""
|
|
25
26
|
|
|
26
27
|
@classmethod
|
|
27
28
|
def _create_cover_thumbnail(cls, cover_image_data):
|
|
@@ -30,7 +31,7 @@ class CoverCreateMixin(CoverPathMixin):
|
|
|
30
31
|
with BytesIO(cover_image_data) as image_io:
|
|
31
32
|
with Image.open(image_io) as cover_image:
|
|
32
33
|
cover_image.thumbnail(
|
|
33
|
-
|
|
34
|
+
_THUMBNAIL_SIZE,
|
|
34
35
|
Image.Resampling.LANCZOS, # type: ignore
|
|
35
36
|
reducing_gap=3.0,
|
|
36
37
|
)
|
codex/librarian/covers/path.py
CHANGED
|
@@ -5,10 +5,9 @@ from pathlib import Path
|
|
|
5
5
|
from fnvhash import fnv1a_32
|
|
6
6
|
|
|
7
7
|
from codex.settings.settings import ROOT_CACHE_PATH
|
|
8
|
-
from codex.threads import QueuedThread
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
class CoverPathMixin
|
|
10
|
+
class CoverPathMixin:
|
|
12
11
|
"""Path methods for covers."""
|
|
13
12
|
|
|
14
13
|
COVERS_ROOT = ROOT_CACHE_PATH / "covers"
|
codex/librarian/covers/purge.py
CHANGED
|
@@ -4,7 +4,7 @@ import os
|
|
|
4
4
|
import shutil
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
-
from codex.librarian.covers.
|
|
7
|
+
from codex.librarian.covers.create import CoverCreateThread
|
|
8
8
|
from codex.librarian.covers.status import CoverStatusTypes
|
|
9
9
|
from codex.librarian.notifier.tasks import LIBRARY_CHANGED_TASK
|
|
10
10
|
from codex.models import Comic
|
|
@@ -12,7 +12,7 @@ from codex.models.paths import CustomCover
|
|
|
12
12
|
from codex.status import Status
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class
|
|
15
|
+
class CoverPurgeThread(CoverCreateThread):
|
|
16
16
|
"""Cover Purge methods."""
|
|
17
17
|
|
|
18
18
|
_CLEANUP_STATUS_MAP = (
|
codex/librarian/librariand.py
CHANGED
|
@@ -7,6 +7,8 @@ from typing import NamedTuple
|
|
|
7
7
|
|
|
8
8
|
from caseconverter import snakecase
|
|
9
9
|
|
|
10
|
+
from codex.librarian.bookmark.bookmarkd import BookmarkThread
|
|
11
|
+
from codex.librarian.bookmark.tasks import BookmarkTask
|
|
10
12
|
from codex.librarian.covers.coverd import CoverThread
|
|
11
13
|
from codex.librarian.covers.tasks import CoverTask
|
|
12
14
|
from codex.librarian.cron.crond import CronThread
|
|
@@ -46,6 +48,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
46
48
|
"""Librarian Process."""
|
|
47
49
|
|
|
48
50
|
_THREAD_CLASSES = (
|
|
51
|
+
BookmarkThread,
|
|
49
52
|
NotifierThread,
|
|
50
53
|
DelayedTasksThread,
|
|
51
54
|
CoverThread,
|
|
@@ -88,6 +91,8 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
88
91
|
match task:
|
|
89
92
|
case CoverTask():
|
|
90
93
|
self._threads.cover_thread.queue.put(task)
|
|
94
|
+
case BookmarkTask():
|
|
95
|
+
self._threads.bookmark_thread.queue.put(task)
|
|
91
96
|
case WatchdogEventTask():
|
|
92
97
|
self._threads.watchdog_event_batcher_thread.queue.put(task)
|
|
93
98
|
case ImportTask():
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""A Codex database event emitter for use by the observer."""
|
|
2
2
|
|
|
3
|
+
from logging import DEBUG, WARNING
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from threading import Condition
|
|
5
6
|
|
|
@@ -91,29 +92,30 @@ class DatabasePollingEmitter(EventEmitter, WorkerBaseMixin):
|
|
|
91
92
|
|
|
92
93
|
def _is_watch_path_ok(self, library):
|
|
93
94
|
"""Return a special timeout value if there's a problem with the watch dir."""
|
|
95
|
+
ok = False
|
|
96
|
+
msg = ""
|
|
97
|
+
log_level = WARNING
|
|
94
98
|
if not library.poll:
|
|
95
99
|
# Wait forever. Manual poll only
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if self._watch_path_unmounted.exists():
|
|
100
|
+
ok = None
|
|
101
|
+
elif not self._watch_path.is_dir():
|
|
102
|
+
msg = f"Library {self._watch_path} not found. Not Polling."
|
|
103
|
+
elif self._watch_path_unmounted.exists():
|
|
101
104
|
# Maybe overkill of caution here
|
|
102
|
-
self.
|
|
103
|
-
|
|
104
|
-
)
|
|
105
|
-
return False
|
|
106
|
-
if not tuple(self._watch_path.iterdir()):
|
|
105
|
+
msg = f"Library {self._watch_path} looks like an unmounted docker volume. Not polling."
|
|
106
|
+
elif not tuple(self._watch_path.iterdir()):
|
|
107
107
|
# Maybe overkill of caution here too
|
|
108
|
-
self.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
108
|
+
msg = f"{self._watch_path} is empty. Suspect it may be unmounted. Not polling."
|
|
109
|
+
elif library.update_in_progress:
|
|
110
|
+
msg = f"Library {library.path} update in progress. Not polling."
|
|
111
|
+
log_level = DEBUG
|
|
112
|
+
else:
|
|
113
|
+
ok = True
|
|
114
|
+
|
|
115
|
+
if msg:
|
|
116
|
+
self.log.log(log_level, msg)
|
|
117
|
+
|
|
118
|
+
return ok
|
|
117
119
|
|
|
118
120
|
@property
|
|
119
121
|
def timeout(self) -> int | None: # type: ignore
|
|
@@ -20,7 +20,6 @@ from codex.threads import AggregateMessageQueuedThread
|
|
|
20
20
|
class WatchdogEventBatcherThread(AggregateMessageQueuedThread):
|
|
21
21
|
"""Batch watchdog events into bulk database tasks."""
|
|
22
22
|
|
|
23
|
-
CLS_SUFFIX = -len("Event")
|
|
24
23
|
DBDIFF_TASK_PARAMS = MappingProxyType(
|
|
25
24
|
{
|
|
26
25
|
"library_id": None,
|
codex/models/admin.py
CHANGED
codex/models/functions.py
CHANGED
|
@@ -36,12 +36,13 @@ class FTS5Match(Lookup):
|
|
|
36
36
|
|
|
37
37
|
def as_sql(self, compiler, connection):
|
|
38
38
|
"""Generate MATCH sql."""
|
|
39
|
-
lhs, lhs_params = self.process_lhs(compiler, connection)
|
|
39
|
+
# lhs, lhs_params = self.process_lhs(compiler, connection)
|
|
40
|
+
# fts_table = lhs.split(".")[0]
|
|
40
41
|
rhs, rhs_params = self.process_rhs(compiler, connection)
|
|
41
42
|
# MATCH works on the table itself not the one_to_one rel.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
# Force the table name without substitutions by the optimizer
|
|
44
|
+
sql = "codex_comicfts MATCH " + rhs
|
|
45
|
+
params = rhs_params
|
|
45
46
|
return sql, params
|
|
46
47
|
|
|
47
48
|
|
|
@@ -62,7 +63,7 @@ class Like(Lookup):
|
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
class ComicFTSRank(Func):
|
|
65
|
-
"""Sqlite3 FTS5 inverse rank
|
|
66
|
+
"""Sqlite3 FTS5 inverse rank function."""
|
|
66
67
|
|
|
67
68
|
function = "rank"
|
|
68
69
|
template = '("codex_comicfts"."rank" * -1)'
|
codex/models/query.py
CHANGED
|
@@ -86,6 +86,12 @@ class GroupByQuerySet(QuerySet):
|
|
|
86
86
|
obj.query.set_force_group_by(fields, model=model)
|
|
87
87
|
return obj
|
|
88
88
|
|
|
89
|
+
def demote_joins(self, tables):
|
|
90
|
+
"""Force INNER JOINS."""
|
|
91
|
+
obj = self._chain() # type: ignore
|
|
92
|
+
obj.query.demote_joins(tables)
|
|
93
|
+
return obj
|
|
94
|
+
|
|
89
95
|
|
|
90
96
|
class GroupByManager(Manager.from_queryset(GroupByQuerySet)):
|
|
91
97
|
"""Use GroupBy QuerySet."""
|
codex/run.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""The main runnable for codex. Sets up codex and runs hypercorn."""
|
|
3
3
|
|
|
4
4
|
import asyncio
|
|
5
|
+
import logging
|
|
5
6
|
from os import execv
|
|
6
7
|
|
|
7
8
|
from django.db import connection
|
|
@@ -24,14 +25,11 @@ LOG = get_logger(__name__)
|
|
|
24
25
|
|
|
25
26
|
def codex_startup():
|
|
26
27
|
"""Start up codex."""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
LOG.info(f"Running Codex v{VERSION}")
|
|
30
|
-
codex_init()
|
|
31
|
-
return loggerd
|
|
28
|
+
LOG.info(f"Starting Codex v{VERSION}")
|
|
29
|
+
return codex_init()
|
|
32
30
|
|
|
33
31
|
|
|
34
|
-
def
|
|
32
|
+
def _database_checkpoint():
|
|
35
33
|
"""Write wal to disk and truncate it."""
|
|
36
34
|
with connection.cursor() as cursor:
|
|
37
35
|
cursor.execute("PRAGMA wal_checkpoint(TRUNCATE);")
|
|
@@ -48,8 +46,9 @@ def restart():
|
|
|
48
46
|
|
|
49
47
|
def codex_shutdown(loggerd):
|
|
50
48
|
"""Shutdown for codex."""
|
|
51
|
-
|
|
49
|
+
_database_checkpoint()
|
|
52
50
|
LOG.info("Goodbye.")
|
|
51
|
+
logging.shutdown()
|
|
53
52
|
loggerd.stop()
|
|
54
53
|
if RESTART_EVENT.is_set():
|
|
55
54
|
restart()
|
|
@@ -57,6 +56,7 @@ def codex_shutdown(loggerd):
|
|
|
57
56
|
|
|
58
57
|
def run():
|
|
59
58
|
"""Run Codex."""
|
|
59
|
+
LOG.info(f"Running Codex v{VERSION}")
|
|
60
60
|
librarian = LibrarianDaemon(LIBRARIAN_QUEUE, LOG_QUEUE, BROADCAST_QUEUE)
|
|
61
61
|
librarian.start()
|
|
62
62
|
asyncio.run(
|
|
@@ -71,8 +71,10 @@ def run():
|
|
|
71
71
|
|
|
72
72
|
def main():
|
|
73
73
|
"""Set up and run Codex."""
|
|
74
|
-
loggerd =
|
|
75
|
-
|
|
74
|
+
loggerd = CodexLogQueueListener(LOG_QUEUE)
|
|
75
|
+
loggerd.start()
|
|
76
|
+
if codex_startup():
|
|
77
|
+
run()
|
|
76
78
|
codex_shutdown(loggerd)
|
|
77
79
|
|
|
78
80
|
|
codex/serializers/admin/tasks.py
CHANGED
|
@@ -6,11 +6,11 @@ from rest_framework.serializers import (
|
|
|
6
6
|
Serializer,
|
|
7
7
|
)
|
|
8
8
|
|
|
9
|
-
from codex.
|
|
9
|
+
from codex.choices import ADMIN_TASKS
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class AdminLibrarianTaskSerializer(Serializer):
|
|
13
13
|
"""Get tasks from front end."""
|
|
14
14
|
|
|
15
|
-
task = ChoiceField(choices=
|
|
15
|
+
task = ChoiceField(choices=ADMIN_TASKS)
|
|
16
16
|
library_id = IntegerField(required=False)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"""Serializer mixins."""
|
|
2
2
|
|
|
3
3
|
from datetime import datetime, timezone
|
|
4
|
+
from itertools import chain
|
|
4
5
|
|
|
5
6
|
from rest_framework.serializers import (
|
|
6
7
|
BooleanField,
|
|
7
8
|
CharField,
|
|
8
|
-
DateTimeField,
|
|
9
9
|
DecimalField,
|
|
10
10
|
IntegerField,
|
|
11
11
|
ListField,
|
|
@@ -14,7 +14,8 @@ from rest_framework.serializers import (
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
from codex.logger.logging import get_logger
|
|
17
|
-
from codex.
|
|
17
|
+
from codex.util import max_none
|
|
18
|
+
from codex.views.const import EPOCH_START
|
|
18
19
|
|
|
19
20
|
LOG = get_logger(__name__)
|
|
20
21
|
|
|
@@ -31,16 +32,15 @@ class BrowserAggregateSerializerMixin(Serializer):
|
|
|
31
32
|
|
|
32
33
|
# Bookmark annotations
|
|
33
34
|
page = IntegerField(read_only=True)
|
|
34
|
-
bookmark_updated_at = DateTimeField(read_only=True, allow_null=True)
|
|
35
35
|
finished = BooleanField(read_only=True)
|
|
36
36
|
progress = DecimalField(
|
|
37
37
|
max_digits=5, decimal_places=2, read_only=True, coerce_to_string=False
|
|
38
38
|
)
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
for dt_str in
|
|
40
|
+
@staticmethod
|
|
41
|
+
def _get_max_updated_at(mtime, updated_ats) -> datetime:
|
|
42
|
+
"""Because orm won't aggregate aggregates."""
|
|
43
|
+
for dt_str in updated_ats:
|
|
44
44
|
if not dt_str:
|
|
45
45
|
continue
|
|
46
46
|
try:
|
|
@@ -52,13 +52,17 @@ class BrowserAggregateSerializerMixin(Serializer):
|
|
|
52
52
|
f"computing group mtime: {dt_str} is not a valid datetime string."
|
|
53
53
|
)
|
|
54
54
|
continue
|
|
55
|
-
|
|
56
55
|
mtime = max(dt, mtime)
|
|
56
|
+
return mtime
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
def get_mtime(self, obj) -> int:
|
|
59
|
+
"""Compute mtime from json array aggregates."""
|
|
60
|
+
updated_ats = (
|
|
61
|
+
obj.updated_ats
|
|
62
|
+
if obj.bmua_is_max
|
|
63
|
+
else chain(obj.updated_ats, obj.bookmark_updated_ats)
|
|
64
|
+
)
|
|
65
|
+
mtime = self._get_max_updated_at(EPOCH_START, updated_ats)
|
|
66
|
+
if obj.bmua_is_max:
|
|
67
|
+
mtime: datetime = max_none(mtime, obj.bookmark_updated_at, EPOCH_START) # type: ignore
|
|
64
68
|
return int(mtime.timestamp() * 1000)
|
|
@@ -8,13 +8,11 @@ from rest_framework.serializers import (
|
|
|
8
8
|
Serializer,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
|
+
from codex.choices import BROWSER_ORDER_BY_CHOICES
|
|
11
12
|
from codex.serializers.browser.filters import BrowserSettingsFilterSerializer
|
|
12
|
-
from codex.serializers.choices import CHOICES, VUETIFY_NULL_CODE
|
|
13
13
|
from codex.serializers.fields import BreadcrumbsField, TimestampField, TopGroupField
|
|
14
14
|
from codex.serializers.route import SimpleRouteSerializer
|
|
15
15
|
|
|
16
|
-
VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
|
|
17
|
-
|
|
18
16
|
|
|
19
17
|
class BrowserSettingsShowGroupFlagsSerializer(Serializer):
|
|
20
18
|
"""Show Group Flags."""
|
|
@@ -37,7 +35,9 @@ class BrowserCoverInputSerializerBase(BrowserFilterChoicesInputSerilalizer):
|
|
|
37
35
|
|
|
38
36
|
custom_covers = BooleanField(required=False)
|
|
39
37
|
dynamic_covers = BooleanField(required=False)
|
|
40
|
-
order_by = ChoiceField(
|
|
38
|
+
order_by = ChoiceField(
|
|
39
|
+
choices=tuple(BROWSER_ORDER_BY_CHOICES.keys()), required=False
|
|
40
|
+
)
|
|
41
41
|
order_reverse = BooleanField(required=False)
|
|
42
42
|
show = BrowserSettingsShowGroupFlagsSerializer(required=False)
|
|
43
43
|
|
codex/serializers/fields.py
CHANGED
|
@@ -17,11 +17,15 @@ from rest_framework.serializers import (
|
|
|
17
17
|
MultipleChoiceField,
|
|
18
18
|
)
|
|
19
19
|
|
|
20
|
+
from codex.choices import (
|
|
21
|
+
BROWSER_BOOKMARK_FILTER_CHOICES,
|
|
22
|
+
BROWSER_TOP_GROUP_CHOICES,
|
|
23
|
+
DUMMY_NULL_NAME,
|
|
24
|
+
VUETIFY_NULL_CODE,
|
|
25
|
+
)
|
|
20
26
|
from codex.logger.logging import get_logger
|
|
21
|
-
from codex.serializers.choices import CHOICES, DUMMY_NULL_NAME, VUETIFY_NULL_CODE
|
|
22
27
|
from codex.serializers.route import RouteSerializer
|
|
23
28
|
|
|
24
|
-
VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
|
|
25
29
|
LOG = get_logger(__name__)
|
|
26
30
|
|
|
27
31
|
|
|
@@ -69,7 +73,7 @@ class VuetifyIntegerField(VuetifyNullCodeFieldMixin, IntegerField): # type: ign
|
|
|
69
73
|
class VuetifyCharField(VuetifyNullCodeFieldMixin, CharField): # type: ignore
|
|
70
74
|
"""Char Field with null code conversion."""
|
|
71
75
|
|
|
72
|
-
NULL_CODE =
|
|
76
|
+
NULL_CODE = str(VUETIFY_NULL_CODE)
|
|
73
77
|
|
|
74
78
|
|
|
75
79
|
class VuetifyBooleanField(VuetifyNullCodeFieldMixin, BooleanField): # type: ignore
|
|
@@ -82,7 +86,7 @@ class BookmarkFilterField(ChoiceField):
|
|
|
82
86
|
def __init__(self, *args, **kwargs):
|
|
83
87
|
"""Use bookmark filter choices."""
|
|
84
88
|
super().__init__(
|
|
85
|
-
*args, choices=tuple(
|
|
89
|
+
*args, choices=tuple(BROWSER_BOOKMARK_FILTER_CHOICES.keys()), **kwargs
|
|
86
90
|
)
|
|
87
91
|
|
|
88
92
|
|
|
@@ -105,7 +109,7 @@ class BreadcrumbsField(ListField):
|
|
|
105
109
|
class TopGroupField(ChoiceField):
|
|
106
110
|
"""Valid Top Groups Only."""
|
|
107
111
|
|
|
108
|
-
class_choices = tuple(
|
|
112
|
+
class_choices = tuple(BROWSER_TOP_GROUP_CHOICES.keys())
|
|
109
113
|
|
|
110
114
|
def __init__(self, *args, **kwargs):
|
|
111
115
|
"""Initialize with choices."""
|