codex 1.6.8__py3-none-any.whl → 1.6.9a0__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 (697) hide show
  1. codex/librarian/covers/create.py +1 -1
  2. codex/librarian/cron/__init__.py +1 -0
  3. codex/librarian/cron/crond.py +87 -0
  4. codex/librarian/importer/importerd.py +24 -19
  5. codex/librarian/importer/moved.py +176 -73
  6. codex/librarian/importer/query_fks.py +18 -12
  7. codex/librarian/janitor/janitor.py +12 -4
  8. codex/librarian/janitor/latest_version.py +54 -0
  9. codex/librarian/janitor/status.py +1 -0
  10. codex/librarian/janitor/tasks.py +14 -7
  11. codex/librarian/janitor/time.py +12 -0
  12. codex/librarian/janitor/update.py +21 -2
  13. codex/librarian/janitor/vacuum.py +1 -2
  14. codex/librarian/librariand.py +9 -3
  15. codex/librarian/tasks.py +5 -1
  16. codex/librarian/telemeter/__init__.py +1 -0
  17. codex/librarian/{stats.py → telemeter/stats.py} +21 -16
  18. codex/librarian/telemeter/tasks.py +8 -0
  19. codex/librarian/telemeter/telemeter.py +80 -0
  20. codex/librarian/telemeter/time.py +61 -0
  21. codex/librarian/watchdog/db_snapshot.py +1 -1
  22. codex/librarian/watchdog/dir_snapshot_diff.py +19 -17
  23. codex/librarian/watchdog/emitter.py +12 -14
  24. codex/librarian/watchdog/events.py +1 -1
  25. codex/librarian/watchdog/observers.py +4 -4
  26. codex/migrations/0028_telemeter.py +48 -0
  27. codex/models/admin.py +2 -0
  28. codex/models/comic.py +1 -1
  29. codex/search/backend.py +10 -3
  30. codex/serializers/browser/mixins.py +1 -2
  31. codex/settings/settings.py +6 -2
  32. codex/signals/django_signals.py +0 -9
  33. codex/startup.py +1 -3
  34. codex/static_root/assets/VCheckbox-BkQ2enlR.c750c7d8cecc.js +1 -0
  35. codex/static_root/assets/VCheckbox-BkQ2enlR.c750c7d8cecc.js.br +0 -0
  36. codex/static_root/assets/VCheckbox-BkQ2enlR.c750c7d8cecc.js.gz +0 -0
  37. codex/static_root/assets/VCheckbox-BkQ2enlR.js +1 -0
  38. codex/static_root/assets/VCheckbox-BkQ2enlR.js.br +0 -0
  39. codex/static_root/assets/VCheckbox-BkQ2enlR.js.gz +0 -0
  40. codex/static_root/assets/VCheckboxBtn-C4qxJC_o.ff4628662392.js +1 -0
  41. codex/static_root/assets/VCheckboxBtn-C4qxJC_o.ff4628662392.js.br +0 -0
  42. codex/static_root/assets/VCheckboxBtn-C4qxJC_o.ff4628662392.js.gz +0 -0
  43. codex/static_root/assets/VCheckboxBtn-C4qxJC_o.js +1 -0
  44. codex/static_root/assets/VCheckboxBtn-C4qxJC_o.js.br +0 -0
  45. codex/static_root/assets/VCheckboxBtn-C4qxJC_o.js.gz +0 -0
  46. codex/static_root/assets/VCombobox-CJYErhVj.5ae39362d05a.js +1 -0
  47. codex/static_root/assets/VCombobox-CJYErhVj.5ae39362d05a.js.br +0 -0
  48. codex/static_root/assets/VCombobox-CJYErhVj.5ae39362d05a.js.gz +0 -0
  49. codex/static_root/assets/VCombobox-CJYErhVj.js +1 -0
  50. codex/static_root/assets/VCombobox-CJYErhVj.js.br +0 -0
  51. codex/static_root/assets/VCombobox-CJYErhVj.js.gz +0 -0
  52. codex/static_root/assets/VCombobox-bwEYrXCg.9f25a34994e2.css +1 -0
  53. codex/static_root/assets/VCombobox-bwEYrXCg.9f25a34994e2.css.br +0 -0
  54. codex/static_root/assets/VCombobox-bwEYrXCg.9f25a34994e2.css.gz +0 -0
  55. codex/static_root/assets/VCombobox-bwEYrXCg.css +1 -0
  56. codex/static_root/assets/VCombobox-bwEYrXCg.css.br +0 -0
  57. codex/static_root/assets/VCombobox-bwEYrXCg.css.gz +0 -0
  58. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css +1 -0
  59. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.br +0 -0
  60. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.gz +0 -0
  61. codex/static_root/assets/VDialog-38np3TSA.css +1 -0
  62. codex/static_root/assets/VDialog-38np3TSA.css.br +0 -0
  63. codex/static_root/assets/VDialog-38np3TSA.css.gz +0 -0
  64. codex/static_root/assets/VDialog-U73OI2x0.a74b15bb2eb4.js +1 -0
  65. codex/static_root/assets/VDialog-U73OI2x0.a74b15bb2eb4.js.br +0 -0
  66. codex/static_root/assets/VDialog-U73OI2x0.a74b15bb2eb4.js.gz +0 -0
  67. codex/static_root/assets/VDialog-U73OI2x0.js +1 -0
  68. codex/static_root/assets/VDialog-U73OI2x0.js.br +0 -0
  69. codex/static_root/assets/VDialog-U73OI2x0.js.gz +0 -0
  70. codex/static_root/assets/VExpansionPanels-6G1AUajL.5b17872981df.css +1 -0
  71. codex/static_root/assets/VExpansionPanels-6G1AUajL.5b17872981df.css.br +0 -0
  72. codex/static_root/assets/VExpansionPanels-6G1AUajL.5b17872981df.css.gz +0 -0
  73. codex/static_root/assets/VExpansionPanels-6G1AUajL.css +1 -0
  74. codex/static_root/assets/VExpansionPanels-6G1AUajL.css.br +0 -0
  75. codex/static_root/assets/VExpansionPanels-6G1AUajL.css.gz +0 -0
  76. codex/static_root/assets/VExpansionPanels-CfbnFRnp.11d5ca74c514.js +1 -0
  77. codex/static_root/assets/VExpansionPanels-CfbnFRnp.11d5ca74c514.js.br +0 -0
  78. codex/static_root/assets/VExpansionPanels-CfbnFRnp.11d5ca74c514.js.gz +0 -0
  79. codex/static_root/assets/VExpansionPanels-CfbnFRnp.js +1 -0
  80. codex/static_root/assets/VExpansionPanels-CfbnFRnp.js.br +0 -0
  81. codex/static_root/assets/VExpansionPanels-CfbnFRnp.js.gz +0 -0
  82. codex/static_root/assets/VRadioGroup-Dk6ucuGh.b364223f1e2b.js +1 -0
  83. codex/static_root/assets/VRadioGroup-Dk6ucuGh.b364223f1e2b.js.br +3 -0
  84. codex/static_root/assets/VRadioGroup-Dk6ucuGh.b364223f1e2b.js.gz +0 -0
  85. codex/static_root/assets/VRadioGroup-Dk6ucuGh.js +1 -0
  86. codex/static_root/assets/VRadioGroup-Dk6ucuGh.js.br +3 -0
  87. codex/static_root/assets/VRadioGroup-Dk6ucuGh.js.gz +0 -0
  88. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css +1 -0
  89. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.br +0 -0
  90. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.gz +0 -0
  91. codex/static_root/assets/VSelect-Ca2bIOso.css +1 -0
  92. codex/static_root/assets/VSelect-Ca2bIOso.css.br +0 -0
  93. codex/static_root/assets/VSelect-Ca2bIOso.css.gz +0 -0
  94. codex/static_root/assets/VSelect-gqsRzrIv.f022ea270144.js +1 -0
  95. codex/static_root/assets/VSelect-gqsRzrIv.f022ea270144.js.br +0 -0
  96. codex/static_root/assets/VSelect-gqsRzrIv.f022ea270144.js.gz +0 -0
  97. codex/static_root/assets/VSelect-gqsRzrIv.js +1 -0
  98. codex/static_root/assets/VSelect-gqsRzrIv.js.br +0 -0
  99. codex/static_root/assets/VSelect-gqsRzrIv.js.gz +0 -0
  100. codex/static_root/assets/VSelectionControl-BVJLEOcO.5d398b4b3b1c.js +1 -0
  101. codex/static_root/assets/VSelectionControl-BVJLEOcO.5d398b4b3b1c.js.br +0 -0
  102. codex/static_root/assets/VSelectionControl-BVJLEOcO.5d398b4b3b1c.js.gz +0 -0
  103. codex/static_root/assets/VSelectionControl-BVJLEOcO.js +1 -0
  104. codex/static_root/assets/VSelectionControl-BVJLEOcO.js.br +0 -0
  105. codex/static_root/assets/VSelectionControl-BVJLEOcO.js.gz +0 -0
  106. codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css +1 -0
  107. codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css.br +0 -0
  108. codex/static_root/assets/VSelectionControl-CiViEYoC.c77bb19a4a06.css.gz +0 -0
  109. codex/static_root/assets/VSelectionControl-CiViEYoC.css +1 -0
  110. codex/static_root/assets/VSelectionControl-CiViEYoC.css.br +0 -0
  111. codex/static_root/assets/VSelectionControl-CiViEYoC.css.gz +0 -0
  112. codex/static_root/assets/{VSlideGroup-KhqCfdKQ.984b37842a8b.js → VSlideGroup-P_U-rmf2.ca3efe698702.js} +1 -1
  113. codex/static_root/assets/VSlideGroup-P_U-rmf2.ca3efe698702.js.br +0 -0
  114. codex/static_root/assets/VSlideGroup-P_U-rmf2.ca3efe698702.js.gz +0 -0
  115. codex/static_root/assets/{VSlideGroup-KhqCfdKQ.js → VSlideGroup-P_U-rmf2.js} +1 -1
  116. codex/static_root/assets/VSlideGroup-P_U-rmf2.js.br +0 -0
  117. codex/static_root/assets/VSlideGroup-P_U-rmf2.js.gz +0 -0
  118. codex/static_root/assets/{VTable-CJJzxZcw.edb1bb582e5c.js → VTable-Coq-QwtQ.2b04104cbf05.js} +1 -1
  119. codex/static_root/assets/VTable-Coq-QwtQ.2b04104cbf05.js.br +0 -0
  120. codex/static_root/assets/VTable-Coq-QwtQ.2b04104cbf05.js.gz +0 -0
  121. codex/static_root/assets/{VTable-CJJzxZcw.js → VTable-Coq-QwtQ.js} +1 -1
  122. codex/static_root/assets/VTable-Coq-QwtQ.js.br +0 -0
  123. codex/static_root/assets/VTable-Coq-QwtQ.js.gz +0 -0
  124. codex/static_root/assets/VTable-Y5Sc3hJd.c36f0a6a30cf.css +1 -0
  125. codex/static_root/assets/VTable-Y5Sc3hJd.c36f0a6a30cf.css.br +0 -0
  126. codex/static_root/assets/VTable-Y5Sc3hJd.c36f0a6a30cf.css.gz +0 -0
  127. codex/static_root/assets/VTable-Y5Sc3hJd.css +1 -0
  128. codex/static_root/assets/VTable-Y5Sc3hJd.css.br +0 -0
  129. codex/static_root/assets/VTable-Y5Sc3hJd.css.gz +0 -0
  130. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css +1 -0
  131. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
  132. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
  133. codex/static_root/assets/VTextField-Crc3gdNh.css +1 -0
  134. codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
  135. codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
  136. codex/static_root/assets/VTextField-D8612eNs.bff65c141149.js +1 -0
  137. codex/static_root/assets/VTextField-D8612eNs.bff65c141149.js.br +0 -0
  138. codex/static_root/assets/VTextField-D8612eNs.bff65c141149.js.gz +0 -0
  139. codex/static_root/assets/VTextField-D8612eNs.js +1 -0
  140. codex/static_root/assets/VTextField-D8612eNs.js.br +0 -0
  141. codex/static_root/assets/VTextField-D8612eNs.js.gz +0 -0
  142. codex/static_root/assets/VWindowItem-BViiLFXH.70619152a4a5.js +1 -0
  143. codex/static_root/assets/VWindowItem-BViiLFXH.70619152a4a5.js.br +0 -0
  144. codex/static_root/assets/VWindowItem-BViiLFXH.70619152a4a5.js.gz +0 -0
  145. codex/static_root/assets/VWindowItem-BViiLFXH.js +1 -0
  146. codex/static_root/assets/VWindowItem-BViiLFXH.js.br +0 -0
  147. codex/static_root/assets/VWindowItem-BViiLFXH.js.gz +0 -0
  148. codex/static_root/assets/{admin-D4sw3N4T.b590f88a62cf.css → admin-CWEjaFr5.7d4523387384.css} +1 -1
  149. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.br +0 -0
  150. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.gz +0 -0
  151. codex/static_root/assets/{admin-D4sw3N4T.css → admin-CWEjaFr5.css} +1 -1
  152. codex/static_root/assets/admin-CWEjaFr5.css.br +0 -0
  153. codex/static_root/assets/admin-CWEjaFr5.css.gz +0 -0
  154. codex/static_root/assets/admin-RVemMSzT.fcbdca9f6b46.js +1 -0
  155. codex/static_root/assets/admin-RVemMSzT.fcbdca9f6b46.js.br +0 -0
  156. codex/static_root/assets/admin-RVemMSzT.fcbdca9f6b46.js.gz +0 -0
  157. codex/static_root/assets/admin-RVemMSzT.js +1 -0
  158. codex/static_root/assets/admin-RVemMSzT.js.br +0 -0
  159. codex/static_root/assets/admin-RVemMSzT.js.gz +0 -0
  160. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css +1 -0
  161. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.br +0 -0
  162. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.gz +0 -0
  163. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css +1 -0
  164. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.br +0 -0
  165. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.gz +0 -0
  166. codex/static_root/assets/admin-drawer-panel-Ul8BRcOp.0acaf708f24a.js +30 -0
  167. codex/static_root/assets/admin-drawer-panel-Ul8BRcOp.0acaf708f24a.js.br +0 -0
  168. codex/static_root/assets/admin-drawer-panel-Ul8BRcOp.0acaf708f24a.js.gz +0 -0
  169. codex/static_root/assets/admin-drawer-panel-Ul8BRcOp.js +30 -0
  170. codex/static_root/assets/admin-drawer-panel-Ul8BRcOp.js.br +0 -0
  171. codex/static_root/assets/admin-drawer-panel-Ul8BRcOp.js.gz +0 -0
  172. codex/static_root/assets/{browser-Cy0OgQJC.css → browser-Binc3n9H.313722d27ca3.css} +1 -1
  173. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.br +0 -0
  174. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.gz +0 -0
  175. codex/static_root/assets/{browser-Cy0OgQJC.fabbd1aaffcf.css → browser-Binc3n9H.css} +1 -1
  176. codex/static_root/assets/browser-Binc3n9H.css.br +0 -0
  177. codex/static_root/assets/browser-Binc3n9H.css.gz +0 -0
  178. codex/static_root/assets/browser-C9pnPT8U.bc239343d007.js +1 -0
  179. codex/static_root/assets/browser-C9pnPT8U.bc239343d007.js.br +0 -0
  180. codex/static_root/assets/browser-C9pnPT8U.bc239343d007.js.gz +0 -0
  181. codex/static_root/assets/browser-C9pnPT8U.js +1 -0
  182. codex/static_root/assets/browser-C9pnPT8U.js.br +0 -0
  183. codex/static_root/assets/browser-C9pnPT8U.js.gz +0 -0
  184. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css +1 -0
  185. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.br +0 -0
  186. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.gz +0 -0
  187. codex/static_root/assets/change-password-dialog-Cebek1-c.css +1 -0
  188. codex/static_root/assets/change-password-dialog-Cebek1-c.css.br +0 -0
  189. codex/static_root/assets/change-password-dialog-Cebek1-c.css.gz +0 -0
  190. codex/static_root/assets/change-password-dialog-DjxFqIRX.1a5ac7100192.js +1 -0
  191. codex/static_root/assets/change-password-dialog-DjxFqIRX.1a5ac7100192.js.br +0 -0
  192. codex/static_root/assets/change-password-dialog-DjxFqIRX.1a5ac7100192.js.gz +0 -0
  193. codex/static_root/assets/change-password-dialog-DjxFqIRX.js +1 -0
  194. codex/static_root/assets/change-password-dialog-DjxFqIRX.js.br +0 -0
  195. codex/static_root/assets/change-password-dialog-DjxFqIRX.js.gz +0 -0
  196. codex/static_root/assets/confirm-dialog-B-dLb_50.62d3ea64b93a.js +1 -0
  197. codex/static_root/assets/confirm-dialog-B-dLb_50.62d3ea64b93a.js.br +0 -0
  198. codex/static_root/assets/confirm-dialog-B-dLb_50.62d3ea64b93a.js.gz +0 -0
  199. codex/static_root/assets/confirm-dialog-B-dLb_50.js +1 -0
  200. codex/static_root/assets/confirm-dialog-B-dLb_50.js.br +0 -0
  201. codex/static_root/assets/confirm-dialog-B-dLb_50.js.gz +0 -0
  202. codex/static_root/assets/{datetime-column-DqSk_iUb.260bcbb4c8e2.js → datetime-column-BRZDPl2k.f6ae3ce94da7.js} +1 -1
  203. codex/static_root/assets/datetime-column-BRZDPl2k.f6ae3ce94da7.js.br +0 -0
  204. codex/static_root/assets/datetime-column-BRZDPl2k.f6ae3ce94da7.js.gz +0 -0
  205. codex/static_root/assets/{datetime-column-DqSk_iUb.js → datetime-column-BRZDPl2k.js} +1 -1
  206. codex/static_root/assets/datetime-column-BRZDPl2k.js.br +0 -0
  207. codex/static_root/assets/datetime-column-BRZDPl2k.js.gz +0 -0
  208. codex/static_root/assets/{filter-CvqVrkAJ.8996d1b1db2a.js → filter-Tk2N9xgO.02f23827434b.js} +1 -1
  209. codex/static_root/assets/filter-Tk2N9xgO.02f23827434b.js.br +0 -0
  210. codex/static_root/assets/filter-Tk2N9xgO.02f23827434b.js.gz +0 -0
  211. codex/static_root/assets/{filter-CvqVrkAJ.js → filter-Tk2N9xgO.js} +1 -1
  212. codex/static_root/assets/filter-Tk2N9xgO.js.br +0 -0
  213. codex/static_root/assets/filter-Tk2N9xgO.js.gz +0 -0
  214. codex/static_root/assets/flag-tab-C_v4CugS.928c1035d6ce.js +1 -0
  215. codex/static_root/assets/flag-tab-C_v4CugS.928c1035d6ce.js.br +0 -0
  216. codex/static_root/assets/flag-tab-C_v4CugS.928c1035d6ce.js.gz +0 -0
  217. codex/static_root/assets/flag-tab-C_v4CugS.js +1 -0
  218. codex/static_root/assets/flag-tab-C_v4CugS.js.br +0 -0
  219. codex/static_root/assets/flag-tab-C_v4CugS.js.gz +0 -0
  220. codex/static_root/assets/group-tab-Dn5N4Ebx.d2cf9c300427.js +1 -0
  221. codex/static_root/assets/group-tab-Dn5N4Ebx.d2cf9c300427.js.br +0 -0
  222. codex/static_root/assets/group-tab-Dn5N4Ebx.d2cf9c300427.js.gz +0 -0
  223. codex/static_root/assets/group-tab-Dn5N4Ebx.js +1 -0
  224. codex/static_root/assets/group-tab-Dn5N4Ebx.js.br +0 -0
  225. codex/static_root/assets/group-tab-Dn5N4Ebx.js.gz +0 -0
  226. codex/static_root/assets/http-error-DbRCRWKA.c31ec2f67f3b.js +1 -0
  227. codex/static_root/assets/http-error-DbRCRWKA.c31ec2f67f3b.js.br +0 -0
  228. codex/static_root/assets/http-error-DbRCRWKA.c31ec2f67f3b.js.gz +0 -0
  229. codex/static_root/assets/http-error-DbRCRWKA.js +1 -0
  230. codex/static_root/assets/http-error-DbRCRWKA.js.br +0 -0
  231. codex/static_root/assets/http-error-DbRCRWKA.js.gz +0 -0
  232. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css +1 -0
  233. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.br +2 -0
  234. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.gz +0 -0
  235. codex/static_root/assets/library-tab-Bh7ErAk7.css +1 -0
  236. codex/static_root/assets/library-tab-Bh7ErAk7.css.br +2 -0
  237. codex/static_root/assets/library-tab-Bh7ErAk7.css.gz +0 -0
  238. codex/static_root/assets/library-tab-CnvofvVA.419ab4e21896.js +1 -0
  239. codex/static_root/assets/library-tab-CnvofvVA.419ab4e21896.js.br +0 -0
  240. codex/static_root/assets/library-tab-CnvofvVA.419ab4e21896.js.gz +0 -0
  241. codex/static_root/assets/library-tab-CnvofvVA.js +1 -0
  242. codex/static_root/assets/library-tab-CnvofvVA.js.br +0 -0
  243. codex/static_root/assets/library-tab-CnvofvVA.js.gz +0 -0
  244. codex/static_root/assets/main-BHIo0ep5.6ec78d1380f0.js +6 -0
  245. codex/static_root/assets/main-BHIo0ep5.6ec78d1380f0.js.br +0 -0
  246. codex/static_root/assets/main-BHIo0ep5.6ec78d1380f0.js.gz +0 -0
  247. codex/static_root/assets/main-BHIo0ep5.js +6 -0
  248. codex/static_root/assets/main-BHIo0ep5.js.br +0 -0
  249. codex/static_root/assets/main-BHIo0ep5.js.gz +0 -0
  250. codex/static_root/assets/{main-Ztp855PW.css → main-BZ73p_WW.css} +1 -1
  251. codex/static_root/assets/main-BZ73p_WW.css.br +0 -0
  252. codex/static_root/assets/{main-Ztp855PW.css.gz → main-BZ73p_WW.css.gz} +0 -0
  253. codex/static_root/assets/{main-Ztp855PW.d46d718fac45.css → main-BZ73p_WW.d529e8b3f52a.css} +1 -1
  254. codex/static_root/assets/main-BZ73p_WW.d529e8b3f52a.css.br +0 -0
  255. codex/static_root/assets/{main-Ztp855PW.d46d718fac45.css.gz → main-BZ73p_WW.d529e8b3f52a.css.gz} +0 -0
  256. codex/static_root/assets/pager-full-pdf-DuOmJXkZ.5eaa11c55b1d.js +1 -0
  257. codex/static_root/assets/pager-full-pdf-DuOmJXkZ.5eaa11c55b1d.js.br +0 -0
  258. codex/static_root/assets/pager-full-pdf-DuOmJXkZ.5eaa11c55b1d.js.gz +0 -0
  259. codex/static_root/assets/pager-full-pdf-DuOmJXkZ.js +1 -0
  260. codex/static_root/assets/pager-full-pdf-DuOmJXkZ.js.br +0 -0
  261. codex/static_root/assets/pager-full-pdf-DuOmJXkZ.js.gz +0 -0
  262. codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css +1 -0
  263. codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.br +0 -0
  264. codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.gz +0 -0
  265. codex/static_root/assets/pagination-toolbar-C45-onwe.css +1 -0
  266. codex/static_root/assets/pagination-toolbar-C45-onwe.css.br +0 -0
  267. codex/static_root/assets/pagination-toolbar-C45-onwe.css.gz +0 -0
  268. codex/static_root/assets/pagination-toolbar-CzvikaTz.a025f20590fa.js +1 -0
  269. codex/static_root/assets/pagination-toolbar-CzvikaTz.a025f20590fa.js.br +0 -0
  270. codex/static_root/assets/pagination-toolbar-CzvikaTz.a025f20590fa.js.gz +0 -0
  271. codex/static_root/assets/pagination-toolbar-CzvikaTz.js +1 -0
  272. codex/static_root/assets/pagination-toolbar-CzvikaTz.js.br +0 -0
  273. codex/static_root/assets/pagination-toolbar-CzvikaTz.js.gz +0 -0
  274. codex/static_root/assets/pdf-doc-B5yncCrs.97ba5cf21b63.js +305 -0
  275. codex/static_root/assets/pdf-doc-B5yncCrs.97ba5cf21b63.js.br +0 -0
  276. codex/static_root/assets/pdf-doc-B5yncCrs.97ba5cf21b63.js.gz +0 -0
  277. codex/static_root/assets/pdf-doc-B5yncCrs.js +305 -0
  278. codex/static_root/assets/pdf-doc-B5yncCrs.js.br +0 -0
  279. codex/static_root/assets/pdf-doc-B5yncCrs.js.gz +0 -0
  280. codex/static_root/assets/pdf-doc-CG5-AZ2M.b94aac7e3313.css +1 -0
  281. codex/static_root/assets/pdf-doc-CG5-AZ2M.b94aac7e3313.css.br +1 -0
  282. codex/static_root/assets/pdf-doc-CG5-AZ2M.b94aac7e3313.css.gz +0 -0
  283. codex/static_root/assets/pdf-doc-CG5-AZ2M.css +1 -0
  284. codex/static_root/assets/pdf-doc-CG5-AZ2M.css.br +1 -0
  285. codex/static_root/assets/pdf-doc-CG5-AZ2M.css.gz +0 -0
  286. codex/static_root/assets/reader-BR3TSv3h.b00087748514.js +2 -0
  287. codex/static_root/assets/reader-BR3TSv3h.b00087748514.js.br +0 -0
  288. codex/static_root/assets/reader-BR3TSv3h.b00087748514.js.gz +0 -0
  289. codex/static_root/assets/reader-BR3TSv3h.js +2 -0
  290. codex/static_root/assets/reader-BR3TSv3h.js.br +0 -0
  291. codex/static_root/assets/reader-BR3TSv3h.js.gz +0 -0
  292. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css +1 -0
  293. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.br +0 -0
  294. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.gz +0 -0
  295. codex/static_root/assets/reader-_stAWL-4.css +1 -0
  296. codex/static_root/assets/reader-_stAWL-4.css.br +0 -0
  297. codex/static_root/assets/reader-_stAWL-4.css.gz +0 -0
  298. codex/static_root/assets/{relation-chips-BGitp3yx.b3e7d178a615.css → relation-chips-ByfWwuQc.01269a188e01.css} +1 -1
  299. codex/static_root/assets/relation-chips-ByfWwuQc.01269a188e01.css.br +0 -0
  300. codex/static_root/assets/relation-chips-ByfWwuQc.01269a188e01.css.gz +0 -0
  301. codex/static_root/assets/{relation-chips-BGitp3yx.css → relation-chips-ByfWwuQc.css} +1 -1
  302. codex/static_root/assets/relation-chips-ByfWwuQc.css.br +0 -0
  303. codex/static_root/assets/relation-chips-ByfWwuQc.css.gz +0 -0
  304. codex/static_root/assets/relation-chips-C-62eg7F.d234f5d05881.js +1 -0
  305. codex/static_root/assets/relation-chips-C-62eg7F.d234f5d05881.js.br +0 -0
  306. codex/static_root/assets/relation-chips-C-62eg7F.d234f5d05881.js.gz +0 -0
  307. codex/static_root/assets/relation-chips-C-62eg7F.js +1 -0
  308. codex/static_root/assets/relation-chips-C-62eg7F.js.br +0 -0
  309. codex/static_root/assets/relation-chips-C-62eg7F.js.gz +0 -0
  310. codex/static_root/assets/settings-drawer-CpNFdMBP.e5b1af37e0a0.js +2 -0
  311. codex/static_root/assets/settings-drawer-CpNFdMBP.e5b1af37e0a0.js.br +0 -0
  312. codex/static_root/assets/settings-drawer-CpNFdMBP.e5b1af37e0a0.js.gz +0 -0
  313. codex/static_root/assets/settings-drawer-CpNFdMBP.js +2 -0
  314. codex/static_root/assets/settings-drawer-CpNFdMBP.js.br +0 -0
  315. codex/static_root/assets/settings-drawer-CpNFdMBP.js.gz +0 -0
  316. codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css +1 -0
  317. codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css.br +0 -0
  318. codex/static_root/assets/settings-drawer-DKncO_vX.0a2cd24cd904.css.gz +0 -0
  319. codex/static_root/assets/settings-drawer-DKncO_vX.css +1 -0
  320. codex/static_root/assets/settings-drawer-DKncO_vX.css.br +0 -0
  321. codex/static_root/assets/settings-drawer-DKncO_vX.css.gz +0 -0
  322. codex/static_root/assets/stats-tab-B0-L7btW.8f7914d5c8ac.js +1 -0
  323. codex/static_root/assets/stats-tab-B0-L7btW.8f7914d5c8ac.js.br +0 -0
  324. codex/static_root/assets/stats-tab-B0-L7btW.8f7914d5c8ac.js.gz +0 -0
  325. codex/static_root/assets/stats-tab-B0-L7btW.js +1 -0
  326. codex/static_root/assets/stats-tab-B0-L7btW.js.br +0 -0
  327. codex/static_root/assets/stats-tab-B0-L7btW.js.gz +0 -0
  328. codex/static_root/assets/{stats-tab-CI0u-X7Y.2d1b6a0d4139.css → stats-tab-BXGisn5N.9b7a71e7fe28.css} +1 -1
  329. codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.br +0 -0
  330. codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.gz +0 -0
  331. codex/static_root/assets/{stats-tab-CI0u-X7Y.css → stats-tab-BXGisn5N.css} +1 -1
  332. codex/static_root/assets/stats-tab-BXGisn5N.css.br +0 -0
  333. codex/static_root/assets/stats-tab-BXGisn5N.css.gz +0 -0
  334. codex/static_root/assets/task-tab-CJEMX4mU.31d0755cc8f7.js +1 -0
  335. codex/static_root/assets/task-tab-CJEMX4mU.31d0755cc8f7.js.br +0 -0
  336. codex/static_root/assets/task-tab-CJEMX4mU.31d0755cc8f7.js.gz +0 -0
  337. codex/static_root/assets/task-tab-CJEMX4mU.js +1 -0
  338. codex/static_root/assets/task-tab-CJEMX4mU.js.br +0 -0
  339. codex/static_root/assets/task-tab-CJEMX4mU.js.gz +0 -0
  340. codex/static_root/assets/unauthorized-U1yum5qV.4a44970b19c7.js +1 -0
  341. codex/static_root/assets/unauthorized-U1yum5qV.4a44970b19c7.js.br +0 -0
  342. codex/static_root/assets/unauthorized-U1yum5qV.4a44970b19c7.js.gz +0 -0
  343. codex/static_root/assets/unauthorized-U1yum5qV.js +1 -0
  344. codex/static_root/assets/unauthorized-U1yum5qV.js.br +0 -0
  345. codex/static_root/assets/unauthorized-U1yum5qV.js.gz +0 -0
  346. codex/static_root/assets/{unauthorized-DGIM1VFy.c93d0b3ea5a4.css → unauthorized-muDsv-rC.2166ec8b3a32.css} +1 -1
  347. codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.br +0 -0
  348. codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.gz +0 -0
  349. codex/static_root/assets/{unauthorized-DGIM1VFy.css → unauthorized-muDsv-rC.css} +1 -1
  350. codex/static_root/assets/unauthorized-muDsv-rC.css.br +0 -0
  351. codex/static_root/assets/unauthorized-muDsv-rC.css.gz +0 -0
  352. codex/static_root/assets/user-tab-8DHYNhT5.3160ac8849a9.js +1 -0
  353. codex/static_root/assets/user-tab-8DHYNhT5.3160ac8849a9.js.br +0 -0
  354. codex/static_root/assets/user-tab-8DHYNhT5.3160ac8849a9.js.gz +0 -0
  355. codex/static_root/assets/user-tab-8DHYNhT5.js +1 -0
  356. codex/static_root/assets/user-tab-8DHYNhT5.js.br +0 -0
  357. codex/static_root/assets/user-tab-8DHYNhT5.js.gz +0 -0
  358. codex/static_root/js/choices-admin.1a20a5648f20.json +1 -0
  359. codex/static_root/js/choices-admin.1a20a5648f20.json.br +0 -0
  360. codex/static_root/js/choices-admin.1a20a5648f20.json.gz +0 -0
  361. codex/static_root/js/choices-admin.json +1 -1
  362. codex/static_root/js/choices-admin.json.br +0 -0
  363. codex/static_root/js/choices-admin.json.gz +0 -0
  364. codex/static_root/manifest.c82a23abe985.json +635 -0
  365. codex/static_root/manifest.c82a23abe985.json.br +0 -0
  366. codex/static_root/manifest.c82a23abe985.json.gz +0 -0
  367. codex/static_root/manifest.json +295 -270
  368. codex/static_root/manifest.json.br +0 -0
  369. codex/static_root/manifest.json.gz +0 -0
  370. codex/static_root/staticfiles.json +1 -1
  371. codex/templates/pwa/serviceworker.js +4 -4
  372. codex/urls/root.py +2 -6
  373. codex/version.py +0 -53
  374. codex/views/admin/flag.py +10 -1
  375. codex/views/admin/stats.py +1 -1
  376. codex/views/admin/tasks.py +2 -0
  377. codex/views/admin/user.py +4 -2
  378. codex/views/bookmark.py +16 -23
  379. codex/views/browser/filters/annotations.py +5 -4
  380. codex/views/browser/filters/search.py +1 -1
  381. codex/views/browser/order_by.py +1 -1
  382. codex/views/browser/validate.py +4 -7
  383. codex/views/const.py +1 -2
  384. codex/views/mixins.py +1 -1
  385. codex/views/reader/arcs.py +1 -1
  386. codex/views/reader/books.py +3 -3
  387. codex/views/reader/init.py +3 -2
  388. codex/views/reader/page.py +3 -4
  389. codex/views/version.py +10 -2
  390. {codex-1.6.8.dist-info → codex-1.6.9a0.dist-info}/METADATA +47 -51
  391. {codex-1.6.8.dist-info → codex-1.6.9a0.dist-info}/RECORD +394 -380
  392. codex/librarian/janitor/janitord.py +0 -76
  393. codex/static_root/assets/VCheckbox-DE3METqJ.947b68d61150.js +0 -1
  394. codex/static_root/assets/VCheckbox-DE3METqJ.947b68d61150.js.br +0 -0
  395. codex/static_root/assets/VCheckbox-DE3METqJ.947b68d61150.js.gz +0 -0
  396. codex/static_root/assets/VCheckbox-DE3METqJ.js +0 -1
  397. codex/static_root/assets/VCheckbox-DE3METqJ.js.br +0 -0
  398. codex/static_root/assets/VCheckbox-DE3METqJ.js.gz +0 -0
  399. codex/static_root/assets/VCheckboxBtn-DetOzpfX.e4293f13e16f.js +0 -1
  400. codex/static_root/assets/VCheckboxBtn-DetOzpfX.e4293f13e16f.js.br +0 -0
  401. codex/static_root/assets/VCheckboxBtn-DetOzpfX.e4293f13e16f.js.gz +0 -0
  402. codex/static_root/assets/VCheckboxBtn-DetOzpfX.js +0 -1
  403. codex/static_root/assets/VCheckboxBtn-DetOzpfX.js.br +0 -0
  404. codex/static_root/assets/VCheckboxBtn-DetOzpfX.js.gz +0 -0
  405. codex/static_root/assets/VCombobox-BksEr4D_.73429bb75f42.css +0 -1
  406. codex/static_root/assets/VCombobox-BksEr4D_.73429bb75f42.css.br +0 -0
  407. codex/static_root/assets/VCombobox-BksEr4D_.73429bb75f42.css.gz +0 -0
  408. codex/static_root/assets/VCombobox-BksEr4D_.css +0 -1
  409. codex/static_root/assets/VCombobox-BksEr4D_.css.br +0 -0
  410. codex/static_root/assets/VCombobox-BksEr4D_.css.gz +0 -0
  411. codex/static_root/assets/VCombobox-D2XmpiHi.3427b660b686.js +0 -1
  412. codex/static_root/assets/VCombobox-D2XmpiHi.3427b660b686.js.br +0 -0
  413. codex/static_root/assets/VCombobox-D2XmpiHi.3427b660b686.js.gz +0 -0
  414. codex/static_root/assets/VCombobox-D2XmpiHi.js +0 -1
  415. codex/static_root/assets/VCombobox-D2XmpiHi.js.br +0 -0
  416. codex/static_root/assets/VCombobox-D2XmpiHi.js.gz +0 -0
  417. codex/static_root/assets/VDialog-BXw6XFu7.38cde34df95a.js +0 -1
  418. codex/static_root/assets/VDialog-BXw6XFu7.38cde34df95a.js.br +0 -0
  419. codex/static_root/assets/VDialog-BXw6XFu7.38cde34df95a.js.gz +0 -0
  420. codex/static_root/assets/VDialog-BXw6XFu7.js +0 -1
  421. codex/static_root/assets/VDialog-BXw6XFu7.js.br +0 -0
  422. codex/static_root/assets/VDialog-BXw6XFu7.js.gz +0 -0
  423. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css +0 -1
  424. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.br +0 -0
  425. codex/static_root/assets/VDialog-lQ13FnRR.1c801ad756b2.css.gz +0 -0
  426. codex/static_root/assets/VDialog-lQ13FnRR.css +0 -1
  427. codex/static_root/assets/VDialog-lQ13FnRR.css.br +0 -0
  428. codex/static_root/assets/VDialog-lQ13FnRR.css.gz +0 -0
  429. codex/static_root/assets/VExpansionPanels-DsEXVcCv.20b475255e66.css +0 -1
  430. codex/static_root/assets/VExpansionPanels-DsEXVcCv.20b475255e66.css.br +0 -0
  431. codex/static_root/assets/VExpansionPanels-DsEXVcCv.20b475255e66.css.gz +0 -0
  432. codex/static_root/assets/VExpansionPanels-DsEXVcCv.css +0 -1
  433. codex/static_root/assets/VExpansionPanels-DsEXVcCv.css.br +0 -0
  434. codex/static_root/assets/VExpansionPanels-DsEXVcCv.css.gz +0 -0
  435. codex/static_root/assets/VExpansionPanels-cdsBiWJW.289873cadd1b.js +0 -1
  436. codex/static_root/assets/VExpansionPanels-cdsBiWJW.289873cadd1b.js.br +0 -0
  437. codex/static_root/assets/VExpansionPanels-cdsBiWJW.289873cadd1b.js.gz +0 -0
  438. codex/static_root/assets/VExpansionPanels-cdsBiWJW.js +0 -1
  439. codex/static_root/assets/VExpansionPanels-cdsBiWJW.js.br +0 -0
  440. codex/static_root/assets/VExpansionPanels-cdsBiWJW.js.gz +0 -0
  441. codex/static_root/assets/VRadioGroup-DPsPhrgJ.2232643fa4b2.js +0 -1
  442. codex/static_root/assets/VRadioGroup-DPsPhrgJ.2232643fa4b2.js.br +0 -0
  443. codex/static_root/assets/VRadioGroup-DPsPhrgJ.2232643fa4b2.js.gz +0 -0
  444. codex/static_root/assets/VRadioGroup-DPsPhrgJ.js +0 -1
  445. codex/static_root/assets/VRadioGroup-DPsPhrgJ.js.br +0 -0
  446. codex/static_root/assets/VRadioGroup-DPsPhrgJ.js.gz +0 -0
  447. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css +0 -1
  448. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.br +0 -0
  449. codex/static_root/assets/VSelect-ARDhJiQK.372c098fb475.css.gz +0 -0
  450. codex/static_root/assets/VSelect-ARDhJiQK.css +0 -1
  451. codex/static_root/assets/VSelect-ARDhJiQK.css.br +0 -0
  452. codex/static_root/assets/VSelect-ARDhJiQK.css.gz +0 -0
  453. codex/static_root/assets/VSelect-wpuzLrPv.f46f25efca5b.js +0 -1
  454. codex/static_root/assets/VSelect-wpuzLrPv.f46f25efca5b.js.br +0 -0
  455. codex/static_root/assets/VSelect-wpuzLrPv.f46f25efca5b.js.gz +0 -0
  456. codex/static_root/assets/VSelect-wpuzLrPv.js +0 -1
  457. codex/static_root/assets/VSelect-wpuzLrPv.js.br +0 -0
  458. codex/static_root/assets/VSelect-wpuzLrPv.js.gz +0 -0
  459. codex/static_root/assets/VSelectionControl-AnuOsZjr.0ca164ae628f.css +0 -1
  460. codex/static_root/assets/VSelectionControl-AnuOsZjr.0ca164ae628f.css.br +0 -0
  461. codex/static_root/assets/VSelectionControl-AnuOsZjr.0ca164ae628f.css.gz +0 -0
  462. codex/static_root/assets/VSelectionControl-AnuOsZjr.css +0 -1
  463. codex/static_root/assets/VSelectionControl-AnuOsZjr.css.br +0 -0
  464. codex/static_root/assets/VSelectionControl-AnuOsZjr.css.gz +0 -0
  465. codex/static_root/assets/VSelectionControl-DTgtf_T6.bbb99484db2e.js +0 -1
  466. codex/static_root/assets/VSelectionControl-DTgtf_T6.bbb99484db2e.js.br +0 -0
  467. codex/static_root/assets/VSelectionControl-DTgtf_T6.bbb99484db2e.js.gz +0 -0
  468. codex/static_root/assets/VSelectionControl-DTgtf_T6.js +0 -1
  469. codex/static_root/assets/VSelectionControl-DTgtf_T6.js.br +0 -0
  470. codex/static_root/assets/VSelectionControl-DTgtf_T6.js.gz +0 -0
  471. codex/static_root/assets/VSlideGroup-KhqCfdKQ.984b37842a8b.js.br +0 -0
  472. codex/static_root/assets/VSlideGroup-KhqCfdKQ.984b37842a8b.js.gz +0 -0
  473. codex/static_root/assets/VSlideGroup-KhqCfdKQ.js.br +0 -0
  474. codex/static_root/assets/VSlideGroup-KhqCfdKQ.js.gz +0 -0
  475. codex/static_root/assets/VTable-CJJzxZcw.edb1bb582e5c.js.br +0 -0
  476. codex/static_root/assets/VTable-CJJzxZcw.edb1bb582e5c.js.gz +0 -0
  477. codex/static_root/assets/VTable-CJJzxZcw.js.br +0 -0
  478. codex/static_root/assets/VTable-CJJzxZcw.js.gz +0 -0
  479. codex/static_root/assets/VTable-DflzroDq.160504d82195.css +0 -1
  480. codex/static_root/assets/VTable-DflzroDq.160504d82195.css.br +0 -0
  481. codex/static_root/assets/VTable-DflzroDq.160504d82195.css.gz +0 -0
  482. codex/static_root/assets/VTable-DflzroDq.css +0 -1
  483. codex/static_root/assets/VTable-DflzroDq.css.br +0 -0
  484. codex/static_root/assets/VTable-DflzroDq.css.gz +0 -0
  485. codex/static_root/assets/VTextField-BJxb_QuX.79ef5fb485f2.js +0 -1
  486. codex/static_root/assets/VTextField-BJxb_QuX.79ef5fb485f2.js.br +0 -0
  487. codex/static_root/assets/VTextField-BJxb_QuX.79ef5fb485f2.js.gz +0 -0
  488. codex/static_root/assets/VTextField-BJxb_QuX.js +0 -1
  489. codex/static_root/assets/VTextField-BJxb_QuX.js.br +0 -0
  490. codex/static_root/assets/VTextField-BJxb_QuX.js.gz +0 -0
  491. codex/static_root/assets/VTextField-Bc1tD4nS.2026682c8437.css +0 -1
  492. codex/static_root/assets/VTextField-Bc1tD4nS.2026682c8437.css.br +0 -0
  493. codex/static_root/assets/VTextField-Bc1tD4nS.2026682c8437.css.gz +0 -0
  494. codex/static_root/assets/VTextField-Bc1tD4nS.css +0 -1
  495. codex/static_root/assets/VTextField-Bc1tD4nS.css.br +0 -0
  496. codex/static_root/assets/VTextField-Bc1tD4nS.css.gz +0 -0
  497. codex/static_root/assets/VWindowItem-BWJJgE5I.8daf8cde5589.js +0 -1
  498. codex/static_root/assets/VWindowItem-BWJJgE5I.8daf8cde5589.js.br +0 -0
  499. codex/static_root/assets/VWindowItem-BWJJgE5I.8daf8cde5589.js.gz +0 -0
  500. codex/static_root/assets/VWindowItem-BWJJgE5I.js +0 -1
  501. codex/static_root/assets/VWindowItem-BWJJgE5I.js.br +0 -0
  502. codex/static_root/assets/VWindowItem-BWJJgE5I.js.gz +0 -0
  503. codex/static_root/assets/admin-D4sw3N4T.b590f88a62cf.css.br +0 -0
  504. codex/static_root/assets/admin-D4sw3N4T.b590f88a62cf.css.gz +0 -0
  505. codex/static_root/assets/admin-D4sw3N4T.css.br +0 -0
  506. codex/static_root/assets/admin-D4sw3N4T.css.gz +0 -0
  507. codex/static_root/assets/admin-DrXckZDB.5feb8fde2d3a.js +0 -1
  508. codex/static_root/assets/admin-DrXckZDB.5feb8fde2d3a.js.br +0 -0
  509. codex/static_root/assets/admin-DrXckZDB.5feb8fde2d3a.js.gz +0 -0
  510. codex/static_root/assets/admin-DrXckZDB.js +0 -1
  511. codex/static_root/assets/admin-DrXckZDB.js.br +0 -0
  512. codex/static_root/assets/admin-DrXckZDB.js.gz +0 -0
  513. codex/static_root/assets/admin-drawer-panel-Crj9u2NV.6b03e2a2ae14.js +0 -56
  514. codex/static_root/assets/admin-drawer-panel-Crj9u2NV.6b03e2a2ae14.js.br +0 -0
  515. codex/static_root/assets/admin-drawer-panel-Crj9u2NV.6b03e2a2ae14.js.gz +0 -0
  516. codex/static_root/assets/admin-drawer-panel-Crj9u2NV.js +0 -56
  517. codex/static_root/assets/admin-drawer-panel-Crj9u2NV.js.br +0 -0
  518. codex/static_root/assets/admin-drawer-panel-Crj9u2NV.js.gz +0 -0
  519. codex/static_root/assets/admin-drawer-panel-Dm28c2tG.css +0 -1
  520. codex/static_root/assets/admin-drawer-panel-Dm28c2tG.css.br +0 -0
  521. codex/static_root/assets/admin-drawer-panel-Dm28c2tG.css.gz +0 -0
  522. codex/static_root/assets/admin-drawer-panel-Dm28c2tG.fb4e885ba7c4.css +0 -1
  523. codex/static_root/assets/admin-drawer-panel-Dm28c2tG.fb4e885ba7c4.css.br +0 -0
  524. codex/static_root/assets/admin-drawer-panel-Dm28c2tG.fb4e885ba7c4.css.gz +0 -0
  525. codex/static_root/assets/browser-C50xLvMD.ebeac0d428d2.js +0 -1
  526. codex/static_root/assets/browser-C50xLvMD.ebeac0d428d2.js.br +0 -0
  527. codex/static_root/assets/browser-C50xLvMD.ebeac0d428d2.js.gz +0 -0
  528. codex/static_root/assets/browser-C50xLvMD.js +0 -1
  529. codex/static_root/assets/browser-C50xLvMD.js.br +0 -0
  530. codex/static_root/assets/browser-C50xLvMD.js.gz +0 -0
  531. codex/static_root/assets/browser-Cy0OgQJC.css.br +0 -0
  532. codex/static_root/assets/browser-Cy0OgQJC.css.gz +0 -0
  533. codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.br +0 -0
  534. codex/static_root/assets/browser-Cy0OgQJC.fabbd1aaffcf.css.gz +0 -0
  535. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css +0 -1
  536. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.br +0 -0
  537. codex/static_root/assets/change-password-dialog-0u6IJA87.5f9a9aa573b7.css.gz +0 -0
  538. codex/static_root/assets/change-password-dialog-0u6IJA87.css +0 -1
  539. codex/static_root/assets/change-password-dialog-0u6IJA87.css.br +0 -0
  540. codex/static_root/assets/change-password-dialog-0u6IJA87.css.gz +0 -0
  541. codex/static_root/assets/change-password-dialog-Cm6AnxGf.66f4e604144b.js +0 -1
  542. codex/static_root/assets/change-password-dialog-Cm6AnxGf.66f4e604144b.js.br +0 -0
  543. codex/static_root/assets/change-password-dialog-Cm6AnxGf.66f4e604144b.js.gz +0 -0
  544. codex/static_root/assets/change-password-dialog-Cm6AnxGf.js +0 -1
  545. codex/static_root/assets/change-password-dialog-Cm6AnxGf.js.br +0 -0
  546. codex/static_root/assets/change-password-dialog-Cm6AnxGf.js.gz +0 -0
  547. codex/static_root/assets/confirm-dialog-Mznixjrb.927eaa9bea39.js +0 -1
  548. codex/static_root/assets/confirm-dialog-Mznixjrb.927eaa9bea39.js.br +0 -0
  549. codex/static_root/assets/confirm-dialog-Mznixjrb.927eaa9bea39.js.gz +0 -0
  550. codex/static_root/assets/confirm-dialog-Mznixjrb.js +0 -1
  551. codex/static_root/assets/confirm-dialog-Mznixjrb.js.br +0 -0
  552. codex/static_root/assets/confirm-dialog-Mznixjrb.js.gz +0 -0
  553. codex/static_root/assets/datetime-column-DqSk_iUb.260bcbb4c8e2.js.br +0 -0
  554. codex/static_root/assets/datetime-column-DqSk_iUb.260bcbb4c8e2.js.gz +0 -0
  555. codex/static_root/assets/datetime-column-DqSk_iUb.js.br +0 -0
  556. codex/static_root/assets/datetime-column-DqSk_iUb.js.gz +0 -0
  557. codex/static_root/assets/filter-CvqVrkAJ.8996d1b1db2a.js.br +0 -0
  558. codex/static_root/assets/filter-CvqVrkAJ.8996d1b1db2a.js.gz +0 -0
  559. codex/static_root/assets/filter-CvqVrkAJ.js.br +0 -0
  560. codex/static_root/assets/filter-CvqVrkAJ.js.gz +0 -0
  561. codex/static_root/assets/flag-tab-Bdh3jAbG.7731e3229cca.js +0 -1
  562. codex/static_root/assets/flag-tab-Bdh3jAbG.7731e3229cca.js.br +0 -0
  563. codex/static_root/assets/flag-tab-Bdh3jAbG.7731e3229cca.js.gz +0 -0
  564. codex/static_root/assets/flag-tab-Bdh3jAbG.js +0 -1
  565. codex/static_root/assets/flag-tab-Bdh3jAbG.js.br +0 -0
  566. codex/static_root/assets/flag-tab-Bdh3jAbG.js.gz +0 -0
  567. codex/static_root/assets/group-tab-89t0MyqI.1c743da230be.js +0 -1
  568. codex/static_root/assets/group-tab-89t0MyqI.1c743da230be.js.br +0 -0
  569. codex/static_root/assets/group-tab-89t0MyqI.1c743da230be.js.gz +0 -0
  570. codex/static_root/assets/group-tab-89t0MyqI.js +0 -1
  571. codex/static_root/assets/group-tab-89t0MyqI.js.br +0 -0
  572. codex/static_root/assets/group-tab-89t0MyqI.js.gz +0 -0
  573. codex/static_root/assets/http-error-BJTIqHG7.68507d59e73a.js +0 -1
  574. codex/static_root/assets/http-error-BJTIqHG7.68507d59e73a.js.br +0 -3
  575. codex/static_root/assets/http-error-BJTIqHG7.68507d59e73a.js.gz +0 -0
  576. codex/static_root/assets/http-error-BJTIqHG7.js +0 -1
  577. codex/static_root/assets/http-error-BJTIqHG7.js.br +0 -3
  578. codex/static_root/assets/http-error-BJTIqHG7.js.gz +0 -0
  579. codex/static_root/assets/library-tab-BygwYPnO.94e92aa60bf2.css +0 -1
  580. codex/static_root/assets/library-tab-BygwYPnO.94e92aa60bf2.css.br +0 -0
  581. codex/static_root/assets/library-tab-BygwYPnO.94e92aa60bf2.css.gz +0 -0
  582. codex/static_root/assets/library-tab-BygwYPnO.css +0 -1
  583. codex/static_root/assets/library-tab-BygwYPnO.css.br +0 -0
  584. codex/static_root/assets/library-tab-BygwYPnO.css.gz +0 -0
  585. codex/static_root/assets/library-tab-DoebKL9-.6eef922082b3.js +0 -1
  586. codex/static_root/assets/library-tab-DoebKL9-.6eef922082b3.js.br +0 -0
  587. codex/static_root/assets/library-tab-DoebKL9-.6eef922082b3.js.gz +0 -0
  588. codex/static_root/assets/library-tab-DoebKL9-.js +0 -1
  589. codex/static_root/assets/library-tab-DoebKL9-.js.br +0 -0
  590. codex/static_root/assets/library-tab-DoebKL9-.js.gz +0 -0
  591. codex/static_root/assets/main-DArXKRWW.47a8064195fe.js +0 -6
  592. codex/static_root/assets/main-DArXKRWW.47a8064195fe.js.br +0 -0
  593. codex/static_root/assets/main-DArXKRWW.47a8064195fe.js.gz +0 -0
  594. codex/static_root/assets/main-DArXKRWW.js +0 -6
  595. codex/static_root/assets/main-DArXKRWW.js.br +0 -0
  596. codex/static_root/assets/main-DArXKRWW.js.gz +0 -0
  597. codex/static_root/assets/main-Ztp855PW.css.br +0 -0
  598. codex/static_root/assets/main-Ztp855PW.d46d718fac45.css.br +0 -0
  599. codex/static_root/assets/pagination-toolbar-BA9ifTed.b4865334ee08.css +0 -1
  600. codex/static_root/assets/pagination-toolbar-BA9ifTed.b4865334ee08.css.br +0 -0
  601. codex/static_root/assets/pagination-toolbar-BA9ifTed.b4865334ee08.css.gz +0 -0
  602. codex/static_root/assets/pagination-toolbar-BA9ifTed.css +0 -1
  603. codex/static_root/assets/pagination-toolbar-BA9ifTed.css.br +0 -0
  604. codex/static_root/assets/pagination-toolbar-BA9ifTed.css.gz +0 -0
  605. codex/static_root/assets/pagination-toolbar-m9-HzvQE.178146e802bf.js +0 -1
  606. codex/static_root/assets/pagination-toolbar-m9-HzvQE.178146e802bf.js.br +0 -0
  607. codex/static_root/assets/pagination-toolbar-m9-HzvQE.178146e802bf.js.gz +0 -0
  608. codex/static_root/assets/pagination-toolbar-m9-HzvQE.js +0 -1
  609. codex/static_root/assets/pagination-toolbar-m9-HzvQE.js.br +0 -0
  610. codex/static_root/assets/pagination-toolbar-m9-HzvQE.js.gz +0 -0
  611. codex/static_root/assets/pdf-doc-CMzaSV_K.8cd4b3e65f9a.css +0 -1
  612. codex/static_root/assets/pdf-doc-CMzaSV_K.8cd4b3e65f9a.css.br +0 -0
  613. codex/static_root/assets/pdf-doc-CMzaSV_K.8cd4b3e65f9a.css.gz +0 -0
  614. codex/static_root/assets/pdf-doc-CMzaSV_K.css +0 -1
  615. codex/static_root/assets/pdf-doc-CMzaSV_K.css.br +0 -0
  616. codex/static_root/assets/pdf-doc-CMzaSV_K.css.gz +0 -0
  617. codex/static_root/assets/pdf-doc-xL1qP1mK.643e4fab2b8d.js +0 -305
  618. codex/static_root/assets/pdf-doc-xL1qP1mK.643e4fab2b8d.js.br +0 -0
  619. codex/static_root/assets/pdf-doc-xL1qP1mK.643e4fab2b8d.js.gz +0 -0
  620. codex/static_root/assets/pdf-doc-xL1qP1mK.js +0 -305
  621. codex/static_root/assets/pdf-doc-xL1qP1mK.js.br +0 -0
  622. codex/static_root/assets/pdf-doc-xL1qP1mK.js.gz +0 -0
  623. codex/static_root/assets/reader-DwHuZOrV.b6067501f379.js +0 -2
  624. codex/static_root/assets/reader-DwHuZOrV.b6067501f379.js.br +0 -0
  625. codex/static_root/assets/reader-DwHuZOrV.b6067501f379.js.gz +0 -0
  626. codex/static_root/assets/reader-DwHuZOrV.js +0 -2
  627. codex/static_root/assets/reader-DwHuZOrV.js.br +0 -0
  628. codex/static_root/assets/reader-DwHuZOrV.js.gz +0 -0
  629. codex/static_root/assets/reader-YbzUiD0T.css +0 -1
  630. codex/static_root/assets/reader-YbzUiD0T.css.br +0 -0
  631. codex/static_root/assets/reader-YbzUiD0T.css.gz +0 -0
  632. codex/static_root/assets/reader-YbzUiD0T.efb15835b39d.css +0 -1
  633. codex/static_root/assets/reader-YbzUiD0T.efb15835b39d.css.br +0 -0
  634. codex/static_root/assets/reader-YbzUiD0T.efb15835b39d.css.gz +0 -0
  635. codex/static_root/assets/relation-chips-BGitp3yx.b3e7d178a615.css.br +0 -0
  636. codex/static_root/assets/relation-chips-BGitp3yx.b3e7d178a615.css.gz +0 -0
  637. codex/static_root/assets/relation-chips-BGitp3yx.css.br +0 -0
  638. codex/static_root/assets/relation-chips-BGitp3yx.css.gz +0 -0
  639. codex/static_root/assets/relation-chips-CCg5vRIV.59c17fb77441.js +0 -1
  640. codex/static_root/assets/relation-chips-CCg5vRIV.59c17fb77441.js.br +0 -0
  641. codex/static_root/assets/relation-chips-CCg5vRIV.59c17fb77441.js.gz +0 -0
  642. codex/static_root/assets/relation-chips-CCg5vRIV.js +0 -1
  643. codex/static_root/assets/relation-chips-CCg5vRIV.js.br +0 -0
  644. codex/static_root/assets/relation-chips-CCg5vRIV.js.gz +0 -0
  645. codex/static_root/assets/settings-drawer-CUQX8aKC.82a95dcef142.js +0 -2
  646. codex/static_root/assets/settings-drawer-CUQX8aKC.82a95dcef142.js.br +0 -0
  647. codex/static_root/assets/settings-drawer-CUQX8aKC.82a95dcef142.js.gz +0 -0
  648. codex/static_root/assets/settings-drawer-CUQX8aKC.js +0 -2
  649. codex/static_root/assets/settings-drawer-CUQX8aKC.js.br +0 -0
  650. codex/static_root/assets/settings-drawer-CUQX8aKC.js.gz +0 -0
  651. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css +0 -1
  652. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.br +0 -0
  653. codex/static_root/assets/settings-drawer-nZ9HvKSZ.01744e131806.css.gz +0 -0
  654. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css +0 -1
  655. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.br +0 -0
  656. codex/static_root/assets/settings-drawer-nZ9HvKSZ.css.gz +0 -0
  657. codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.br +0 -0
  658. codex/static_root/assets/stats-tab-CI0u-X7Y.2d1b6a0d4139.css.gz +0 -0
  659. codex/static_root/assets/stats-tab-CI0u-X7Y.css.br +0 -0
  660. codex/static_root/assets/stats-tab-CI0u-X7Y.css.gz +0 -0
  661. codex/static_root/assets/stats-tab-CyDREZdm.000d9e8e00a1.js +0 -1
  662. codex/static_root/assets/stats-tab-CyDREZdm.000d9e8e00a1.js.br +0 -0
  663. codex/static_root/assets/stats-tab-CyDREZdm.000d9e8e00a1.js.gz +0 -0
  664. codex/static_root/assets/stats-tab-CyDREZdm.js +0 -1
  665. codex/static_root/assets/stats-tab-CyDREZdm.js.br +0 -0
  666. codex/static_root/assets/stats-tab-CyDREZdm.js.gz +0 -0
  667. codex/static_root/assets/task-tab-RfF_m-c2.4ea612865931.js +0 -1
  668. codex/static_root/assets/task-tab-RfF_m-c2.4ea612865931.js.br +0 -0
  669. codex/static_root/assets/task-tab-RfF_m-c2.4ea612865931.js.gz +0 -0
  670. codex/static_root/assets/task-tab-RfF_m-c2.js +0 -1
  671. codex/static_root/assets/task-tab-RfF_m-c2.js.br +0 -0
  672. codex/static_root/assets/task-tab-RfF_m-c2.js.gz +0 -0
  673. codex/static_root/assets/unauthorized-BY4OkaUc.7a55b1214630.js +0 -1
  674. codex/static_root/assets/unauthorized-BY4OkaUc.7a55b1214630.js.br +0 -0
  675. codex/static_root/assets/unauthorized-BY4OkaUc.7a55b1214630.js.gz +0 -0
  676. codex/static_root/assets/unauthorized-BY4OkaUc.js +0 -1
  677. codex/static_root/assets/unauthorized-BY4OkaUc.js.br +0 -0
  678. codex/static_root/assets/unauthorized-BY4OkaUc.js.gz +0 -0
  679. codex/static_root/assets/unauthorized-DGIM1VFy.c93d0b3ea5a4.css.br +0 -0
  680. codex/static_root/assets/unauthorized-DGIM1VFy.c93d0b3ea5a4.css.gz +0 -0
  681. codex/static_root/assets/unauthorized-DGIM1VFy.css.br +0 -0
  682. codex/static_root/assets/unauthorized-DGIM1VFy.css.gz +0 -0
  683. codex/static_root/assets/user-tab-tSrCTVFj.6c5b9a53a03a.js +0 -1
  684. codex/static_root/assets/user-tab-tSrCTVFj.6c5b9a53a03a.js.br +0 -0
  685. codex/static_root/assets/user-tab-tSrCTVFj.6c5b9a53a03a.js.gz +0 -0
  686. codex/static_root/assets/user-tab-tSrCTVFj.js +0 -1
  687. codex/static_root/assets/user-tab-tSrCTVFj.js.br +0 -0
  688. codex/static_root/assets/user-tab-tSrCTVFj.js.gz +0 -0
  689. codex/static_root/js/choices-admin.3521e2a863df.json +0 -1
  690. codex/static_root/js/choices-admin.3521e2a863df.json.br +0 -0
  691. codex/static_root/js/choices-admin.3521e2a863df.json.gz +0 -0
  692. codex/static_root/manifest.37e83529bf91.json +0 -610
  693. codex/static_root/manifest.37e83529bf91.json.br +0 -0
  694. codex/static_root/manifest.37e83529bf91.json.gz +0 -0
  695. {codex-1.6.8.dist-info → codex-1.6.9a0.dist-info}/LICENSE +0 -0
  696. {codex-1.6.8.dist-info → codex-1.6.9a0.dist-info}/WHEEL +0 -0
  697. {codex-1.6.8.dist-info → codex-1.6.9a0.dist-info}/entry_points.txt +0 -0
