codex 1.7.3a2__py3-none-any.whl → 1.7.4__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/asgi.py +2 -1
- codex/choices.py +12 -12
- codex/choices_to_json.py +8 -8
- codex/db.py +2 -2
- codex/exceptions.py +6 -7
- codex/librarian/bookmark/bookmarkd.py +8 -4
- codex/librarian/bookmark/update.py +82 -48
- codex/librarian/covers/create.py +10 -8
- codex/librarian/covers/path.py +6 -5
- codex/librarian/covers/purge.py +2 -2
- codex/librarian/importer/aggregate.py +2 -2
- codex/librarian/importer/cache.py +1 -4
- codex/librarian/importer/create_comics.py +2 -2
- codex/librarian/importer/create_covers.py +1 -1
- codex/librarian/importer/create_fks.py +4 -3
- codex/librarian/importer/deleted.py +3 -3
- codex/librarian/importer/extract.py +56 -37
- codex/librarian/importer/failed_imports.py +1 -1
- codex/librarian/importer/importer.py +3 -1
- codex/librarian/importer/importerd.py +3 -3
- codex/librarian/importer/link_comics.py +2 -1
- codex/librarian/importer/moved.py +2 -7
- codex/librarian/importer/query_fks.py +1 -4
- codex/librarian/importer/tasks.py +1 -1
- codex/librarian/janitor/integrity.py +27 -20
- codex/librarian/janitor/janitor.py +1 -1
- codex/librarian/janitor/latest_version.py +1 -1
- codex/librarian/janitor/update.py +2 -2
- codex/librarian/janitor/vacuum.py +1 -1
- codex/librarian/librariand.py +3 -2
- codex/librarian/notifier/notifierd.py +2 -1
- codex/librarian/search/optimize.py +1 -1
- codex/librarian/search/remove.py +1 -1
- codex/librarian/search/update.py +4 -4
- codex/librarian/telemeter/stats.py +7 -6
- codex/librarian/telemeter/telemeter.py +0 -1
- codex/librarian/watchdog/db_snapshot.py +3 -3
- codex/librarian/watchdog/dir_snapshot_diff.py +3 -4
- codex/librarian/watchdog/emitter.py +4 -4
- codex/librarian/watchdog/event_batcherd.py +2 -1
- codex/librarian/watchdog/events.py +1 -2
- codex/librarian/watchdog/observers.py +15 -10
- codex/logger/logging.py +1 -1
- codex/memory.py +3 -2
- codex/migrations/0001_init.py +1 -2
- codex/migrations/0014_pdf_issue_suffix_remove_cover_image_sort_name.py +1 -2
- codex/migrations/0018_rename_userbookmark_bookmark.py +1 -2
- codex/migrations/0028_telemeter.py +1 -5
- codex/models/base.py +3 -1
- codex/models/bookmark.py +4 -7
- codex/models/comic.py +2 -2
- codex/models/functions.py +12 -5
- codex/models/groups.py +1 -1
- codex/models/library.py +1 -1
- codex/models/paths.py +4 -4
- codex/models/query.py +8 -4
- codex/models/util.py +1 -1
- codex/permissions.py +1 -1
- codex/run.py +1 -1
- codex/serializers/admin/libraries.py +1 -3
- codex/serializers/admin/stats.py +4 -4
- codex/serializers/auth.py +4 -2
- codex/serializers/browser/choices.py +87 -9
- codex/serializers/browser/filters.py +9 -37
- codex/serializers/browser/metadata.py +2 -1
- codex/serializers/browser/mixins.py +7 -3
- codex/serializers/browser/mtime.py +3 -5
- codex/serializers/browser/page.py +5 -4
- codex/serializers/browser/settings.py +7 -5
- codex/serializers/fields/__init__.py +37 -0
- codex/serializers/fields/auth.py +45 -0
- codex/serializers/fields/browser.py +85 -0
- codex/serializers/fields/group.py +13 -0
- codex/serializers/fields/reader.py +22 -0
- codex/serializers/fields/sanitized.py +13 -0
- codex/serializers/fields/session.py +15 -0
- codex/serializers/fields/stats.py +36 -0
- codex/serializers/fields/vuetify.py +62 -0
- codex/serializers/models/base.py +1 -1
- codex/serializers/models/comic.py +2 -2
- codex/serializers/models/groups.py +0 -2
- codex/serializers/models/named.py +1 -1
- codex/serializers/models/pycountry.py +1 -1
- codex/serializers/opds/authentication.py +4 -2
- codex/serializers/opds/urls.py +2 -1
- codex/serializers/opds/v1.py +4 -3
- codex/serializers/opds/v2/feed.py +2 -1
- codex/serializers/opds/v2/links.py +23 -21
- codex/serializers/opds/v2/metadata.py +4 -8
- codex/serializers/opds/v2/progression.py +5 -3
- codex/serializers/opds/v2/publication.py +16 -18
- codex/serializers/opds/v2/unused.py +15 -8
- codex/serializers/reader.py +16 -14
- codex/serializers/redirect.py +2 -1
- codex/serializers/route.py +6 -4
- codex/serializers/settings.py +5 -3
- codex/serializers/versions.py +2 -1
- codex/settings/settings.py +4 -3
- codex/signals/django_signals.py +1 -1
- codex/static_root/assets/{VCheckbox-FaT6MGfu.f6732060f734.js → VCheckbox-CtTi6T7d.87331fa7e77f.js} +1 -1
- codex/static_root/assets/VCheckbox-CtTi6T7d.87331fa7e77f.js.br +0 -0
- codex/static_root/assets/VCheckbox-CtTi6T7d.87331fa7e77f.js.gz +0 -0
- codex/static_root/assets/{VCheckbox-FaT6MGfu.js → VCheckbox-CtTi6T7d.js} +1 -1
- codex/static_root/assets/VCheckbox-CtTi6T7d.js.br +0 -0
- codex/static_root/assets/VCheckbox-CtTi6T7d.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CZ_P-qUM.847452d574cf.js → VCheckboxBtn-DQafuWTH.dada5e0634ef.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-DQafuWTH.dada5e0634ef.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DQafuWTH.dada5e0634ef.js.gz +0 -0
- codex/static_root/assets/{VCheckboxBtn-CZ_P-qUM.js → VCheckboxBtn-DQafuWTH.js} +1 -1
- codex/static_root/assets/VCheckboxBtn-DQafuWTH.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-DQafuWTH.js.gz +0 -0
- codex/static_root/assets/{VCombobox-C8QLNlSl.6f431e19a453.js → VCombobox-C6TX3Dhp.e619423a6463.js} +1 -1
- codex/static_root/assets/VCombobox-C6TX3Dhp.e619423a6463.js.br +0 -0
- codex/static_root/assets/VCombobox-C6TX3Dhp.e619423a6463.js.gz +0 -0
- codex/static_root/assets/{VCombobox-C8QLNlSl.js → VCombobox-C6TX3Dhp.js} +1 -1
- codex/static_root/assets/VCombobox-C6TX3Dhp.js.br +0 -0
- codex/static_root/assets/VCombobox-C6TX3Dhp.js.gz +0 -0
- codex/static_root/assets/{VDialog-RVLeW7je.c0c7eef71eec.js → VDialog-CknWq2kd.b2d301ec6ac5.js} +1 -1
- codex/static_root/assets/VDialog-CknWq2kd.b2d301ec6ac5.js.br +0 -0
- codex/static_root/assets/VDialog-CknWq2kd.b2d301ec6ac5.js.gz +0 -0
- codex/static_root/assets/{VDialog-RVLeW7je.js → VDialog-CknWq2kd.js} +1 -1
- codex/static_root/assets/VDialog-CknWq2kd.js.br +0 -0
- codex/static_root/assets/VDialog-CknWq2kd.js.gz +0 -0
- codex/static_root/assets/{VDivider-D1Ot4vR2.9dbee744fb3c.js → VDivider-F4DUgr74.62bc92cab97d.js} +1 -1
- codex/static_root/assets/VDivider-F4DUgr74.62bc92cab97d.js.br +0 -0
- codex/static_root/assets/VDivider-F4DUgr74.62bc92cab97d.js.gz +0 -0
- codex/static_root/assets/{VDivider-D1Ot4vR2.js → VDivider-F4DUgr74.js} +1 -1
- codex/static_root/assets/VDivider-F4DUgr74.js.br +0 -0
- codex/static_root/assets/VDivider-F4DUgr74.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DQilGHZk.43f06d0a45e0.js → VExpansionPanels-CprYUTGT.39b8a50cde43.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CprYUTGT.39b8a50cde43.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CprYUTGT.39b8a50cde43.js.gz +0 -0
- codex/static_root/assets/{VExpansionPanels-DQilGHZk.js → VExpansionPanels-CprYUTGT.js} +1 -1
- codex/static_root/assets/VExpansionPanels-CprYUTGT.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-CprYUTGT.js.gz +0 -0
- codex/static_root/assets/{VForm-DNmY-qFJ.81f3a5ff7ec3.js → VForm-DnWAUsrl.4ab0786d658e.js} +1 -1
- codex/static_root/assets/VForm-DnWAUsrl.4ab0786d658e.js.br +0 -0
- codex/static_root/assets/VForm-DnWAUsrl.4ab0786d658e.js.gz +0 -0
- codex/static_root/assets/{VForm-DNmY-qFJ.js → VForm-DnWAUsrl.js} +1 -1
- codex/static_root/assets/VForm-DnWAUsrl.js.br +0 -0
- codex/static_root/assets/VForm-DnWAUsrl.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-DoayJpcS.fe5e0e74ffee.js → VRadioGroup-D71Rszum.befc87b0c476.js} +1 -1
- codex/static_root/assets/VRadioGroup-D71Rszum.befc87b0c476.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D71Rszum.befc87b0c476.js.gz +0 -0
- codex/static_root/assets/{VRadioGroup-DoayJpcS.js → VRadioGroup-D71Rszum.js} +1 -1
- codex/static_root/assets/VRadioGroup-D71Rszum.js.br +0 -0
- codex/static_root/assets/VRadioGroup-D71Rszum.js.gz +0 -0
- codex/static_root/assets/{VSelect-BSLmHX_P.9ab865ec0fad.js → VSelect-CbPDrHNj.4809fbbf00f0.js} +1 -1
- codex/static_root/assets/VSelect-CbPDrHNj.4809fbbf00f0.js.br +0 -0
- codex/static_root/assets/VSelect-CbPDrHNj.4809fbbf00f0.js.gz +0 -0
- codex/static_root/assets/{VSelect-BSLmHX_P.js → VSelect-CbPDrHNj.js} +1 -1
- codex/static_root/assets/VSelect-CbPDrHNj.js.br +0 -0
- codex/static_root/assets/VSelect-CbPDrHNj.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-TaKCeZgb.ad6c96efe57c.js → VSelectionControl-It389cbo.ac75aee480a0.js} +1 -1
- codex/static_root/assets/VSelectionControl-It389cbo.ac75aee480a0.js.br +0 -0
- codex/static_root/assets/VSelectionControl-It389cbo.ac75aee480a0.js.gz +0 -0
- codex/static_root/assets/{VSelectionControl-TaKCeZgb.js → VSelectionControl-It389cbo.js} +1 -1
- codex/static_root/assets/VSelectionControl-It389cbo.js.br +0 -0
- codex/static_root/assets/VSelectionControl-It389cbo.js.gz +0 -0
- codex/static_root/assets/{VTable-BfcOiEpa.77ef3973bb54.js → VTable-D9e3lKk6.7cd0760008c7.js} +1 -1
- codex/static_root/assets/VTable-D9e3lKk6.7cd0760008c7.js.br +0 -0
- codex/static_root/assets/VTable-D9e3lKk6.7cd0760008c7.js.gz +0 -0
- codex/static_root/assets/{VTable-BfcOiEpa.js → VTable-D9e3lKk6.js} +1 -1
- codex/static_root/assets/VTable-D9e3lKk6.js.br +0 -0
- codex/static_root/assets/VTable-D9e3lKk6.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-D8lWcbQY.e4c75ad78718.js → VWindowItem-B4VZ37cq.e45c5018cc73.js} +1 -1
- codex/static_root/assets/VWindowItem-B4VZ37cq.e45c5018cc73.js.br +0 -0
- codex/static_root/assets/VWindowItem-B4VZ37cq.e45c5018cc73.js.gz +0 -0
- codex/static_root/assets/{VWindowItem-D8lWcbQY.js → VWindowItem-B4VZ37cq.js} +1 -1
- codex/static_root/assets/VWindowItem-B4VZ37cq.js.br +0 -0
- codex/static_root/assets/VWindowItem-B4VZ37cq.js.gz +0 -0
- codex/static_root/assets/{admin-CBLHOrM9.8c3c2268bc96.js → admin-Bkd8zuSF.2a29386f3488.js} +1 -1
- codex/static_root/assets/admin-Bkd8zuSF.2a29386f3488.js.br +0 -0
- codex/static_root/assets/admin-Bkd8zuSF.2a29386f3488.js.gz +0 -0
- codex/static_root/assets/{admin-CBLHOrM9.js → admin-Bkd8zuSF.js} +1 -1
- codex/static_root/assets/admin-Bkd8zuSF.js.br +0 -0
- codex/static_root/assets/admin-Bkd8zuSF.js.gz +0 -0
- codex/static_root/assets/{admin-80eqCqtz.d6deb9edb8cb.js → admin-CFTKGwmx.18915c5df4d3.js} +1 -1
- codex/static_root/assets/admin-CFTKGwmx.18915c5df4d3.js.br +0 -0
- codex/static_root/assets/admin-CFTKGwmx.18915c5df4d3.js.gz +0 -0
- codex/static_root/assets/{admin-80eqCqtz.js → admin-CFTKGwmx.js} +1 -1
- codex/static_root/assets/admin-CFTKGwmx.js.br +0 -0
- codex/static_root/assets/admin-CFTKGwmx.js.gz +0 -0
- codex/static_root/assets/{admin-menu-_aAGknKO.0c08c9bd2e7d.js → admin-menu-CxZqtPES.7b3a06ce4b92.js} +1 -1
- codex/static_root/assets/admin-menu-CxZqtPES.7b3a06ce4b92.js.br +0 -0
- codex/static_root/assets/admin-menu-CxZqtPES.7b3a06ce4b92.js.gz +0 -0
- codex/static_root/assets/{admin-menu-_aAGknKO.js → admin-menu-CxZqtPES.js} +1 -1
- codex/static_root/assets/admin-menu-CxZqtPES.js.br +0 -0
- codex/static_root/assets/admin-menu-CxZqtPES.js.gz +0 -0
- codex/static_root/assets/{admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js → admin-settings-button-progress-D2TfW_5T.8a9d411ee79d.js} +1 -1
- codex/static_root/assets/admin-settings-button-progress-D2TfW_5T.8a9d411ee79d.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-D2TfW_5T.8a9d411ee79d.js.gz +0 -0
- codex/static_root/assets/{admin-settings-button-progress-BqRFyhjf.js → admin-settings-button-progress-D2TfW_5T.js} +1 -1
- codex/static_root/assets/admin-settings-button-progress-D2TfW_5T.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-D2TfW_5T.js.gz +0 -0
- codex/static_root/assets/browser-Br2DgAdi.433cca495d61.js +1 -0
- codex/static_root/assets/browser-Br2DgAdi.433cca495d61.js.br +0 -0
- codex/static_root/assets/browser-Br2DgAdi.433cca495d61.js.gz +0 -0
- codex/static_root/assets/browser-Br2DgAdi.js +1 -0
- codex/static_root/assets/browser-Br2DgAdi.js.br +0 -0
- codex/static_root/assets/browser-Br2DgAdi.js.gz +0 -0
- codex/static_root/assets/{browser-chTZ1CM4.521193ab0710.css → browser-DNzQvgkY.aea8b7d7ca40.css} +1 -1
- codex/static_root/assets/browser-DNzQvgkY.aea8b7d7ca40.css.br +0 -0
- codex/static_root/assets/browser-DNzQvgkY.aea8b7d7ca40.css.gz +0 -0
- codex/static_root/assets/{browser-chTZ1CM4.css → browser-DNzQvgkY.css} +1 -1
- codex/static_root/assets/browser-DNzQvgkY.css.br +0 -0
- codex/static_root/assets/browser-DNzQvgkY.css.gz +0 -0
- codex/static_root/assets/{change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js → change-password-dialog-CSylx5Qb.319466c891c9.js} +1 -1
- codex/static_root/assets/change-password-dialog-CSylx5Qb.319466c891c9.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CSylx5Qb.319466c891c9.js.gz +0 -0
- codex/static_root/assets/{change-password-dialog-1ZVP_Q2w.js → change-password-dialog-CSylx5Qb.js} +1 -1
- codex/static_root/assets/change-password-dialog-CSylx5Qb.js.br +0 -0
- codex/static_root/assets/change-password-dialog-CSylx5Qb.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DqCey9Iq.e315d7f8f752.js → confirm-dialog-Bx2R2vgR.d42b6449c495.js} +1 -1
- codex/static_root/assets/confirm-dialog-Bx2R2vgR.d42b6449c495.js.br +0 -0
- codex/static_root/assets/confirm-dialog-Bx2R2vgR.d42b6449c495.js.gz +0 -0
- codex/static_root/assets/{confirm-dialog-DqCey9Iq.js → confirm-dialog-Bx2R2vgR.js} +1 -1
- codex/static_root/assets/confirm-dialog-Bx2R2vgR.js.br +0 -0
- codex/static_root/assets/confirm-dialog-Bx2R2vgR.js.gz +0 -0
- codex/static_root/assets/{datetime-column-CBA8bYeO.17a31f189d66.js → datetime-column-sOrWMDcU.9ad225af18db.js} +1 -1
- codex/static_root/assets/datetime-column-sOrWMDcU.9ad225af18db.js.br +2 -0
- codex/static_root/assets/datetime-column-sOrWMDcU.9ad225af18db.js.gz +0 -0
- codex/static_root/assets/{datetime-column-CBA8bYeO.js → datetime-column-sOrWMDcU.js} +1 -1
- codex/static_root/assets/datetime-column-sOrWMDcU.js.br +2 -0
- codex/static_root/assets/datetime-column-sOrWMDcU.js.gz +0 -0
- codex/static_root/assets/{filter-mnO3lLPo.659442401b16.js → filter-SgjvoAvL.094069d6978d.js} +1 -1
- codex/static_root/assets/filter-SgjvoAvL.094069d6978d.js.br +0 -0
- codex/static_root/assets/filter-SgjvoAvL.094069d6978d.js.gz +0 -0
- codex/static_root/assets/{filter-mnO3lLPo.js → filter-SgjvoAvL.js} +1 -1
- codex/static_root/assets/filter-SgjvoAvL.js.br +0 -0
- codex/static_root/assets/filter-SgjvoAvL.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Bc677pNb.1b8a2f7c0dc3.js → flag-tab-B_0tGWPg.e46d1782b327.js} +1 -1
- codex/static_root/assets/flag-tab-B_0tGWPg.e46d1782b327.js.br +0 -0
- codex/static_root/assets/flag-tab-B_0tGWPg.e46d1782b327.js.gz +0 -0
- codex/static_root/assets/{flag-tab-Bc677pNb.js → flag-tab-B_0tGWPg.js} +1 -1
- codex/static_root/assets/flag-tab-B_0tGWPg.js.br +0 -0
- codex/static_root/assets/flag-tab-B_0tGWPg.js.gz +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css +1 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css.br +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.0f57e109c01b.css.gz +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.css +1 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.css.br +0 -0
- codex/static_root/assets/flag-tab-D2MYXCHk.css.gz +0 -0
- codex/static_root/assets/{forwardRefs-DPRKg5wq.6f09e9d22fa6.js → forwardRefs-DqbPK1-k.bb9d6863ac16.js} +1 -1
- codex/static_root/assets/forwardRefs-DqbPK1-k.bb9d6863ac16.js.br +0 -0
- codex/static_root/assets/forwardRefs-DqbPK1-k.bb9d6863ac16.js.gz +0 -0
- codex/static_root/assets/{forwardRefs-DPRKg5wq.js → forwardRefs-DqbPK1-k.js} +1 -1
- codex/static_root/assets/forwardRefs-DqbPK1-k.js.br +0 -0
- codex/static_root/assets/forwardRefs-DqbPK1-k.js.gz +0 -0
- codex/static_root/assets/{group-tab-BVlBrMwo.1127b51be6e7.js → group-tab-DXxQUa6C.f1f12f39b167.js} +1 -1
- codex/static_root/assets/group-tab-DXxQUa6C.f1f12f39b167.js.br +0 -0
- codex/static_root/assets/group-tab-DXxQUa6C.f1f12f39b167.js.gz +0 -0
- codex/static_root/assets/{group-tab-BVlBrMwo.js → group-tab-DXxQUa6C.js} +1 -1
- codex/static_root/assets/group-tab-DXxQUa6C.js.br +0 -0
- codex/static_root/assets/group-tab-DXxQUa6C.js.gz +0 -0
- codex/static_root/assets/{http-error-C9-_aEBF.d29b164e80de.js → http-error-BVMwwrFo.a7be6a26c9eb.js} +1 -1
- codex/static_root/assets/http-error-BVMwwrFo.a7be6a26c9eb.js.br +0 -0
- codex/static_root/assets/http-error-BVMwwrFo.a7be6a26c9eb.js.gz +0 -0
- codex/static_root/assets/{http-error-C9-_aEBF.js → http-error-BVMwwrFo.js} +1 -1
- codex/static_root/assets/http-error-BVMwwrFo.js.br +0 -0
- codex/static_root/assets/http-error-BVMwwrFo.js.gz +0 -0
- codex/static_root/assets/{index-9nBCksDQ.4561d2608773.js → index-DK0lSHDP.86f18c078fa9.js} +1 -1
- codex/static_root/assets/index-DK0lSHDP.86f18c078fa9.js.br +0 -0
- codex/static_root/assets/index-DK0lSHDP.86f18c078fa9.js.gz +0 -0
- codex/static_root/assets/{index-9nBCksDQ.js → index-DK0lSHDP.js} +1 -1
- codex/static_root/assets/index-DK0lSHDP.js.br +0 -0
- codex/static_root/assets/index-DK0lSHDP.js.gz +0 -0
- codex/static_root/assets/{library-tab-BMe3dKzy.22b6e123ecfb.js → library-tab-NhXuvZ4F.1e8ac3b7c168.js} +1 -1
- codex/static_root/assets/library-tab-NhXuvZ4F.1e8ac3b7c168.js.br +0 -0
- codex/static_root/assets/library-tab-NhXuvZ4F.1e8ac3b7c168.js.gz +0 -0
- codex/static_root/assets/{library-tab-BMe3dKzy.js → library-tab-NhXuvZ4F.js} +1 -1
- codex/static_root/assets/library-tab-NhXuvZ4F.js.br +0 -0
- codex/static_root/assets/library-tab-NhXuvZ4F.js.gz +0 -0
- codex/static_root/assets/{main-BbNUiWEb.565b616eb122.js → main-CYgubU7H.50aa39cf9565.js} +3 -3
- codex/static_root/assets/main-CYgubU7H.50aa39cf9565.js.br +0 -0
- codex/static_root/assets/main-CYgubU7H.50aa39cf9565.js.gz +0 -0
- codex/static_root/assets/{main-BbNUiWEb.js → main-CYgubU7H.js} +3 -3
- codex/static_root/assets/main-CYgubU7H.js.br +0 -0
- codex/static_root/assets/main-CYgubU7H.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf---TZiAf5.a6269ab85085.js +1 -0
- codex/static_root/assets/pager-full-pdf---TZiAf5.a6269ab85085.js.br +0 -0
- codex/static_root/assets/pager-full-pdf---TZiAf5.a6269ab85085.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf---TZiAf5.js +1 -0
- codex/static_root/assets/pager-full-pdf---TZiAf5.js.br +0 -0
- codex/static_root/assets/pager-full-pdf---TZiAf5.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js → pagination-toolbar-CeWXU_qA.abe0805da1cc.js} +1 -1
- codex/static_root/assets/pagination-toolbar-CeWXU_qA.abe0805da1cc.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CeWXU_qA.abe0805da1cc.js.gz +0 -0
- codex/static_root/assets/{pagination-toolbar-Dx5JwWG-.js → pagination-toolbar-CeWXU_qA.js} +1 -1
- codex/static_root/assets/pagination-toolbar-CeWXU_qA.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-CeWXU_qA.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-6ZE_HmLT.10d1e16abc78.js → pdf-doc-Dws_otkd.d215eca27da5.js} +1 -1
- codex/static_root/assets/pdf-doc-Dws_otkd.d215eca27da5.js.br +0 -0
- codex/static_root/assets/pdf-doc-Dws_otkd.d215eca27da5.js.gz +0 -0
- codex/static_root/assets/{pdf-doc-6ZE_HmLT.js → pdf-doc-Dws_otkd.js} +1 -1
- codex/static_root/assets/pdf-doc-Dws_otkd.js.br +0 -0
- codex/static_root/assets/pdf-doc-Dws_otkd.js.gz +0 -0
- codex/static_root/assets/{reader-DdPKDuh5.0c55cef1e98b.js → reader-BCxYgZHF.dde3c4ac39a5.js} +2 -2
- codex/static_root/assets/reader-BCxYgZHF.dde3c4ac39a5.js.br +0 -0
- codex/static_root/assets/reader-BCxYgZHF.dde3c4ac39a5.js.gz +0 -0
- codex/static_root/assets/{reader-DdPKDuh5.js → reader-BCxYgZHF.js} +2 -2
- codex/static_root/assets/reader-BCxYgZHF.js.br +0 -0
- codex/static_root/assets/reader-BCxYgZHF.js.gz +0 -0
- codex/static_root/assets/{relation-chips-C96hi5jm.js → relation-chips-DNRRCvrw.b1bc75fb9349.js} +1 -1
- codex/static_root/assets/relation-chips-DNRRCvrw.b1bc75fb9349.js.br +0 -0
- codex/static_root/assets/relation-chips-DNRRCvrw.b1bc75fb9349.js.gz +0 -0
- codex/static_root/assets/{relation-chips-C96hi5jm.9f07c9eb533a.js → relation-chips-DNRRCvrw.js} +1 -1
- codex/static_root/assets/relation-chips-DNRRCvrw.js.br +0 -0
- codex/static_root/assets/relation-chips-DNRRCvrw.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-CqbT1rid.js → settings-drawer-BuB29L5p.fe2eeea0058f.js} +2 -2
- codex/static_root/assets/settings-drawer-BuB29L5p.fe2eeea0058f.js.br +0 -0
- codex/static_root/assets/settings-drawer-BuB29L5p.fe2eeea0058f.js.gz +0 -0
- codex/static_root/assets/{settings-drawer-CqbT1rid.5639fa00b301.js → settings-drawer-BuB29L5p.js} +2 -2
- codex/static_root/assets/settings-drawer-BuB29L5p.js.br +0 -0
- codex/static_root/assets/settings-drawer-BuB29L5p.js.gz +0 -0
- codex/static_root/assets/{stats-tab-CkT2ZGUQ.b704fa46dc88.js → stats-tab-D0zymxU6.d5267113b51e.js} +1 -1
- codex/static_root/assets/stats-tab-D0zymxU6.d5267113b51e.js.br +0 -0
- codex/static_root/assets/stats-tab-D0zymxU6.d5267113b51e.js.gz +0 -0
- codex/static_root/assets/{stats-tab-CkT2ZGUQ.js → stats-tab-D0zymxU6.js} +1 -1
- codex/static_root/assets/stats-tab-D0zymxU6.js.br +0 -0
- codex/static_root/assets/stats-tab-D0zymxU6.js.gz +0 -0
- codex/static_root/assets/{task-tab-a-DcU3Tj.8c2f7bf3c4c9.js → task-tab-CI9RLdMS.120f395dc9c6.js} +1 -1
- codex/static_root/assets/task-tab-CI9RLdMS.120f395dc9c6.js.br +0 -0
- codex/static_root/assets/task-tab-CI9RLdMS.120f395dc9c6.js.gz +0 -0
- codex/static_root/assets/{task-tab-a-DcU3Tj.js → task-tab-CI9RLdMS.js} +1 -1
- codex/static_root/assets/task-tab-CI9RLdMS.js.br +0 -0
- codex/static_root/assets/task-tab-CI9RLdMS.js.gz +0 -0
- codex/static_root/assets/{unauthorized-BOl5YpL3.f3c4b52718d5.js → unauthorized-BNwg_Xvu.a894b96156e0.js} +1 -1
- codex/static_root/assets/unauthorized-BNwg_Xvu.a894b96156e0.js.br +0 -0
- codex/static_root/assets/unauthorized-BNwg_Xvu.a894b96156e0.js.gz +0 -0
- codex/static_root/assets/{unauthorized-BOl5YpL3.js → unauthorized-BNwg_Xvu.js} +1 -1
- codex/static_root/assets/unauthorized-BNwg_Xvu.js.br +0 -0
- codex/static_root/assets/unauthorized-BNwg_Xvu.js.gz +0 -0
- codex/static_root/assets/{user-tab-BBDZvD8G.aa4263a68b22.js → user-tab-w9LgIMMi.4a89c7b37486.js} +1 -1
- codex/static_root/assets/user-tab-w9LgIMMi.4a89c7b37486.js.br +0 -0
- codex/static_root/assets/user-tab-w9LgIMMi.4a89c7b37486.js.gz +0 -0
- codex/static_root/assets/{user-tab-BBDZvD8G.js → user-tab-w9LgIMMi.js} +1 -1
- codex/static_root/assets/user-tab-w9LgIMMi.js.br +0 -0
- codex/static_root/assets/user-tab-w9LgIMMi.js.gz +0 -0
- codex/static_root/{manifest.225989a17f49.json → manifest.514e63b9f805.json} +253 -253
- codex/static_root/manifest.514e63b9f805.json.br +0 -0
- codex/static_root/manifest.514e63b9f805.json.gz +0 -0
- codex/static_root/manifest.json +253 -253
- 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 +9 -12
- codex/urls/root.py +4 -3
- codex/views/admin/api_key.py +1 -4
- codex/views/admin/library.py +4 -4
- codex/views/admin/stats.py +10 -10
- codex/views/admin/tasks.py +9 -9
- codex/views/auth.py +3 -8
- codex/views/bookmark.py +3 -3
- codex/views/browser/annotate/card.py +2 -2
- codex/views/browser/annotate/order.py +6 -7
- codex/views/browser/bookmark.py +3 -1
- codex/views/browser/breadcrumbs.py +6 -5
- codex/views/browser/browser.py +2 -2
- codex/views/browser/choices.py +6 -7
- codex/views/browser/cover.py +6 -3
- codex/views/browser/download.py +2 -1
- codex/views/browser/filters/bookmark.py +1 -1
- codex/views/browser/filters/field.py +1 -1
- codex/views/browser/filters/filter.py +6 -8
- codex/views/browser/filters/group.py +8 -8
- codex/views/browser/filters/search/field/expression.py +1 -2
- codex/views/browser/filters/search/field/optimize.py +1 -1
- codex/views/browser/filters/search/field/parse.py +2 -2
- codex/views/browser/filters/search/parse.py +4 -19
- codex/views/browser/group_mtime.py +1 -1
- codex/views/browser/metadata/copy_intersections.py +1 -1
- codex/views/browser/metadata/metadata.py +1 -1
- codex/views/browser/mtime.py +1 -1
- codex/views/browser/order_by.py +3 -5
- codex/views/browser/params.py +1 -1
- codex/views/browser/settings.py +1 -1
- codex/views/browser/validate.py +8 -5
- codex/views/const.py +0 -3
- codex/views/frontend.py +1 -1
- codex/views/mixins.py +3 -2
- codex/views/opds/authentication_v1.py +1 -1
- codex/views/opds/const.py +4 -4
- codex/views/opds/util.py +3 -3
- codex/views/opds/v1/entry/entry.py +4 -2
- codex/views/opds/v1/entry/links.py +4 -5
- codex/views/opds/v1/facets.py +8 -4
- codex/views/opds/v1/feed.py +5 -5
- codex/views/opds/v1/links.py +11 -6
- codex/views/opds/v2/const.py +5 -2
- codex/views/opds/v2/feed.py +3 -3
- codex/views/opds/v2/progression.py +8 -9
- codex/views/opds/v2/publications.py +10 -20
- codex/views/public.py +1 -1
- codex/views/reader/books.py +3 -2
- codex/views/reader/page.py +3 -3
- codex/views/reader/params.py +2 -2
- codex/views/reader/reader.py +6 -6
- codex/views/reader/settings.py +1 -1
- codex/views/session.py +8 -2
- codex/views/settings.py +2 -2
- codex/views/timezone.py +2 -2
- codex/views/util.py +10 -9
- codex/views/version.py +1 -1
- {codex-1.7.3a2.dist-info → codex-1.7.4.dist-info}/METADATA +3 -1
- {codex-1.7.3a2.dist-info → codex-1.7.4.dist-info}/RECORD +409 -401
- {codex-1.7.3a2.dist-info → codex-1.7.4.dist-info}/WHEEL +1 -1
- codex/serializers/fields.py +0 -183
- codex/static_root/assets/VCheckbox-FaT6MGfu.f6732060f734.js.br +0 -0
- codex/static_root/assets/VCheckbox-FaT6MGfu.f6732060f734.js.gz +0 -0
- codex/static_root/assets/VCheckbox-FaT6MGfu.js.br +0 -0
- codex/static_root/assets/VCheckbox-FaT6MGfu.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.847452d574cf.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.847452d574cf.js.gz +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.js.br +0 -0
- codex/static_root/assets/VCheckboxBtn-CZ_P-qUM.js.gz +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.6f431e19a453.js.br +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.6f431e19a453.js.gz +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.js.br +0 -0
- codex/static_root/assets/VCombobox-C8QLNlSl.js.gz +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.c0c7eef71eec.js.br +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.c0c7eef71eec.js.gz +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.js.br +0 -0
- codex/static_root/assets/VDialog-RVLeW7je.js.gz +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.9dbee744fb3c.js.br +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.9dbee744fb3c.js.gz +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.js.br +0 -0
- codex/static_root/assets/VDivider-D1Ot4vR2.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.43f06d0a45e0.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.43f06d0a45e0.js.gz +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.js.br +0 -0
- codex/static_root/assets/VExpansionPanels-DQilGHZk.js.gz +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js.br +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.81f3a5ff7ec3.js.gz +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.js.br +0 -0
- codex/static_root/assets/VForm-DNmY-qFJ.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.fe5e0e74ffee.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.fe5e0e74ffee.js.gz +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.js.br +0 -0
- codex/static_root/assets/VRadioGroup-DoayJpcS.js.gz +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js.br +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.9ab865ec0fad.js.gz +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.js.br +0 -0
- codex/static_root/assets/VSelect-BSLmHX_P.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.ad6c96efe57c.js.br +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.ad6c96efe57c.js.gz +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.js.br +0 -0
- codex/static_root/assets/VSelectionControl-TaKCeZgb.js.gz +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.77ef3973bb54.js.br +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.77ef3973bb54.js.gz +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.js.br +0 -0
- codex/static_root/assets/VTable-BfcOiEpa.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js.br +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.e4c75ad78718.js.gz +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.js.br +0 -0
- codex/static_root/assets/VWindowItem-D8lWcbQY.js.gz +0 -0
- codex/static_root/assets/admin-80eqCqtz.d6deb9edb8cb.js.br +0 -0
- codex/static_root/assets/admin-80eqCqtz.d6deb9edb8cb.js.gz +0 -0
- codex/static_root/assets/admin-80eqCqtz.js.br +0 -0
- codex/static_root/assets/admin-80eqCqtz.js.gz +0 -0
- codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js.br +0 -0
- codex/static_root/assets/admin-CBLHOrM9.8c3c2268bc96.js.gz +0 -0
- codex/static_root/assets/admin-CBLHOrM9.js.br +0 -0
- codex/static_root/assets/admin-CBLHOrM9.js.gz +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.0c08c9bd2e7d.js.br +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.0c08c9bd2e7d.js.gz +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.js.br +0 -0
- codex/static_root/assets/admin-menu-_aAGknKO.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.2bb1194b21e2.js.gz +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.js.br +0 -0
- codex/static_root/assets/admin-settings-button-progress-BqRFyhjf.js.gz +0 -0
- codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js +0 -1
- codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js.br +0 -0
- codex/static_root/assets/browser-DAprXYuP.8bb92965dd9d.js.gz +0 -0
- codex/static_root/assets/browser-DAprXYuP.js +0 -1
- codex/static_root/assets/browser-DAprXYuP.js.br +0 -0
- codex/static_root/assets/browser-DAprXYuP.js.gz +0 -0
- codex/static_root/assets/browser-chTZ1CM4.521193ab0710.css.br +0 -0
- codex/static_root/assets/browser-chTZ1CM4.521193ab0710.css.gz +0 -0
- codex/static_root/assets/browser-chTZ1CM4.css.br +0 -0
- codex/static_root/assets/browser-chTZ1CM4.css.gz +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js.br +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.ae0d9e5bd42b.js.gz +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.js.br +0 -0
- codex/static_root/assets/change-password-dialog-1ZVP_Q2w.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.e315d7f8f752.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.e315d7f8f752.js.gz +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.js.br +0 -0
- codex/static_root/assets/confirm-dialog-DqCey9Iq.js.gz +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.17a31f189d66.js.br +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.17a31f189d66.js.gz +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.js.br +0 -0
- codex/static_root/assets/datetime-column-CBA8bYeO.js.gz +0 -0
- codex/static_root/assets/filter-mnO3lLPo.659442401b16.js.br +0 -0
- codex/static_root/assets/filter-mnO3lLPo.659442401b16.js.gz +0 -0
- codex/static_root/assets/filter-mnO3lLPo.js.br +0 -0
- codex/static_root/assets/filter-mnO3lLPo.js.gz +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.1b8a2f7c0dc3.js.br +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.1b8a2f7c0dc3.js.gz +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.js.br +0 -0
- codex/static_root/assets/flag-tab-Bc677pNb.js.gz +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css +0 -1
- codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css.br +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.be8eed14384e.css.gz +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.css +0 -1
- codex/static_root/assets/flag-tab-glFvEgEl.css.br +0 -0
- codex/static_root/assets/flag-tab-glFvEgEl.css.gz +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.6f09e9d22fa6.js.br +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.6f09e9d22fa6.js.gz +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.js.br +0 -0
- codex/static_root/assets/forwardRefs-DPRKg5wq.js.gz +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js.br +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.1127b51be6e7.js.gz +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.js.br +0 -0
- codex/static_root/assets/group-tab-BVlBrMwo.js.gz +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js.br +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.d29b164e80de.js.gz +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.js.br +0 -0
- codex/static_root/assets/http-error-C9-_aEBF.js.gz +0 -0
- codex/static_root/assets/index-9nBCksDQ.4561d2608773.js.br +0 -0
- codex/static_root/assets/index-9nBCksDQ.4561d2608773.js.gz +0 -0
- codex/static_root/assets/index-9nBCksDQ.js.br +0 -0
- codex/static_root/assets/index-9nBCksDQ.js.gz +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js.br +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.22b6e123ecfb.js.gz +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.js.br +0 -0
- codex/static_root/assets/library-tab-BMe3dKzy.js.gz +0 -0
- codex/static_root/assets/main-BbNUiWEb.565b616eb122.js.br +0 -0
- codex/static_root/assets/main-BbNUiWEb.565b616eb122.js.gz +0 -0
- codex/static_root/assets/main-BbNUiWEb.js.br +0 -0
- codex/static_root/assets/main-BbNUiWEb.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js +0 -1
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.6d4ec47c14f7.js.gz +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.js +0 -1
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.js.br +0 -0
- codex/static_root/assets/pager-full-pdf-JwAUlsNI.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.ba0e8b645dfa.js.gz +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.js.br +0 -0
- codex/static_root/assets/pagination-toolbar-Dx5JwWG-.js.gz +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.10d1e16abc78.js.br +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.10d1e16abc78.js.gz +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.js.br +0 -0
- codex/static_root/assets/pdf-doc-6ZE_HmLT.js.gz +0 -0
- codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js.br +0 -0
- codex/static_root/assets/reader-DdPKDuh5.0c55cef1e98b.js.gz +0 -0
- codex/static_root/assets/reader-DdPKDuh5.js.br +0 -0
- codex/static_root/assets/reader-DdPKDuh5.js.gz +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.9f07c9eb533a.js.br +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.9f07c9eb533a.js.gz +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.js.br +0 -0
- codex/static_root/assets/relation-chips-C96hi5jm.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js.br +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.5639fa00b301.js.gz +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.js.br +0 -0
- codex/static_root/assets/settings-drawer-CqbT1rid.js.gz +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.b704fa46dc88.js.br +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.b704fa46dc88.js.gz +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.js.br +0 -0
- codex/static_root/assets/stats-tab-CkT2ZGUQ.js.gz +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js.br +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.8c2f7bf3c4c9.js.gz +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.js.br +0 -0
- codex/static_root/assets/task-tab-a-DcU3Tj.js.gz +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js.br +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.f3c4b52718d5.js.gz +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.js.br +0 -0
- codex/static_root/assets/unauthorized-BOl5YpL3.js.gz +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js.br +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.aa4263a68b22.js.gz +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.js.br +0 -0
- codex/static_root/assets/user-tab-BBDZvD8G.js.gz +0 -0
- codex/static_root/manifest.225989a17f49.json.br +0 -0
- codex/static_root/manifest.225989a17f49.json.gz +0 -0
- {codex-1.7.3a2.dist-info → codex-1.7.4.dist-info}/LICENSE +0 -0
- {codex-1.7.3a2.dist-info → codex-1.7.4.dist-info}/entry_points.txt +0 -0
|
@@ -9,8 +9,13 @@ from comicbox.box import Comicbox
|
|
|
9
9
|
from comicbox.box.computed import IDENTIFIERS_KEY
|
|
10
10
|
from comicbox.exceptions import UnsupportedArchiveTypeError
|
|
11
11
|
from comicbox.schemas.comicbox_mixin import CONTRIBUTORS_KEY
|
|
12
|
-
from django.db.models import
|
|
13
|
-
|
|
12
|
+
from django.db.models.fields import (
|
|
13
|
+
CharField,
|
|
14
|
+
DecimalField,
|
|
15
|
+
PositiveSmallIntegerField,
|
|
16
|
+
TextField,
|
|
17
|
+
)
|
|
18
|
+
from nh3 import clean
|
|
14
19
|
from rarfile import BadRarFile
|
|
15
20
|
|
|
16
21
|
from codex.librarian.importer.const import FIS, STORY_ARCS_METADATA_KEY
|
|
@@ -36,29 +41,39 @@ _MD_INVALID_KEYS = frozenset(
|
|
|
36
41
|
"updated_at",
|
|
37
42
|
)
|
|
38
43
|
)
|
|
39
|
-
_MD_VALID_KEYS =
|
|
40
|
-
(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
_MD_VALID_KEYS = tuple(
|
|
45
|
+
sorted(
|
|
46
|
+
frozenset(
|
|
47
|
+
(
|
|
48
|
+
*(
|
|
49
|
+
field.name
|
|
50
|
+
for field in Comic._meta.get_fields()
|
|
51
|
+
if field.name not in _MD_INVALID_KEYS
|
|
52
|
+
),
|
|
53
|
+
"story_arcs",
|
|
54
|
+
)
|
|
55
|
+
)
|
|
47
56
|
)
|
|
48
57
|
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _get_keys_by_field_type(field_type):
|
|
61
|
+
return tuple(
|
|
62
|
+
sorted(
|
|
63
|
+
frozenset(
|
|
64
|
+
field.name
|
|
65
|
+
for field in Comic._meta.get_fields()
|
|
66
|
+
if isinstance(field, field_type)
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
_MD_CHAR_KEYS = _get_keys_by_field_type(CharField)
|
|
73
|
+
_MD_TEXT_KEYS = _get_keys_by_field_type(TextField)
|
|
74
|
+
_MD_STR_KEYS = tuple(sorted(_MD_CHAR_KEYS + _MD_TEXT_KEYS))
|
|
75
|
+
_MD_DECIMAL_KEYS = _get_keys_by_field_type(DecimalField)
|
|
76
|
+
_MD_PSI_KEYS = _get_keys_by_field_type(PositiveSmallIntegerField)
|
|
62
77
|
_PSI_MAX = 2**31 - 1
|
|
63
78
|
_SI_MIN = 2**15 * -1
|
|
64
79
|
_SI_MAX = 2**15 - 1
|
|
@@ -94,7 +109,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
94
109
|
|
|
95
110
|
@classmethod
|
|
96
111
|
def _clean_decimal(cls, value, field_name: str):
|
|
97
|
-
field: DecimalField = Comic._meta.get_field(field_name) # type: ignore
|
|
112
|
+
field: DecimalField = Comic._meta.get_field(field_name) # type: ignore[reportAssignmentType]
|
|
98
113
|
try:
|
|
99
114
|
quantize_str = Decimal(f"1e-{field.decimal_places}")
|
|
100
115
|
value = value.quantize(quantize_str, rounding=ROUND_DOWN)
|
|
@@ -145,11 +160,15 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
145
160
|
cls._assign_or_pop(obj, key, value)
|
|
146
161
|
|
|
147
162
|
@staticmethod
|
|
148
|
-
def
|
|
163
|
+
def _clean_strfield(model, field_name, value):
|
|
149
164
|
try:
|
|
150
|
-
field: CharField = model._meta.get_field(field_name) # type: ignore
|
|
151
165
|
if value:
|
|
152
|
-
value = value
|
|
166
|
+
value = value.strip()
|
|
167
|
+
field = model._meta.get_field(field_name) # type: ignore[reportAssignmentType]
|
|
168
|
+
max_length = getattr(field, "max_length", 0)
|
|
169
|
+
if max_length:
|
|
170
|
+
value = value[:max_length]
|
|
171
|
+
value = clean(value)
|
|
153
172
|
if not value:
|
|
154
173
|
value = None
|
|
155
174
|
except Exception:
|
|
@@ -157,10 +176,10 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
157
176
|
return value
|
|
158
177
|
|
|
159
178
|
@classmethod
|
|
160
|
-
def
|
|
161
|
-
for key in
|
|
179
|
+
def _clean_strfields(cls, md: dict[str, Any]) -> None:
|
|
180
|
+
for key in _MD_STR_KEYS:
|
|
162
181
|
value = md.get(key)
|
|
163
|
-
value = cls.
|
|
182
|
+
value = cls._clean_strfield(Comic, key, value)
|
|
164
183
|
cls._assign_or_pop(md, key, value)
|
|
165
184
|
|
|
166
185
|
@classmethod
|
|
@@ -182,10 +201,10 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
182
201
|
return
|
|
183
202
|
clean_contributors = {}
|
|
184
203
|
for role, persons in contributors.items():
|
|
185
|
-
clean_role = cls.
|
|
204
|
+
clean_role = cls._clean_strfield(ContributorRole, "name", role)
|
|
186
205
|
clean_persons = set()
|
|
187
206
|
for person in persons:
|
|
188
|
-
clean_person = cls.
|
|
207
|
+
clean_person = cls._clean_strfield(ContributorPerson, "name", person)
|
|
189
208
|
if clean_person:
|
|
190
209
|
clean_persons.add(clean_person)
|
|
191
210
|
clean_contributors[clean_role] = clean_persons
|
|
@@ -198,7 +217,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
198
217
|
return
|
|
199
218
|
clean_story_arcs = {}
|
|
200
219
|
for story_arc, number in story_arcs.items():
|
|
201
|
-
clean_story_arc = cls.
|
|
220
|
+
clean_story_arc = cls._clean_strfield(StoryArc, "name", story_arc)
|
|
202
221
|
if clean_story_arc:
|
|
203
222
|
clean_story_arcs[clean_story_arc] = number
|
|
204
223
|
cls._assign_or_pop(md, STORY_ARCS_METADATA_KEY, clean_story_arcs)
|
|
@@ -210,11 +229,11 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
210
229
|
return
|
|
211
230
|
clean_identifiers = {}
|
|
212
231
|
for nid, identifier in identifiers.items():
|
|
213
|
-
clean_id_type = cls.
|
|
232
|
+
clean_id_type = cls._clean_strfield(IdentifierType, "name", nid)
|
|
214
233
|
nss = identifier.get("nss")
|
|
215
|
-
clean_nss = cls.
|
|
234
|
+
clean_nss = cls._clean_strfield(Identifier, "nss", nss)
|
|
216
235
|
url = identifier.get("url")
|
|
217
|
-
clean_url = cls.
|
|
236
|
+
clean_url = cls._clean_strfield(Identifier, "url", url)
|
|
218
237
|
if clean_nss:
|
|
219
238
|
clean_identifier = {"nss": nss}
|
|
220
239
|
if clean_url:
|
|
@@ -230,7 +249,7 @@ class ExtractMetadataImporter(QueryForeignKeysImporter):
|
|
|
230
249
|
cls._clean_comic_positive_small_ints(md)
|
|
231
250
|
cls._clean_comic_small_ints(md)
|
|
232
251
|
cls._clean_comic_decimals(md)
|
|
233
|
-
cls.
|
|
252
|
+
cls._clean_strfields(md)
|
|
234
253
|
cls._clean_contributors(md)
|
|
235
254
|
cls._clean_story_arcs(md)
|
|
236
255
|
cls._clean_identifiers(md)
|
|
@@ -120,7 +120,7 @@ class FailedImportsImporter(DeletedImporter):
|
|
|
120
120
|
create_objs,
|
|
121
121
|
update_conflicts=True,
|
|
122
122
|
update_fields=_BULK_UPDATE_FAILED_IMPORT_FIELDS,
|
|
123
|
-
unique_fields=FailedImport._meta.unique_together[0],
|
|
123
|
+
unique_fields=FailedImport._meta.unique_together[0],
|
|
124
124
|
)
|
|
125
125
|
count = len(create_objs)
|
|
126
126
|
if count:
|
|
@@ -43,7 +43,9 @@ class ComicImporter(MovedImporter):
|
|
|
43
43
|
|
|
44
44
|
# Wait to start the search index update in case more updates are incoming.
|
|
45
45
|
until = time() + 1
|
|
46
|
-
delayed_search_task = DelayedTasks(
|
|
46
|
+
delayed_search_task = DelayedTasks(
|
|
47
|
+
until, (SearchIndexUpdateTask(rebuild=False),)
|
|
48
|
+
)
|
|
47
49
|
self.librarian_queue.put(delayed_search_task)
|
|
48
50
|
else:
|
|
49
51
|
self.log.info("No updates neccissary.")
|
|
@@ -35,7 +35,7 @@ class ComicImporterThread(QueuedThread):
|
|
|
35
35
|
import_comics = Comic.objects.filter(pk__in=task.pks).only("path", "library_id")
|
|
36
36
|
library_path_map = {}
|
|
37
37
|
for import_comic in import_comics:
|
|
38
|
-
library_id = import_comic.library_id # type: ignore
|
|
38
|
+
library_id = import_comic.library_id # type: ignore[reportAttributeAccessIssue]
|
|
39
39
|
if library_id not in library_path_map:
|
|
40
40
|
library_path_map[library_id] = set()
|
|
41
41
|
library_path_map[library_id].add(import_comic.path)
|
|
@@ -89,7 +89,7 @@ class ComicImporterThread(QueuedThread):
|
|
|
89
89
|
importer.bulk_folders_moved()
|
|
90
90
|
return True
|
|
91
91
|
|
|
92
|
-
def _adopt_orphan_folders(self, janitor
|
|
92
|
+
def _adopt_orphan_folders(self, janitor: bool):
|
|
93
93
|
"""Find orphan folders and move them into their correct place."""
|
|
94
94
|
status = Status(ImportStatusTypes.ADOPT_FOLDERS)
|
|
95
95
|
moved_status = Status(ImportStatusTypes.DIRS_MOVED)
|
|
@@ -118,6 +118,6 @@ class ComicImporterThread(QueuedThread):
|
|
|
118
118
|
elif isinstance(task, UpdateGroupsTask):
|
|
119
119
|
self._update_groups(task)
|
|
120
120
|
elif isinstance(task, AdoptOrphanFoldersTask):
|
|
121
|
-
self._adopt_orphan_folders(task.janitor)
|
|
121
|
+
self._adopt_orphan_folders(janitor=task.janitor)
|
|
122
122
|
else:
|
|
123
123
|
self.log.warning(f"Bad task sent to library updater {task}")
|
|
@@ -186,7 +186,8 @@ class LinkComicsImporter(LinkCoversImporter):
|
|
|
186
186
|
return tms, all_del_pks
|
|
187
187
|
|
|
188
188
|
def bulk_fix_comic_m2m_field(self, field_name, m2m_links, status):
|
|
189
|
-
"""
|
|
189
|
+
"""
|
|
190
|
+
Recreate an m2m field for a set of comics.
|
|
190
191
|
|
|
191
192
|
Since we can't bulk_update or bulk_create m2m fields use a trick.
|
|
192
193
|
bulk_create() on the through table:
|
|
@@ -46,9 +46,7 @@ class MovedImporter(AggregateMetadataImporter):
|
|
|
46
46
|
new_path = self.task.files_moved[comic.path]
|
|
47
47
|
comic.path = new_path
|
|
48
48
|
new_path = Path(new_path)
|
|
49
|
-
comic.parent_folder = Folder.objects.get(
|
|
50
|
-
path=new_path.parent
|
|
51
|
-
)
|
|
49
|
+
comic.parent_folder = Folder.objects.get(path=new_path.parent)
|
|
52
50
|
comic.updated_at = Now()
|
|
53
51
|
comic.presave()
|
|
54
52
|
folder_m2m_links[comic.pk] = Folder.objects.filter(
|
|
@@ -137,7 +135,7 @@ class MovedImporter(AggregateMetadataImporter):
|
|
|
137
135
|
f"Unlinked {len(unlink_groups)} {model.__name__} moved custom covers."
|
|
138
136
|
)
|
|
139
137
|
|
|
140
|
-
self._remove_covers(unlink_pks, custom=True)
|
|
138
|
+
self._remove_covers(unlink_pks, custom=True)
|
|
141
139
|
|
|
142
140
|
def _bulk_covers_moved(self, status=None):
|
|
143
141
|
"""Move covers."""
|
|
@@ -194,8 +192,6 @@ class MovedImporter(AggregateMetadataImporter):
|
|
|
194
192
|
folder.updated_at = Now()
|
|
195
193
|
update_folders.append(folder)
|
|
196
194
|
|
|
197
|
-
# delete_folders = Folder.objects.filter(library=self.library, path__in=new_paths)
|
|
198
|
-
|
|
199
195
|
update_folders = sorted(update_folders, key=lambda x: len(Path(x.path).parts))
|
|
200
196
|
|
|
201
197
|
Folder.objects.bulk_update(update_folders, MOVED_BULK_FOLDER_UPDATE_FIELDS)
|
|
@@ -212,7 +208,6 @@ class MovedImporter(AggregateMetadataImporter):
|
|
|
212
208
|
"""Move folders under existing folders."""
|
|
213
209
|
while True:
|
|
214
210
|
# Get existing parent folders
|
|
215
|
-
# dest_parent_paths = tuple(str(path) for path in dest_parent_folder_paths_map)
|
|
216
211
|
dest_parent_paths = tuple(dest_parent_folder_paths_map.keys())
|
|
217
212
|
extant_parent_folders = Folder.objects.filter(
|
|
218
213
|
library=self.library, path__in=dest_parent_paths
|
|
@@ -229,9 +229,7 @@ class QueryForeignKeysImporter(QueryCustomCoversImporter):
|
|
|
229
229
|
|
|
230
230
|
group_filter = Q()
|
|
231
231
|
for group_tree, count_value in update_group_trees.items():
|
|
232
|
-
compare_filter =
|
|
233
|
-
for field_name, value in zip(compare_fields, group_tree, strict=False):
|
|
234
|
-
compare_filter[field_name] = value
|
|
232
|
+
compare_filter = dict(zip(compare_fields, group_tree, strict=False))
|
|
235
233
|
compare_filter[count_field_name] = count_value
|
|
236
234
|
group_filter |= Q(**compare_filter)
|
|
237
235
|
return group_filter
|
|
@@ -494,7 +492,6 @@ class QueryForeignKeysImporter(QueryCustomCoversImporter):
|
|
|
494
492
|
|
|
495
493
|
def _query_missing_simple_models(self, names, fk_data, status):
|
|
496
494
|
"""Find missing named models and folders."""
|
|
497
|
-
# count = 0
|
|
498
495
|
if not names:
|
|
499
496
|
return 0
|
|
500
497
|
create_fks, base_cls, field, fk_field = fk_data
|
|
@@ -18,7 +18,7 @@ class ImportDBDiffTask(ImportTask):
|
|
|
18
18
|
|
|
19
19
|
dirs_moved: Mapping[str, str] = field(default_factory=dict)
|
|
20
20
|
dirs_modified: frozenset[str] = frozenset()
|
|
21
|
-
# dirs_created: frozenset[str] | None = frozenset()
|
|
21
|
+
# dirs_created: frozenset[str] | None = frozenset() # noqa: ERA001
|
|
22
22
|
dirs_deleted: frozenset[str] = frozenset()
|
|
23
23
|
|
|
24
24
|
files_moved: Mapping[str, str] = field(default_factory=dict)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Uses app.get_model() because functions may also be called before the models are ready on startup.
|
|
3
3
|
|
|
4
4
|
import re
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from django.apps import apps
|
|
7
8
|
from django.db import DEFAULT_DB_ALIAS, connections
|
|
@@ -19,6 +20,13 @@ from codex.settings.settings import (
|
|
|
19
20
|
from codex.status import Status
|
|
20
21
|
from codex.worker_base import WorkerBaseMixin
|
|
21
22
|
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from django.db.models.manager import BaseManager
|
|
25
|
+
|
|
26
|
+
from codex.models.base import BaseModel
|
|
27
|
+
from codex.models.comic import Comic
|
|
28
|
+
|
|
29
|
+
|
|
22
30
|
REPAIR_FLAG_PATH = CONFIG_PATH / "rebuild_db"
|
|
23
31
|
REBUILT_DB_PATH = DB_PATH.parent / (DB_PATH.name + ".rebuilt")
|
|
24
32
|
BACKUP_DB_PATH = DB_PATH.parent / (DB_PATH.name + ".bak")
|
|
@@ -105,7 +113,7 @@ def _null_bad_fk_rels_table(table_name, bad_rows, log):
|
|
|
105
113
|
fields = model._meta.fields
|
|
106
114
|
field_names = {}
|
|
107
115
|
update_objs = []
|
|
108
|
-
objs = model.objects.filter(pk__in=bad_rows.keys())
|
|
116
|
+
objs: BaseManager[BaseModel] = model.objects.filter(pk__in=bad_rows.keys()) # type: ignore[reportAssignmentType]
|
|
109
117
|
update_fields = {"updated_at"}
|
|
110
118
|
now = Now()
|
|
111
119
|
for obj in objs:
|
|
@@ -119,7 +127,7 @@ def _null_bad_fk_rels_table(table_name, bad_rows, log):
|
|
|
119
127
|
field_name = field_names[fkid]
|
|
120
128
|
setattr(obj, field_name, None)
|
|
121
129
|
update_fields.add(field_name)
|
|
122
|
-
obj.updated_at = now
|
|
130
|
+
obj.updated_at = now
|
|
123
131
|
update_objs.append(obj)
|
|
124
132
|
except Exception as exc:
|
|
125
133
|
log.warning(
|
|
@@ -142,10 +150,10 @@ def _null_bad_fk_rels(bad_fk_rels, log):
|
|
|
142
150
|
for table_name, bad_rows in bad_fk_rels.items():
|
|
143
151
|
try:
|
|
144
152
|
fix_comic_pks |= _null_bad_fk_rels_table(table_name, bad_rows, log)
|
|
145
|
-
except Exception
|
|
153
|
+
except Exception:
|
|
146
154
|
pks = sorted(bad_rows.keys())
|
|
147
|
-
log.
|
|
148
|
-
f"Unable to null {len(pks)} {table_name} rows with bad foreign keys
|
|
155
|
+
log.exception(
|
|
156
|
+
f"Unable to null {len(pks)} {table_name} rows with bad foreign keys"
|
|
149
157
|
)
|
|
150
158
|
log.error(f"{table_name}: {pks}") # noqa: TRY400
|
|
151
159
|
return fix_comic_pks
|
|
@@ -175,10 +183,8 @@ def _delete_bad_m2m_rows(bad_m2m_rows, log):
|
|
|
175
183
|
for table_name, ids in bad_m2m_rows.items():
|
|
176
184
|
try:
|
|
177
185
|
count += _delete_bad_m2m_rows_table(table_name, ids, fix_comic_pks, log)
|
|
178
|
-
except Exception
|
|
179
|
-
log.
|
|
180
|
-
f"Could not delete bad foreign keys in table {table_name} - {exc}"
|
|
181
|
-
)
|
|
186
|
+
except Exception:
|
|
187
|
+
log.exception(f"Could not delete bad foreign keys in table {table_name}")
|
|
182
188
|
if count:
|
|
183
189
|
log.info(f"Removed {count} bad foreign key relations.")
|
|
184
190
|
return fix_comic_pks
|
|
@@ -189,7 +195,9 @@ def _mark_comics_for_update(fix_comic_pks, log):
|
|
|
189
195
|
if not fix_comic_pks:
|
|
190
196
|
return
|
|
191
197
|
comic_model = apps.get_model(app_label="codex", model_name="comic")
|
|
192
|
-
outdated_comics = comic_model.objects.filter(
|
|
198
|
+
outdated_comics: BaseManager[Comic] = comic_model.objects.filter(
|
|
199
|
+
pk__in=fix_comic_pks
|
|
200
|
+
).only( # type: ignore[reportAssignmentType]
|
|
193
201
|
"stat", "updated_at"
|
|
194
202
|
)
|
|
195
203
|
if not outdated_comics:
|
|
@@ -198,12 +206,12 @@ def _mark_comics_for_update(fix_comic_pks, log):
|
|
|
198
206
|
update_comics = []
|
|
199
207
|
now = Now()
|
|
200
208
|
for comic in outdated_comics:
|
|
201
|
-
stat_list = comic.stat
|
|
209
|
+
stat_list = comic.stat
|
|
202
210
|
if not stat_list:
|
|
203
211
|
continue
|
|
204
212
|
stat_list[8] = 0.0
|
|
205
|
-
comic.stat = stat_list # type: ignore
|
|
206
|
-
comic.updated_at = now
|
|
213
|
+
comic.stat = stat_list # type: ignore[reportAttributeAccessIssue]
|
|
214
|
+
comic.updated_at = now
|
|
207
215
|
update_comics.append(comic)
|
|
208
216
|
|
|
209
217
|
if update_comics:
|
|
@@ -227,8 +235,8 @@ def fix_foreign_keys(log=None):
|
|
|
227
235
|
fix_comic_pks |= _delete_bad_m2m_rows(bad_m2m_rows, log)
|
|
228
236
|
try:
|
|
229
237
|
_mark_comics_for_update(fix_comic_pks, log)
|
|
230
|
-
except Exception
|
|
231
|
-
LOG.
|
|
238
|
+
except Exception:
|
|
239
|
+
LOG.exception("Could not mark comics with bad relations for update")
|
|
232
240
|
|
|
233
241
|
|
|
234
242
|
def _repair_extra_custom_cover_libraries(library_model, log):
|
|
@@ -277,7 +285,7 @@ def _is_integrity_ok(results):
|
|
|
277
285
|
)
|
|
278
286
|
|
|
279
287
|
|
|
280
|
-
def integrity_check(long
|
|
288
|
+
def integrity_check(long, log=None):
|
|
281
289
|
"""Run sqlite3 integrity check."""
|
|
282
290
|
if not log:
|
|
283
291
|
log = LOG
|
|
@@ -318,9 +326,8 @@ def fts_integrity_check(log=None):
|
|
|
318
326
|
if results:
|
|
319
327
|
# I'm not sure if this raises or puts the error in the results.
|
|
320
328
|
raise ValueError(results) # noqa: TRY301
|
|
321
|
-
except Exception
|
|
322
|
-
log.
|
|
323
|
-
log.warning(exc)
|
|
329
|
+
except Exception:
|
|
330
|
+
log.exception("Full Text Search Index failed integrity check.")
|
|
324
331
|
else:
|
|
325
332
|
log.info("Full Text Search Index passed integrity check.")
|
|
326
333
|
success = True
|
|
@@ -340,7 +347,7 @@ class IntegrityMixin(WorkerBaseMixin):
|
|
|
340
347
|
finally:
|
|
341
348
|
self.status_controller.finish(status)
|
|
342
349
|
|
|
343
|
-
def integrity_check(self, long
|
|
350
|
+
def integrity_check(self, long: bool):
|
|
344
351
|
"""Integrity check task."""
|
|
345
352
|
subtitle = "" if long else "Quick"
|
|
346
353
|
status = Status(JanitorStatusTypes.INTEGRITY_CHECK, subtitle=subtitle)
|
|
@@ -109,7 +109,7 @@ class Janitor(
|
|
|
109
109
|
case JanitorVacuumTask():
|
|
110
110
|
self.vacuum_db()
|
|
111
111
|
case JanitorBackupTask():
|
|
112
|
-
self.backup_db()
|
|
112
|
+
self.backup_db(show_status=True)
|
|
113
113
|
case JanitorLatestVersionTask():
|
|
114
114
|
self.update_latest_version(task.force)
|
|
115
115
|
case JanitorUpdateTask():
|
|
@@ -28,7 +28,7 @@ class LatestVersionMixin(WorkerBaseMixin):
|
|
|
28
28
|
response = requests.get(_REPO_URL, timeout=_REPO_TIMEOUT)
|
|
29
29
|
return json.loads(response.text)["info"]["version"]
|
|
30
30
|
|
|
31
|
-
def update_latest_version(self, force
|
|
31
|
+
def update_latest_version(self, force: bool):
|
|
32
32
|
"""Get the latest version from a remote repo using a cache."""
|
|
33
33
|
status = Status(JanitorStatusTypes.CODEX_LATEST_VERSION)
|
|
34
34
|
try:
|
|
@@ -27,7 +27,7 @@ class UpdateMixin(WorkerBaseMixin):
|
|
|
27
27
|
versio_latest_version = Version(latest_version)
|
|
28
28
|
|
|
29
29
|
installed_versio_version = Version(VERSION)
|
|
30
|
-
if versio_latest_version.parts[1] and not installed_versio_version.parts[1]: # type: ignore
|
|
30
|
+
if versio_latest_version.parts[1] and not installed_versio_version.parts[1]: # type: ignore[reportIndexIssue]
|
|
31
31
|
pre_blurb = "latest version is a prerelease. But installed version is not."
|
|
32
32
|
else:
|
|
33
33
|
result = versio_latest_version > installed_versio_version
|
|
@@ -35,7 +35,7 @@ class UpdateMixin(WorkerBaseMixin):
|
|
|
35
35
|
self.log.debug(f"{latest_version=} > {VERSION=} = {result}{pre_blurb}")
|
|
36
36
|
return result
|
|
37
37
|
|
|
38
|
-
def update_codex(self, force
|
|
38
|
+
def update_codex(self, force: bool):
|
|
39
39
|
"""Update the package and restart everything if the version changed."""
|
|
40
40
|
status = Status(JanitorStatusTypes.CODEX_UPDATE)
|
|
41
41
|
try:
|
|
@@ -30,7 +30,7 @@ class VacuumMixin(WorkerBaseMixin):
|
|
|
30
30
|
finally:
|
|
31
31
|
self.status_controller.finish(status)
|
|
32
32
|
|
|
33
|
-
def backup_db(self, backup_path=BACKUP_DB_PATH
|
|
33
|
+
def backup_db(self, show_status: bool, backup_path=BACKUP_DB_PATH):
|
|
34
34
|
"""Backup the database."""
|
|
35
35
|
status = Status(JanitorStatusTypes.DB_BACKUP) if show_status else ""
|
|
36
36
|
try:
|
codex/librarian/librariand.py
CHANGED
|
@@ -79,7 +79,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
79
79
|
startup_tasks = (
|
|
80
80
|
AdoptOrphanFoldersTask(),
|
|
81
81
|
WatchdogSyncTask(),
|
|
82
|
-
SearchIndexUpdateTask(False),
|
|
82
|
+
SearchIndexUpdateTask(rebuild=False),
|
|
83
83
|
)
|
|
84
84
|
|
|
85
85
|
for task in startup_tasks:
|
|
@@ -193,7 +193,8 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
|
|
|
193
193
|
self.log_queue.join_thread()
|
|
194
194
|
|
|
195
195
|
def run(self):
|
|
196
|
-
"""
|
|
196
|
+
"""
|
|
197
|
+
Process tasks from the queue.
|
|
197
198
|
|
|
198
199
|
This process also runs the crond thread and the Watchdog Observer
|
|
199
200
|
threads.
|
|
@@ -16,7 +16,8 @@ class NotifierThread(AggregateMessageQueuedThread):
|
|
|
16
16
|
self.cache[item.text] = item
|
|
17
17
|
|
|
18
18
|
def _send_task(self, task):
|
|
19
|
-
"""
|
|
19
|
+
"""
|
|
20
|
+
Send a group_send message to the mulitprocess broadcast channel.
|
|
20
21
|
|
|
21
22
|
A random consumer awaiting the broadcast channel will consume it,
|
|
22
23
|
and do a group_send with it's message.
|
|
@@ -22,7 +22,7 @@ class OptimizeMixin(QueuedThread):
|
|
|
22
22
|
self.abort_event = abort_event
|
|
23
23
|
super().__init__(*args, **kwargs)
|
|
24
24
|
|
|
25
|
-
def optimize(self, janitor
|
|
25
|
+
def optimize(self, janitor: bool):
|
|
26
26
|
"""Remove records not in the database from the index, trapping exceptions."""
|
|
27
27
|
start_time = time()
|
|
28
28
|
status = Status(SearchIndexStatusTypes.SEARCH_INDEX_OPTIMIZE)
|
codex/librarian/search/remove.py
CHANGED
|
@@ -21,7 +21,7 @@ class RemoveMixin(OptimizeMixin):
|
|
|
21
21
|
self.status_controller.finish(clear_status)
|
|
22
22
|
self.log.info("Old search index cleared.")
|
|
23
23
|
|
|
24
|
-
def _remove_stale_records(self, status):
|
|
24
|
+
def _remove_stale_records(self, status):
|
|
25
25
|
"""Remove records not in the database from the index."""
|
|
26
26
|
start_time = time()
|
|
27
27
|
delete_comicfts = ComicFTS.objects.filter(comic__isnull=True)
|
codex/librarian/search/update.py
CHANGED
|
@@ -14,7 +14,7 @@ from codex.librarian.search.status import SearchIndexStatusTypes
|
|
|
14
14
|
from codex.models import Comic, Library
|
|
15
15
|
from codex.models.comic import ComicFTS
|
|
16
16
|
from codex.models.functions import GroupConcat
|
|
17
|
-
from codex.serializers.fields import CountryField, LanguageField, PyCountryField
|
|
17
|
+
from codex.serializers.fields.browser import CountryField, LanguageField, PyCountryField
|
|
18
18
|
from codex.settings.settings import SEARCH_INDEX_BATCH_SIZE
|
|
19
19
|
from codex.status import Status
|
|
20
20
|
|
|
@@ -159,7 +159,7 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
159
159
|
obj_list.append(comicfts)
|
|
160
160
|
self.log.debug(f"{len(obj_list)}/{comics.count()} entries prepped.")
|
|
161
161
|
|
|
162
|
-
def _get_comicfts_list(self, comics, create
|
|
162
|
+
def _get_comicfts_list(self, comics, create: bool):
|
|
163
163
|
"""Create a ComicFTS object for bulk_create or bulk_update."""
|
|
164
164
|
country_field = CountryField()
|
|
165
165
|
language_field = LanguageField()
|
|
@@ -178,7 +178,7 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
178
178
|
self.log.debug(f"{verb} no search entries.")
|
|
179
179
|
self.status_controller.finish(status)
|
|
180
180
|
|
|
181
|
-
def _update_search_index_operate(self, comics_qs, create
|
|
181
|
+
def _update_search_index_operate(self, comics_qs, create: bool):
|
|
182
182
|
count = comics_qs.count()
|
|
183
183
|
verb = "create" if create else "update"
|
|
184
184
|
if not count:
|
|
@@ -285,7 +285,7 @@ class FTSUpdateMixin(RemoveMixin):
|
|
|
285
285
|
elapsed = naturaldelta(elapsed_time)
|
|
286
286
|
self.log.info(f"Search index updated in {elapsed}.")
|
|
287
287
|
|
|
288
|
-
def update_search_index(self, rebuild
|
|
288
|
+
def update_search_index(self, rebuild: bool):
|
|
289
289
|
"""Update or Rebuild the search index."""
|
|
290
290
|
start_time = time()
|
|
291
291
|
self.abort_event.clear()
|
|
@@ -62,11 +62,12 @@ class CodexStats:
|
|
|
62
62
|
request_model_set = self.params.get(key, {})
|
|
63
63
|
all_models = _KEY_MODELS_MAP[key]
|
|
64
64
|
if request_model_set:
|
|
65
|
-
models = [
|
|
66
|
-
|
|
67
|
-
for model in all_models
|
|
68
|
-
|
|
69
|
-
|
|
65
|
+
models = [
|
|
66
|
+
model
|
|
67
|
+
for model in all_models
|
|
68
|
+
for model_name in request_model_set
|
|
69
|
+
if model.__name__.lower() == model_name.lower()
|
|
70
|
+
]
|
|
70
71
|
else:
|
|
71
72
|
models = all_models
|
|
72
73
|
return tuple(models)
|
|
@@ -149,7 +150,7 @@ class CodexStats:
|
|
|
149
150
|
|
|
150
151
|
def _add_file_types(self, obj):
|
|
151
152
|
"""Query for file types."""
|
|
152
|
-
if self.params and "
|
|
153
|
+
if self.params and "file_types" not in self.params:
|
|
153
154
|
return
|
|
154
155
|
file_types = {}
|
|
155
156
|
qs = (
|
|
@@ -61,12 +61,12 @@ class CodexDatabaseSnapshot(DirectorySnapshot, LoggerBaseMixin):
|
|
|
61
61
|
def __init__(
|
|
62
62
|
self,
|
|
63
63
|
path,
|
|
64
|
-
_recursive=True, # unused, always recursive
|
|
64
|
+
_recursive=True, # noqa: FBT002 unused, always recursive
|
|
65
65
|
stat=os.stat,
|
|
66
66
|
_listdir=os.listdir, # unused for database
|
|
67
|
-
force=False,
|
|
67
|
+
force=False, # noqa: FBT002
|
|
68
68
|
log_queue=None,
|
|
69
|
-
covers_only=False,
|
|
69
|
+
covers_only=False, # noqa: FBT002
|
|
70
70
|
):
|
|
71
71
|
"""Initialize like DirectorySnapshot but use a database walk."""
|
|
72
72
|
self._covers_only = covers_only
|
|
@@ -33,7 +33,8 @@ class CodexDirectorySnapshotDiff(DirectorySnapshotDiff):
|
|
|
33
33
|
return self._get_inode(data.ref, path) == self._get_inode(data.snapshot, path)
|
|
34
34
|
|
|
35
35
|
def _is_stats_equal(self, data, old_path, new_path):
|
|
36
|
-
"""
|
|
36
|
+
"""
|
|
37
|
+
Return if the mtime and size are equal.
|
|
37
38
|
|
|
38
39
|
For old paths in the ref and new paths in the snapshot.
|
|
39
40
|
"""
|
|
@@ -84,9 +85,7 @@ class CodexDirectorySnapshotDiff(DirectorySnapshotDiff):
|
|
|
84
85
|
if not self._is_stats_equal(data, old_path, new_path):
|
|
85
86
|
data.modified.add(new_path)
|
|
86
87
|
|
|
87
|
-
def __init__(
|
|
88
|
-
self, ref, snapshot, ignore_device=False, inode_only_modified=False
|
|
89
|
-
):
|
|
88
|
+
def __init__(self, ref, snapshot, ignore_device: bool, inode_only_modified: bool):
|
|
90
89
|
"""Create diff object."""
|
|
91
90
|
self._ignore_device = ignore_device
|
|
92
91
|
self._inode_only_modified = inode_only_modified
|