codex 1.6.3__py3-none-any.whl → 1.6.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.

Files changed (402) hide show
  1. codex/librarian/importer/importer.py +1 -1
  2. codex/librarian/janitor/cleanup.py +2 -4
  3. codex/librarian/janitor/update.py +10 -9
  4. codex/librarian/search/merge.py +37 -40
  5. codex/librarian/search/remove.py +30 -26
  6. codex/librarian/search/update.py +47 -38
  7. codex/librarian/stats.py +177 -0
  8. codex/librarian/watchdog/events.py +2 -2
  9. codex/serializers/admin/stats.py +77 -65
  10. codex/serializers/fields.py +23 -0
  11. codex/settings/settings.py +3 -3
  12. codex/static_root/assets/{VCheckbox-CXQlndFT.c17c006a62e6.js → VCheckbox-DtXDauZz.54986ad416a1.js} +1 -1
  13. codex/static_root/assets/VCheckbox-DtXDauZz.54986ad416a1.js.br +0 -0
  14. codex/static_root/assets/VCheckbox-DtXDauZz.54986ad416a1.js.gz +0 -0
  15. codex/static_root/assets/{VCheckbox-CXQlndFT.js → VCheckbox-DtXDauZz.js} +1 -1
  16. codex/static_root/assets/VCheckbox-DtXDauZz.js.br +0 -0
  17. codex/static_root/assets/VCheckbox-DtXDauZz.js.gz +0 -0
  18. codex/static_root/assets/{VCheckboxBtn-DB72Bclg.ab414c6ae2b8.js → VCheckboxBtn-B-iT1MnG.8505f6eed0fe.js} +1 -1
  19. codex/static_root/assets/VCheckboxBtn-B-iT1MnG.8505f6eed0fe.js.br +0 -0
  20. codex/static_root/assets/VCheckboxBtn-B-iT1MnG.8505f6eed0fe.js.gz +0 -0
  21. codex/static_root/assets/{VCheckboxBtn-DB72Bclg.js → VCheckboxBtn-B-iT1MnG.js} +1 -1
  22. codex/static_root/assets/VCheckboxBtn-B-iT1MnG.js.br +0 -0
  23. codex/static_root/assets/VCheckboxBtn-B-iT1MnG.js.gz +0 -0
  24. codex/static_root/assets/{VCombobox-BDcFPzYf.0b80447cd638.js → VCombobox-DZmum3X-.330883d59742.js} +1 -1
  25. codex/static_root/assets/VCombobox-DZmum3X-.330883d59742.js.br +0 -0
  26. codex/static_root/assets/VCombobox-DZmum3X-.330883d59742.js.gz +0 -0
  27. codex/static_root/assets/{VCombobox-BDcFPzYf.js → VCombobox-DZmum3X-.js} +1 -1
  28. codex/static_root/assets/VCombobox-DZmum3X-.js.br +0 -0
  29. codex/static_root/assets/VCombobox-DZmum3X-.js.gz +0 -0
  30. codex/static_root/assets/{VDialog-BB4zqrGw.2decc14ea597.js → VDialog-DUdNpXLj.6088d518d3af.js} +1 -1
  31. codex/static_root/assets/VDialog-DUdNpXLj.6088d518d3af.js.br +0 -0
  32. codex/static_root/assets/VDialog-DUdNpXLj.6088d518d3af.js.gz +0 -0
  33. codex/static_root/assets/{VDialog-BB4zqrGw.js → VDialog-DUdNpXLj.js} +1 -1
  34. codex/static_root/assets/VDialog-DUdNpXLj.js.br +0 -0
  35. codex/static_root/assets/VDialog-DUdNpXLj.js.gz +0 -0
  36. codex/static_root/assets/{VExpansionPanels-SZc735F5.19004a0eba16.js → VExpansionPanels-CurqcyFg.860017abf25d.js} +1 -1
  37. codex/static_root/assets/VExpansionPanels-CurqcyFg.860017abf25d.js.br +0 -0
  38. codex/static_root/assets/VExpansionPanels-CurqcyFg.860017abf25d.js.gz +0 -0
  39. codex/static_root/assets/{VExpansionPanels-SZc735F5.js → VExpansionPanels-CurqcyFg.js} +1 -1
  40. codex/static_root/assets/VExpansionPanels-CurqcyFg.js.br +0 -0
  41. codex/static_root/assets/VExpansionPanels-CurqcyFg.js.gz +0 -0
  42. codex/static_root/assets/{VRadioGroup-tFEuhFRq.774e14a4945a.js → VRadioGroup-B7n5ReZ-.8acbc71ac411.js} +1 -1
  43. codex/static_root/assets/VRadioGroup-B7n5ReZ-.8acbc71ac411.js.br +0 -0
  44. codex/static_root/assets/VRadioGroup-B7n5ReZ-.8acbc71ac411.js.gz +0 -0
  45. codex/static_root/assets/{VRadioGroup-tFEuhFRq.js → VRadioGroup-B7n5ReZ-.js} +1 -1
  46. codex/static_root/assets/VRadioGroup-B7n5ReZ-.js.br +0 -0
  47. codex/static_root/assets/VRadioGroup-B7n5ReZ-.js.gz +0 -0
  48. codex/static_root/assets/{VSelect-Dd5Zy8Uy.f225abf824ff.js → VSelect-FYWbH55D.83e91b93fdd1.js} +1 -1
  49. codex/static_root/assets/VSelect-FYWbH55D.83e91b93fdd1.js.br +0 -0
  50. codex/static_root/assets/VSelect-FYWbH55D.83e91b93fdd1.js.gz +0 -0
  51. codex/static_root/assets/{VSelect-Dd5Zy8Uy.js → VSelect-FYWbH55D.js} +1 -1
  52. codex/static_root/assets/VSelect-FYWbH55D.js.br +0 -0
  53. codex/static_root/assets/VSelect-FYWbH55D.js.gz +0 -0
  54. codex/static_root/assets/{VSelectionControl-DImIQPJV.1f14d2113063.js → VSelectionControl-CUlT3cc_.63f60240eb49.js} +1 -1
  55. codex/static_root/assets/VSelectionControl-CUlT3cc_.63f60240eb49.js.br +0 -0
  56. codex/static_root/assets/VSelectionControl-CUlT3cc_.63f60240eb49.js.gz +0 -0
  57. codex/static_root/assets/{VSelectionControl-DImIQPJV.js → VSelectionControl-CUlT3cc_.js} +1 -1
  58. codex/static_root/assets/VSelectionControl-CUlT3cc_.js.br +0 -0
  59. codex/static_root/assets/VSelectionControl-CUlT3cc_.js.gz +0 -0
  60. codex/static_root/assets/{VSlideGroup-CDaAHRAK.783800d70a8a.js → VSlideGroup-CRJazQeR.cb89b2222518.js} +1 -1
  61. codex/static_root/assets/VSlideGroup-CRJazQeR.cb89b2222518.js.br +0 -0
  62. codex/static_root/assets/VSlideGroup-CRJazQeR.cb89b2222518.js.gz +0 -0
  63. codex/static_root/assets/{VSlideGroup-CDaAHRAK.js → VSlideGroup-CRJazQeR.js} +1 -1
  64. codex/static_root/assets/VSlideGroup-CRJazQeR.js.br +0 -0
  65. codex/static_root/assets/VSlideGroup-CRJazQeR.js.gz +0 -0
  66. codex/static_root/assets/{VTable-CkCZonsu.81e34381271f.js → VTable-DtyBEZ5i.6848f15ea03f.js} +1 -1
  67. codex/static_root/assets/VTable-DtyBEZ5i.6848f15ea03f.js.br +0 -0
  68. codex/static_root/assets/VTable-DtyBEZ5i.6848f15ea03f.js.gz +0 -0
  69. codex/static_root/assets/{VTable-CkCZonsu.js → VTable-DtyBEZ5i.js} +1 -1
  70. codex/static_root/assets/VTable-DtyBEZ5i.js.br +0 -0
  71. codex/static_root/assets/VTable-DtyBEZ5i.js.gz +0 -0
  72. codex/static_root/assets/{VTextField-fNAzFu1M.00dec8623f18.js → VTextField-DEiVah7O.fbf3130f2dea.js} +1 -1
  73. codex/static_root/assets/VTextField-DEiVah7O.fbf3130f2dea.js.br +0 -0
  74. codex/static_root/assets/VTextField-DEiVah7O.fbf3130f2dea.js.gz +0 -0
  75. codex/static_root/assets/{VTextField-fNAzFu1M.js → VTextField-DEiVah7O.js} +1 -1
  76. codex/static_root/assets/VTextField-DEiVah7O.js.br +0 -0
  77. codex/static_root/assets/VTextField-DEiVah7O.js.gz +0 -0
  78. codex/static_root/assets/{VWindowItem-_YBMW1SU.0836924f8c12.js → VWindowItem-BVIaHO-S.419e4ecf523d.js} +1 -1
  79. codex/static_root/assets/VWindowItem-BVIaHO-S.419e4ecf523d.js.br +0 -0
  80. codex/static_root/assets/VWindowItem-BVIaHO-S.419e4ecf523d.js.gz +0 -0
  81. codex/static_root/assets/{VWindowItem-_YBMW1SU.js → VWindowItem-BVIaHO-S.js} +1 -1
  82. codex/static_root/assets/VWindowItem-BVIaHO-S.js.br +0 -0
  83. codex/static_root/assets/VWindowItem-BVIaHO-S.js.gz +0 -0
  84. codex/static_root/assets/{admin-DYmGyJVg.262e6032ee86.js → admin-S53JleJt.f629d90f73a1.js} +1 -1
  85. codex/static_root/assets/admin-S53JleJt.f629d90f73a1.js.br +0 -0
  86. codex/static_root/assets/admin-S53JleJt.f629d90f73a1.js.gz +0 -0
  87. codex/static_root/assets/{admin-DYmGyJVg.js → admin-S53JleJt.js} +1 -1
  88. codex/static_root/assets/admin-S53JleJt.js.br +0 -0
  89. codex/static_root/assets/admin-S53JleJt.js.gz +0 -0
  90. codex/static_root/assets/{admin-drawer-panel-c3f3H29x.fa2b12c75b06.js → admin-drawer-panel-CnAnGx7w.1e4eb021cb21.js} +20 -20
  91. codex/static_root/assets/admin-drawer-panel-CnAnGx7w.1e4eb021cb21.js.br +0 -0
  92. codex/static_root/assets/admin-drawer-panel-CnAnGx7w.1e4eb021cb21.js.gz +0 -0
  93. codex/static_root/assets/{admin-drawer-panel-c3f3H29x.js → admin-drawer-panel-CnAnGx7w.js} +20 -20
  94. codex/static_root/assets/admin-drawer-panel-CnAnGx7w.js.br +0 -0
  95. codex/static_root/assets/admin-drawer-panel-CnAnGx7w.js.gz +0 -0
  96. codex/static_root/assets/{browser-Ck3zWCOk.d98c77747e6d.js → browser-BHHgqDP7.7ada4400d63f.js} +1 -1
  97. codex/static_root/assets/browser-BHHgqDP7.7ada4400d63f.js.br +0 -0
  98. codex/static_root/assets/browser-BHHgqDP7.7ada4400d63f.js.gz +0 -0
  99. codex/static_root/assets/{browser-Ck3zWCOk.js → browser-BHHgqDP7.js} +1 -1
  100. codex/static_root/assets/browser-BHHgqDP7.js.br +0 -0
  101. codex/static_root/assets/browser-BHHgqDP7.js.gz +0 -0
  102. codex/static_root/assets/{browser-DgawKPSA.c7cf59dc4b21.css → browser-Cy0OgQJC.css} +1 -1
  103. codex/static_root/assets/browser-Cy0OgQJC.css.br +0 -0
  104. codex/static_root/assets/browser-Cy0OgQJC.css.gz +0 -0
  105. codex/static_root/assets/{browser-DgawKPSA.css → browser-Cy0OgQJC.fabbd1aaffcf.css} +1 -1
  106. codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.br +0 -0
  107. codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.gz +0 -0
  108. codex/static_root/assets/{change-password-dialog-BbIO0YI5.2bb0a5dfb190.js → change-password-dialog-Du1bwEt1.a4c0205c9bc4.js} +1 -1
  109. codex/static_root/assets/change-password-dialog-Du1bwEt1.a4c0205c9bc4.js.br +0 -0
  110. codex/static_root/assets/change-password-dialog-Du1bwEt1.a4c0205c9bc4.js.gz +0 -0
  111. codex/static_root/assets/{change-password-dialog-BbIO0YI5.js → change-password-dialog-Du1bwEt1.js} +1 -1
  112. codex/static_root/assets/change-password-dialog-Du1bwEt1.js.br +0 -0
  113. codex/static_root/assets/change-password-dialog-Du1bwEt1.js.gz +0 -0
  114. codex/static_root/assets/{confirm-dialog-BrzuoYAT.a0a1b6364708.js → confirm-dialog-eKN71B8a.af6187dc12bd.js} +1 -1
  115. codex/static_root/assets/confirm-dialog-eKN71B8a.af6187dc12bd.js.br +0 -0
  116. codex/static_root/assets/confirm-dialog-eKN71B8a.af6187dc12bd.js.gz +0 -0
  117. codex/static_root/assets/{confirm-dialog-BrzuoYAT.js → confirm-dialog-eKN71B8a.js} +1 -1
  118. codex/static_root/assets/confirm-dialog-eKN71B8a.js.br +0 -0
  119. codex/static_root/assets/confirm-dialog-eKN71B8a.js.gz +0 -0
  120. codex/static_root/assets/{datetime-column-D8-UeX_p.e33b1556805b.js → datetime-column-CWp600fz.e8a969126bf5.js} +1 -1
  121. codex/static_root/assets/datetime-column-CWp600fz.e8a969126bf5.js.br +0 -0
  122. codex/static_root/assets/datetime-column-CWp600fz.e8a969126bf5.js.gz +0 -0
  123. codex/static_root/assets/{datetime-column-D8-UeX_p.js → datetime-column-CWp600fz.js} +1 -1
  124. codex/static_root/assets/datetime-column-CWp600fz.js.br +0 -0
  125. codex/static_root/assets/datetime-column-CWp600fz.js.gz +0 -0
  126. codex/static_root/assets/{filter-XMAX5Nmg.c6ed87350f7d.js → filter-_XbKRiIR.2c7ac68a2d03.js} +1 -1
  127. codex/static_root/assets/filter-_XbKRiIR.2c7ac68a2d03.js.br +0 -0
  128. codex/static_root/assets/filter-_XbKRiIR.2c7ac68a2d03.js.gz +0 -0
  129. codex/static_root/assets/{filter-XMAX5Nmg.js → filter-_XbKRiIR.js} +1 -1
  130. codex/static_root/assets/filter-_XbKRiIR.js.br +0 -0
  131. codex/static_root/assets/filter-_XbKRiIR.js.gz +0 -0
  132. codex/static_root/assets/flag-tab-DBqpLKi7.css +1 -0
  133. codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
  134. codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
  135. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +1 -0
  136. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
  137. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
  138. codex/static_root/assets/flag-tab-DR_nY0Jg.7323e386c6ed.js +1 -0
  139. codex/static_root/assets/flag-tab-DR_nY0Jg.7323e386c6ed.js.br +0 -0
  140. codex/static_root/assets/flag-tab-DR_nY0Jg.7323e386c6ed.js.gz +0 -0
  141. codex/static_root/assets/flag-tab-DR_nY0Jg.js +1 -0
  142. codex/static_root/assets/flag-tab-DR_nY0Jg.js.br +0 -0
  143. codex/static_root/assets/flag-tab-DR_nY0Jg.js.gz +0 -0
  144. codex/static_root/assets/{group-tab-D9n5gZCA.634223001c44.js → group-tab-JKsMHOmK.d2c2f3fceec1.js} +1 -1
  145. codex/static_root/assets/group-tab-JKsMHOmK.d2c2f3fceec1.js.br +0 -0
  146. codex/static_root/assets/group-tab-JKsMHOmK.d2c2f3fceec1.js.gz +0 -0
  147. codex/static_root/assets/{group-tab-D9n5gZCA.js → group-tab-JKsMHOmK.js} +1 -1
  148. codex/static_root/assets/group-tab-JKsMHOmK.js.br +0 -0
  149. codex/static_root/assets/group-tab-JKsMHOmK.js.gz +0 -0
  150. codex/static_root/assets/{http-error-C_EFLi0c.c7e906206b62.js → http-error-Bw1wUv3U.c8313a2fb129.js} +1 -1
  151. codex/static_root/assets/http-error-Bw1wUv3U.c8313a2fb129.js.br +0 -0
  152. codex/static_root/assets/http-error-Bw1wUv3U.c8313a2fb129.js.gz +0 -0
  153. codex/static_root/assets/{http-error-C_EFLi0c.js → http-error-Bw1wUv3U.js} +1 -1
  154. codex/static_root/assets/http-error-Bw1wUv3U.js.br +0 -0
  155. codex/static_root/assets/http-error-Bw1wUv3U.js.gz +0 -0
  156. codex/static_root/assets/{library-tab-fDxuhPBs.285b36d9cec0.js → library-tab-D9GTugZ6.8e3e6ebd38b7.js} +1 -1
  157. codex/static_root/assets/library-tab-D9GTugZ6.8e3e6ebd38b7.js.br +0 -0
  158. codex/static_root/assets/library-tab-D9GTugZ6.8e3e6ebd38b7.js.gz +0 -0
  159. codex/static_root/assets/{library-tab-fDxuhPBs.js → library-tab-D9GTugZ6.js} +1 -1
  160. codex/static_root/assets/library-tab-D9GTugZ6.js.br +0 -0
  161. codex/static_root/assets/library-tab-D9GTugZ6.js.gz +0 -0
  162. codex/static_root/assets/main-BJiV8IG3.9d708627091f.js +6 -0
  163. codex/static_root/assets/main-BJiV8IG3.9d708627091f.js.br +0 -0
  164. codex/static_root/assets/main-BJiV8IG3.9d708627091f.js.gz +0 -0
  165. codex/static_root/assets/main-BJiV8IG3.js +6 -0
  166. codex/static_root/assets/main-BJiV8IG3.js.br +0 -0
  167. codex/static_root/assets/main-BJiV8IG3.js.gz +0 -0
  168. codex/static_root/assets/{pagination-toolbar-BD6nzIKF.f3ca4874c203.js → pagination-toolbar-i2VoXaGH.c3dbb703af2e.js} +1 -1
  169. codex/static_root/assets/pagination-toolbar-i2VoXaGH.c3dbb703af2e.js.br +0 -0
  170. codex/static_root/assets/pagination-toolbar-i2VoXaGH.c3dbb703af2e.js.gz +0 -0
  171. codex/static_root/assets/{pagination-toolbar-BD6nzIKF.js → pagination-toolbar-i2VoXaGH.js} +1 -1
  172. codex/static_root/assets/pagination-toolbar-i2VoXaGH.js.br +0 -0
  173. codex/static_root/assets/pagination-toolbar-i2VoXaGH.js.gz +0 -0
  174. codex/static_root/assets/{pdf-doc-iF15lKRp.de9a9d394438.js → pdf-doc-BLxZLD38.4cf9622c535f.js} +1 -1
  175. codex/static_root/assets/pdf-doc-BLxZLD38.4cf9622c535f.js.br +0 -0
  176. codex/static_root/assets/pdf-doc-BLxZLD38.4cf9622c535f.js.gz +0 -0
  177. codex/static_root/assets/{pdf-doc-iF15lKRp.js → pdf-doc-BLxZLD38.js} +1 -1
  178. codex/static_root/assets/pdf-doc-BLxZLD38.js.br +0 -0
  179. codex/static_root/assets/pdf-doc-BLxZLD38.js.gz +0 -0
  180. codex/static_root/assets/{reader-5Ja_bAaT.2fe33a3891f6.js → reader-D2O_yHTW.70d616ddc2cc.js} +2 -2
  181. codex/static_root/assets/reader-D2O_yHTW.70d616ddc2cc.js.br +0 -0
  182. codex/static_root/assets/reader-D2O_yHTW.70d616ddc2cc.js.gz +0 -0
  183. codex/static_root/assets/{reader-5Ja_bAaT.js → reader-D2O_yHTW.js} +2 -2
  184. codex/static_root/assets/reader-D2O_yHTW.js.br +0 -0
  185. codex/static_root/assets/reader-D2O_yHTW.js.gz +0 -0
  186. codex/static_root/assets/{relation-chips-CUWkvLNo.3d18d0c09d39.js → relation-chips-CmeSIsRq.f169f7c3f48d.js} +1 -1
  187. codex/static_root/assets/relation-chips-CmeSIsRq.f169f7c3f48d.js.br +0 -0
  188. codex/static_root/assets/relation-chips-CmeSIsRq.f169f7c3f48d.js.gz +0 -0
  189. codex/static_root/assets/{relation-chips-CUWkvLNo.js → relation-chips-CmeSIsRq.js} +1 -1
  190. codex/static_root/assets/relation-chips-CmeSIsRq.js.br +0 -0
  191. codex/static_root/assets/relation-chips-CmeSIsRq.js.gz +0 -0
  192. codex/static_root/assets/{settings-drawer-rn3JdMxA.js → settings-drawer-XH4sNDNT.98ee4b24901c.js} +2 -2
  193. codex/static_root/assets/settings-drawer-XH4sNDNT.98ee4b24901c.js.br +0 -0
  194. codex/static_root/assets/settings-drawer-XH4sNDNT.98ee4b24901c.js.gz +0 -0
  195. codex/static_root/assets/{settings-drawer-rn3JdMxA.a9d7b45c2138.js → settings-drawer-XH4sNDNT.js} +2 -2
  196. codex/static_root/assets/settings-drawer-XH4sNDNT.js.br +0 -0
  197. codex/static_root/assets/settings-drawer-XH4sNDNT.js.gz +0 -0
  198. codex/static_root/assets/stats-tab-BGxkeKnY.44cf18ca895d.js +1 -0
  199. codex/static_root/assets/stats-tab-BGxkeKnY.44cf18ca895d.js.br +0 -0
  200. codex/static_root/assets/stats-tab-BGxkeKnY.44cf18ca895d.js.gz +0 -0
  201. codex/static_root/assets/stats-tab-BGxkeKnY.js +1 -0
  202. codex/static_root/assets/stats-tab-BGxkeKnY.js.br +0 -0
  203. codex/static_root/assets/stats-tab-BGxkeKnY.js.gz +0 -0
  204. codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css +1 -0
  205. codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.br +0 -0
  206. codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.gz +0 -0
  207. codex/static_root/assets/stats-tab-CI0u-X7Y.css +1 -0
  208. codex/static_root/assets/stats-tab-CI0u-X7Y.css.br +0 -0
  209. codex/static_root/assets/stats-tab-CI0u-X7Y.css.gz +0 -0
  210. codex/static_root/assets/{task-tab-CbzDX0IR.7b43bdf9cf42.js → task-tab-B74Kah_b.58627993d3c6.js} +1 -1
  211. codex/static_root/assets/task-tab-B74Kah_b.58627993d3c6.js.br +0 -0
  212. codex/static_root/assets/task-tab-B74Kah_b.58627993d3c6.js.gz +0 -0
  213. codex/static_root/assets/{task-tab-CbzDX0IR.js → task-tab-B74Kah_b.js} +1 -1
  214. codex/static_root/assets/task-tab-B74Kah_b.js.br +0 -0
  215. codex/static_root/assets/task-tab-B74Kah_b.js.gz +0 -0
  216. codex/static_root/assets/{unauthorized-CllrtnFV.2a18fab3dd41.js → unauthorized-BF45der6.d34f8022124b.js} +1 -1
  217. codex/static_root/assets/unauthorized-BF45der6.d34f8022124b.js.br +0 -0
  218. codex/static_root/assets/unauthorized-BF45der6.d34f8022124b.js.gz +0 -0
  219. codex/static_root/assets/{unauthorized-CllrtnFV.js → unauthorized-BF45der6.js} +1 -1
  220. codex/static_root/assets/unauthorized-BF45der6.js.br +0 -0
  221. codex/static_root/assets/unauthorized-BF45der6.js.gz +0 -0
  222. codex/static_root/assets/{user-tab-FY1xALpi.05a88f51df4f.js → user-tab-BP2TTPg5.e63a5661a1a5.js} +1 -1
  223. codex/static_root/assets/user-tab-BP2TTPg5.e63a5661a1a5.js.br +0 -0
  224. codex/static_root/assets/user-tab-BP2TTPg5.e63a5661a1a5.js.gz +0 -0
  225. codex/static_root/assets/{user-tab-FY1xALpi.js → user-tab-BP2TTPg5.js} +1 -1
  226. codex/static_root/assets/user-tab-BP2TTPg5.js.br +0 -0
  227. codex/static_root/assets/user-tab-BP2TTPg5.js.gz +0 -0
  228. codex/static_root/{manifest.166aa32817b1.json → manifest.7033f9a22987.json} +216 -216
  229. codex/static_root/manifest.7033f9a22987.json.br +0 -0
  230. codex/static_root/manifest.7033f9a22987.json.gz +0 -0
  231. codex/static_root/manifest.json +216 -216
  232. codex/static_root/manifest.json.br +0 -0
  233. codex/static_root/manifest.json.gz +0 -0
  234. codex/static_root/staticfiles.json +1 -1
  235. codex/urls/root.py +12 -2
  236. codex/views/admin/stats.py +38 -186
  237. codex/views/opds/v1/entry/entry.py +14 -18
  238. codex/views/opds/v1/feed.py +5 -1
  239. codex/views/opds/v1/links.py +1 -0
  240. codex/views/reader/arcs.py +5 -4
  241. {codex-1.6.3.dist-info → codex-1.6.4.dist-info}/METADATA +18 -7
  242. {codex-1.6.3.dist-info → codex-1.6.4.dist-info}/RECORD +245 -245
  243. codex/settings/logging.py +0 -20
  244. codex/static_root/assets/VCheckbox-CXQlndFT.c17c006a62e6.js.br +0 -0
  245. codex/static_root/assets/VCheckbox-CXQlndFT.c17c006a62e6.js.gz +0 -0
  246. codex/static_root/assets/VCheckbox-CXQlndFT.js.br +0 -0
  247. codex/static_root/assets/VCheckbox-CXQlndFT.js.gz +0 -0
  248. codex/static_root/assets/VCheckboxBtn-DB72Bclg.ab414c6ae2b8.js.br +0 -0
  249. codex/static_root/assets/VCheckboxBtn-DB72Bclg.ab414c6ae2b8.js.gz +0 -0
  250. codex/static_root/assets/VCheckboxBtn-DB72Bclg.js.br +0 -0
  251. codex/static_root/assets/VCheckboxBtn-DB72Bclg.js.gz +0 -0
  252. codex/static_root/assets/VCombobox-BDcFPzYf.0b80447cd638.js.br +0 -0
  253. codex/static_root/assets/VCombobox-BDcFPzYf.0b80447cd638.js.gz +0 -0
  254. codex/static_root/assets/VCombobox-BDcFPzYf.js.br +0 -0
  255. codex/static_root/assets/VCombobox-BDcFPzYf.js.gz +0 -0
  256. codex/static_root/assets/VDialog-BB4zqrGw.2decc14ea597.js.br +0 -0
  257. codex/static_root/assets/VDialog-BB4zqrGw.2decc14ea597.js.gz +0 -0
  258. codex/static_root/assets/VDialog-BB4zqrGw.js.br +0 -0
  259. codex/static_root/assets/VDialog-BB4zqrGw.js.gz +0 -0
  260. codex/static_root/assets/VExpansionPanels-SZc735F5.19004a0eba16.js.br +0 -0
  261. codex/static_root/assets/VExpansionPanels-SZc735F5.19004a0eba16.js.gz +0 -0
  262. codex/static_root/assets/VExpansionPanels-SZc735F5.js.br +0 -0
  263. codex/static_root/assets/VExpansionPanels-SZc735F5.js.gz +0 -0
  264. codex/static_root/assets/VRadioGroup-tFEuhFRq.774e14a4945a.js.br +0 -0
  265. codex/static_root/assets/VRadioGroup-tFEuhFRq.774e14a4945a.js.gz +0 -0
  266. codex/static_root/assets/VRadioGroup-tFEuhFRq.js.br +0 -0
  267. codex/static_root/assets/VRadioGroup-tFEuhFRq.js.gz +0 -0
  268. codex/static_root/assets/VSelect-Dd5Zy8Uy.f225abf824ff.js.br +0 -0
  269. codex/static_root/assets/VSelect-Dd5Zy8Uy.f225abf824ff.js.gz +0 -0
  270. codex/static_root/assets/VSelect-Dd5Zy8Uy.js.br +0 -0
  271. codex/static_root/assets/VSelect-Dd5Zy8Uy.js.gz +0 -0
  272. codex/static_root/assets/VSelectionControl-DImIQPJV.1f14d2113063.js.br +0 -0
  273. codex/static_root/assets/VSelectionControl-DImIQPJV.1f14d2113063.js.gz +0 -0
  274. codex/static_root/assets/VSelectionControl-DImIQPJV.js.br +0 -0
  275. codex/static_root/assets/VSelectionControl-DImIQPJV.js.gz +0 -0
  276. codex/static_root/assets/VSlideGroup-CDaAHRAK.783800d70a8a.js.br +0 -0
  277. codex/static_root/assets/VSlideGroup-CDaAHRAK.783800d70a8a.js.gz +0 -0
  278. codex/static_root/assets/VSlideGroup-CDaAHRAK.js.br +0 -0
  279. codex/static_root/assets/VSlideGroup-CDaAHRAK.js.gz +0 -0
  280. codex/static_root/assets/VTable-CkCZonsu.81e34381271f.js.br +0 -0
  281. codex/static_root/assets/VTable-CkCZonsu.81e34381271f.js.gz +0 -0
  282. codex/static_root/assets/VTable-CkCZonsu.js.br +0 -0
  283. codex/static_root/assets/VTable-CkCZonsu.js.gz +0 -0
  284. codex/static_root/assets/VTextField-fNAzFu1M.00dec8623f18.js.br +0 -0
  285. codex/static_root/assets/VTextField-fNAzFu1M.00dec8623f18.js.gz +0 -0
  286. codex/static_root/assets/VTextField-fNAzFu1M.js.br +0 -0
  287. codex/static_root/assets/VTextField-fNAzFu1M.js.gz +0 -0
  288. codex/static_root/assets/VWindowItem-_YBMW1SU.0836924f8c12.js.br +0 -0
  289. codex/static_root/assets/VWindowItem-_YBMW1SU.0836924f8c12.js.gz +0 -0
  290. codex/static_root/assets/VWindowItem-_YBMW1SU.js.br +0 -0
  291. codex/static_root/assets/VWindowItem-_YBMW1SU.js.gz +0 -0
  292. codex/static_root/assets/admin-DYmGyJVg.262e6032ee86.js.br +0 -0
  293. codex/static_root/assets/admin-DYmGyJVg.262e6032ee86.js.gz +0 -0
  294. codex/static_root/assets/admin-DYmGyJVg.js.br +0 -0
  295. codex/static_root/assets/admin-DYmGyJVg.js.gz +0 -0
  296. codex/static_root/assets/admin-drawer-panel-c3f3H29x.fa2b12c75b06.js.br +0 -0
  297. codex/static_root/assets/admin-drawer-panel-c3f3H29x.fa2b12c75b06.js.gz +0 -0
  298. codex/static_root/assets/admin-drawer-panel-c3f3H29x.js.br +0 -0
  299. codex/static_root/assets/admin-drawer-panel-c3f3H29x.js.gz +0 -0
  300. codex/static_root/assets/browser-Ck3zWCOk.d98c77747e6d.js.br +0 -0
  301. codex/static_root/assets/browser-Ck3zWCOk.d98c77747e6d.js.gz +0 -0
  302. codex/static_root/assets/browser-Ck3zWCOk.js.br +0 -0
  303. codex/static_root/assets/browser-Ck3zWCOk.js.gz +0 -0
  304. codex/static_root/assets/browser-DgawKPSA.c7cf59dc4b21.css.br +0 -0
  305. codex/static_root/assets/browser-DgawKPSA.c7cf59dc4b21.css.gz +0 -0
  306. codex/static_root/assets/browser-DgawKPSA.css.br +0 -0
  307. codex/static_root/assets/browser-DgawKPSA.css.gz +0 -0
  308. codex/static_root/assets/change-password-dialog-BbIO0YI5.2bb0a5dfb190.js.br +0 -0
  309. codex/static_root/assets/change-password-dialog-BbIO0YI5.2bb0a5dfb190.js.gz +0 -0
  310. codex/static_root/assets/change-password-dialog-BbIO0YI5.js.br +0 -0
  311. codex/static_root/assets/change-password-dialog-BbIO0YI5.js.gz +0 -0
  312. codex/static_root/assets/confirm-dialog-BrzuoYAT.a0a1b6364708.js.br +0 -0
  313. codex/static_root/assets/confirm-dialog-BrzuoYAT.a0a1b6364708.js.gz +0 -0
  314. codex/static_root/assets/confirm-dialog-BrzuoYAT.js.br +0 -0
  315. codex/static_root/assets/confirm-dialog-BrzuoYAT.js.gz +0 -0
  316. codex/static_root/assets/datetime-column-D8-UeX_p.e33b1556805b.js.br +0 -0
  317. codex/static_root/assets/datetime-column-D8-UeX_p.e33b1556805b.js.gz +0 -0
  318. codex/static_root/assets/datetime-column-D8-UeX_p.js.br +0 -0
  319. codex/static_root/assets/datetime-column-D8-UeX_p.js.gz +0 -0
  320. codex/static_root/assets/filter-XMAX5Nmg.c6ed87350f7d.js.br +0 -0
  321. codex/static_root/assets/filter-XMAX5Nmg.c6ed87350f7d.js.gz +0 -0
  322. codex/static_root/assets/filter-XMAX5Nmg.js.br +0 -0
  323. codex/static_root/assets/filter-XMAX5Nmg.js.gz +0 -0
  324. codex/static_root/assets/flag-tab-BLEUVMot.c14936c90346.js +0 -1
  325. codex/static_root/assets/flag-tab-BLEUVMot.c14936c90346.js.br +0 -0
  326. codex/static_root/assets/flag-tab-BLEUVMot.c14936c90346.js.gz +0 -0
  327. codex/static_root/assets/flag-tab-BLEUVMot.js +0 -1
  328. codex/static_root/assets/flag-tab-BLEUVMot.js.br +0 -0
  329. codex/static_root/assets/flag-tab-BLEUVMot.js.gz +0 -0
  330. codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css +0 -1
  331. codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.br +0 -0
  332. codex/static_root/assets/flag-tab-BNwbLfrN.b755a99fff7e.css.gz +0 -0
  333. codex/static_root/assets/flag-tab-BNwbLfrN.css +0 -1
  334. codex/static_root/assets/flag-tab-BNwbLfrN.css.br +0 -0
  335. codex/static_root/assets/flag-tab-BNwbLfrN.css.gz +0 -0
  336. codex/static_root/assets/group-tab-D9n5gZCA.634223001c44.js.br +0 -0
  337. codex/static_root/assets/group-tab-D9n5gZCA.634223001c44.js.gz +0 -0
  338. codex/static_root/assets/group-tab-D9n5gZCA.js.br +0 -0
  339. codex/static_root/assets/group-tab-D9n5gZCA.js.gz +0 -0
  340. codex/static_root/assets/http-error-C_EFLi0c.c7e906206b62.js.br +0 -0
  341. codex/static_root/assets/http-error-C_EFLi0c.c7e906206b62.js.gz +0 -0
  342. codex/static_root/assets/http-error-C_EFLi0c.js.br +0 -0
  343. codex/static_root/assets/http-error-C_EFLi0c.js.gz +0 -0
  344. codex/static_root/assets/library-tab-fDxuhPBs.285b36d9cec0.js.br +0 -0
  345. codex/static_root/assets/library-tab-fDxuhPBs.285b36d9cec0.js.gz +0 -0
  346. codex/static_root/assets/library-tab-fDxuhPBs.js.br +0 -0
  347. codex/static_root/assets/library-tab-fDxuhPBs.js.gz +0 -0
  348. codex/static_root/assets/main-B3MfqYNN.30d1310fc082.js +0 -6
  349. codex/static_root/assets/main-B3MfqYNN.30d1310fc082.js.br +0 -0
  350. codex/static_root/assets/main-B3MfqYNN.30d1310fc082.js.gz +0 -0
  351. codex/static_root/assets/main-B3MfqYNN.js +0 -6
  352. codex/static_root/assets/main-B3MfqYNN.js.br +0 -0
  353. codex/static_root/assets/main-B3MfqYNN.js.gz +0 -0
  354. codex/static_root/assets/pagination-toolbar-BD6nzIKF.f3ca4874c203.js.br +0 -0
  355. codex/static_root/assets/pagination-toolbar-BD6nzIKF.f3ca4874c203.js.gz +0 -0
  356. codex/static_root/assets/pagination-toolbar-BD6nzIKF.js.br +0 -0
  357. codex/static_root/assets/pagination-toolbar-BD6nzIKF.js.gz +0 -0
  358. codex/static_root/assets/pdf-doc-iF15lKRp.de9a9d394438.js.br +0 -0
  359. codex/static_root/assets/pdf-doc-iF15lKRp.de9a9d394438.js.gz +0 -0
  360. codex/static_root/assets/pdf-doc-iF15lKRp.js.br +0 -0
  361. codex/static_root/assets/pdf-doc-iF15lKRp.js.gz +0 -0
  362. codex/static_root/assets/reader-5Ja_bAaT.2fe33a3891f6.js.br +0 -0
  363. codex/static_root/assets/reader-5Ja_bAaT.2fe33a3891f6.js.gz +0 -0
  364. codex/static_root/assets/reader-5Ja_bAaT.js.br +0 -0
  365. codex/static_root/assets/reader-5Ja_bAaT.js.gz +0 -0
  366. codex/static_root/assets/relation-chips-CUWkvLNo.3d18d0c09d39.js.br +0 -0
  367. codex/static_root/assets/relation-chips-CUWkvLNo.3d18d0c09d39.js.gz +0 -0
  368. codex/static_root/assets/relation-chips-CUWkvLNo.js.br +0 -0
  369. codex/static_root/assets/relation-chips-CUWkvLNo.js.gz +0 -0
  370. codex/static_root/assets/settings-drawer-rn3JdMxA.a9d7b45c2138.js.br +0 -0
  371. codex/static_root/assets/settings-drawer-rn3JdMxA.a9d7b45c2138.js.gz +0 -0
  372. codex/static_root/assets/settings-drawer-rn3JdMxA.js.br +0 -0
  373. codex/static_root/assets/settings-drawer-rn3JdMxA.js.gz +0 -0
  374. codex/static_root/assets/stats-tab-B6L-YFRA.1c90593f7b5c.css +0 -1
  375. codex/static_root/assets/stats-tab-B6L-YFRA.1c90593f7b5c.css.br +0 -0
  376. codex/static_root/assets/stats-tab-B6L-YFRA.1c90593f7b5c.css.gz +0 -0
  377. codex/static_root/assets/stats-tab-B6L-YFRA.css +0 -1
  378. codex/static_root/assets/stats-tab-B6L-YFRA.css.br +0 -0
  379. codex/static_root/assets/stats-tab-B6L-YFRA.css.gz +0 -0
  380. codex/static_root/assets/stats-tab-zts2Xsmg.9fb959ccf4da.js +0 -1
  381. codex/static_root/assets/stats-tab-zts2Xsmg.9fb959ccf4da.js.br +0 -0
  382. codex/static_root/assets/stats-tab-zts2Xsmg.9fb959ccf4da.js.gz +0 -0
  383. codex/static_root/assets/stats-tab-zts2Xsmg.js +0 -1
  384. codex/static_root/assets/stats-tab-zts2Xsmg.js.br +0 -0
  385. codex/static_root/assets/stats-tab-zts2Xsmg.js.gz +0 -0
  386. codex/static_root/assets/task-tab-CbzDX0IR.7b43bdf9cf42.js.br +0 -0
  387. codex/static_root/assets/task-tab-CbzDX0IR.7b43bdf9cf42.js.gz +0 -0
  388. codex/static_root/assets/task-tab-CbzDX0IR.js.br +0 -0
  389. codex/static_root/assets/task-tab-CbzDX0IR.js.gz +0 -0
  390. codex/static_root/assets/unauthorized-CllrtnFV.2a18fab3dd41.js.br +0 -0
  391. codex/static_root/assets/unauthorized-CllrtnFV.2a18fab3dd41.js.gz +0 -0
  392. codex/static_root/assets/unauthorized-CllrtnFV.js.br +0 -0
  393. codex/static_root/assets/unauthorized-CllrtnFV.js.gz +0 -0
  394. codex/static_root/assets/user-tab-FY1xALpi.05a88f51df4f.js.br +0 -0
  395. codex/static_root/assets/user-tab-FY1xALpi.05a88f51df4f.js.gz +0 -0
  396. codex/static_root/assets/user-tab-FY1xALpi.js.br +0 -0
  397. codex/static_root/assets/user-tab-FY1xALpi.js.gz +0 -0
  398. codex/static_root/manifest.166aa32817b1.json.br +0 -0
  399. codex/static_root/manifest.166aa32817b1.json.gz +0 -0
  400. {codex-1.6.3.dist-info → codex-1.6.4.dist-info}/LICENSE +0 -0
  401. {codex-1.6.3.dist-info → codex-1.6.4.dist-info}/WHEEL +0 -0
  402. {codex-1.6.3.dist-info → codex-1.6.4.dist-info}/entry_points.txt +0 -0