@@ -8,6 +8,20 @@ class JanitorTask:
8
8
  """Tasks for the janitor."""
9
9
 
10
10
 
11
+ @dataclass
12
+ class JanitorLatestVersionTask(JanitorTask):
13
+ """Get the latest version."""
14
+
15
+ force: bool = False
16
+
17
+
18
+ @dataclass
19
+ class JanitorUpdateTask(JanitorTask):
20
+ """Task for updater."""
21
+
22
+ force: bool = False
23
+
24
+
11
25
  @dataclass
12
26
  class JanitorBackupTask(JanitorTask):
13
27
  """Backup the database."""
@@ -23,13 +37,6 @@ class JanitorShutdownTask(JanitorTask):
23
37
  """for shutdown."""
24
38
 
25
39
 
26
- @dataclass
27
- class JanitorUpdateTask(JanitorTask):
28
- """Task for updater."""
29
-
30
- force: bool
31
-
32
-
33
40
  @dataclass
34
41
  class JanitorVacuumTask(JanitorTask):
35
42
  """Vacuum the database."""
@@ -0,0 +1,12 @@
1
+ """Janitor Scheduled time."""
2
+
3
+ from datetime import datetime, time, timedelta
4
+
5
+ from django.utils import timezone as django_timezone
6
+
7
+
8
+ def get_janitor_time(_log):
9
+ """Get midnight relative to now."""
10
+ tomorrow = django_timezone.now() + timedelta(days=1)
11
+ tomorrow = tomorrow.astimezone()
12
+ return datetime.combine(tomorrow, time.min).astimezone()
@@ -5,17 +5,36 @@ import signal
5
5
  import subprocess
