codex 1.7.0a0__py3-none-any.whl → 1.7.0a1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (715) hide show
  1. codex/choices.py +404 -0
  2. codex/choices_to_json.py +122 -0
  3. codex/db.py +31 -15
  4. codex/exceptions.py +2 -3
  5. codex/librarian/bookmark/__init__.py +1 -0
  6. codex/librarian/bookmark/bookmarkd.py +38 -0
  7. codex/librarian/bookmark/tasks.py +19 -0
  8. codex/librarian/bookmark/update.py +154 -0
  9. codex/librarian/covers/coverd.py +3 -4
  10. codex/librarian/covers/create.py +8 -7
  11. codex/librarian/covers/path.py +1 -2
  12. codex/librarian/covers/purge.py +2 -2
  13. codex/librarian/janitor/integrity.py +1 -0
  14. codex/librarian/librariand.py +5 -0
  15. codex/librarian/watchdog/emitter.py +21 -19
  16. codex/librarian/watchdog/event_batcherd.py +0 -1
  17. codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +4 -0
  18. codex/models/admin.py +0 -5
  19. codex/models/functions.py +6 -5
  20. codex/models/query.py +6 -0
  21. codex/run.py +11 -9
  22. codex/serializers/admin/tasks.py +2 -2
  23. codex/serializers/browser/mixins.py +18 -14
  24. codex/serializers/browser/settings.py +4 -4
  25. codex/serializers/fields.py +9 -5
  26. codex/startup.py +6 -4
  27. codex/static_root/assets/VCheckbox-B38iL9f_.81d9c00753e7.js +1 -0
  28. codex/static_root/assets/VCheckbox-B38iL9f_.81d9c00753e7.js.br +0 -0
  29. codex/static_root/assets/VCheckbox-B38iL9f_.81d9c00753e7.js.gz +0 -0
  30. codex/static_root/assets/VCheckbox-B38iL9f_.js +1 -0
  31. codex/static_root/assets/VCheckbox-B38iL9f_.js.br +0 -0
  32. codex/static_root/assets/VCheckbox-B38iL9f_.js.gz +0 -0
  33. codex/static_root/assets/VCheckboxBtn-BbNnqvzj.aa7fa886f86a.js +1 -0
  34. codex/static_root/assets/VCheckboxBtn-BbNnqvzj.aa7fa886f86a.js.br +0 -0
  35. codex/static_root/assets/VCheckboxBtn-BbNnqvzj.aa7fa886f86a.js.gz +0 -0
  36. codex/static_root/assets/VCheckboxBtn-BbNnqvzj.js +1 -0
  37. codex/static_root/assets/VCheckboxBtn-BbNnqvzj.js.br +0 -0
  38. codex/static_root/assets/VCheckboxBtn-BbNnqvzj.js.gz +0 -0
  39. codex/static_root/assets/VCombobox-BaV3XhIr.bf0c945d80a9.js +1 -0
  40. codex/static_root/assets/VCombobox-BaV3XhIr.bf0c945d80a9.js.br +0 -0
  41. codex/static_root/assets/VCombobox-BaV3XhIr.bf0c945d80a9.js.gz +0 -0
  42. codex/static_root/assets/VCombobox-BaV3XhIr.js +1 -0
  43. codex/static_root/assets/VCombobox-BaV3XhIr.js.br +0 -0
  44. codex/static_root/assets/VCombobox-BaV3XhIr.js.gz +0 -0
  45. codex/static_root/assets/VDialog-CSvrKKb2.css +1 -0
  46. codex/static_root/assets/VDialog-CSvrKKb2.css.br +0 -0
  47. codex/static_root/assets/VDialog-CSvrKKb2.css.gz +0 -0
  48. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css +1 -0
  49. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.br +0 -0
  50. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.gz +0 -0
  51. codex/static_root/assets/VDialog-DJR54d5g.9d6a52f62db3.js +1 -0
  52. codex/static_root/assets/VDialog-DJR54d5g.9d6a52f62db3.js.br +0 -0
  53. codex/static_root/assets/VDialog-DJR54d5g.9d6a52f62db3.js.gz +0 -0
  54. codex/static_root/assets/VDialog-DJR54d5g.js +1 -0
  55. codex/static_root/assets/VDialog-DJR54d5g.js.br +0 -0
  56. codex/static_root/assets/VDialog-DJR54d5g.js.gz +0 -0
  57. codex/static_root/assets/VExpansionPanels-CJWqDA9d.acf5acdb3a81.js +1 -0
  58. codex/static_root/assets/VExpansionPanels-CJWqDA9d.acf5acdb3a81.js.br +0 -0
  59. codex/static_root/assets/VExpansionPanels-CJWqDA9d.acf5acdb3a81.js.gz +0 -0
  60. codex/static_root/assets/VExpansionPanels-CJWqDA9d.js +1 -0
  61. codex/static_root/assets/VExpansionPanels-CJWqDA9d.js.br +0 -0
  62. codex/static_root/assets/VExpansionPanels-CJWqDA9d.js.gz +0 -0
  63. codex/static_root/assets/VRadioGroup-BFLE3yCT.8b78cefe4206.js +1 -0
  64. codex/static_root/assets/VRadioGroup-BFLE3yCT.8b78cefe4206.js.br +0 -0
  65. codex/static_root/assets/VRadioGroup-BFLE3yCT.8b78cefe4206.js.gz +0 -0
  66. codex/static_root/assets/VRadioGroup-BFLE3yCT.js +1 -0
  67. codex/static_root/assets/VRadioGroup-BFLE3yCT.js.br +0 -0
  68. codex/static_root/assets/VRadioGroup-BFLE3yCT.js.gz +0 -0
  69. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +1 -0
  70. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
  71. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
  72. codex/static_root/assets/VSelect-DLu8bpcs.css +1 -0
  73. codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
  74. codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
  75. codex/static_root/assets/VSelect-Dvsi19pi.6d38dc99e0a8.js +1 -0
  76. codex/static_root/assets/VSelect-Dvsi19pi.6d38dc99e0a8.js.br +0 -0
  77. codex/static_root/assets/VSelect-Dvsi19pi.6d38dc99e0a8.js.gz +0 -0
  78. codex/static_root/assets/VSelect-Dvsi19pi.js +1 -0
  79. codex/static_root/assets/VSelect-Dvsi19pi.js.br +0 -0
  80. codex/static_root/assets/VSelect-Dvsi19pi.js.gz +0 -0
  81. codex/static_root/assets/VSelectionControl-BioGLzkQ.106c3be4b6d4.js +1 -0
  82. codex/static_root/assets/VSelectionControl-BioGLzkQ.106c3be4b6d4.js.br +0 -0
  83. codex/static_root/assets/VSelectionControl-BioGLzkQ.106c3be4b6d4.js.gz +0 -0
  84. codex/static_root/assets/VSelectionControl-BioGLzkQ.js +1 -0
  85. codex/static_root/assets/VSelectionControl-BioGLzkQ.js.br +0 -0
  86. codex/static_root/assets/VSelectionControl-BioGLzkQ.js.gz +0 -0
  87. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +1 -0
  88. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
  89. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
  90. codex/static_root/assets/VSlideGroup-BtXPhMOT.css +1 -0
  91. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
  92. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
  93. codex/static_root/assets/VSlideGroup-CFkaPBuS.1d168fc87687.js +1 -0
  94. codex/static_root/assets/VSlideGroup-CFkaPBuS.1d168fc87687.js.br +0 -0
  95. codex/static_root/assets/VSlideGroup-CFkaPBuS.1d168fc87687.js.gz +0 -0
  96. codex/static_root/assets/VSlideGroup-CFkaPBuS.js +1 -0
  97. codex/static_root/assets/VSlideGroup-CFkaPBuS.js.br +0 -0
  98. codex/static_root/assets/VSlideGroup-CFkaPBuS.js.gz +0 -0
  99. codex/static_root/assets/VTable-B7x3Oih2.d0010dab39ce.js +1 -0
  100. codex/static_root/assets/VTable-B7x3Oih2.d0010dab39ce.js.br +0 -0
  101. codex/static_root/assets/VTable-B7x3Oih2.d0010dab39ce.js.gz +0 -0
  102. codex/static_root/assets/VTable-B7x3Oih2.js +1 -0
  103. codex/static_root/assets/VTable-B7x3Oih2.js.br +0 -0
  104. codex/static_root/assets/VTable-B7x3Oih2.js.gz +0 -0
  105. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css +1 -0
  106. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.br +0 -0
  107. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.gz +0 -0
  108. codex/static_root/assets/VTextField-BnCEPZIq.css +1 -0
  109. codex/static_root/assets/VTextField-BnCEPZIq.css.br +0 -0
  110. codex/static_root/assets/VTextField-BnCEPZIq.css.gz +0 -0
  111. codex/static_root/assets/VTextField-rsRdtAxl.488a06f530fe.js +1 -0
  112. codex/static_root/assets/VTextField-rsRdtAxl.488a06f530fe.js.br +0 -0
  113. codex/static_root/assets/VTextField-rsRdtAxl.488a06f530fe.js.gz +0 -0
  114. codex/static_root/assets/VTextField-rsRdtAxl.js +1 -0
  115. codex/static_root/assets/VTextField-rsRdtAxl.js.br +0 -0
  116. codex/static_root/assets/VTextField-rsRdtAxl.js.gz +0 -0
  117. codex/static_root/assets/VWindowItem-CibnlNHs.75a48ded87d5.js +1 -0
  118. codex/static_root/assets/VWindowItem-CibnlNHs.75a48ded87d5.js.br +0 -0
  119. codex/static_root/assets/VWindowItem-CibnlNHs.75a48ded87d5.js.gz +0 -0
  120. codex/static_root/assets/VWindowItem-CibnlNHs.js +1 -0
  121. codex/static_root/assets/VWindowItem-CibnlNHs.js.br +0 -0
  122. codex/static_root/assets/VWindowItem-CibnlNHs.js.gz +0 -0
  123. codex/static_root/assets/admin-CaVmJ-fa.251f5d3cc755.js +1 -0
  124. codex/static_root/assets/admin-CaVmJ-fa.251f5d3cc755.js.br +0 -0
  125. codex/static_root/assets/admin-CaVmJ-fa.251f5d3cc755.js.gz +0 -0
  126. codex/static_root/assets/admin-CaVmJ-fa.js +1 -0
  127. codex/static_root/assets/admin-CaVmJ-fa.js.br +0 -0
  128. codex/static_root/assets/admin-CaVmJ-fa.js.gz +0 -0
  129. codex/static_root/assets/{admin-CWEjaFr5.7d4523387384.css → admin-DJ7pIXDQ.css} +1 -1
  130. codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
  131. codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
  132. codex/static_root/assets/{admin-CWEjaFr5.css → admin-DJ7pIXDQ.e2a77af73ef2.css} +1 -1
  133. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
  134. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
  135. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css +1 -0
  136. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.br +0 -0
  137. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.gz +0 -0
  138. codex/static_root/assets/admin-menu-B9bzD6hV.css +1 -0
  139. codex/static_root/assets/admin-menu-B9bzD6hV.css.br +0 -0
  140. codex/static_root/assets/admin-menu-B9bzD6hV.css.gz +0 -0
  141. codex/static_root/assets/admin-menu-Cb7A0Ilo.ddf1e9339257.js +1 -0
  142. codex/static_root/assets/admin-menu-Cb7A0Ilo.ddf1e9339257.js.br +0 -0
  143. codex/static_root/assets/admin-menu-Cb7A0Ilo.ddf1e9339257.js.gz +0 -0
  144. codex/static_root/assets/admin-menu-Cb7A0Ilo.js +1 -0
  145. codex/static_root/assets/admin-menu-Cb7A0Ilo.js.br +0 -0
  146. codex/static_root/assets/admin-menu-Cb7A0Ilo.js.gz +0 -0
  147. codex/static_root/assets/admin-settings-button-progress-DID-drll.979420762aea.js +1 -0
  148. codex/static_root/assets/admin-settings-button-progress-DID-drll.979420762aea.js.br +0 -0
  149. codex/static_root/assets/admin-settings-button-progress-DID-drll.979420762aea.js.gz +0 -0
  150. codex/static_root/assets/admin-settings-button-progress-DID-drll.js +1 -0
  151. codex/static_root/assets/admin-settings-button-progress-DID-drll.js.br +0 -0
  152. codex/static_root/assets/admin-settings-button-progress-DID-drll.js.gz +0 -0
  153. codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.2548c49a853e.css +1 -0
  154. codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.css +1 -0
  155. codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css +1 -0
  156. codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.br +0 -0
  157. codex/static_root/assets/browser-Br5CorL4.6e2a416309d5.css.gz +0 -0
  158. codex/static_root/assets/browser-Br5CorL4.css +1 -0
  159. codex/static_root/assets/browser-Br5CorL4.css.br +0 -0
  160. codex/static_root/assets/browser-Br5CorL4.css.gz +0 -0
  161. codex/static_root/assets/browser-xxQTUEuu.1686c20dc492.js +1 -0
  162. codex/static_root/assets/browser-xxQTUEuu.1686c20dc492.js.br +0 -0
  163. codex/static_root/assets/browser-xxQTUEuu.1686c20dc492.js.gz +0 -0
  164. codex/static_root/assets/browser-xxQTUEuu.js +1 -0
  165. codex/static_root/assets/browser-xxQTUEuu.js.br +0 -0
  166. codex/static_root/assets/browser-xxQTUEuu.js.gz +0 -0
  167. codex/static_root/assets/change-password-dialog-DXY2Xg9k.4a7ca6108810.js +1 -0
  168. codex/static_root/assets/change-password-dialog-DXY2Xg9k.4a7ca6108810.js.br +0 -0
  169. codex/static_root/assets/change-password-dialog-DXY2Xg9k.4a7ca6108810.js.gz +0 -0
  170. codex/static_root/assets/change-password-dialog-DXY2Xg9k.js +1 -0
  171. codex/static_root/assets/change-password-dialog-DXY2Xg9k.js.br +0 -0
  172. codex/static_root/assets/change-password-dialog-DXY2Xg9k.js.gz +0 -0
  173. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css +1 -0
  174. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.br +0 -0
  175. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.gz +0 -0
  176. codex/static_root/assets/change-password-dialog-DrIXdKUC.css +1 -0
  177. codex/static_root/assets/change-password-dialog-DrIXdKUC.css.br +0 -0
  178. codex/static_root/assets/change-password-dialog-DrIXdKUC.css.gz +0 -0
  179. codex/static_root/assets/confirm-dialog-B7w6z2rf.472011c06253.css +1 -0
  180. codex/static_root/assets/confirm-dialog-B7w6z2rf.css +1 -0
  181. codex/static_root/assets/confirm-dialog-BEVEU-kA.281b6e877fb5.js +1 -0
  182. codex/static_root/assets/confirm-dialog-BEVEU-kA.281b6e877fb5.js.br +0 -0
  183. codex/static_root/assets/confirm-dialog-BEVEU-kA.281b6e877fb5.js.gz +0 -0
  184. codex/static_root/assets/confirm-dialog-BEVEU-kA.js +1 -0
  185. codex/static_root/assets/confirm-dialog-BEVEU-kA.js.br +0 -0
  186. codex/static_root/assets/confirm-dialog-BEVEU-kA.js.gz +0 -0
  187. codex/static_root/assets/datetime-column-DhrZEHwa.80e95aef7bdb.js +1 -0
  188. codex/static_root/assets/datetime-column-DhrZEHwa.80e95aef7bdb.js.br +1 -0
  189. codex/static_root/assets/datetime-column-DhrZEHwa.80e95aef7bdb.js.gz +0 -0
  190. codex/static_root/assets/datetime-column-DhrZEHwa.js +1 -0
  191. codex/static_root/assets/datetime-column-DhrZEHwa.js.br +1 -0
  192. codex/static_root/assets/datetime-column-DhrZEHwa.js.gz +0 -0
  193. codex/static_root/assets/filter-BJ7TmFiU.3cefe2d609c7.js +1 -0
  194. codex/static_root/assets/filter-BJ7TmFiU.3cefe2d609c7.js.br +0 -0
  195. codex/static_root/assets/filter-BJ7TmFiU.3cefe2d609c7.js.gz +0 -0
  196. codex/static_root/assets/filter-BJ7TmFiU.js +1 -0
  197. codex/static_root/assets/filter-BJ7TmFiU.js.br +0 -0
  198. codex/static_root/assets/filter-BJ7TmFiU.js.gz +0 -0
  199. codex/static_root/assets/flag-tab-AJj0sesB.2ab1be15d10f.js +1 -0
  200. codex/static_root/assets/flag-tab-AJj0sesB.2ab1be15d10f.js.br +0 -0
  201. codex/static_root/assets/flag-tab-AJj0sesB.2ab1be15d10f.js.gz +0 -0
  202. codex/static_root/assets/flag-tab-AJj0sesB.js +1 -0
  203. codex/static_root/assets/flag-tab-AJj0sesB.js.br +0 -0
  204. codex/static_root/assets/flag-tab-AJj0sesB.js.gz +0 -0
  205. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +1 -0
  206. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
  207. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
  208. codex/static_root/assets/flag-tab-CqWovuBu.css +1 -0
  209. codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
  210. codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
  211. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css +1 -0
  212. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.br +0 -0
  213. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.gz +0 -0
  214. codex/static_root/assets/group-tab-DkIN1Heu.css +1 -0
  215. codex/static_root/assets/group-tab-DkIN1Heu.css.br +0 -0
  216. codex/static_root/assets/group-tab-DkIN1Heu.css.gz +0 -0
  217. codex/static_root/assets/group-tab-DuxOH4Hn.8ac96cfe7c43.js +1 -0
  218. codex/static_root/assets/group-tab-DuxOH4Hn.8ac96cfe7c43.js.br +0 -0
  219. codex/static_root/assets/group-tab-DuxOH4Hn.8ac96cfe7c43.js.gz +0 -0
  220. codex/static_root/assets/group-tab-DuxOH4Hn.js +1 -0
  221. codex/static_root/assets/group-tab-DuxOH4Hn.js.br +0 -0
  222. codex/static_root/assets/group-tab-DuxOH4Hn.js.gz +0 -0
  223. codex/static_root/assets/http-error-DIeFpbQV.086e1dd09555.js +1 -0
  224. codex/static_root/assets/http-error-DIeFpbQV.086e1dd09555.js.br +0 -0
  225. codex/static_root/assets/http-error-DIeFpbQV.086e1dd09555.js.gz +0 -0
  226. codex/static_root/assets/http-error-DIeFpbQV.js +1 -0
  227. codex/static_root/assets/http-error-DIeFpbQV.js.br +0 -0
  228. codex/static_root/assets/http-error-DIeFpbQV.js.gz +0 -0
  229. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css +1 -0
  230. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.br +0 -0
  231. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.gz +0 -0
  232. codex/static_root/assets/index-CL_6g4tJ.css +1 -0
  233. codex/static_root/assets/index-CL_6g4tJ.css.br +0 -0
  234. codex/static_root/assets/index-CL_6g4tJ.css.gz +0 -0
  235. codex/static_root/assets/index-MN9BB_He.201e30aa00ef.js +1 -0
  236. codex/static_root/assets/index-MN9BB_He.201e30aa00ef.js.br +0 -0
  237. codex/static_root/assets/index-MN9BB_He.201e30aa00ef.js.gz +0 -0
  238. codex/static_root/assets/index-MN9BB_He.js +1 -0
  239. codex/static_root/assets/index-MN9BB_He.js.br +0 -0
  240. codex/static_root/assets/index-MN9BB_He.js.gz +0 -0
  241. codex/static_root/assets/{library-tab-Bh7ErAk7.7c44200ee727.css → library-tab-Wew8YOLv.0c1780a6b68e.css} +1 -1
  242. codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.br +0 -0
  243. codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.gz +0 -0
  244. codex/static_root/assets/{library-tab-Bh7ErAk7.css → library-tab-Wew8YOLv.css} +1 -1
  245. codex/static_root/assets/library-tab-Wew8YOLv.css.br +0 -0
  246. codex/static_root/assets/library-tab-Wew8YOLv.css.gz +0 -0
  247. codex/static_root/assets/library-tab-jpeUUWzV.70dae13f6dab.js +1 -0
  248. codex/static_root/assets/library-tab-jpeUUWzV.70dae13f6dab.js.br +0 -0
  249. codex/static_root/assets/library-tab-jpeUUWzV.70dae13f6dab.js.gz +0 -0
  250. codex/static_root/assets/library-tab-jpeUUWzV.js +1 -0
  251. codex/static_root/assets/library-tab-jpeUUWzV.js.br +0 -0
  252. codex/static_root/assets/library-tab-jpeUUWzV.js.gz +0 -0
  253. codex/static_root/assets/main-Cs7q8tob.86ef2567544d.js +35 -0
  254. codex/static_root/assets/main-Cs7q8tob.86ef2567544d.js.br +0 -0
  255. codex/static_root/assets/main-Cs7q8tob.86ef2567544d.js.gz +0 -0
  256. codex/static_root/assets/main-Cs7q8tob.js +35 -0
  257. codex/static_root/assets/main-Cs7q8tob.js.br +0 -0
  258. codex/static_root/assets/main-Cs7q8tob.js.gz +0 -0
  259. codex/static_root/assets/pager-full-pdf-D_ecZhu-.ff93ca6c55e0.js +1 -0
  260. codex/static_root/assets/pager-full-pdf-D_ecZhu-.ff93ca6c55e0.js.br +0 -0
  261. codex/static_root/assets/pager-full-pdf-D_ecZhu-.ff93ca6c55e0.js.gz +0 -0
  262. codex/static_root/assets/pager-full-pdf-D_ecZhu-.js +1 -0
  263. codex/static_root/assets/pager-full-pdf-D_ecZhu-.js.br +0 -0
  264. codex/static_root/assets/pager-full-pdf-D_ecZhu-.js.gz +0 -0
  265. codex/static_root/assets/pagination-toolbar-yeU7sPiY.ce0751d6009d.js +1 -0
  266. codex/static_root/assets/pagination-toolbar-yeU7sPiY.ce0751d6009d.js.br +0 -0
  267. codex/static_root/assets/pagination-toolbar-yeU7sPiY.ce0751d6009d.js.gz +0 -0
  268. codex/static_root/assets/pagination-toolbar-yeU7sPiY.js +1 -0
  269. codex/static_root/assets/pagination-toolbar-yeU7sPiY.js.br +0 -0
  270. codex/static_root/assets/pagination-toolbar-yeU7sPiY.js.gz +0 -0
  271. codex/static_root/assets/{pdf-doc-BcRNpoaW.882f6e967869.js → pdf-doc-Czi9tnhD.4d4b8b7ff5d7.js} +3 -3
  272. codex/static_root/assets/pdf-doc-Czi9tnhD.4d4b8b7ff5d7.js.br +0 -0
  273. codex/static_root/assets/pdf-doc-Czi9tnhD.4d4b8b7ff5d7.js.gz +0 -0
  274. codex/static_root/assets/{pdf-doc-BcRNpoaW.js → pdf-doc-Czi9tnhD.js} +3 -3
  275. codex/static_root/assets/pdf-doc-Czi9tnhD.js.br +0 -0
  276. codex/static_root/assets/pdf-doc-Czi9tnhD.js.gz +0 -0
  277. codex/static_root/assets/reader-DQKMY8MS.d9d3451f2cff.js +2 -0
  278. codex/static_root/assets/reader-DQKMY8MS.d9d3451f2cff.js.br +0 -0
  279. codex/static_root/assets/reader-DQKMY8MS.d9d3451f2cff.js.gz +0 -0
  280. codex/static_root/assets/reader-DQKMY8MS.js +2 -0
  281. codex/static_root/assets/reader-DQKMY8MS.js.br +0 -0
  282. codex/static_root/assets/reader-DQKMY8MS.js.gz +0 -0
  283. codex/static_root/assets/{reader-DveZZr9x.7b21b9f8f628.css → reader-OpKdTZ_B.css} +1 -1
  284. codex/static_root/assets/reader-OpKdTZ_B.css.br +0 -0
  285. codex/static_root/assets/reader-OpKdTZ_B.css.gz +0 -0
  286. codex/static_root/assets/{reader-DveZZr9x.css → reader-OpKdTZ_B.ebd1512b7014.css} +1 -1
  287. codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.br +0 -0
  288. codex/static_root/assets/reader-OpKdTZ_B.ebd1512b7014.css.gz +0 -0
  289. codex/static_root/assets/relation-chips-CFH0Xf78.62ded2739a22.js +1 -0
  290. codex/static_root/assets/relation-chips-CFH0Xf78.62ded2739a22.js.br +0 -0
  291. codex/static_root/assets/relation-chips-CFH0Xf78.62ded2739a22.js.gz +0 -0
  292. codex/static_root/assets/relation-chips-CFH0Xf78.js +1 -0
  293. codex/static_root/assets/relation-chips-CFH0Xf78.js.br +0 -0
  294. codex/static_root/assets/relation-chips-CFH0Xf78.js.gz +0 -0
  295. codex/static_root/assets/settings-drawer-DbOztp2R.b6f981108782.js +2 -0
  296. codex/static_root/assets/settings-drawer-DbOztp2R.b6f981108782.js.br +0 -0
  297. codex/static_root/assets/settings-drawer-DbOztp2R.b6f981108782.js.gz +0 -0
  298. codex/static_root/assets/settings-drawer-DbOztp2R.js +2 -0
  299. codex/static_root/assets/settings-drawer-DbOztp2R.js.br +0 -0
  300. codex/static_root/assets/settings-drawer-DbOztp2R.js.gz +0 -0
  301. codex/static_root/assets/{stats-tab-DpfGsVX3.1cb66f2649a9.css → stats-tab-BAYTqQNg.5f715428c394.css} +1 -1
  302. codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.br +0 -0
  303. codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.gz +0 -0
  304. codex/static_root/assets/{stats-tab-DpfGsVX3.css → stats-tab-BAYTqQNg.css} +1 -1
  305. codex/static_root/assets/stats-tab-BAYTqQNg.css.br +0 -0
  306. codex/static_root/assets/stats-tab-BAYTqQNg.css.gz +0 -0
  307. codex/static_root/assets/stats-tab-DWktIwEj.42666baa0d93.js +1 -0
  308. codex/static_root/assets/stats-tab-DWktIwEj.42666baa0d93.js.br +0 -0
  309. codex/static_root/assets/stats-tab-DWktIwEj.42666baa0d93.js.gz +0 -0
  310. codex/static_root/assets/stats-tab-DWktIwEj.js +1 -0
  311. codex/static_root/assets/stats-tab-DWktIwEj.js.br +0 -0
  312. codex/static_root/assets/stats-tab-DWktIwEj.js.gz +0 -0
  313. codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css +1 -0
  314. codex/static_root/assets/{task-tab-1XiWn9xP.06cb8e1b794d.css.br → task-tab--cXi48kg.5dfd6890a1c2.css.br} +1 -1
  315. codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css.gz +0 -0
  316. codex/static_root/assets/task-tab--cXi48kg.css +1 -0
  317. codex/static_root/assets/{task-tab-1XiWn9xP.css.br → task-tab--cXi48kg.css.br} +1 -1
  318. codex/static_root/assets/task-tab--cXi48kg.css.gz +0 -0
  319. codex/static_root/assets/task-tab-BtTtKJP6.421692e51e82.js +1 -0
  320. codex/static_root/assets/task-tab-BtTtKJP6.421692e51e82.js.br +0 -0
  321. codex/static_root/assets/task-tab-BtTtKJP6.421692e51e82.js.gz +0 -0
  322. codex/static_root/assets/task-tab-BtTtKJP6.js +1 -0
  323. codex/static_root/assets/task-tab-BtTtKJP6.js.br +0 -0
  324. codex/static_root/assets/task-tab-BtTtKJP6.js.gz +0 -0
  325. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +1 -0
  326. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +4 -0
  327. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
  328. codex/static_root/assets/transition-5LRFTPo5.css +1 -0
  329. codex/static_root/assets/transition-5LRFTPo5.css.br +4 -0
  330. codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
  331. codex/static_root/assets/transition-A__CJ90w.970355913cb9.js +1 -0
  332. codex/static_root/assets/transition-A__CJ90w.970355913cb9.js.br +0 -0
  333. codex/static_root/assets/transition-A__CJ90w.970355913cb9.js.gz +0 -0
  334. codex/static_root/assets/transition-A__CJ90w.js +1 -0
  335. codex/static_root/assets/transition-A__CJ90w.js.br +0 -0
  336. codex/static_root/assets/transition-A__CJ90w.js.gz +0 -0
  337. codex/static_root/assets/unauthorized-FF1nwDKt.b3d6255ddb7f.js +1 -0
  338. codex/static_root/assets/unauthorized-FF1nwDKt.b3d6255ddb7f.js.br +0 -0
  339. codex/static_root/assets/unauthorized-FF1nwDKt.b3d6255ddb7f.js.gz +0 -0
  340. codex/static_root/assets/unauthorized-FF1nwDKt.js +1 -0
  341. codex/static_root/assets/unauthorized-FF1nwDKt.js.br +0 -0
  342. codex/static_root/assets/unauthorized-FF1nwDKt.js.gz +0 -0
  343. codex/static_root/assets/user-tab-RwMqZeSN.d4ed24177855.js +1 -0
  344. codex/static_root/assets/user-tab-RwMqZeSN.d4ed24177855.js.br +0 -0
  345. codex/static_root/assets/user-tab-RwMqZeSN.d4ed24177855.js.gz +0 -0
  346. codex/static_root/assets/user-tab-RwMqZeSN.js +1 -0
  347. codex/static_root/assets/user-tab-RwMqZeSN.js.br +0 -0
  348. codex/static_root/assets/user-tab-RwMqZeSN.js.gz +0 -0
  349. codex/static_root/manifest.8dff5f2fb15e.json +712 -0
  350. codex/static_root/manifest.8dff5f2fb15e.json.br +0 -0
  351. codex/static_root/manifest.8dff5f2fb15e.json.gz +0 -0
  352. codex/static_root/manifest.json +377 -300
  353. codex/static_root/manifest.json.br +0 -0
  354. codex/static_root/manifest.json.gz +0 -0
  355. codex/static_root/staticfiles.json +1 -1
  356. codex/status_controller.py +5 -6
  357. codex/threads.py +1 -1
  358. codex/urls/api/auth.py +1 -1
  359. codex/urls/api/browser.py +1 -1
  360. codex/views/admin/library.py +18 -13
  361. codex/views/admin/stats.py +23 -16
  362. codex/views/auth.py +42 -77
  363. codex/views/bookmark.py +16 -153
  364. codex/views/browser/annotate/__init__.py +1 -0
  365. codex/views/browser/annotate/bookmark.py +112 -0
  366. codex/views/browser/annotate/card.py +61 -0
  367. codex/views/browser/annotate/order.py +247 -0
  368. codex/views/browser/breadcrumbs.py +90 -11
  369. codex/views/browser/browser.py +44 -110
  370. codex/views/browser/choices.py +15 -18
  371. codex/views/browser/cover.py +10 -20
  372. codex/views/browser/filters/bookmark.py +18 -19
  373. codex/views/browser/filters/field.py +8 -3
  374. codex/views/browser/filters/filter.py +69 -0
  375. codex/views/browser/filters/group.py +9 -6
  376. codex/views/browser/filters/search/fts.py +5 -19
  377. codex/views/browser/filters/search/parse.py +54 -24
  378. codex/views/browser/group_mtime.py +83 -0
  379. codex/views/browser/metadata/__init__.py +1 -0
  380. codex/views/browser/metadata/annotate.py +109 -0
  381. codex/views/browser/metadata/copy_intersections.py +90 -0
  382. codex/views/browser/metadata/metadata.py +83 -0
  383. codex/views/browser/metadata/query_intersections.py +114 -0
  384. codex/views/browser/mtime.py +2 -10
  385. codex/views/browser/order_by.py +22 -22
  386. codex/views/browser/page_in_bounds.py +2 -2
  387. codex/views/browser/paginate.py +9 -15
  388. codex/views/browser/params.py +64 -0
  389. codex/views/browser/title.py +1 -6
  390. codex/views/browser/validate.py +68 -33
  391. codex/views/download.py +1 -1
  392. codex/views/mixins.py +27 -15
  393. codex/views/opds/const.py +2 -3
  394. codex/views/opds/urls.py +2 -2
  395. codex/views/opds/util.py +2 -3
  396. codex/views/opds/v1/entry/links.py +3 -1
  397. codex/views/opds/v1/facets.py +65 -12
  398. codex/views/opds/v1/feed.py +5 -64
  399. codex/views/opds/v1/links.py +5 -4
  400. codex/views/opds/v2/feed.py +1 -28
  401. codex/views/opds/v2/links.py +26 -1
  402. codex/views/opds/v2/publications.py +5 -6
  403. codex/views/opds/v2/top_links.py +3 -6
  404. codex/views/public.py +2 -2
  405. codex/views/reader/arcs.py +0 -3
  406. codex/views/reader/books.py +57 -41
  407. codex/views/reader/page.py +10 -4
  408. codex/views/reader/{init.py → params.py} +37 -31
  409. codex/views/reader/reader.py +1 -2
  410. codex/views/session.py +43 -54
  411. codex/views/timezone.py +54 -0
  412. codex/views/version.py +1 -1
  413. {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/METADATA +1 -1
  414. {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/RECORD +417 -383
  415. codex/serializers/choices.py +0 -196
  416. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js +0 -1
  417. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.br +0 -0
  418. codex/static_root/assets/VCheckbox-CWiqcZ7a.3feebd47daab.js.gz +0 -0
  419. codex/static_root/assets/VCheckbox-CWiqcZ7a.js +0 -1
  420. codex/static_root/assets/VCheckbox-CWiqcZ7a.js.br +0 -0
  421. codex/static_root/assets/VCheckbox-CWiqcZ7a.js.gz +0 -0
  422. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js +0 -1
  423. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.br +0 -0
  424. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.bc66d5351baa.js.gz +0 -0
  425. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js +0 -1
  426. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.br +0 -0
  427. codex/static_root/assets/VCheckboxBtn-Dk3iB62X.js.gz +0 -0
  428. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js +0 -1
  429. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.br +0 -0
  430. codex/static_root/assets/VCombobox-D1_KU27A.b1d30587a973.js.gz +0 -0
  431. codex/static_root/assets/VCombobox-D1_KU27A.js +0 -1
  432. codex/static_root/assets/VCombobox-D1_KU27A.js.br +0 -0
  433. codex/static_root/assets/VCombobox-D1_KU27A.js.gz +0 -0
  434. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css +0 -1
  435. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.br +0 -0
  436. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.gz +0 -0
  437. codex/static_root/assets/VDialog-38np3TSA.css +0 -1
  438. codex/static_root/assets/VDialog-38np3TSA.css.br +0 -0
  439. codex/static_root/assets/VDialog-38np3TSA.css.gz +0 -0
  440. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js +0 -1
  441. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.br +0 -0
  442. codex/static_root/assets/VDialog-GbxvFMMN.594fb3a39ced.js.gz +0 -0
  443. codex/static_root/assets/VDialog-GbxvFMMN.js +0 -1
  444. codex/static_root/assets/VDialog-GbxvFMMN.js.br +0 -0
  445. codex/static_root/assets/VDialog-GbxvFMMN.js.gz +0 -0
  446. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js +0 -1
  447. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.br +0 -0
  448. codex/static_root/assets/VExpansionPanels-Cywv_bEj.a762e63858e5.js.gz +0 -0
  449. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js +0 -1
  450. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.br +0 -0
  451. codex/static_root/assets/VExpansionPanels-Cywv_bEj.js.gz +0 -0
  452. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js +0 -1
  453. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.br +0 -0
  454. codex/static_root/assets/VRadioGroup-C0etmxpZ.0eb3009a6300.js.gz +0 -0
  455. codex/static_root/assets/VRadioGroup-C0etmxpZ.js +0 -1
  456. codex/static_root/assets/VRadioGroup-C0etmxpZ.js.br +0 -0
  457. codex/static_root/assets/VRadioGroup-C0etmxpZ.js.gz +0 -0
  458. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css +0 -1
  459. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.br +0 -0
  460. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.gz +0 -0
  461. codex/static_root/assets/VSelect-Ca2bIOso.css +0 -1
  462. codex/static_root/assets/VSelect-Ca2bIOso.css.br +0 -0
  463. codex/static_root/assets/VSelect-Ca2bIOso.css.gz +0 -0
  464. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js +0 -1
  465. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.br +0 -0
  466. codex/static_root/assets/VSelect-txrnRiNJ.a5625760018c.js.gz +0 -0
  467. codex/static_root/assets/VSelect-txrnRiNJ.js +0 -1
  468. codex/static_root/assets/VSelect-txrnRiNJ.js.br +0 -0
  469. codex/static_root/assets/VSelect-txrnRiNJ.js.gz +0 -0
  470. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js +0 -1
  471. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.br +0 -0
  472. codex/static_root/assets/VSelectionControl-CY6RmcvW.7e8824224f6c.js.gz +0 -0
  473. codex/static_root/assets/VSelectionControl-CY6RmcvW.js +0 -1
  474. codex/static_root/assets/VSelectionControl-CY6RmcvW.js.br +0 -0
  475. codex/static_root/assets/VSelectionControl-CY6RmcvW.js.gz +0 -0
  476. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css +0 -1
  477. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.br +0 -0
  478. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.gz +0 -0
  479. codex/static_root/assets/VSlideGroup-Dk_1upHt.css +0 -1
  480. codex/static_root/assets/VSlideGroup-Dk_1upHt.css.br +0 -0
  481. codex/static_root/assets/VSlideGroup-Dk_1upHt.css.gz +0 -0
  482. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js +0 -1
  483. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.br +0 -0
  484. codex/static_root/assets/VSlideGroup-DtEQMwY1.6c72325635ed.js.gz +0 -0
  485. codex/static_root/assets/VSlideGroup-DtEQMwY1.js +0 -1
  486. codex/static_root/assets/VSlideGroup-DtEQMwY1.js.br +0 -0
  487. codex/static_root/assets/VSlideGroup-DtEQMwY1.js.gz +0 -0
  488. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js +0 -1
  489. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.br +0 -0
  490. codex/static_root/assets/VTable-2af995xo.6c5bf36631b2.js.gz +0 -0
  491. codex/static_root/assets/VTable-2af995xo.js +0 -1
  492. codex/static_root/assets/VTable-2af995xo.js.br +0 -0
  493. codex/static_root/assets/VTable-2af995xo.js.gz +0 -0
  494. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css +0 -1
  495. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
  496. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
  497. codex/static_root/assets/VTextField-Crc3gdNh.css +0 -1
  498. codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
  499. codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
  500. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js +0 -1
  501. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.br +0 -0
  502. codex/static_root/assets/VTextField-G6CMj7yO.657a130dd302.js.gz +0 -0
  503. codex/static_root/assets/VTextField-G6CMj7yO.js +0 -1
  504. codex/static_root/assets/VTextField-G6CMj7yO.js.br +0 -0
  505. codex/static_root/assets/VTextField-G6CMj7yO.js.gz +0 -0
  506. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js +0 -1
  507. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.br +0 -0
  508. codex/static_root/assets/VWindowItem-CzoGd6Ul.721e5da11ad4.js.gz +0 -0
  509. codex/static_root/assets/VWindowItem-CzoGd6Ul.js +0 -1
  510. codex/static_root/assets/VWindowItem-CzoGd6Ul.js.br +0 -0
  511. codex/static_root/assets/VWindowItem-CzoGd6Ul.js.gz +0 -0
  512. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.br +0 -0
  513. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.gz +0 -0
  514. codex/static_root/assets/admin-CWEjaFr5.css.br +0 -0
  515. codex/static_root/assets/admin-CWEjaFr5.css.gz +0 -0
  516. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js +0 -1
  517. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.br +0 -0
  518. codex/static_root/assets/admin-CsPAIQeG.5175467148a3.js.gz +0 -0
  519. codex/static_root/assets/admin-CsPAIQeG.js +0 -1
  520. codex/static_root/assets/admin-CsPAIQeG.js.br +0 -0
  521. codex/static_root/assets/admin-CsPAIQeG.js.gz +0 -0
  522. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js +0 -30
  523. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.br +0 -0
  524. codex/static_root/assets/admin-drawer-panel-BAT7GA64.73a064628c19.js.gz +0 -0
  525. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js +0 -30
  526. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.br +0 -0
  527. codex/static_root/assets/admin-drawer-panel-BAT7GA64.js.gz +0 -0
  528. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css +0 -1
  529. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.br +0 -0
  530. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.gz +0 -0
  531. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css +0 -1
  532. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.br +0 -0
  533. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.gz +0 -0
  534. codex/static_root/assets/browser-C18l93yc.923860009ef4.js +0 -1
  535. codex/static_root/assets/browser-C18l93yc.923860009ef4.js.br +0 -0
  536. codex/static_root/assets/browser-C18l93yc.923860009ef4.js.gz +0 -0
  537. codex/static_root/assets/browser-C18l93yc.js +0 -1
  538. codex/static_root/assets/browser-C18l93yc.js.br +0 -0
  539. codex/static_root/assets/browser-C18l93yc.js.gz +0 -0
  540. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css +0 -1
  541. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.br +0 -0
  542. codex/static_root/assets/browser-DaAqlBYO.708ecf0904bb.css.gz +0 -0
  543. codex/static_root/assets/browser-DaAqlBYO.css +0 -1
  544. codex/static_root/assets/browser-DaAqlBYO.css.br +0 -0
  545. codex/static_root/assets/browser-DaAqlBYO.css.gz +0 -0
  546. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js +0 -1
  547. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.br +0 -0
  548. codex/static_root/assets/change-password-dialog-BazJdNs9.0a4e02656a35.js.gz +0 -0
  549. codex/static_root/assets/change-password-dialog-BazJdNs9.js +0 -1
  550. codex/static_root/assets/change-password-dialog-BazJdNs9.js.br +0 -0
  551. codex/static_root/assets/change-password-dialog-BazJdNs9.js.gz +0 -0
  552. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css +0 -1
  553. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.br +0 -0
  554. codex/static_root/assets/change-password-dialog-Mlmvz8_4.111421f51e79.css.gz +0 -0
  555. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css +0 -1
  556. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.br +0 -0
  557. codex/static_root/assets/change-password-dialog-Mlmvz8_4.css.gz +0 -0
  558. codex/static_root/assets/confirm-dialog-BmhedLCx.7c2103d319d4.css +0 -1
  559. codex/static_root/assets/confirm-dialog-BmhedLCx.css +0 -1
  560. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js +0 -1
  561. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.br +0 -0
  562. codex/static_root/assets/confirm-dialog-CbW3hlOp.9dd39b443c31.js.gz +0 -0
  563. codex/static_root/assets/confirm-dialog-CbW3hlOp.js +0 -1
  564. codex/static_root/assets/confirm-dialog-CbW3hlOp.js.br +0 -0
  565. codex/static_root/assets/confirm-dialog-CbW3hlOp.js.gz +0 -0
  566. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js +0 -1
  567. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.br +0 -0
  568. codex/static_root/assets/datetime-column-uSqKZU37.b157b0743db5.js.gz +0 -0
  569. codex/static_root/assets/datetime-column-uSqKZU37.js +0 -1
  570. codex/static_root/assets/datetime-column-uSqKZU37.js.br +0 -0
  571. codex/static_root/assets/datetime-column-uSqKZU37.js.gz +0 -0
  572. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js +0 -1
  573. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.br +0 -0
  574. codex/static_root/assets/filter-CBl5KEsB.845a81573c2e.js.gz +0 -0
  575. codex/static_root/assets/filter-CBl5KEsB.js +0 -1
  576. codex/static_root/assets/filter-CBl5KEsB.js.br +0 -0
  577. codex/static_root/assets/filter-CBl5KEsB.js.gz +0 -0
  578. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js +0 -1
  579. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.br +0 -0
  580. codex/static_root/assets/flag-tab-CT8pqM4r.ecb6eee2e7a0.js.gz +0 -0
  581. codex/static_root/assets/flag-tab-CT8pqM4r.js +0 -1
  582. codex/static_root/assets/flag-tab-CT8pqM4r.js.br +0 -0
  583. codex/static_root/assets/flag-tab-CT8pqM4r.js.gz +0 -0
  584. codex/static_root/assets/flag-tab-DBqpLKi7.css +0 -1
  585. codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
  586. codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
  587. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +0 -1
  588. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
  589. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
  590. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js +0 -1
  591. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.br +0 -0
  592. codex/static_root/assets/group-tab-BOW9kigw.7e5007596367.js.gz +0 -0
  593. codex/static_root/assets/group-tab-BOW9kigw.js +0 -1
  594. codex/static_root/assets/group-tab-BOW9kigw.js.br +0 -0
  595. codex/static_root/assets/group-tab-BOW9kigw.js.gz +0 -0
  596. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css +0 -1
  597. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.br +0 -0
  598. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.gz +0 -0
  599. codex/static_root/assets/group-tab-CBKuS3Iv.css +0 -1
  600. codex/static_root/assets/group-tab-CBKuS3Iv.css.br +0 -0
  601. codex/static_root/assets/group-tab-CBKuS3Iv.css.gz +0 -0
  602. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js +0 -1
  603. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.br +0 -4
  604. codex/static_root/assets/http-error-C6yuLApo.45cf99eb7a70.js.gz +0 -0
  605. codex/static_root/assets/http-error-C6yuLApo.js +0 -1
  606. codex/static_root/assets/http-error-C6yuLApo.js.br +0 -4
  607. codex/static_root/assets/http-error-C6yuLApo.js.gz +0 -0
  608. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.br +0 -2
  609. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.gz +0 -0
  610. codex/static_root/assets/library-tab-Bh7ErAk7.css.br +0 -2
  611. codex/static_root/assets/library-tab-Bh7ErAk7.css.gz +0 -0
  612. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js +0 -1
  613. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.br +0 -0
  614. codex/static_root/assets/library-tab-D3WOtpE0.9af34e6e7235.js.gz +0 -0
  615. codex/static_root/assets/library-tab-D3WOtpE0.js +0 -1
  616. codex/static_root/assets/library-tab-D3WOtpE0.js.br +0 -0
  617. codex/static_root/assets/library-tab-D3WOtpE0.js.gz +0 -0
  618. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js +0 -6
  619. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.br +0 -0
  620. codex/static_root/assets/main-CmjU2oTp.6a471f6900e5.js.gz +0 -0
  621. codex/static_root/assets/main-CmjU2oTp.js +0 -6
  622. codex/static_root/assets/main-CmjU2oTp.js.br +0 -0
  623. codex/static_root/assets/main-CmjU2oTp.js.gz +0 -0
  624. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js +0 -1
  625. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.br +0 -0
  626. codex/static_root/assets/pager-full-pdf-BH54_5f6.6e92d0555cd7.js.gz +0 -0
  627. codex/static_root/assets/pager-full-pdf-BH54_5f6.js +0 -1
  628. codex/static_root/assets/pager-full-pdf-BH54_5f6.js.br +0 -0
  629. codex/static_root/assets/pager-full-pdf-BH54_5f6.js.gz +0 -0
  630. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js +0 -1
  631. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.br +0 -0
  632. codex/static_root/assets/pagination-toolbar-JH3Kbffr.451171f7ac33.js.gz +0 -0
  633. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js +0 -1
  634. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.br +0 -0
  635. codex/static_root/assets/pagination-toolbar-JH3Kbffr.js.gz +0 -0
  636. codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.br +0 -0
  637. codex/static_root/assets/pdf-doc-BcRNpoaW.882f6e967869.js.gz +0 -0
  638. codex/static_root/assets/pdf-doc-BcRNpoaW.js.br +0 -0
  639. codex/static_root/assets/pdf-doc-BcRNpoaW.js.gz +0 -0
  640. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js +0 -2
  641. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.br +0 -0
  642. codex/static_root/assets/reader-DIPRg2VB.900370e097aa.js.gz +0 -0
  643. codex/static_root/assets/reader-DIPRg2VB.js +0 -2
  644. codex/static_root/assets/reader-DIPRg2VB.js.br +0 -0
  645. codex/static_root/assets/reader-DIPRg2VB.js.gz +0 -0
  646. codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.br +0 -0
  647. codex/static_root/assets/reader-DveZZr9x.7b21b9f8f628.css.gz +0 -0
  648. codex/static_root/assets/reader-DveZZr9x.css.br +0 -0
  649. codex/static_root/assets/reader-DveZZr9x.css.gz +0 -0
  650. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js +0 -1
  651. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.br +0 -0
  652. codex/static_root/assets/relation-chips-3tTlR5QH.71f18bc92adc.js.gz +0 -0
  653. codex/static_root/assets/relation-chips-3tTlR5QH.js +0 -1
  654. codex/static_root/assets/relation-chips-3tTlR5QH.js.br +0 -0
  655. codex/static_root/assets/relation-chips-3tTlR5QH.js.gz +0 -0
  656. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js +0 -2
  657. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.br +0 -0
  658. codex/static_root/assets/settings-drawer-BdyFhA_r.151f4c3061ed.js.gz +0 -0
  659. codex/static_root/assets/settings-drawer-BdyFhA_r.js +0 -2
  660. codex/static_root/assets/settings-drawer-BdyFhA_r.js.br +0 -0
  661. codex/static_root/assets/settings-drawer-BdyFhA_r.js.gz +0 -0
  662. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js +0 -1
  663. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.br +0 -0
  664. codex/static_root/assets/stats-tab-DeKjwvmh.7557c71b4cfa.js.gz +0 -0
  665. codex/static_root/assets/stats-tab-DeKjwvmh.js +0 -1
  666. codex/static_root/assets/stats-tab-DeKjwvmh.js.br +0 -0
  667. codex/static_root/assets/stats-tab-DeKjwvmh.js.gz +0 -0
  668. codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.br +0 -0
  669. codex/static_root/assets/stats-tab-DpfGsVX3.1cb66f2649a9.css.gz +0 -0
  670. codex/static_root/assets/stats-tab-DpfGsVX3.css.br +0 -0
  671. codex/static_root/assets/stats-tab-DpfGsVX3.css.gz +0 -0
  672. codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css +0 -1
  673. codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css.gz +0 -0
  674. codex/static_root/assets/task-tab-1XiWn9xP.css +0 -1
  675. codex/static_root/assets/task-tab-1XiWn9xP.css.gz +0 -0
  676. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js +0 -1
  677. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.br +0 -0
  678. codex/static_root/assets/task-tab-C8D0qRP_.d4ab174c69a3.js.gz +0 -0
  679. codex/static_root/assets/task-tab-C8D0qRP_.js +0 -1
  680. codex/static_root/assets/task-tab-C8D0qRP_.js.br +0 -0
  681. codex/static_root/assets/task-tab-C8D0qRP_.js.gz +0 -0
  682. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js +0 -1
  683. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.br +0 -0
  684. codex/static_root/assets/unauthorized-BHgBRF4z.8e5c1b9e52b0.js.gz +0 -0
  685. codex/static_root/assets/unauthorized-BHgBRF4z.js +0 -1
  686. codex/static_root/assets/unauthorized-BHgBRF4z.js.br +0 -0
  687. codex/static_root/assets/unauthorized-BHgBRF4z.js.gz +0 -0
  688. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js +0 -1
  689. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.br +0 -0
  690. codex/static_root/assets/user-tab-BU31Z92y.35613c713faf.js.gz +0 -0
  691. codex/static_root/assets/user-tab-BU31Z92y.js +0 -1
  692. codex/static_root/assets/user-tab-BU31Z92y.js.br +0 -0
  693. codex/static_root/assets/user-tab-BU31Z92y.js.gz +0 -0
  694. codex/static_root/js/choices-admin.8aa64c911203.json +0 -1
  695. codex/static_root/js/choices-admin.8aa64c911203.json.br +0 -0
  696. codex/static_root/js/choices-admin.8aa64c911203.json.gz +0 -0
  697. codex/static_root/js/choices-admin.json +0 -1
  698. codex/static_root/js/choices-admin.json.br +0 -0
  699. codex/static_root/js/choices-admin.json.gz +0 -0
  700. codex/static_root/js/choices.2f6b0359e1ef.json +0 -1
  701. codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
  702. codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
  703. codex/static_root/js/choices.json +0 -1
  704. codex/static_root/js/choices.json.br +0 -0
  705. codex/static_root/js/choices.json.gz +0 -0
  706. codex/static_root/manifest.45cb46e19686.json +0 -635
  707. codex/static_root/manifest.45cb46e19686.json.br +0 -0
  708. codex/static_root/manifest.45cb46e19686.json.gz +0 -0
  709. codex/views/browser/annotations.py +0 -339
  710. codex/views/browser/base.py +0 -139
  711. codex/views/browser/filters/annotations.py +0 -125
  712. codex/views/browser/metadata.py +0 -332
  713. {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/LICENSE +0 -0
  714. {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/WHEEL +0 -0
  715. {codex-1.7.0a0.dist-info → codex-1.7.0a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,19 @@
1
+ """Bookmark Tasks."""
2
+
3
+ from collections.abc import Mapping
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+
8
+ @dataclass
9
+ class BookmarkTask:
10
+ """Bookmark Base Class."""
11
+
12
+
13
+ @dataclass
14
+ class BookmarkUpdateTask(BookmarkTask):
15
+ """Bookmark a page."""
16
+
17
+ auth_filter: Mapping[str, int | str]
18
+ comic_filter: Mapping[str, Any]
19
+ updates: Mapping[str, Any]
@@ -0,0 +1,154 @@
1
+ """Sends notifications to connections, reading from a queue."""
2
+
3
+ from django.contrib.auth.models import User
4
+ from django.db.models.expressions import F
5
+ from django.db.models.functions import Now
6
+ from django.db.models.query import Q
7
+
8
+ from codex.librarian.mp_queue import LIBRARIAN_QUEUE
9
+ from codex.librarian.notifier.tasks import NotifierTask
10
+ from codex.models import Bookmark, Comic
11
+ from codex.views.auth import GroupACLMixin
12
+ from codex.views.mixins import BookmarkSearchMixin
13
+
14
+ VERTICAL_READING_DIRECTIONS = frozenset({"ttb", "btt"})
15
+ _BOOKMARK_UPDATE_FIELDS = frozenset(
16
+ {
17
+ "page",
18
+ "finished",
19
+ "fit_to",
20
+ "two_pages",
21
+ "reading_direction",
22
+ }
23
+ )
24
+ _COMIC_ONLY_FIELDS = ("pk", "page_count")
25
+
26
+
27
+ class BookmarkUpdate(GroupACLMixin, BookmarkSearchMixin):
28
+ """Update Bookmarks."""
29
+
30
+ # Used by Bookmarkd and view.bookmark.
31
+
32
+ @staticmethod
33
+ def _update_bookmarks_validate_page(bm, updates):
34
+ """Force bookmark page into valid range."""
35
+ page = updates.get("page")
36
+ if page is None:
37
+ return
38
+ max_page = bm.page_count + 1
39
+ page = max(min(page, max_page), 0)
40
+ if page == max_page:
41
+ # Auto finish on bookmark last page
42
+ bm.finished = True
43
+ updates["page"] = page
44
+
45
+ @staticmethod
46
+ def _update_bookmarks_validate_two_pages(bm, updates):
47
+ """Force vertical view to not use two pages."""
48
+ rd = updates.get("reading_direction")
49
+ if (
50
+ bm.two_pages
51
+ and rd in VERTICAL_READING_DIRECTIONS
52
+ or bm.reading_direction in VERTICAL_READING_DIRECTIONS
53
+ ):
54
+ updates["two_pages"] = None
55
+
56
+ @staticmethod
57
+ def _notify_library_changed(uid):
58
+ """Notify one user that their library changed."""
59
+ group = f"user_{uid}"
60
+ task = NotifierTask("LIBRARY_CHANGED", group)
61
+ LIBRARIAN_QUEUE.put(task)
62
+
63
+ @classmethod
64
+ def _update_bookmarks(cls, search_kwargs, updates, user) -> int:
65
+ """Update existing bookmarks."""
66
+ count = 0
67
+ if not updates:
68
+ return count
69
+ group_acl_filter = cls.get_group_acl_filter(Bookmark, user)
70
+ query_filter = group_acl_filter & Q(**search_kwargs)
71
+ existing_bookmarks = Bookmark.objects.filter(query_filter)
72
+ if updates.get("page") is not None:
73
+ existing_bookmarks = existing_bookmarks.annotate(
74
+ page_count=F("comic__page_count")
75
+ )
76
+
77
+ update_fields = set(updates.keys()) & _BOOKMARK_UPDATE_FIELDS
78
+ update_fields.add("updated_at")
79
+ only_fields = (*update_fields, "pk")
80
+
81
+ existing_bookmarks = existing_bookmarks.only(*only_fields)
82
+ update_bookmarks = []
83
+ for bm in existing_bookmarks:
84
+ cls._update_bookmarks_validate_page(bm, updates)
85
+ cls._update_bookmarks_validate_two_pages(bm, updates)
86
+ for key, value in updates.items():
87
+ setattr(bm, key, value)
88
+ bm.updated_at = Now()
89
+ update_bookmarks.append(bm)
90
+ count = len(update_bookmarks)
91
+ if count:
92
+ Bookmark.objects.bulk_update(update_bookmarks, tuple(update_fields))
93
+ return count
94
+
95
+ @staticmethod
96
+ def _create_bookmarks_validate_two_pages(updates):
97
+ """Force vertical view to not use two pages."""
98
+ if (
99
+ updates.get("two_pages")
100
+ and updates.get("reading_direction") in VERTICAL_READING_DIRECTIONS
101
+ ):
102
+ updates.pop("two_pages", None)
103
+
104
+ @classmethod
105
+ def _create_bookmarks(cls, comic_filter, search_kwargs, updates, user) -> int:
106
+ """Create new bookmarks for comics that don't exist yet."""
107
+ count = 0
108
+ cls._create_bookmarks_validate_two_pages(updates)
109
+ if not updates:
110
+ return count
111
+ create_bookmarks = []
112
+ group_acl_filter = cls.get_group_acl_filter(Comic, user)
113
+ exclude = {}
114
+ for key, value in search_kwargs.items():
115
+ exclude["bookmark__" + key] = value
116
+ query_filter = group_acl_filter & Q(**comic_filter) & ~Q(**exclude)
117
+ create_bookmark_comics = Comic.objects.filter(query_filter).only(
118
+ *_COMIC_ONLY_FIELDS
119
+ )
120
+ for comic in create_bookmark_comics:
121
+ defaults = {"comic": comic}
122
+ defaults.update(updates)
123
+ for key, value in search_kwargs.items():
124
+ if not key.startswith("comic"):
125
+ defaults[key] = value
126
+
127
+ bm = Bookmark(**defaults)
128
+ create_bookmarks.append(bm)
129
+ count = len(create_bookmarks)
130
+ if count:
131
+ Bookmark.objects.bulk_create(
132
+ create_bookmarks,
133
+ update_fields=tuple(_BOOKMARK_UPDATE_FIELDS),
134
+ unique_fields=Bookmark._meta.unique_together[0], # type: ignore
135
+ )
136
+ return count
137
+
138
+ @classmethod
139
+ def update_bookmarks(cls, auth_filter, comic_filter, updates):
140
+ """Update a user bookmark."""
141
+ search_kwargs = cls.get_bookmark_search_kwargs(auth_filter, comic_filter)
142
+
143
+ user = None
144
+ for key, value in auth_filter.items():
145
+ if key == "user":
146
+ user = User.objects.get(pk=value)
147
+ break
148
+
149
+ count = cls._update_bookmarks(search_kwargs, updates, user)
150
+ count += cls._create_bookmarks(comic_filter, search_kwargs, updates, user)
151
+ if count:
152
+ uid = next(iter(auth_filter.values()))
153
+ cls._notify_library_changed(uid)
154
+ return count
@@ -1,7 +1,6 @@
1
1
  """Functions for dealing with comic cover thumbnails."""
2
2
 
3
- from codex.librarian.covers.create import CoverCreateMixin
4
- from codex.librarian.covers.purge import CoverPurgeMixin
3
+ from codex.librarian.covers.purge import CoverPurgeThread
5
4
  from codex.librarian.covers.tasks import (
6
5
  CoverCreateAllTask,
7
6
  CoverRemoveAllTask,
@@ -11,11 +10,11 @@ from codex.librarian.covers.tasks import (
11
10
  )
12
11
 
13
12
 
14
- class CoverThread(CoverCreateMixin, CoverPurgeMixin):
13
+ class CoverThread(CoverPurgeThread):
15
14
  """Create comic covers in it's own thread."""
16
15
 
17
16
  def process_item(self, item):
18
- """Run the contributor."""
17
+ """Run the task method."""
19
18
  task = item
20
19
  if isinstance(task, CoverSaveToCache):
21
20
  self.save_cover_to_cache(task.cover_path, task.data)
@@ -13,15 +13,16 @@ from codex.librarian.covers.status import CoverStatusTypes
13
13
  from codex.librarian.covers.tasks import CoverSaveToCache
14
14
  from codex.models import Comic, CustomCover
15
15
  from codex.status import Status
16
+ from codex.threads import QueuedThread
16
17
 
18
+ _COVER_RATIO = 1.5372233400402415 # modal cover ratio
19
+ THUMBNAIL_WIDTH = 165
20
+ THUMBNAIL_HEIGHT = round(THUMBNAIL_WIDTH * _COVER_RATIO)
21
+ _THUMBNAIL_SIZE = (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
17
22
 
18
- class CoverCreateMixin(CoverPathMixin):
19
- """Create methods for covers."""
20
23
 
21
- _COVER_RATIO = 1.5372233400402415 # modal cover ratio
22
- THUMBNAIL_WIDTH = 165
23
- THUMBNAIL_HEIGHT = round(THUMBNAIL_WIDTH * _COVER_RATIO)
24
- _THUMBNAIL_SIZE = (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT)
24
+ class CoverCreateThread(QueuedThread, CoverPathMixin):
25
+ """Create methods for covers."""
25
26
 
26
27
  @classmethod
27
28
  def _create_cover_thumbnail(cls, cover_image_data):
@@ -30,7 +31,7 @@ class CoverCreateMixin(CoverPathMixin):
30
31
  with BytesIO(cover_image_data) as image_io:
31
32
  with Image.open(image_io) as cover_image:
32
33
  cover_image.thumbnail(
33
- cls._THUMBNAIL_SIZE,
34
+ _THUMBNAIL_SIZE,
34
35
  Image.Resampling.LANCZOS, # type: ignore
35
36
  reducing_gap=3.0,
36
37
  )
@@ -5,10 +5,9 @@ from pathlib import Path
5
5
  from fnvhash import fnv1a_32
6
6
 
7
7
  from codex.settings.settings import ROOT_CACHE_PATH
8
- from codex.threads import QueuedThread
9
8
 
10
9
 
11
- class CoverPathMixin(QueuedThread):
10
+ class CoverPathMixin:
12
11
  """Path methods for covers."""
13
12
 
14
13
  COVERS_ROOT = ROOT_CACHE_PATH / "covers"
@@ -4,7 +4,7 @@ import os
4
4
  import shutil
5
5
  from pathlib import Path
6
6
 
7
- from codex.librarian.covers.path import CoverPathMixin
7
+ from codex.librarian.covers.create import CoverCreateThread
8
8
  from codex.librarian.covers.status import CoverStatusTypes
9
9
  from codex.librarian.notifier.tasks import LIBRARY_CHANGED_TASK
10
10
  from codex.models import Comic
@@ -12,7 +12,7 @@ from codex.models.paths import CustomCover
12
12
  from codex.status import Status
13
13
 
14
14
 
15
- class CoverPurgeMixin(CoverPathMixin):
15
+ class CoverPurgeThread(CoverCreateThread):
16
16
  """Cover Purge methods."""
17
17
 
18
18
  _CLEANUP_STATUS_MAP = (
@@ -35,6 +35,7 @@ def _exec_sql(sql):
35
35
  connection = connections[DEFAULT_DB_ALIAS]
36
36
  connection.prepare_database()
37
37
  with connection.cursor() as cursor:
38
+ cursor.execute("PRAGMA wal_checkpoint(TRUNCATE)")
38
39
  cursor.execute(sql)
39
40
  return cursor.fetchall()
40
41
 
@@ -7,6 +7,8 @@ from typing import NamedTuple
7
7
 
8
8
  from caseconverter import snakecase
9
9
 
10
+ from codex.librarian.bookmark.bookmarkd import BookmarkThread
11
+ from codex.librarian.bookmark.tasks import BookmarkTask
10
12
  from codex.librarian.covers.coverd import CoverThread
11
13
  from codex.librarian.covers.tasks import CoverTask
12
14
  from codex.librarian.cron.crond import CronThread
@@ -46,6 +48,7 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
46
48
  """Librarian Process."""
47
49
 
48
50
  _THREAD_CLASSES = (
51
+ BookmarkThread,
49
52
  NotifierThread,
50
53
  DelayedTasksThread,
51
54
  CoverThread,
@@ -88,6 +91,8 @@ class LibrarianDaemon(Process, LoggerBaseMixin):
88
91
  match task:
89
92
  case CoverTask():
90
93
  self._threads.cover_thread.queue.put(task)
94
+ case BookmarkTask():
95
+ self._threads.bookmark_thread.queue.put(task)
91
96
  case WatchdogEventTask():
92
97
  self._threads.watchdog_event_batcher_thread.queue.put(task)
93
98
  case ImportTask():
@@ -1,5 +1,6 @@
1
1
  """A Codex database event emitter for use by the observer."""
2
2
 
3
+ from logging import DEBUG, WARNING
3
4
  from pathlib import Path
4
5
  from threading import Condition
5
6
 
@@ -91,29 +92,30 @@ class DatabasePollingEmitter(EventEmitter, WorkerBaseMixin):
91
92
 
92
93
  def _is_watch_path_ok(self, library):
93
94
  """Return a special timeout value if there's a problem with the watch dir."""
95
+ ok = False
96
+ msg = ""
97
+ log_level = WARNING
94
98
  if not library.poll:
95
99
  # Wait forever. Manual poll only
96
- return None
97
- if not self._watch_path.is_dir():
98
- self.log.warning(f"Library {self._watch_path} not found.")
99
- return False
100
- if self._watch_path_unmounted.exists():
100
+ ok = None
101
+ elif not self._watch_path.is_dir():
102
+ msg = f"Library {self._watch_path} not found. Not Polling."
103
+ elif self._watch_path_unmounted.exists():
101
104
  # Maybe overkill of caution here
102
- self.log.warning(
103
- f"Library {self._watch_path} looks like an unmounted docker volume."
104
- )
105
- return False
106
- if not tuple(self._watch_path.iterdir()):
105
+ msg = f"Library {self._watch_path} looks like an unmounted docker volume. Not polling."
106
+ elif not tuple(self._watch_path.iterdir()):
107
107
  # Maybe overkill of caution here too
108
- self.log.warning(
109
- f"{self._watch_path} is empty. Suspect it may be unmounted."
110
- )
111
- return False
112
- if library.update_in_progress:
113
- self.log.warning(f"Library {library.path} update in progress.")
114
- return False
115
-
116
- return True
108
+ msg = f"{self._watch_path} is empty. Suspect it may be unmounted. Not polling."
109
+ elif library.update_in_progress:
110
+ msg = f"Library {library.path} update in progress. Not polling."
111
+ log_level = DEBUG
112
+ else:
113
+ ok = True
114
+
115
+ if msg:
116
+ self.log.log(log_level, msg)
117
+
118
+ return ok
117
119
 
118
120
  @property
119
121
  def timeout(self) -> int | None: # type: ignore
@@ -20,7 +20,6 @@ from codex.threads import AggregateMessageQueuedThread
20
20
  class WatchdogEventBatcherThread(AggregateMessageQueuedThread):
21
21
  """Batch watchdog events into bulk database tasks."""
22
22
 
23
- CLS_SUFFIX = -len("Event")
24
23
  DBDIFF_TASK_PARAMS = MappingProxyType(
25
24
  {
26
25
  "library_id": None,
@@ -11,6 +11,10 @@ class Migration(migrations.Migration):
11
11
  ]
12
12
 
13
13
  operations = [
14
+ migrations.AlterUniqueTogether(
15
+ name="groupauth",
16
+ unique_together=set(),
17
+ ),
14
18
  migrations.AlterField(
15
19
  model_name="adminflag",
16
20
  name="key",
codex/models/admin.py CHANGED
@@ -134,8 +134,3 @@ class GroupAuth(BaseModel):
134
134
 
135
135
  group = OneToOneField(Group, db_index=True, on_delete=CASCADE)
136
136
  exclude = BooleanField(db_index=True, default=False)
137
-
138
- class Meta(BaseModel.Meta):
139
- """Constraints."""
140
-
141
- unique_together = ("group",)
codex/models/functions.py CHANGED
@@ -36,12 +36,13 @@ class FTS5Match(Lookup):
36
36
 
37
37
  def as_sql(self, compiler, connection):
38
38
  """Generate MATCH sql."""
39
- lhs, lhs_params = self.process_lhs(compiler, connection)
39
+ # lhs, lhs_params = self.process_lhs(compiler, connection)
40
+ # fts_table = lhs.split(".")[0]
40
41
  rhs, rhs_params = self.process_rhs(compiler, connection)
41
42
  # MATCH works on the table itself not the one_to_one rel.
42
- fts_table = lhs.split(".")[0]
43
- params = lhs_params + rhs_params
44
- sql = f"{fts_table} MATCH {rhs}"
43
+ # Force the table name without substitutions by the optimizer
44
+ sql = "codex_comicfts MATCH " + rhs
45
+ params = rhs_params
45
46
  return sql, params
46
47
 
47
48
 
@@ -62,7 +63,7 @@ class Like(Lookup):
62
63
 
63
64
 
64
65
  class ComicFTSRank(Func):
65
- """Sqlite3 FTS5 inverse rank lookup."""
66
+ """Sqlite3 FTS5 inverse rank function."""
66
67
 
67
68
  function = "rank"
68
69
  template = '("codex_comicfts"."rank" * -1)'
codex/models/query.py CHANGED
@@ -86,6 +86,12 @@ class GroupByQuerySet(QuerySet):
86
86
  obj.query.set_force_group_by(fields, model=model)
87
87
  return obj
88
88
 
89
+ def demote_joins(self, tables):
90
+ """Force INNER JOINS."""
91
+ obj = self._chain() # type: ignore
92
+ obj.query.demote_joins(tables)
93
+ return obj
94
+
89
95
 
90
96
  class GroupByManager(Manager.from_queryset(GroupByQuerySet)):
91
97
  """Use GroupBy QuerySet."""
codex/run.py CHANGED
@@ -2,6 +2,7 @@
2
2
  """The main runnable for codex. Sets up codex and runs hypercorn."""
3
3
 
4
4
  import asyncio
5
+ import logging
5
6
  from os import execv
6
7
 
7
8
  from django.db import connection
@@ -24,14 +25,11 @@ LOG = get_logger(__name__)
24
25
 
25
26
  def codex_startup():
26
27
  """Start up codex."""
27
- loggerd = CodexLogQueueListener(LOG_QUEUE)
28
- loggerd.start()
29
- LOG.info(f"Running Codex v{VERSION}")
30
- codex_init()
31
- return loggerd
28
+ LOG.info(f"Starting Codex v{VERSION}")
29
+ return codex_init()
32
30
 
33
31
 
34
- def database_checkpoint():
32
+ def _database_checkpoint():
35
33
  """Write wal to disk and truncate it."""
36
34
  with connection.cursor() as cursor:
37
35
  cursor.execute("PRAGMA wal_checkpoint(TRUNCATE);")
@@ -48,8 +46,9 @@ def restart():
48
46
 
49
47
  def codex_shutdown(loggerd):
50
48
  """Shutdown for codex."""
51
- database_checkpoint()
49
+ _database_checkpoint()
52
50
  LOG.info("Goodbye.")
51
+ logging.shutdown()
53
52
  loggerd.stop()
54
53
  if RESTART_EVENT.is_set():
55
54
  restart()
@@ -57,6 +56,7 @@ def codex_shutdown(loggerd):
57
56
 
58
57
  def run():
59
58
  """Run Codex."""
59
+ LOG.info(f"Running Codex v{VERSION}")
60
60
  librarian = LibrarianDaemon(LIBRARIAN_QUEUE, LOG_QUEUE, BROADCAST_QUEUE)
61
61
  librarian.start()
62
62
  asyncio.run(
@@ -71,8 +71,10 @@ def run():
71
71
 
72
72
  def main():
73
73
  """Set up and run Codex."""
74
- loggerd = codex_startup()
75
- run()
74
+ loggerd = CodexLogQueueListener(LOG_QUEUE)
75
+ loggerd.start()
76
+ if codex_startup():
77
+ run()
76
78
  codex_shutdown(loggerd)
77
79
 
78
80
 
@@ -6,11 +6,11 @@ from rest_framework.serializers import (
6
6
  Serializer,
7
7
  )
8
8
 
9
- from codex.serializers.choices import CHOICES
9
+ from codex.choices import ADMIN_TASKS
10
10
 
11
11
 
12
12
  class AdminLibrarianTaskSerializer(Serializer):
13
13
  """Get tasks from front end."""
14
14
 
15
- task = ChoiceField(choices=CHOICES["admin"]["tasks"])
15
+ task = ChoiceField(choices=ADMIN_TASKS)
16
16
  library_id = IntegerField(required=False)
@@ -1,11 +1,11 @@
1
1
  """Serializer mixins."""
2
2
 
3
3
  from datetime import datetime, timezone
4
+ from itertools import chain
4
5
 
5
6
  from rest_framework.serializers import (
6
7
  BooleanField,
7
8
  CharField,
8
- DateTimeField,
9
9
  DecimalField,
10
10
  IntegerField,
11
11
  ListField,
@@ -14,7 +14,8 @@ from rest_framework.serializers import (
14
14
  )
15
15
 
16
16
  from codex.logger.logging import get_logger
17
- from codex.views.const import COMIC_GROUP, EPOCH_START
17
+ from codex.util import max_none
18
+ from codex.views.const import EPOCH_START
18
19
 
19
20
  LOG = get_logger(__name__)
20
21
 
@@ -31,16 +32,15 @@ class BrowserAggregateSerializerMixin(Serializer):
31
32
 
32
33
  # Bookmark annotations
33
34
  page = IntegerField(read_only=True)
34
- bookmark_updated_at = DateTimeField(read_only=True, allow_null=True)
35
35
  finished = BooleanField(read_only=True)
36
36
  progress = DecimalField(
37
37
  max_digits=5, decimal_places=2, read_only=True, coerce_to_string=False
38
38
  )
39
39
 
40
- def get_mtime(self, obj) -> int:
41
- """Compute mtime from json array aggregates."""
42
- mtime = EPOCH_START
43
- for dt_str in obj.updated_ats:
40
+ @staticmethod
41
+ def _get_max_updated_at(mtime, updated_ats) -> datetime:
42
+ """Because orm won't aggregate aggregates."""
43
+ for dt_str in updated_ats:
44
44
  if not dt_str:
45
45
  continue
46
46
  try:
@@ -52,13 +52,17 @@ class BrowserAggregateSerializerMixin(Serializer):
52
52
  f"computing group mtime: {dt_str} is not a valid datetime string."
53
53
  )
54
54
  continue
55
-
56
55
  mtime = max(dt, mtime)
56
+ return mtime
57
57
 
58
- if obj.group != COMIC_GROUP and (
59
- mbua := getattr(obj, "max_bookmark_updated_at", None)
60
- ):
61
- mtime = max(mtime, mbua)
62
-
63
- # print(obj.group, obj.pk, obj.name, obj.updated_ats, obj.max_bookmark_updated_at, "max:", mtime)
58
+ def get_mtime(self, obj) -> int:
59
+ """Compute mtime from json array aggregates."""
60
+ updated_ats = (
61
+ obj.updated_ats
62
+ if obj.bmua_is_max
63
+ else chain(obj.updated_ats, obj.bookmark_updated_ats)
64
+ )
65
+ mtime = self._get_max_updated_at(EPOCH_START, updated_ats)
66
+ if obj.bmua_is_max:
67
+ mtime: datetime = max_none(mtime, obj.bookmark_updated_at, EPOCH_START) # type: ignore
64
68
  return int(mtime.timestamp() * 1000)
@@ -8,13 +8,11 @@ from rest_framework.serializers import (
8
8
  Serializer,
9
9
  )
10
10
 
11
+ from codex.choices import BROWSER_ORDER_BY_CHOICES
11
12
  from codex.serializers.browser.filters import BrowserSettingsFilterSerializer
12
- from codex.serializers.choices import CHOICES, VUETIFY_NULL_CODE
13
13
  from codex.serializers.fields import BreadcrumbsField, TimestampField, TopGroupField
14
14
  from codex.serializers.route import SimpleRouteSerializer
15
15
 
16
- VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
17
-
18
16
 
19
17
  class BrowserSettingsShowGroupFlagsSerializer(Serializer):
20
18
  """Show Group Flags."""
@@ -37,7 +35,9 @@ class BrowserCoverInputSerializerBase(BrowserFilterChoicesInputSerilalizer):
37
35
 
38
36
  custom_covers = BooleanField(required=False)
39
37
  dynamic_covers = BooleanField(required=False)
40
- order_by = ChoiceField(choices=tuple(CHOICES["orderBy"].keys()), required=False)
38
+ order_by = ChoiceField(
39
+ choices=tuple(BROWSER_ORDER_BY_CHOICES.keys()), required=False
40
+ )
41
41
  order_reverse = BooleanField(required=False)
42
42
  show = BrowserSettingsShowGroupFlagsSerializer(required=False)
43
43
 
@@ -17,11 +17,15 @@ from rest_framework.serializers import (
17
17
  MultipleChoiceField,
18
18
  )
19
19
 
20
+ from codex.choices import (
21
+ BROWSER_BOOKMARK_FILTER_CHOICES,
22
+ BROWSER_TOP_GROUP_CHOICES,
23
+ DUMMY_NULL_NAME,
24
+ VUETIFY_NULL_CODE,
25
+ )
20
26
  from codex.logger.logging import get_logger
21
- from codex.serializers.choices import CHOICES, DUMMY_NULL_NAME, VUETIFY_NULL_CODE
22
27
  from codex.serializers.route import RouteSerializer
23
28
 
24
- VUETIFY_NULL_CODE_STR = str(VUETIFY_NULL_CODE)
25
29
  LOG = get_logger(__name__)
26
30
 
27
31
 
@@ -69,7 +73,7 @@ class VuetifyIntegerField(VuetifyNullCodeFieldMixin, IntegerField): # type: ign
69
73
  class VuetifyCharField(VuetifyNullCodeFieldMixin, CharField): # type: ignore
70
74
  """Char Field with null code conversion."""
71
75
 
72
- NULL_CODE = VUETIFY_NULL_CODE_STR
76
+ NULL_CODE = str(VUETIFY_NULL_CODE)
73
77
 
74
78
 
75
79
  class VuetifyBooleanField(VuetifyNullCodeFieldMixin, BooleanField): # type: ignore
@@ -82,7 +86,7 @@ class BookmarkFilterField(ChoiceField):
82
86
  def __init__(self, *args, **kwargs):
83
87
  """Use bookmark filter choices."""
84
88
  super().__init__(
85
- *args, choices=tuple(CHOICES["bookmarkFilter"].keys()), **kwargs
89
+ *args, choices=tuple(BROWSER_BOOKMARK_FILTER_CHOICES.keys()), **kwargs
86
90
  )
87
91
 
88
92
 
@@ -105,7 +109,7 @@ class BreadcrumbsField(ListField):
105
109
  class TopGroupField(ChoiceField):
106
110
  """Valid Top Groups Only."""
107
111
 
108
- class_choices = tuple(CHOICES["topGroup"].keys())
112
+ class_choices = tuple(BROWSER_TOP_GROUP_CHOICES.keys())
109
113
 
110
114
  def __init__(self, *args, **kwargs):
111
115
  """Initialize with choices."""