@@ -42,7 +42,7 @@ class ComicImporter(MovedImporter):
42
42
  self.librarian_queue.put(LIBRARY_CHANGED_TASK)
43
43
 
44
44
  # Wait to start the search index update in case more updates are incoming.
45
- until = time() + 3
45
+ until = time() + 1
46
46
  delayed_search_task = DelayedTasks(until, (SearchIndexUpdateTask(False),))
47
47
  self.librarian_queue.put(delayed_search_task)
48
48
  else:
@@ -111,7 +111,6 @@ class CleanupMixin(WorkerBaseMixin):
111
111
 
112
112
  def cleanup_custom_covers(self):
113
113
  """Clean up unused custom covers."""
114
- start = time()
115
114
  covers = CustomCover.objects.only("path")
116
115
  status = Status(JanitorStatusTypes.CLEANUP_COVERS, 0, covers.count())
117
116
  delete_pks = []
@@ -128,12 +127,11 @@ class CleanupMixin(WorkerBaseMixin):
128
127
  level = logging.INFO if status.complete else logging.DEBUG
129
128
  self.log.log(level, f"Deleted {count} CustomCovers without source images.")
130
129
  finally:
131
- until = start + 2
130
+ until = time() + 1
132
131
  self.status_controller.finish(status, until=until)