6
6
  import sys
7
7
 
8
+ from versio.version import Version
9
+
8
10
  from codex.librarian.janitor.status import JanitorStatusTypes
9
11
  from codex.librarian.tasks import LibrarianShutdownTask
10
12
  from codex.models import AdminFlag
13
+ from codex.models.admin import Timestamp
11
14
  from codex.status import Status
12
- from codex.version import PACKAGE_NAME, VERSION, get_version, is_outdated
15
+ from codex.version import VERSION, get_version
13
16
  from codex.worker_base import WorkerBaseMixin
14
17
 
15
18
 
16
19
  class UpdateMixin(WorkerBaseMixin):
17
20
  """Update codex methods for janitor."""
18
21
 
22
+ def _is_outdated(self):
23
+ """Is codex outdated."""
24
+ result = False
25
+ ts = Timestamp.objects.get(key=Timestamp.TimestampChoices.CODEX_VERSION.value)
26
+ latest_version = ts.version
27
+ versio_latest_version = Version(latest_version)
28
+
29
+ installed_versio_version = Version(VERSION)
30
+ if versio_latest_version.parts[1] and not installed_versio_version.parts[1]: # type: ignore
31
+ pre_blurb = "latest version is a prerelease. But installed version is not."
32
+ else:
33
+ result = versio_latest_version > installed_versio_version
34
+ pre_blurb = ""
35
+ self.log.info(f"{latest_version=} > {VERSION=} = {result}{pre_blurb}")
36
+ return result
37
+
19
38
  def update_codex(self, force=False):
