codex 1.6.19__py3-none-any.whl → 1.7.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of codex might be problematic. Click here for more details.

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