133
132
 
134
133
  def cleanup_sessions(self):
135
134
  """Delete corrupt sessions."""
136
- start = time()
137
135
  status = Status(JanitorStatusTypes.CLEANUP_SESSIONS)
138
136
  try:
139
137
  self.status_controller.start(status)
@@ -152,5 +150,5 @@ class CleanupMixin(WorkerBaseMixin):
152
150
  count, _ = bad_sessions.delete()
153
151
  self.log.info(f"Deleted {count} corrupt sessions.")
154
152
  finally:
155
- until = start + 2
153
+ until = time() + 1
156
154
  self.status_controller.finish(status, until=until)
@@ -33,15 +33,16 @@ class UpdateMixin(WorkerBaseMixin):
33
33
 
34
34
  self.log.info("Codex seems outdated. Trying to update.")
35
35
 
36
- subprocess.run(
37
- ( # noqa: S603
38
- sys.executable,
39
- "-m",
40
- "pip",
41
- "install",
42
- "--upgrade",
43
- "codex",
44
- ),
36
+ args = (
37
+ sys.executable,
38
+ "-m",
39
+ "pip",
40
+ "install",
41
+ "--upgrade",
42
+ "codex",
43
+ )
44
+ subprocess.run( # noqa: S603
45
+ args,
45
46
  check=True,
46
47
  )
47
48
  except Exception:
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING
7
7
  from humanize import naturaldelta, naturalsize
8
8
 
9
9
  from codex.librarian.search.status import SearchIndexStatusTypes
10
- from codex.librarian.search.tasks import SearchIndexRemoveStaleTask
11
10
  from codex.librarian.search.version import VersionMixin
12
11
  from codex.settings.settings import SEARCH_INDEX_PATH
13
12
  from codex.status import Status
@@ -39,50 +38,48 @@ class MergeMixin(VersionMixin):
39
38
  size += Path(segment).stat().st_size
40
39
  return size
41
40
 
42
- def merge_search_index(self, optimize=False):
41
+ def _merge_search_index(self, optimize, status, name):
43
42
  """Optimize search index."""
44
- verb = "All" if optimize else "Small"
45
- name = f"Merge {verb} Segments"
46
- status = Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE, subtitle=name)
47
- try:
48
- statii = (
49
- status,
50
- Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE),
51
- )
52
- self.status_controller.start_many(statii)
53
- start = time()
43
+ self.status_controller.start(status)
44
+ start = time()
54
45
 