20
39
  """Update the package and restart everything if the version changed."""
21
40
  status = Status(JanitorStatusTypes.CODEX_UPDATE)
@@ -27,7 +46,7 @@ class UpdateMixin(WorkerBaseMixin):
27
46
  eau = AdminFlag.objects.only("on").get(
28
47
  key=AdminFlag.FlagChoices.AUTO_UPDATE.value
29
48
  )
30
- if not eau.on or not is_outdated(PACKAGE_NAME):
49
+ if not eau.on or not self._is_outdated():
31
50
  self.log.info("Codex is up to date.")
32
51
  return
33
52
 
@@ -21,8 +21,7 @@ class VacuumMixin(WorkerBaseMixin):
21
21
  self.status_controller.start(status)
22
22
  old_size = DB_PATH.stat().st_size
23
23
  with connection.cursor() as cursor:
24
- cursor.execute("PRAGMA analysis_limit=400;")
25
- cursor.execute("PRAGMA optimize;")
24
+ cursor.execute("PRAGMA optimize")
26
25
  cursor.execute("VACUUM")
27
26
  cursor.execute("PRAGMA wal_checkpoint(TRUNCATE)")
28
27
  new_size = DB_PATH.stat().st_size
@@ -9,6 +9,7 @@ from caseconverter import snakecase
9
9
 
10
10
  from codex.librarian.covers.coverd import CoverThread
11
11
  from codex.librarian.covers.tasks import CoverTask
12
+ from codex.librarian.cron.crond import CronThread
12
13
  from codex.librarian.delayed_taskd import DelayedTasksThread
13
14
  from codex.librarian.importer.importerd import ComicImporterThread
14
15
  from codex.librarian.importer.tasks import (
@@ -16,7 +17,6 @@ from codex.librarian.importer.tasks import (
16
17
  ImportTask,
17
18
  )
18
19
  from codex.librarian.janitor.janitor import Janitor
19
- from codex.librarian.janitor.janitord import JanitorThread
20
20
  from codex.librarian.janitor.tasks import JanitorTask
21
21
  from codex.librarian.notifier.notifierd import NotifierThread
22
22
  from codex.librarian.notifier.tasks import NotifierTask
@@ -26,7 +26,9 @@ from codex.librarian.search.tasks import (
26
26
  SearchIndexerTask,
27
27
  SearchIndexRebuildIfDBChangedTask,
28
28
  )
29
- from codex.librarian.tasks import DelayedTasks, LibrarianShutdownTask
29
+ from codex.librarian.tasks import DelayedTasks, LibrarianShutdownTask, WakeCronTask
30
+ from codex.librarian.telemeter.tasks import TelemeterTask
31
+ from codex.librarian.telemeter.telemeter import send_telemetry
30
32
  from codex.librarian.watchdog.event_batcherd import WatchdogEventBatcherThread
31
33
  from codex.librarian.watchdog.observers import (
32
34
  LibraryEventObserver,
@@ -52,7 +54,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
52
54
  WatchdogEventBatcherThread,
53
55
  LibraryEventObserver,
54
56
  LibraryPollingObserver,
55
- JanitorThread,
57
+ CronThread,
56
58
  )
57
59
  _THREAD_CLASS_MAP = MappingProxyType(
58
60
  {
@@ -105,6 +107,10 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
105
107
  self.log.debug("Told search indexers to stop for db updates.")
106
108
  else:
107
109
  self._threads.search_indexer_thread.queue.put(task)
110
+ case WakeCronTask():
111
+ self._threads.cron_thread.end_timeout()
112
+ case TelemeterTask():
113
+ send_telemetry(self.log)
108
114
  case JanitorTask():
109
115
  self.janitor.run(task)
110
116
  case DelayedTasks():
codex/librarian/tasks.py CHANGED
@@ -12,4 +12,8 @@ class DelayedTasks:
12
12
 
13
13
 
14
14
  class LibrarianShutdownTask:
15
- """Empty task."""
15
+ """Signal task."""
16
+
17
+
18
+ class WakeCronTask:
19
+ """Signal task."""
@@ -0,0 +1 @@
1
+ """Telemeter."""
@@ -111,8 +111,10 @@ class CodexStats:
111
111
 
112
112
  return user_stats, anon_session_count
113
113
 
114
- def _get_platform(self, obj):
114
+ def _add_platform(self, obj):
115
115
  """Add dict of platform information to object."""
116
+ if self.params and "platform" not in self.params:
117
+ return
116
118
  platform = {
117
119
  "docker": self._is_docker(),
118
120
  "machine": machine(),
@@ -126,8 +128,10 @@ class CodexStats:
126
128
  }
127
129
  obj["platform"] = platform
128
130
 
129
- def _get_config(self, obj):
131
+ def _add_config(self, obj):
130
132
  """Add dict of config informaation to object."""
133
+ if self.params and "config" not in self.params:
134
+ return
131
135
  config = self._get_model_counts("config")
132
136
  sessions, config["user_anonymous_count"] = self._get_session_stats()
133
137
  config["user_registered_count"] = config.pop("users_count", 0)
@@ -135,14 +139,18 @@ class CodexStats:
135
139
  obj["config"] = config
136
140
  obj["sessions"] = sessions
137
141
 
138
- def _get_groups(self, obj):
142
+ def _add_groups(self, obj):
139
143
  """Add dict of groups information to object."""
144
+ if self.params and "groups" not in self.params:
145
+ return
140
146
  groups = self._get_model_counts("groups")
147
+ groups["issue_count"] = groups.pop("comic_count", 0)
141
148
  obj["groups"] = groups
142
149
 
143
- @staticmethod
144
- def _get_file_types(obj):
150
+ def _add_file_types(self, obj):
145
151
  """Query for file types."""
152
+ if self.params and "fileTypes" not in self.params:
153
+ return
146
154
  file_types = {}
147
155
  qs = (
148
156
  Comic.objects.values("file_type")
@@ -156,22 +164,19 @@ class CodexStats:
156
164
  sorted_fts = dict(sorted(file_types.items()))
157
165
  obj["file_types"] = sorted_fts
158
166
 
159
- def _get_metadata(self, obj):
167
+ def _add_metadata(self, obj):
160
168
  """Add dict of metadata counts to object."""
169
+ if self.params and "metadata" not in self.params:
170
+ return
161
171
  metadata = self._get_model_counts("metadata")
162
172
  obj["metadata"] = metadata
163
173
 
164
174
  def get(self):
165
175
  """Construct the stats object."""
166
176
  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
+ self._add_platform(obj)
178
+ self._add_config(obj)
179
+ self._add_groups(obj)
180
+ self._add_file_types(obj)
181
+ self._add_metadata(obj)
177
182
  return obj
@@ -0,0 +1,8 @@
1
+ """Telemter tasks."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class TelemeterTask:
8
+ """Send telemetry."""
@@ -0,0 +1,80 @@
1
+ """Telemeter job."""
2
+
3
+ import json
4
+ from base64 import a85decode
5
+ from logging import getLogger
6
+ from lzma import compress
7
+ from uuid import uuid4
8
+
9
+ from requests import Session
10
+
11
+ from codex.librarian.telemeter.stats import CodexStats
12
+ from codex.models.admin import AdminFlag, Timestamp
13
+ from codex.settings.settings import DEBUG
14
+
15
+ # Version
16
+ _APP_NAME = "codex"
17
+ _VERSION = "1"
18
+
19
+ # Sending
20
+ # this isn't meant to fool you. it's meant to discourage lazy scraper bots.
21
+ _BASE = "".join(
22
+ (
23
+ a85decode(b"BQS?8F#ks-@:XCm@;\\+").decode(),
24
+ a85decode(b"Ea`frF)to6Bk]hRFCB94/c").decode(),
25
+ a85decode(b"@rGmhGV*rI@:Wqi/n&^<").decode(),
26
+ )
27
+ )
28
+ _HEADERS = {"Content-Type": "application/xz"}
29
+ _POST = _BASE + f"/stats/{_APP_NAME}/{_VERSION}"
30
+ _TIMEOUT = 5
31
+
32
+ LOG = getLogger(__name__)
33
+
34
+
35
+ def get_telemeter_timestamp():
36
+ """Get or create timestamp."""
37
+ key = Timestamp.TimestampChoices.TELEMETER_SENT.value
38
+ defaults = {"key": key}
39
+ ts, _ = Timestamp.objects.get_or_create(defaults=defaults, key=key)
40
+ if not ts.version:
41
+ ts.version = str(uuid4())
42
+ ts.save()
43
+ return ts
44
+
45
+
46
+ def _post_stats(log, data):
47
+ """Post telemetry to endpoint."""
48
+ data_json = json.dumps(data)
49
+ json_bytes = data_json.encode()
50
+ compressed_data = compress(json_bytes)
51
+ if DEBUG:
52
+ msg = f"{_POST} {len(compressed_data)}, {_HEADERS}, {_TIMEOUT}"
53
+ log.debug(msg)
54
+ else:
55
+ with Session() as session:
56
+ response = session.post(
57
+ _POST, data=compressed_data, headers=_HEADERS, timeout=_TIMEOUT
58
+ )
59
+ response.raise_for_status()
60
+
61
+
62
+ def _send_telemetry(log):
63
+ """Send telemetry to server."""
64
+ if not AdminFlag.objects.get(key=AdminFlag.FlagChoices.SEND_TELEMETRY.value).on:
65
+ reason = "Send Telemetry flag is off."
66
+ raise ValueError(reason)
67
+ ts = get_telemeter_timestamp()
68
+ stats = CodexStats().get()
69
+ data = {"stats": stats, "uuid": ts.version}
70
+ _post_stats(log, data)
71
+ # log.debug("Sent anonymous stats")
72
+ ts.save() # update updated_at
73
+
74
+
75
+ def send_telemetry(log):
76
+ """Send anonymous telemetry during one window per week."""
77
+ try:
78
+ _send_telemetry(log)
79
+ except Exception as exc:
80
+ log.debug(f"Failed to send anonyomous stats: {exc}")
@@ -0,0 +1,61 @@
1
+ """Get telemeter send time."""
2
+
3
+ from datetime import datetime, timedelta, timezone
4
+ from uuid import UUID
5
+
6
+ from codex.librarian.telemeter.telemeter import get_telemeter_timestamp
7
+ from codex.models.admin import AdminFlag
8
+
9
+ # Timing
10
+ _ONE_DAY = 24 * 60 * 60
11
+ _SECS_PER_WEEK = 7 * _ONE_DAY
12
+ _MAX_UUID = 2**128
13
+ _UUID_DIVISOR = _MAX_UUID / _SECS_PER_WEEK
14
+
15
+
16
+ def _get_utc_start_of_week():
17
+ """Get timestamp for this Monady 00:00:00."""
18
+ # Monday, now o'clock.
19
+ utc_now = datetime.now(tz=timezone.utc)
20
+ start_of_week = utc_now - timedelta(days=utc_now.weekday())
21
+ # Monday, midnight.
22
+ return start_of_week.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(
23
+ tz=timezone.utc
24
+ )
25
+
26
+
27
+ def _is_created_recently(ts):
28
+ """Don't send if created recently."""
29
+ now = datetime.now(tz=timezone.utc)
30
+ since = ts.created_at - now
31
+ return abs(since.total_seconds()) < _ONE_DAY
32
+
33
+
34
+ def _get_scheduled_time(ts):
35
+ """Compute the time of week to send from the uuid."""
36
+ start_of_week = _get_utc_start_of_week()
37
+ uuid = UUID(ts.version)
38
+ seconds_after_week_start = uuid.int / _UUID_DIVISOR
39
+ time_of_week = timedelta(seconds=seconds_after_week_start)
40
+ telemeter_time = start_of_week + time_of_week
41
+ telemeter_time = telemeter_time.astimezone(tz=timezone.utc)
42
+ if telemeter_time < ts.updated_at:
43
+ # Already ran this week, or created this week after run time.
44
+ telemeter_time = 0
45
+ return telemeter_time
46
+
47
+
48
+ def get_telemeter_time(log):
49
+ """Get the time to send telemetry."""
50
+ # Should we schedule telemeter at all?
51
+ if not AdminFlag.objects.get(key=AdminFlag.FlagChoices.SEND_TELEMETRY.value).on:
52
+ log.debug("Telemeter disabled. Not scheduled.")
53
+ return 0
54
+
55
+ ts = get_telemeter_timestamp()
56
+
57
+ if _is_created_recently(ts):
58
+ log.debug("Telemeter created recently. Not scheduled.")
59
+ return 0
60
+
61
+ return _get_scheduled_time(ts)
@@ -58,7 +58,7 @@ class CodexDatabaseSnapshot(DirectorySnapshot, LoggerBaseMixin):
58
58
  i = (st.st_ino, st.st_dev)
59
59
  self._inode_to_path[i] = path
60
60
 
61
- def __init__( # noqa: PLR0913
61
+ def __init__(
62
62
  self,
63
63
  path,
64
64
  _recursive=True, # unused, always recursive
@@ -21,9 +21,9 @@ class SnapshotDiffData:
21
21
  class CodexDirectorySnapshotDiff(DirectorySnapshotDiff):
22
22
  """Custom Diff allows inode only changes to be 'modified'."""
23
23
 
24
- def _get_inode(self, directory, full_path):
24
+ def _get_inode(self, snapshot, full_path):
25
25
  """Get inode optionally ignoring device stats."""
26
- result = directory.inode(full_path)
26
+ result = snapshot.inode(full_path)
27
27
  if self._ignore_device:
28
28
  result = result[0]
29
29
  return result
@@ -33,7 +33,7 @@ class CodexDirectorySnapshotDiff(DirectorySnapshotDiff):
33
33
  return self._get_inode(data.ref, path) == self._get_inode(data.snapshot, path)
34
34
 
35
35
  def _is_stats_equal(self, data, old_path, new_path):
36
- """Return if the mtimze and size are equal.
36
+ """Return if the mtime and size are equal.
37
37
 
38
38
  For old paths in the ref and new paths in the snapshot.
39
39
  """
@@ -48,25 +48,27 @@ class CodexDirectorySnapshotDiff(DirectorySnapshotDiff):
48
48
  if self._inode_only_modified:
49
49
  data.modified.add(path)
50
50
  else:
51
+ # Add these to created and deleted. But...
52
+ # The _find_moved_paths() inode tracker should
53
+ # sort them out.
54
+ # This is the same as in upstream dirsnapshot.py
51
55
  data.created.add(path)
52
56
  data.deleted.add(path)
53
57
 
54
58
  def _find_moved_paths(self, data):
55
- """Find moved paths."""
56
- for path in tuple(data.deleted):
57
- inode = data.ref.inode(path)
58
- new_path = data.snapshot.path(inode)
59
- if new_path:
59
+ """Find moved paths in deleted and created."""
60
+ for old_path in tuple(data.deleted):
61
+ inode = self._get_inode(data.ref, old_path)
62
+ if new_path := data.snapshot.path(inode):
60
63
  # file is not deleted but moved
61
- data.deleted.remove(path)
62
- data.moved.add((path, new_path))
64
+ data.deleted.remove(old_path)
65
+ data.moved.add((old_path, new_path))
63
66
 
64
- for path in tuple(data.created):
65
- inode = data.snapshot.inode(path)
66
- old_path = data.ref.path(inode)
67
- if old_path:
68
- data.created.remove(path)
69
- data.moved.add((old_path, path))
67
+ for new_path in tuple(data.created):
68
+ inode = self._get_inode(data.snapshot, new_path)
69
+ if old_path := data.ref.path(inode):
70
+ data.created.remove(new_path)
71
+ data.moved.add((old_path, new_path))
70
72
 
71
73
  def _find_modified_paths(self, data):
72
74
  """Find modified paths."""
@@ -80,7 +82,7 @@ class CodexDirectorySnapshotDiff(DirectorySnapshotDiff):
80
82
  # Check moved paths for modificiations.
81
83
  for old_path, new_path in data.moved:
82
84
  if not self._is_stats_equal(data, old_path, new_path):
83
- data.modified.add(old_path)
85
+ data.modified.add(new_path)
84
86
 
85
87
  def __init__( # C901, PLR0912
86
88
  self, ref, snapshot, ignore_device=False, inode_only_modified=False
@@ -25,20 +25,18 @@ from codex.models import Library
25
25
  from codex.status import Status
26
26
  from codex.worker_base import WorkerBaseMixin
27
27
 
28
- _CODEX_EVENT_FILTER = frozenset(
29
- {
30
- FileMovedEvent,
31
- FileModifiedEvent,
32
- FileCreatedEvent,
33
- FileDeletedEvent,
34
- # FileClosedEvent,
35
- # FileOpenedEvent,
36
- DirMovedEvent,
37
- DirModifiedEvent,
38
- DirDeletedEvent,
39
- # DirCreatedEvent,
40
- }
41
- )
28
+ _CODEX_EVENT_FILTER = [
29
+ FileMovedEvent,
30
+ FileModifiedEvent,
31
+ FileCreatedEvent,
32
+ FileDeletedEvent,
33
+ # FileClosedEvent,
34
+ # FileOpenedEvent,
35
+ DirMovedEvent,
36
+ DirModifiedEvent,
37
+ DirDeletedEvent,
38
+ # DirCreatedEvent,
39
+ ]
42
40
  _DOCKER_UNMOUNTED_FN = "DOCKER_UNMOUNTED_VOLUME"
43
41
 
44
42
 
@@ -234,7 +234,7 @@ class CodexCustomCoverEventHandler(CodexEventHandlerBase):
234
234
  try:
235
235
  if event.is_directory or event.event_type in self.IGNORED_EVENTS:
236
236
  return
237
- src_cover_match = self._match_group_cover_image(event.src_path)
237
+ src_cover_match = self._match_group_cover_image(str(event.src_path))
238
238
  if event.event_type == EVENT_TYPE_MOVED:
239
239
  send_event = self._transform_event_moved(event, src_cover_match)
240
240
  elif src_cover_match:
@@ -110,15 +110,15 @@ class UatuMixin(BaseObserver, WorkerBaseMixin):
110
110
  https://pythonhosted.org/watchdog/_modules/watchdog/observers/api.html#BaseObserver
111
111
  """
112
112
  if self._emitter_class != DatabasePollingEmitter:
113
- return super().schedule(event_handler, path, recursive)
113
+ return super().schedule(event_handler, path, recursive=recursive)
114
114
  with self._lock:
115
- watch = ObservedWatch(path, recursive)
115
+ watch = ObservedWatch(path, recursive=recursive)
116
116
  self._add_handler_for_watch(event_handler, watch)
117
117
 
118
118
  # If we don't have an emitter for this watch already, create it.
119
119
  if self._emitter_for_watch.get(watch) is None:
120
120
  covers_only = isinstance(event_handler, CodexCustomCoverEventHandler)
121
- emitter = self._emitter_class(
121
+ emitter = DatabasePollingEmitter(
122
122
  event_queue=self.event_queue,
123
123
  watch=watch,
124
124
  timeout=self.timeout,
@@ -166,7 +166,7 @@ class LibraryPollingObserver(UatuMixin):
166
166
 
167
167
  for emitter in self.emitters:
168
168
  if emitter.watch.path in paths:
169
- emitter.poll(force)
169
+ emitter.poll(force) # type: ignore
170
170
  except Exception:
171
171
  self.log.exception(
172
172
  f"{self.__class__.__name__}.poll({library_pks}, {force})"
@@ -0,0 +1,48 @@
1
+ """Generated by Django 5.0.6 on 2024-07-02 21:01."""
2
+
3
+ from typing import ClassVar
4
+
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ """Migrate db."""
10
+
11
+ dependencies: ClassVar[list] = [ # type: ignore
12
+ ("codex", "0027_import_order_and_covers"),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AlterField(
17
+ model_name="adminflag",
18
+ name="key",
19
+ field=models.CharField(
20
+ choices=[
21
+ ("FV", "Folder View"),
22
+ ("RG", "Registration"),
23
+ ("NU", "Non Users"),
24
+ ("AU", "Auto Update"),
25
+ ("SO", "Search Index Optimize"),
26
+ ("IM", "Import Metadata"),
27
+ ("ST", "Send Telemetry"),
28
+ ],
29
+ db_index=True,
30
+ max_length=2,
31
+ ),
32
+ ),
33
+ migrations.AlterField(
34
+ model_name="timestamp",
35
+ name="key",
36
+ field=models.CharField(
37
+ choices=[
38
+ ("JA", "Janitor"),
39
+ ("VR", "Codex Version"),
40
+ ("SI", "Search Index UUID"),
41
+ ("AP", "API Key"),
42
+ ("TS", "Telemeter Sent"),
43
+ ],
44
+ db_index=True,
45
+ max_length=2,
46
+ ),
47
+ ),
48
+ ]
codex/models/admin.py CHANGED
@@ -39,6 +39,7 @@ class AdminFlag(BaseModel):
39
39
  AUTO_UPDATE = "AU"
40
40
  SEARCH_INDEX_OPTIMIZE = "SO"
41
41
  IMPORT_METADATA = "IM"
42
+ SEND_TELEMETRY = "ST"
42
43
 
43
44
  FALSE_DEFAULTS = frozenset({FlagChoices.AUTO_UPDATE})
44
45
 
@@ -86,6 +87,7 @@ class Timestamp(BaseModel):
86
87
  CODEX_VERSION = "VR", _("Codex Version")
87
88
  SEARCH_INDEX_UUID = "SI", _("Search Index UUID")
88
89
  API_KEY = "AP", _("API Key")
90
+ TELEMETER_SENT = "TS", _("Telemeter Sent")
89
91
 
90
92
  key = CharField(db_index=True, max_length=2, choices=TimestampChoices.choices)
91
93
  version = CharField(max_length=MAX_FIELD_LEN, default="")
codex/models/comic.py CHANGED
@@ -246,7 +246,7 @@ class Comic(WatchedPathBrowserGroup):
246
246
  return issue_str
247
247
 
248
248
  @classmethod
249
- def get_title( # noqa: PLR0913
249
+ def get_title(
250
250
  cls, obj, volume=True, zero_pad=None, name=True, filename_fallback=False
251
251
  ):
252
252
  """Create the comic title for display."""
codex/search/backend.py CHANGED
@@ -122,7 +122,10 @@ class CodexSearchBackend(CodexBackendSearchMixin, WhooshSearchBackend, WorkerBas
122
122
  "created_at": ("created",),
123
123
  "critical_rating": gen_multipart_field_aliases("critical_rating"),
124
124
  "genres": ("genre",),
125
- "file_type": ("type",),
125
+ "file_type": (
126
+ "filetype",
127
+ "type",
128
+ ),
126
129
  "identifier": ("id", "nss"),
127
130
  "identifier_type": ("idtype", "id_type", "nid"),
128
131
  "issue_number": ("number",),
@@ -132,7 +135,11 @@ class CodexSearchBackend(CodexBackendSearchMixin, WhooshSearchBackend, WorkerBas
132
135
  "original_format": ("format",),
133
136
  "page_count": ("pages",),
134
137
  "reading_direction": ("direction",),
135
- "search_path": ("path", *gen_multipart_field_aliases("folders")),
138
+ "search_path": (
139
+ "path",
140
+ "filename",
141
+ *gen_multipart_field_aliases("folders"),
142
+ ),
136
143
  "series_groups": gen_multipart_field_aliases("series_groups"),
137
144
  "scan_info": ("scan",),
138
145
  "stories": ("story",),
@@ -431,7 +438,7 @@ class CodexSearchBackend(CodexBackendSearchMixin, WhooshSearchBackend, WorkerBas
431
438
  raise
432
439
  return count
433
440
 
434
- def update( # noqa: PLR0913, # type: ignore
441
+ def update( # type: ignore
435
442
  self,
436
443
  index,
437
444
  iterable,
@@ -53,8 +53,7 @@ class BrowserAggregateSerializerMixin(Serializer):
53
53
  )
54
54
  continue
55
55
 
56
- if dt > mtime:
57
- mtime = dt
56
+ mtime = max(dt, mtime)
58
57
 
59
58
  if obj.group != COMIC_GROUP and (
60
59
  mbua := getattr(obj, "max_bookmark_updated_at", None)