55
- segments, old_num_segments = self._get_segments_and_len()
56
- if self._is_index_optimized(old_num_segments):
57
- return
58
- self.status_controller.start(status)
59
- old_size = self._get_segments_size(segments)
60
- # Optimize
46
+ segments, old_num_segments = self._get_segments_and_len()
47
+ if self._is_index_optimized(old_num_segments):
48
+ return
49
+ self.status_controller.start(status)
50
+ old_size = self._get_segments_size(segments)
51
+ # Optimize
52
+ self.log.info(
53
+ f"Search index found in {old_num_segments} segments," f" merging {name}..."
54
+ )
55
+ backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
56
+ if optimize:
57
+ backend.optimize()
58
+ else:
59
+ backend.merge_small()
60
+
61
+ # Finish
62
+ segments, new_num_segments = self._get_segments_and_len()
63
+ new_size = self._get_segments_size(segments)
64
+ saved = naturalsize(old_size - new_size)
65
+ num_segments_diff = old_num_segments - new_num_segments
66
+ elapsed_time = time() - start
67
+ elapsed = naturaldelta(elapsed_time)
68
+ if num_segments_diff:
61
69
  self.log.info(
62
- f"Search index found in {old_num_segments} segments,"
63
- f" merging {name}..."
70
+ f"Merged {num_segments_diff} search index segments in {elapsed}."
71
+ f"Saved {saved}."
64
72
  )
65
- backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
66
- if optimize:
67
- backend.optimize()
68
- else:
69
- backend.merge_small()
73
+ else:
74
+ self.log.info("No small search index segments found.")
70
75
 
71
- # Finish
72
- segments, new_num_segments = self._get_segments_and_len()
73
- new_size = self._get_segments_size(segments)
74
- saved = naturalsize(old_size - new_size)
75
- num_segments_diff = old_num_segments - new_num_segments
76
- elapsed_time = time() - start
77
- elapsed = naturaldelta(elapsed_time)
78
- if num_segments_diff:
79
- self.log.info(
80
- f"Merged {num_segments_diff} search index segments in {elapsed}."
81
- f"Saved {saved}."
82
- )
83
- else:
84
- self.log.info("No small search index segments found.")
85
- self.librarian_queue.put(SearchIndexRemoveStaleTask())
76
+ def merge_search_index(self, optimize=False):
77
+ """Optimize search index, trapping exceptions."""
78
+ verb = "All" if optimize else "Small"
79
+ name = f"Merge {verb} Segments"
80
+ status = Status(SearchIndexStatusTypes.SEARCH_INDEX_MERGE, subtitle=name)
81
+ try:
82
+ self._merge_search_index(optimize, status, name)
86
83
  except Exception:
87
84
  self.log.exception("Search index merge.")
88
85
  finally:
@@ -38,39 +38,43 @@ class RemoveMixin(VersionMixin):
38
38
  return delete_docnums
39
39
  return delete_docnums
40
40
 
41
+ def _remove_stale_records(self, backend: CodexSearchBackend | None, status): # type: ignore
42
+ """Remove records not in the database from the index."""
43
+ start_time = time()
44
+ if not backend:
45
+ backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
46
+ if not backend.setup_complete:
47
+ backend.setup(False)
48
+
49
+ delete_docnums = self._get_delete_docnums(backend)
50
+ num_delete_docnums = len(delete_docnums)
51
+ count = 0
52
+ if num_delete_docnums:
53
+ status.total = num_delete_docnums
54
+ self.status_controller.start(status)
55
+ count = backend.remove_docnums(delete_docnums)
56
+
57
+ # Finish
58
+ if count:
59
+ elapsed_time = time() - start_time
60
+ elapsed = naturaldelta(elapsed_time)
61
+ cps = int(count / elapsed_time)
62
+ self.log.info(
63
+ f"Removed {count} stale records from the search index"
64
+ f" in {elapsed} at {cps} per second."
65
+ )
66
+ else:
67
+ self.log.debug("No stale records to remove from the search index.")
68
+
41
69
  def remove_stale_records(
42
70
  self,
43
71
  backend: CodexSearchBackend | None = None, # type: ignore
44
72
  ):
45
- """Remove records not in the database from the index."""
73
+ """Remove records not in the database from the index, trapping exceptions."""
46
74
  self.abort_event.clear()
47
75
  status = Status(SearchIndexStatusTypes.SEARCH_INDEX_REMOVE)
48
76
  try:
49
- start_time = time()
50
- if not backend:
51
- backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
52
- if not backend.setup_complete:
53
- backend.setup(False)
54
-
55
- delete_docnums = self._get_delete_docnums(backend)
56
- num_delete_docnums = len(delete_docnums)
57
- count = 0
58
- if num_delete_docnums:
59
- status.total = num_delete_docnums
60
- self.status_controller.start(status)
61
- count = backend.remove_docnums(delete_docnums)
62
-
63
- # Finish
64
- if count:
65
- elapsed_time = time() - start_time
66
- elapsed = naturaldelta(elapsed_time)
67
- cps = int(count / elapsed_time)
68
- self.log.info(
69
- f"Removed {count} stale records from the search index"
70
- f" in {elapsed} at {cps} per second."
71
- )
72
- else:
73
- self.log.debug("No stale records to remove from the search index.")
77
+ self._remove_stale_records(backend, status)
74
78
  except Exception:
75
79
  self.log.exception("Removing stale records:")
76
80
  finally:
@@ -301,7 +301,7 @@ class UpdateMixin(RemoveMixin):
301
301
  except Exception:
302
302
  self.log.exception("Update search index with multiprocessing")
303
303
  finally:
304
- until = start_time + 1
304
+ until = time() + 1
305
305
  self.status_controller.finish(status, until=until)
306
306
 
307
307
  def clear_search_index(self):
@@ -313,50 +313,59 @@ class UpdateMixin(RemoveMixin):
313
313
  self.status_controller.finish(clear_status)
314
314
  self.log.info("Old search index cleared.")
315
315
 
316
- def update_search_index(self, rebuild=False):
316
+ def _update_search_index(self, start_time, rebuild):
317
317
  """Update or Rebuild the search index."""
318
- start_time = time()
319
- self.abort_event.clear()
320
- try:
321
- any_update_in_progress = Library.objects.filter(
322
- covers_only=False, update_in_progress=True
323
- ).exists()
324
- if any_update_in_progress:
325
- self.log.debug(
326
- "Database update in progress, not updating search index yet."
327
- )
328
- return
318
+ remove_stale = False
319
+ any_update_in_progress = Library.objects.filter(
320
+ covers_only=False, update_in_progress=True
321
+ ).exists()
322
+ if any_update_in_progress:
323
+ self.log.debug(
324
+ "Database update in progress, not updating search index yet."
325
+ )
326
+ return remove_stale
329
327
 
330
- if not rebuild and not self.is_search_index_uuid_match():
331
- rebuild = True
328
+ if not rebuild and not self.is_search_index_uuid_match():
329
+ rebuild = True
332
330
 
333
- self._init_statuses(rebuild)
331
+ self._init_statuses(rebuild)
334
332
 
335
- # Clear
336
- if rebuild:
337
- self.log.info("Rebuilding search index...")
338
- self.clear_search_index()
333
+ # Clear
334
+ if rebuild:
335
+ self.log.info("Rebuilding search index...")
336
+ self.clear_search_index()
339
337
 
340
- # Update
341
- backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
342
- backend.setup(False)
343
- if self.abort_event.is_set():
344
- return
345
- qs = self._get_queryset(backend, rebuild)
346
- self._mp_update(backend, qs)
338
+ # Update
339
+ backend: CodexSearchBackend = self.engine.get_backend() # type: ignore
340
+ backend.setup(False)
341
+ if self.abort_event.is_set():
342
+ self.log.debug("Abort update search index.")
343
+ return remove_stale
344
+ qs = self._get_queryset(backend, rebuild)
345
+ self._mp_update(backend, qs)
347
346
 
348
- # Finish
349
- if rebuild:
350
- self.set_search_index_version()
351
- else:
352
- task = SearchIndexRemoveStaleTask()
353
- self.librarian_queue.put(task)
347
+ # Finish
348
+ if rebuild:
349
+ self.set_search_index_version()
350
+ else:
351
+ remove_stale = True
354
352
 
355
- elapsed_time = time() - start_time
356
- elapsed = naturaldelta(elapsed_time)
357
- self.log.info(f"Search index updated in {elapsed}.")
353
+ elapsed_time = time() - start_time
354
+ elapsed = naturaldelta(elapsed_time)
355
+ self.log.info(f"Search index updated in {elapsed}.")
356
+ return remove_stale
357
+
358
+ def update_search_index(self, rebuild=False):
359
+ """Update or Rebuild the search index."""
360
+ start_time = time()
361
+ self.abort_event.clear()
362
+ remove_stale = False
363
+ try:
364
+ remove_stale = self._update_search_index(start_time, rebuild)
358
365
  except Exception:
359
366
  self.log.exception("Update search index")
360
367
  finally:
361
- until = start_time + 2
362
- self.status_controller.finish_many(self._STATUS_FINISH_TYPES, until=until)
368
+ self.status_controller.finish_many(self._STATUS_FINISH_TYPES)
369
+ if remove_stale:
370
+ task = SearchIndexRemoveStaleTask()
371
+ self.librarian_queue.put(task)
@@ -0,0 +1,177 @@
1
+ """Admin Flag View."""
2
+
3
+ from multiprocessing import cpu_count
4
+ from pathlib import Path
5
+ from platform import machine, python_version, release, system
6
+ from types import MappingProxyType
7
+
8
+ from caseconverter import snakecase
9
+ from django.contrib.sessions.models import Session
10
+ from django.db.models import Count
11
+
12
+ from codex.logger.logging import get_logger
13
+ from codex.models import (
14
+ Comic,
15
+ Library,
16
+ )
17
+ from codex.version import VERSION
18
+ from codex.views.const import CONFIG_MODELS, METADATA_MODELS, STATS_GROUP_MODELS
19
+ from codex.views.session import SessionView
20
+
21
+ LOG = get_logger(__name__)
22
+ _KEY_MODELS_MAP = MappingProxyType(
23
+ {
24
+ "config": CONFIG_MODELS,
25
+ "groups": STATS_GROUP_MODELS,
26
+ "metadata": METADATA_MODELS,
27
+ }
28
+ )
29
+ _DOCKERENV_PATH = Path("/.dockerenv")
30
+ _CGROUP_PATH = Path("/proc/self/cgroup")
31
+ _USER_STATS = MappingProxyType(
32
+ {
33
+ SessionView.BROWSER_SESSION_KEY: ("top_group", "order_by", "dynamic_covers"),
34
+ SessionView.READER_SESSION_KEY: (
35
+ "finish_on_last_page",
36
+ "fit_to",
37
+ "reading_direction",
38
+ ),
39
+ }
40
+ )
41
+
42
+
43
+ class CodexStats:
44
+ """Collect codex stats."""
45
+
46
+ def __init__(self, params=None):
47
+ """Specify which stats to collect. Default to all."""
48
+ if not params:
49
+ params = {}
50
+ self.params = params
51
+
52
+ @classmethod
53
+ def _is_docker(cls):
54
+ """Test if we're in a docker container."""
55
+ try:
56
+ return _DOCKERENV_PATH.is_file() or "docker" in _CGROUP_PATH.read_text()
57
+ except Exception:
58
+ return False
59
+
60
+ def _get_models(self, key):
61
+ """Get models from request params."""
62
+ request_model_set = self.params.get(key, {})
63
+ all_models = _KEY_MODELS_MAP[key]
64
+ if request_model_set:
65
+ models = []
66
+ for model_name in request_model_set:
67
+ for model in all_models:
68
+ if model.__name__.lower() == model_name.lower():
69
+ models.append(model)
70
+ else:
71
+ models = all_models
72
+ return tuple(models)
73
+
74
+ def _get_model_counts(self, key):
75
+ """Get database counts of each model group."""
76
+ models = self._get_models(key)
77
+ obj = {}
78
+ for model in models:
79
+ name = snakecase(model.__name__) + "_count"
80
+ qs = model.objects
81
+ if model == Library:
82
+ qs = qs.filter(covers_only=False)
83
+ obj[name] = qs.count()
84
+ return obj
85
+
86
+ @staticmethod
87
+ def _aggregate_session_key(session, session_key, session_subkeys, user_stats):
88
+ session_dict = session.get(session_key, {})
89
+ for key in session_subkeys:
90
+ value = session_dict.get(key)
91
+ if value is None:
92
+ continue
93
+ if key not in user_stats:
94
+ user_stats[key] = {}
95
+ if value not in user_stats[key]:
96
+ user_stats[key][value] = 0
97
+ user_stats[key][value] += 1
98
+
99
+ @classmethod
100
+ def _get_session_stats(cls):
101
+ """Return the number of anonymous sessions."""
102
+ sessions = Session.objects.all()
103
+ anon_session_count = 0
104
+ user_stats = {}
105
+ for encoded_session in sessions:
106
+ session = encoded_session.get_decoded()
107
+ if not session.get("_auth_user_id"):
108
+ anon_session_count += 1
109
+ for session_key, subkeys in _USER_STATS.items():
110
+ cls._aggregate_session_key(session, session_key, subkeys, user_stats)
111
+
112
+ return user_stats, anon_session_count
113
+
114
+ def _get_platform(self, obj):
115
+ """Add dict of platform information to object."""
116
+ platform = {
117
+ "docker": self._is_docker(),
118
+ "machine": machine(),
119
+ "cores": cpu_count(),
120
+ "system": {
121
+ "name": system(),
122
+ "release": release(),
123
+ },
124
+ "python_version": python_version(),
125
+ "codex_version": VERSION,
126
+ }
127
+ obj["platform"] = platform
128
+
129
+ def _get_config(self, obj):
130
+ """Add dict of config informaation to object."""
131
+ config = self._get_model_counts("config")
132
+ sessions, config["user_anonymous_count"] = self._get_session_stats()
133
+ config["user_registered_count"] = config.pop("users_count", 0)
134
+ config["auth_group_count"] = config.pop("groups_count", 0)
135
+ obj["config"] = config
136
+ obj["sessions"] = sessions
137
+
138
+ def _get_groups(self, obj):
139
+ """Add dict of groups information to object."""
140
+ groups = self._get_model_counts("groups")
141
+ obj["groups"] = groups
142
+
143
+ @staticmethod
144
+ def _get_file_types(obj):
145
+ """Query for file types."""
146
+ file_types = {}
147
+ qs = (
148
+ Comic.objects.values("file_type")
149
+ .annotate(count=Count("file_type"))
150
+ .order_by()
151
+ )
152
+ for query_group in qs:
153
+ value = query_group["file_type"]
154
+ name = value.lower() if value else "unknown"
155
+ file_types[name] = query_group["count"]
156
+ sorted_fts = dict(sorted(file_types.items()))
157
+ obj["file_types"] = sorted_fts
158
+
159
+ def _get_metadata(self, obj):
160
+ """Add dict of metadata counts to object."""
161
+ metadata = self._get_model_counts("metadata")
162
+ obj["metadata"] = metadata
163
+
164
+ def get(self):
165
+ """Construct the stats object."""
166
+ obj = {}
167
+ if not self.params or "platform" in self.params:
168
+ self._get_platform(obj)
169
+ if not self.params or "config" in self.params:
170
+ self._get_config(obj)
171
+ if not self.params or "groups" in self.params:
172
+ self._get_groups(obj)
173
+ if not self.params or "fileTypes" in self.params:
174
+ self._get_file_types(obj)
175
+ if not self.params or "metadata" in self.params:
176
+ self._get_metadata(obj)
177
+ return obj
@@ -191,8 +191,8 @@ class CodexLibraryEventHandler(CodexEventHandlerBase):
191
191
  events = self._transform_file_event(event)
192
192
 
193
193
  # Send it to the EventBatcher
194
- for event in events:
195
- task = WatchdogEventTask(self.library_pk, event)
194
+ for sub_event in events:
195
+ task = WatchdogEventTask(self.library_pk, sub_event)
196
196
  self.librarian_queue.put(task)
197
197
 
198
198
  # Calls stub event dispatchers