codex 1.6.19__py3-none-any.whl → 1.7.0__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 (842) hide show
  1. codex/choices.py +419 -0
  2. codex/choices_to_json.py +122 -0
  3. codex/db.py +134 -0
  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 +3 -3
  13. codex/librarian/importer/const.py +7 -1
  14. codex/librarian/importer/create_fks.py +5 -2
  15. codex/librarian/importer/importerd.py +17 -12
  16. codex/librarian/importer/moved.py +44 -13
  17. codex/librarian/importer/tasks.py +2 -0
  18. codex/librarian/janitor/cleanup.py +21 -8
  19. codex/librarian/janitor/integrity.py +371 -0
  20. codex/librarian/janitor/janitor.py +50 -15
  21. codex/librarian/janitor/latest_version.py +2 -1
  22. codex/librarian/janitor/status.py +5 -0
  23. codex/librarian/janitor/tasks.py +37 -0
  24. codex/librarian/janitor/update.py +1 -1
  25. codex/librarian/librariand.py +37 -24
  26. codex/librarian/search/optimize.py +43 -0
  27. codex/librarian/search/remove.py +18 -46
  28. codex/librarian/search/searchd.py +14 -17
  29. codex/librarian/search/status.py +2 -1
  30. codex/librarian/search/tasks.py +4 -9
  31. codex/librarian/search/update.py +223 -301
  32. codex/librarian/watchdog/emitter.py +21 -19
  33. codex/librarian/watchdog/event_batcherd.py +0 -1
  34. codex/librarian/watchdog/observers.py +2 -1
  35. codex/migrations/0029_comicfts.py +58 -0
  36. codex/migrations/0030_nocase_collation_day_month_indexes_status_types.py +202 -0
  37. codex/models/admin.py +15 -11
  38. codex/models/base.py +9 -0
  39. codex/models/bookmark.py +3 -3
  40. codex/models/comic.py +68 -16
  41. codex/models/functions.py +60 -2
  42. codex/models/groups.py +6 -1
  43. codex/models/library.py +1 -3
  44. codex/models/paths.py +9 -3
  45. codex/models/query.py +6 -0
  46. codex/run.py +11 -9
  47. codex/serializers/admin/tasks.py +2 -2
  48. codex/serializers/browser/mixins.py +18 -14
  49. codex/serializers/browser/page.py +2 -0
  50. codex/serializers/browser/settings.py +4 -4
  51. codex/serializers/fields.py +9 -5
  52. codex/settings/settings.py +16 -17
  53. codex/signals/django_signals.py +4 -10
  54. codex/startup.py +7 -39
  55. codex/static_root/assets/VCheckbox-D_CWEmqR.84f2b49311e8.js +1 -0
  56. codex/static_root/assets/VCheckbox-D_CWEmqR.84f2b49311e8.js.br +0 -0
  57. codex/static_root/assets/VCheckbox-D_CWEmqR.84f2b49311e8.js.gz +0 -0
  58. codex/static_root/assets/VCheckbox-D_CWEmqR.js +1 -0
  59. codex/static_root/assets/VCheckbox-D_CWEmqR.js.br +0 -0
  60. codex/static_root/assets/VCheckbox-D_CWEmqR.js.gz +0 -0
  61. codex/static_root/assets/VCheckboxBtn-B1BhcFvi.0eaf3c516474.js +1 -0
  62. codex/static_root/assets/VCheckboxBtn-B1BhcFvi.0eaf3c516474.js.br +0 -0
  63. codex/static_root/assets/VCheckboxBtn-B1BhcFvi.0eaf3c516474.js.gz +0 -0
  64. codex/static_root/assets/VCheckboxBtn-B1BhcFvi.js +1 -0
  65. codex/static_root/assets/VCheckboxBtn-B1BhcFvi.js.br +0 -0
  66. codex/static_root/assets/VCheckboxBtn-B1BhcFvi.js.gz +0 -0
  67. codex/static_root/assets/VCombobox-CZAW6_3Q.0e2401e819a5.js +1 -0
  68. codex/static_root/assets/VCombobox-CZAW6_3Q.0e2401e819a5.js.br +0 -0
  69. codex/static_root/assets/VCombobox-CZAW6_3Q.0e2401e819a5.js.gz +0 -0
  70. codex/static_root/assets/VCombobox-CZAW6_3Q.js +1 -0
  71. codex/static_root/assets/VCombobox-CZAW6_3Q.js.br +0 -0
  72. codex/static_root/assets/VCombobox-CZAW6_3Q.js.gz +0 -0
  73. codex/static_root/assets/VDialog-CSHgHIlG.a0bcd336cb7b.js +1 -0
  74. codex/static_root/assets/VDialog-CSHgHIlG.a0bcd336cb7b.js.br +0 -0
  75. codex/static_root/assets/VDialog-CSHgHIlG.a0bcd336cb7b.js.gz +0 -0
  76. codex/static_root/assets/VDialog-CSHgHIlG.js +1 -0
  77. codex/static_root/assets/VDialog-CSHgHIlG.js.br +0 -0
  78. codex/static_root/assets/VDialog-CSHgHIlG.js.gz +0 -0
  79. codex/static_root/assets/VDialog-CSvrKKb2.css +1 -0
  80. codex/static_root/assets/VDialog-CSvrKKb2.css.br +0 -0
  81. codex/static_root/assets/VDialog-CSvrKKb2.css.gz +0 -0
  82. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css +1 -0
  83. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.br +0 -0
  84. codex/static_root/assets/VDialog-CSvrKKb2.fd4d50904b17.css.gz +0 -0
  85. codex/static_root/assets/VExpansionPanels-CG6MpdMC.e96ae8afad97.js +1 -0
  86. codex/static_root/assets/VExpansionPanels-CG6MpdMC.e96ae8afad97.js.br +0 -0
  87. codex/static_root/assets/VExpansionPanels-CG6MpdMC.e96ae8afad97.js.gz +0 -0
  88. codex/static_root/assets/VExpansionPanels-CG6MpdMC.js +1 -0
  89. codex/static_root/assets/VExpansionPanels-CG6MpdMC.js.br +0 -0
  90. codex/static_root/assets/VExpansionPanels-CG6MpdMC.js.gz +0 -0
  91. codex/static_root/assets/VRadioGroup-CAgQxkiU.f26ecb0c3e34.js +1 -0
  92. codex/static_root/assets/VRadioGroup-CAgQxkiU.f26ecb0c3e34.js.br +0 -0
  93. codex/static_root/assets/VRadioGroup-CAgQxkiU.f26ecb0c3e34.js.gz +0 -0
  94. codex/static_root/assets/VRadioGroup-CAgQxkiU.js +1 -0
  95. codex/static_root/assets/VRadioGroup-CAgQxkiU.js.br +0 -0
  96. codex/static_root/assets/VRadioGroup-CAgQxkiU.js.gz +0 -0
  97. codex/static_root/assets/VSelect-C1gez8rl.9b11d2f00faf.js +1 -0
  98. codex/static_root/assets/VSelect-C1gez8rl.9b11d2f00faf.js.br +0 -0
  99. codex/static_root/assets/VSelect-C1gez8rl.9b11d2f00faf.js.gz +0 -0
  100. codex/static_root/assets/VSelect-C1gez8rl.js +1 -0
  101. codex/static_root/assets/VSelect-C1gez8rl.js.br +0 -0
  102. codex/static_root/assets/VSelect-C1gez8rl.js.gz +0 -0
  103. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css +1 -0
  104. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.br +0 -0
  105. codex/static_root/assets/VSelect-DLu8bpcs.98bdcf35411c.css.gz +0 -0
  106. codex/static_root/assets/VSelect-DLu8bpcs.css +1 -0
  107. codex/static_root/assets/VSelect-DLu8bpcs.css.br +0 -0
  108. codex/static_root/assets/VSelect-DLu8bpcs.css.gz +0 -0
  109. codex/static_root/assets/VSelectionControl-DBqDlKjK.160beaa1b56c.js +1 -0
  110. codex/static_root/assets/VSelectionControl-DBqDlKjK.160beaa1b56c.js.br +0 -0
  111. codex/static_root/assets/VSelectionControl-DBqDlKjK.160beaa1b56c.js.gz +0 -0
  112. codex/static_root/assets/VSelectionControl-DBqDlKjK.js +1 -0
  113. codex/static_root/assets/VSelectionControl-DBqDlKjK.js.br +0 -0
  114. codex/static_root/assets/VSelectionControl-DBqDlKjK.js.gz +0 -0
  115. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css +1 -0
  116. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.br +0 -0
  117. codex/static_root/assets/VSlideGroup-BtXPhMOT.830f136ffd41.css.gz +0 -0
  118. codex/static_root/assets/VSlideGroup-BtXPhMOT.css +1 -0
  119. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.br +0 -0
  120. codex/static_root/assets/VSlideGroup-BtXPhMOT.css.gz +0 -0
  121. codex/static_root/assets/VSlideGroup-SqR_NAL1.40ba1d838a6e.js +1 -0
  122. codex/static_root/assets/VSlideGroup-SqR_NAL1.40ba1d838a6e.js.br +0 -0
  123. codex/static_root/assets/VSlideGroup-SqR_NAL1.40ba1d838a6e.js.gz +0 -0
  124. codex/static_root/assets/VSlideGroup-SqR_NAL1.js +1 -0
  125. codex/static_root/assets/VSlideGroup-SqR_NAL1.js.br +0 -0
  126. codex/static_root/assets/VSlideGroup-SqR_NAL1.js.gz +0 -0
  127. codex/static_root/assets/VTable-CYC-wMpk.8f15d66044da.js +1 -0
  128. codex/static_root/assets/VTable-CYC-wMpk.8f15d66044da.js.br +0 -0
  129. codex/static_root/assets/VTable-CYC-wMpk.8f15d66044da.js.gz +0 -0
  130. codex/static_root/assets/VTable-CYC-wMpk.js +1 -0
  131. codex/static_root/assets/VTable-CYC-wMpk.js.br +0 -0
  132. codex/static_root/assets/VTable-CYC-wMpk.js.gz +0 -0
  133. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css +1 -0
  134. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.br +0 -0
  135. codex/static_root/assets/VTextField-BnCEPZIq.b0c32b2c0b14.css.gz +0 -0
  136. codex/static_root/assets/VTextField-BnCEPZIq.css +1 -0
  137. codex/static_root/assets/VTextField-BnCEPZIq.css.br +0 -0
  138. codex/static_root/assets/VTextField-BnCEPZIq.css.gz +0 -0
  139. codex/static_root/assets/VTextField-DMRKRbbT.911ac84d8c9d.js +1 -0
  140. codex/static_root/assets/VTextField-DMRKRbbT.911ac84d8c9d.js.br +0 -0
  141. codex/static_root/assets/VTextField-DMRKRbbT.911ac84d8c9d.js.gz +0 -0
  142. codex/static_root/assets/VTextField-DMRKRbbT.js +1 -0
  143. codex/static_root/assets/VTextField-DMRKRbbT.js.br +0 -0
  144. codex/static_root/assets/VTextField-DMRKRbbT.js.gz +0 -0
  145. codex/static_root/assets/VWindowItem-D0P2xNJl.6a10e3f3a153.js +1 -0
  146. codex/static_root/assets/VWindowItem-D0P2xNJl.6a10e3f3a153.js.br +0 -0
  147. codex/static_root/assets/VWindowItem-D0P2xNJl.6a10e3f3a153.js.gz +0 -0
  148. codex/static_root/assets/VWindowItem-D0P2xNJl.js +1 -0
  149. codex/static_root/assets/VWindowItem-D0P2xNJl.js.br +0 -0
  150. codex/static_root/assets/VWindowItem-D0P2xNJl.js.gz +0 -0
  151. codex/static_root/assets/admin-CD90VMen.4c2b6f2a5111.js +1 -0
  152. codex/static_root/assets/admin-CD90VMen.4c2b6f2a5111.js.br +0 -0
  153. codex/static_root/assets/admin-CD90VMen.4c2b6f2a5111.js.gz +0 -0
  154. codex/static_root/assets/admin-CD90VMen.js +1 -0
  155. codex/static_root/assets/admin-CD90VMen.js.br +0 -0
  156. codex/static_root/assets/admin-CD90VMen.js.gz +0 -0
  157. codex/static_root/assets/{admin-CWEjaFr5.7d4523387384.css → admin-DJ7pIXDQ.css} +1 -1
  158. codex/static_root/assets/admin-DJ7pIXDQ.css.br +0 -0
  159. codex/static_root/assets/admin-DJ7pIXDQ.css.gz +0 -0
  160. codex/static_root/assets/{admin-CWEjaFr5.css → admin-DJ7pIXDQ.e2a77af73ef2.css} +1 -1
  161. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.br +0 -0
  162. codex/static_root/assets/admin-DJ7pIXDQ.e2a77af73ef2.css.gz +0 -0
  163. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css +1 -0
  164. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.br +0 -0
  165. codex/static_root/assets/admin-menu-B9bzD6hV.c13b7df2ff57.css.gz +0 -0
  166. codex/static_root/assets/admin-menu-B9bzD6hV.css +1 -0
  167. codex/static_root/assets/admin-menu-B9bzD6hV.css.br +0 -0
  168. codex/static_root/assets/admin-menu-B9bzD6hV.css.gz +0 -0
  169. codex/static_root/assets/admin-menu-BpEPGKr_.c9f9178c5a82.js +1 -0
  170. codex/static_root/assets/admin-menu-BpEPGKr_.c9f9178c5a82.js.br +0 -0
  171. codex/static_root/assets/admin-menu-BpEPGKr_.c9f9178c5a82.js.gz +0 -0
  172. codex/static_root/assets/admin-menu-BpEPGKr_.js +1 -0
  173. codex/static_root/assets/admin-menu-BpEPGKr_.js.br +0 -0
  174. codex/static_root/assets/admin-menu-BpEPGKr_.js.gz +0 -0
  175. codex/static_root/assets/admin-settings-button-progress-DDF4rY07.4f1158b650ab.js +1 -0
  176. codex/static_root/assets/admin-settings-button-progress-DDF4rY07.4f1158b650ab.js.br +0 -0
  177. codex/static_root/assets/admin-settings-button-progress-DDF4rY07.4f1158b650ab.js.gz +0 -0
  178. codex/static_root/assets/admin-settings-button-progress-DDF4rY07.js +1 -0
  179. codex/static_root/assets/admin-settings-button-progress-DDF4rY07.js.br +0 -0
  180. codex/static_root/assets/admin-settings-button-progress-DDF4rY07.js.gz +0 -0
  181. codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.2548c49a853e.css +1 -0
  182. codex/static_root/assets/admin-settings-button-progress-DUUQfVsZ.css +1 -0
  183. codex/static_root/assets/browser-C0KS3g2E.0bf1c9c6319c.js +1 -0
  184. codex/static_root/assets/browser-C0KS3g2E.0bf1c9c6319c.js.br +0 -0
  185. codex/static_root/assets/browser-C0KS3g2E.0bf1c9c6319c.js.gz +0 -0
  186. codex/static_root/assets/browser-C0KS3g2E.js +1 -0
  187. codex/static_root/assets/browser-C0KS3g2E.js.br +0 -0
  188. codex/static_root/assets/browser-C0KS3g2E.js.gz +0 -0
  189. codex/static_root/assets/browser-JFL2hLjt.21cb2f7335f7.css +1 -0
  190. codex/static_root/assets/browser-JFL2hLjt.21cb2f7335f7.css.br +0 -0
  191. codex/static_root/assets/browser-JFL2hLjt.21cb2f7335f7.css.gz +0 -0
  192. codex/static_root/assets/browser-JFL2hLjt.css +1 -0
  193. codex/static_root/assets/browser-JFL2hLjt.css.br +0 -0
  194. codex/static_root/assets/browser-JFL2hLjt.css.gz +0 -0
  195. codex/static_root/assets/change-password-dialog-DXJalgW4.a9f34729599d.js +1 -0
  196. codex/static_root/assets/change-password-dialog-DXJalgW4.a9f34729599d.js.br +0 -0
  197. codex/static_root/assets/change-password-dialog-DXJalgW4.a9f34729599d.js.gz +0 -0
  198. codex/static_root/assets/change-password-dialog-DXJalgW4.js +1 -0
  199. codex/static_root/assets/change-password-dialog-DXJalgW4.js.br +0 -0
  200. codex/static_root/assets/change-password-dialog-DXJalgW4.js.gz +0 -0
  201. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css +1 -0
  202. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.br +0 -0
  203. codex/static_root/assets/change-password-dialog-DrIXdKUC.217574b45b02.css.gz +0 -0
  204. codex/static_root/assets/change-password-dialog-DrIXdKUC.css +1 -0
  205. codex/static_root/assets/change-password-dialog-DrIXdKUC.css.br +0 -0
  206. codex/static_root/assets/change-password-dialog-DrIXdKUC.css.gz +0 -0
  207. codex/static_root/assets/confirm-dialog-B7w6z2rf.472011c06253.css +1 -0
  208. codex/static_root/assets/confirm-dialog-B7w6z2rf.css +1 -0
  209. codex/static_root/assets/confirm-dialog-CqDEpU9k.e1811488121d.js +1 -0
  210. codex/static_root/assets/confirm-dialog-CqDEpU9k.e1811488121d.js.br +0 -0
  211. codex/static_root/assets/confirm-dialog-CqDEpU9k.e1811488121d.js.gz +0 -0
  212. codex/static_root/assets/confirm-dialog-CqDEpU9k.js +1 -0
  213. codex/static_root/assets/confirm-dialog-CqDEpU9k.js.br +0 -0
  214. codex/static_root/assets/confirm-dialog-CqDEpU9k.js.gz +0 -0
  215. codex/static_root/assets/datetime-column-UxNaJr8b.c06a11531af3.js +1 -0
  216. codex/static_root/assets/datetime-column-UxNaJr8b.c06a11531af3.js.br +0 -0
  217. codex/static_root/assets/datetime-column-UxNaJr8b.c06a11531af3.js.gz +0 -0
  218. codex/static_root/assets/datetime-column-UxNaJr8b.js +1 -0
  219. codex/static_root/assets/datetime-column-UxNaJr8b.js.br +0 -0
  220. codex/static_root/assets/datetime-column-UxNaJr8b.js.gz +0 -0
  221. codex/static_root/assets/filter-DskcqZHA.e707b5a05b4b.js +1 -0
  222. codex/static_root/assets/filter-DskcqZHA.e707b5a05b4b.js.br +0 -0
  223. codex/static_root/assets/filter-DskcqZHA.e707b5a05b4b.js.gz +0 -0
  224. codex/static_root/assets/filter-DskcqZHA.js +1 -0
  225. codex/static_root/assets/filter-DskcqZHA.js.br +0 -0
  226. codex/static_root/assets/filter-DskcqZHA.js.gz +0 -0
  227. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css +1 -0
  228. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.br +0 -0
  229. codex/static_root/assets/flag-tab-CqWovuBu.4dfc6b251e85.css.gz +0 -0
  230. codex/static_root/assets/flag-tab-CqWovuBu.css +1 -0
  231. codex/static_root/assets/flag-tab-CqWovuBu.css.br +0 -0
  232. codex/static_root/assets/flag-tab-CqWovuBu.css.gz +0 -0
  233. codex/static_root/assets/flag-tab-xBeWEQ0w.582e77d9e885.js +1 -0
  234. codex/static_root/assets/flag-tab-xBeWEQ0w.582e77d9e885.js.br +0 -0
  235. codex/static_root/assets/flag-tab-xBeWEQ0w.582e77d9e885.js.gz +0 -0
  236. codex/static_root/assets/flag-tab-xBeWEQ0w.js +1 -0
  237. codex/static_root/assets/flag-tab-xBeWEQ0w.js.br +0 -0
  238. codex/static_root/assets/flag-tab-xBeWEQ0w.js.gz +0 -0
  239. codex/static_root/assets/group-tab-Czt2x61x.56e2886b1bac.js +1 -0
  240. codex/static_root/assets/group-tab-Czt2x61x.56e2886b1bac.js.br +0 -0
  241. codex/static_root/assets/group-tab-Czt2x61x.56e2886b1bac.js.gz +0 -0
  242. codex/static_root/assets/group-tab-Czt2x61x.js +1 -0
  243. codex/static_root/assets/group-tab-Czt2x61x.js.br +0 -0
  244. codex/static_root/assets/group-tab-Czt2x61x.js.gz +0 -0
  245. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css +1 -0
  246. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.br +0 -0
  247. codex/static_root/assets/group-tab-DkIN1Heu.948e907b1e6b.css.gz +0 -0
  248. codex/static_root/assets/group-tab-DkIN1Heu.css +1 -0
  249. codex/static_root/assets/group-tab-DkIN1Heu.css.br +0 -0
  250. codex/static_root/assets/group-tab-DkIN1Heu.css.gz +0 -0
  251. codex/static_root/assets/http-error-NHcwE5e_.d49089e40589.js +1 -0
  252. codex/static_root/assets/http-error-NHcwE5e_.d49089e40589.js.br +0 -0
  253. codex/static_root/assets/http-error-NHcwE5e_.d49089e40589.js.gz +0 -0
  254. codex/static_root/assets/http-error-NHcwE5e_.js +1 -0
  255. codex/static_root/assets/http-error-NHcwE5e_.js.br +0 -0
  256. codex/static_root/assets/http-error-NHcwE5e_.js.gz +0 -0
  257. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css +1 -0
  258. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.br +0 -0
  259. codex/static_root/assets/index-CL_6g4tJ.523d7b3d350f.css.gz +0 -0
  260. codex/static_root/assets/index-CL_6g4tJ.css +1 -0
  261. codex/static_root/assets/index-CL_6g4tJ.css.br +0 -0
  262. codex/static_root/assets/index-CL_6g4tJ.css.gz +0 -0
  263. codex/static_root/assets/index-DYNNO5kn.24a2d270253a.js +1 -0
  264. codex/static_root/assets/index-DYNNO5kn.24a2d270253a.js.br +0 -0
  265. codex/static_root/assets/index-DYNNO5kn.24a2d270253a.js.gz +0 -0
  266. codex/static_root/assets/index-DYNNO5kn.js +1 -0
  267. codex/static_root/assets/index-DYNNO5kn.js.br +0 -0
  268. codex/static_root/assets/index-DYNNO5kn.js.gz +0 -0
  269. codex/static_root/assets/library-tab-CgIqcjvj.60327dfa8ecb.js +1 -0
  270. codex/static_root/assets/library-tab-CgIqcjvj.60327dfa8ecb.js.br +0 -0
  271. codex/static_root/assets/library-tab-CgIqcjvj.60327dfa8ecb.js.gz +0 -0
  272. codex/static_root/assets/library-tab-CgIqcjvj.js +1 -0
  273. codex/static_root/assets/library-tab-CgIqcjvj.js.br +0 -0
  274. codex/static_root/assets/library-tab-CgIqcjvj.js.gz +0 -0
  275. codex/static_root/assets/{library-tab-Bh7ErAk7.7c44200ee727.css → library-tab-Wew8YOLv.0c1780a6b68e.css} +1 -1
  276. codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.br +0 -0
  277. codex/static_root/assets/library-tab-Wew8YOLv.0c1780a6b68e.css.gz +0 -0
  278. codex/static_root/assets/{library-tab-Bh7ErAk7.css → library-tab-Wew8YOLv.css} +1 -1
  279. codex/static_root/assets/library-tab-Wew8YOLv.css.br +0 -0
  280. codex/static_root/assets/library-tab-Wew8YOLv.css.gz +0 -0
  281. codex/static_root/assets/main-B3EwXQ3Q.14dfc21e272c.js +35 -0
  282. codex/static_root/assets/main-B3EwXQ3Q.14dfc21e272c.js.br +0 -0
  283. codex/static_root/assets/main-B3EwXQ3Q.14dfc21e272c.js.gz +0 -0
  284. codex/static_root/assets/main-B3EwXQ3Q.js +35 -0
  285. codex/static_root/assets/main-B3EwXQ3Q.js.br +0 -0
  286. codex/static_root/assets/main-B3EwXQ3Q.js.gz +0 -0
  287. codex/static_root/assets/pager-full-pdf-YkzUXx0B.f39d84e2380a.js +1 -0
  288. codex/static_root/assets/pager-full-pdf-YkzUXx0B.f39d84e2380a.js.br +0 -0
  289. codex/static_root/assets/pager-full-pdf-YkzUXx0B.f39d84e2380a.js.gz +0 -0
  290. codex/static_root/assets/pager-full-pdf-YkzUXx0B.js +1 -0
  291. codex/static_root/assets/pager-full-pdf-YkzUXx0B.js.br +0 -0
  292. codex/static_root/assets/pager-full-pdf-YkzUXx0B.js.gz +0 -0
  293. codex/static_root/assets/pagination-toolbar-CrruiVov.3bf9b795350d.js +1 -0
  294. codex/static_root/assets/pagination-toolbar-CrruiVov.3bf9b795350d.js.br +0 -0
  295. codex/static_root/assets/pagination-toolbar-CrruiVov.3bf9b795350d.js.gz +0 -0
  296. codex/static_root/assets/pagination-toolbar-CrruiVov.js +1 -0
  297. codex/static_root/assets/pagination-toolbar-CrruiVov.js.br +0 -0
  298. codex/static_root/assets/pagination-toolbar-CrruiVov.js.gz +0 -0
  299. codex/static_root/assets/{pagination-toolbar-C45-onwe.4722195bb60b.css → pagination-toolbar-DGrxp2OD.css} +1 -1
  300. codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.br +0 -0
  301. codex/static_root/assets/pagination-toolbar-DGrxp2OD.css.gz +0 -0
  302. codex/static_root/assets/{pagination-toolbar-C45-onwe.css → pagination-toolbar-DGrxp2OD.f960a624f04f.css} +1 -1
  303. codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.br +0 -0
  304. codex/static_root/assets/pagination-toolbar-DGrxp2OD.f960a624f04f.css.gz +0 -0
  305. codex/static_root/assets/{pdf-doc-BjlRPuf4.0f056fa321b6.js → pdf-doc-ctqZIwOP.7e7b2c815ea4.js} +3 -3
  306. codex/static_root/assets/pdf-doc-ctqZIwOP.7e7b2c815ea4.js.br +0 -0
  307. codex/static_root/assets/pdf-doc-ctqZIwOP.7e7b2c815ea4.js.gz +0 -0
  308. codex/static_root/assets/{pdf-doc-BjlRPuf4.js → pdf-doc-ctqZIwOP.js} +3 -3
  309. codex/static_root/assets/pdf-doc-ctqZIwOP.js.br +0 -0
  310. codex/static_root/assets/pdf-doc-ctqZIwOP.js.gz +0 -0
  311. codex/static_root/assets/reader-DPLhdrJD.268417c618c5.js +2 -0
  312. codex/static_root/assets/reader-DPLhdrJD.268417c618c5.js.br +0 -0
  313. codex/static_root/assets/reader-DPLhdrJD.268417c618c5.js.gz +0 -0
  314. codex/static_root/assets/reader-DPLhdrJD.js +2 -0
  315. codex/static_root/assets/reader-DPLhdrJD.js.br +0 -0
  316. codex/static_root/assets/reader-DPLhdrJD.js.gz +0 -0
  317. codex/static_root/assets/reader-nD70BeEz.06e85e73c391.css +1 -0
  318. codex/static_root/assets/reader-nD70BeEz.06e85e73c391.css.br +0 -0
  319. codex/static_root/assets/reader-nD70BeEz.06e85e73c391.css.gz +0 -0
  320. codex/static_root/assets/reader-nD70BeEz.css +1 -0
  321. codex/static_root/assets/reader-nD70BeEz.css.br +0 -0
  322. codex/static_root/assets/reader-nD70BeEz.css.gz +0 -0
  323. codex/static_root/assets/relation-chips-DOALMcWL.5d3b8100ba03.js +1 -0
  324. codex/static_root/assets/relation-chips-DOALMcWL.5d3b8100ba03.js.br +0 -0
  325. codex/static_root/assets/relation-chips-DOALMcWL.5d3b8100ba03.js.gz +0 -0
  326. codex/static_root/assets/relation-chips-DOALMcWL.js +1 -0
  327. codex/static_root/assets/relation-chips-DOALMcWL.js.br +0 -0
  328. codex/static_root/assets/relation-chips-DOALMcWL.js.gz +0 -0
  329. codex/static_root/assets/settings-drawer-BVj1U_LC.c68369e0c2de.js +2 -0
  330. codex/static_root/assets/settings-drawer-BVj1U_LC.c68369e0c2de.js.br +0 -0
  331. codex/static_root/assets/settings-drawer-BVj1U_LC.c68369e0c2de.js.gz +0 -0
  332. codex/static_root/assets/settings-drawer-BVj1U_LC.js +2 -0
  333. codex/static_root/assets/settings-drawer-BVj1U_LC.js.br +0 -0
  334. codex/static_root/assets/settings-drawer-BVj1U_LC.js.gz +0 -0
  335. codex/static_root/assets/{stats-tab-BXGisn5N.9b7a71e7fe28.css → stats-tab-BAYTqQNg.5f715428c394.css} +1 -1
  336. codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.br +0 -0
  337. codex/static_root/assets/stats-tab-BAYTqQNg.5f715428c394.css.gz +0 -0
  338. codex/static_root/assets/{stats-tab-BXGisn5N.css → stats-tab-BAYTqQNg.css} +1 -1
  339. codex/static_root/assets/stats-tab-BAYTqQNg.css.br +0 -0
  340. codex/static_root/assets/stats-tab-BAYTqQNg.css.gz +0 -0
  341. codex/static_root/assets/stats-tab-DDLwwwnP.35305258097b.js +1 -0
  342. codex/static_root/assets/stats-tab-DDLwwwnP.35305258097b.js.br +0 -0
  343. codex/static_root/assets/stats-tab-DDLwwwnP.35305258097b.js.gz +0 -0
  344. codex/static_root/assets/stats-tab-DDLwwwnP.js +1 -0
  345. codex/static_root/assets/stats-tab-DDLwwwnP.js.br +0 -0
  346. codex/static_root/assets/stats-tab-DDLwwwnP.js.gz +0 -0
  347. codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css +1 -0
  348. codex/static_root/assets/{task-tab-1XiWn9xP.06cb8e1b794d.css.br → task-tab--cXi48kg.5dfd6890a1c2.css.br} +1 -1
  349. codex/static_root/assets/task-tab--cXi48kg.5dfd6890a1c2.css.gz +0 -0
  350. codex/static_root/assets/task-tab--cXi48kg.css +1 -0
  351. codex/static_root/assets/{task-tab-1XiWn9xP.css.br → task-tab--cXi48kg.css.br} +1 -1
  352. codex/static_root/assets/task-tab--cXi48kg.css.gz +0 -0
  353. codex/static_root/assets/task-tab-BpP3n2dO.c853eb565200.js +1 -0
  354. codex/static_root/assets/task-tab-BpP3n2dO.c853eb565200.js.br +0 -0
  355. codex/static_root/assets/task-tab-BpP3n2dO.c853eb565200.js.gz +0 -0
  356. codex/static_root/assets/task-tab-BpP3n2dO.js +1 -0
  357. codex/static_root/assets/task-tab-BpP3n2dO.js.br +0 -0
  358. codex/static_root/assets/task-tab-BpP3n2dO.js.gz +0 -0
  359. codex/static_root/assets/transition-3g2arGgl.c6e6f91b7978.js +1 -0
  360. codex/static_root/assets/transition-3g2arGgl.c6e6f91b7978.js.br +0 -0
  361. codex/static_root/assets/transition-3g2arGgl.c6e6f91b7978.js.gz +0 -0
  362. codex/static_root/assets/transition-3g2arGgl.js +1 -0
  363. codex/static_root/assets/transition-3g2arGgl.js.br +0 -0
  364. codex/static_root/assets/transition-3g2arGgl.js.gz +0 -0
  365. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css +1 -0
  366. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.br +4 -0
  367. codex/static_root/assets/transition-5LRFTPo5.3d726f9bbcb0.css.gz +0 -0
  368. codex/static_root/assets/transition-5LRFTPo5.css +1 -0
  369. codex/static_root/assets/transition-5LRFTPo5.css.br +4 -0
  370. codex/static_root/assets/transition-5LRFTPo5.css.gz +0 -0
  371. codex/static_root/assets/{unauthorized-muDsv-rC.css → unauthorized-BjhIc97q.728511c92cd0.css} +1 -1
  372. codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.br +0 -0
  373. codex/static_root/assets/unauthorized-BjhIc97q.728511c92cd0.css.gz +0 -0
  374. codex/static_root/assets/{unauthorized-muDsv-rC.2166ec8b3a32.css → unauthorized-BjhIc97q.css} +1 -1
  375. codex/static_root/assets/unauthorized-BjhIc97q.css.br +0 -0
  376. codex/static_root/assets/unauthorized-BjhIc97q.css.gz +0 -0
  377. codex/static_root/assets/unauthorized-CdumQ1Vf.79d9a61eb572.js +1 -0
  378. codex/static_root/assets/unauthorized-CdumQ1Vf.79d9a61eb572.js.br +0 -0
  379. codex/static_root/assets/unauthorized-CdumQ1Vf.79d9a61eb572.js.gz +0 -0
  380. codex/static_root/assets/unauthorized-CdumQ1Vf.js +1 -0
  381. codex/static_root/assets/unauthorized-CdumQ1Vf.js.br +0 -0
  382. codex/static_root/assets/unauthorized-CdumQ1Vf.js.gz +0 -0
  383. codex/static_root/assets/user-tab-D6oOKZTP.05944b00c700.js +1 -0
  384. codex/static_root/assets/user-tab-D6oOKZTP.05944b00c700.js.br +0 -0
  385. codex/static_root/assets/user-tab-D6oOKZTP.05944b00c700.js.gz +0 -0
  386. codex/static_root/assets/user-tab-D6oOKZTP.js +1 -0
  387. codex/static_root/assets/user-tab-D6oOKZTP.js.br +0 -0
  388. codex/static_root/assets/user-tab-D6oOKZTP.js.gz +0 -0
  389. codex/static_root/manifest.c5f1de88b1ac.json +712 -0
  390. codex/static_root/manifest.c5f1de88b1ac.json.br +0 -0
  391. codex/static_root/manifest.c5f1de88b1ac.json.gz +0 -0
  392. codex/static_root/manifest.json +371 -294
  393. codex/static_root/manifest.json.br +0 -0
  394. codex/static_root/manifest.json.gz +0 -0
  395. codex/static_root/staticfiles.json +1 -1
  396. codex/status_controller.py +15 -10
  397. codex/threads.py +1 -1
  398. codex/urls/api/auth.py +1 -1
  399. codex/urls/api/browser.py +1 -1
  400. codex/views/admin/library.py +18 -13
  401. codex/views/admin/stats.py +23 -16
  402. codex/views/admin/tasks.py +45 -40
  403. codex/views/auth.py +50 -74
  404. codex/views/bookmark.py +16 -153
  405. codex/views/browser/annotate/__init__.py +1 -0
  406. codex/views/browser/annotate/bookmark.py +112 -0
  407. codex/views/browser/annotate/card.py +61 -0
  408. codex/views/browser/annotate/order.py +247 -0
  409. codex/views/browser/breadcrumbs.py +90 -11
  410. codex/views/browser/browser.py +82 -117
  411. codex/views/browser/choices.py +15 -18
  412. codex/views/browser/cover.py +18 -24
  413. codex/views/browser/filters/bookmark.py +18 -19
  414. codex/views/browser/filters/field.py +8 -3
  415. codex/views/browser/filters/filter.py +69 -0
  416. codex/views/browser/filters/group.py +9 -6
  417. codex/views/browser/filters/search/__init__.py +1 -0
  418. codex/views/browser/filters/search/aliases.py +81 -0
  419. codex/views/browser/filters/search/field/__init__.py +1 -0
  420. codex/views/browser/filters/search/field/column.py +53 -0
  421. codex/views/browser/filters/search/field/expression.py +177 -0
  422. codex/views/browser/filters/search/field/filter.py +70 -0
  423. codex/views/browser/filters/search/field/optimize.py +71 -0
  424. codex/views/browser/filters/search/field/parse.py +187 -0
  425. codex/views/browser/filters/search/fts.py +24 -0
  426. codex/views/browser/filters/search/parse.py +250 -0
  427. codex/views/browser/group_mtime.py +83 -0
  428. codex/views/browser/metadata/__init__.py +1 -0
  429. codex/views/browser/metadata/annotate.py +109 -0
  430. codex/views/browser/metadata/copy_intersections.py +90 -0
  431. codex/views/browser/metadata/metadata.py +83 -0
  432. codex/views/browser/metadata/query_intersections.py +112 -0
  433. codex/views/browser/mtime.py +2 -10
  434. codex/views/browser/order_by.py +22 -16
  435. codex/views/browser/page_in_bounds.py +88 -0
  436. codex/views/browser/paginate.py +32 -123
  437. codex/views/browser/params.py +64 -0
  438. codex/views/browser/title.py +1 -6
  439. codex/views/browser/validate.py +68 -33
  440. codex/views/const.py +0 -1
  441. codex/views/download.py +1 -1
  442. codex/views/mixins.py +27 -15
  443. codex/views/opds/const.py +2 -3
  444. codex/views/opds/urls.py +2 -2
  445. codex/views/opds/util.py +2 -3
  446. codex/views/opds/v1/entry/links.py +3 -1
  447. codex/views/opds/v1/facets.py +65 -12
  448. codex/views/opds/v1/feed.py +7 -65
  449. codex/views/opds/v1/links.py +5 -4
  450. codex/views/opds/v2/feed.py +2 -28
  451. codex/views/opds/v2/links.py +27 -2
  452. codex/views/opds/v2/publications.py +5 -6
  453. codex/views/opds/v2/top_links.py +3 -6
  454. codex/views/public.py +2 -2
  455. codex/views/reader/arcs.py +0 -3
  456. codex/views/reader/books.py +57 -41
  457. codex/views/reader/page.py +13 -7
  458. codex/views/reader/{init.py → params.py} +37 -31
  459. codex/views/reader/reader.py +1 -2
  460. codex/views/session.py +43 -54
  461. codex/views/timezone.py +54 -0
  462. codex/views/version.py +1 -1
  463. {codex-1.6.19.dist-info → codex-1.7.0.dist-info}/METADATA +15 -10
  464. {codex-1.6.19.dist-info → codex-1.7.0.dist-info}/RECORD +467 -484
  465. codex/_vendor/__init__.py +0 -0
  466. codex/_vendor/django_haystack-3.2.1.dist-info/AUTHORS +0 -127
  467. codex/_vendor/django_haystack-3.2.1.dist-info/INSTALLER +0 -1
  468. codex/_vendor/django_haystack-3.2.1.dist-info/LICENSE +0 -31
  469. codex/_vendor/django_haystack-3.2.1.dist-info/METADATA +0 -98
  470. codex/_vendor/django_haystack-3.2.1.dist-info/RECORD +0 -97
  471. codex/_vendor/django_haystack-3.2.1.dist-info/REQUESTED +0 -0
  472. codex/_vendor/django_haystack-3.2.1.dist-info/WHEEL +0 -5
  473. codex/_vendor/django_haystack-3.2.1.dist-info/top_level.txt +0 -1
  474. codex/_vendor/haystack/__init__.py +0 -80
  475. codex/_vendor/haystack/admin.py +0 -163
  476. codex/_vendor/haystack/apps.py +0 -31
  477. codex/_vendor/haystack/backends/__init__.py +0 -1109
  478. codex/_vendor/haystack/backends/elasticsearch2_backend.py +0 -390
  479. codex/_vendor/haystack/backends/elasticsearch5_backend.py +0 -483
  480. codex/_vendor/haystack/backends/elasticsearch7_backend.py +0 -586
  481. codex/_vendor/haystack/backends/elasticsearch_backend.py +0 -1141
  482. codex/_vendor/haystack/backends/simple_backend.py +0 -132
  483. codex/_vendor/haystack/backends/solr_backend.py +0 -991
  484. codex/_vendor/haystack/backends/whoosh_backend.py +0 -1104
  485. codex/_vendor/haystack/constants.py +0 -57
  486. codex/_vendor/haystack/exceptions.py +0 -57
  487. codex/_vendor/haystack/fields.py +0 -562
  488. codex/_vendor/haystack/forms.py +0 -135
  489. codex/_vendor/haystack/generic_views.py +0 -141
  490. codex/_vendor/haystack/indexes.py +0 -548
  491. codex/_vendor/haystack/inputs.py +0 -169
  492. codex/_vendor/haystack/management/__init__.py +0 -0
  493. codex/_vendor/haystack/management/commands/__init__.py +0 -0
  494. codex/_vendor/haystack/management/commands/build_solr_schema.py +0 -187
  495. codex/_vendor/haystack/management/commands/clear_index.py +0 -67
  496. codex/_vendor/haystack/management/commands/haystack_info.py +0 -22
  497. codex/_vendor/haystack/management/commands/rebuild_index.py +0 -65
  498. codex/_vendor/haystack/management/commands/update_index.py +0 -438
  499. codex/_vendor/haystack/manager.py +0 -105
  500. codex/_vendor/haystack/models.py +0 -257
  501. codex/_vendor/haystack/panels.py +0 -89
  502. codex/_vendor/haystack/query.py +0 -771
  503. codex/_vendor/haystack/routers.py +0 -14
  504. codex/_vendor/haystack/signals.py +0 -88
  505. codex/_vendor/haystack/templates/panels/haystack.html +0 -33
  506. codex/_vendor/haystack/templates/search_configuration/schema.xml +0 -1056
  507. codex/_vendor/haystack/templates/search_configuration/solrconfig.xml +0 -1446
  508. codex/_vendor/haystack/templatetags/__init__.py +0 -0
  509. codex/_vendor/haystack/templatetags/highlight.py +0 -131
  510. codex/_vendor/haystack/templatetags/more_like_this.py +0 -119
  511. codex/_vendor/haystack/urls.py +0 -5
  512. codex/_vendor/haystack/utils/__init__.py +0 -84
  513. codex/_vendor/haystack/utils/app_loading.py +0 -34
  514. codex/_vendor/haystack/utils/geo.py +0 -71
  515. codex/_vendor/haystack/utils/highlighting.py +0 -165
  516. codex/_vendor/haystack/utils/loading.py +0 -374
  517. codex/_vendor/haystack/utils/log.py +0 -21
  518. codex/_vendor/haystack/version.py +0 -16
  519. codex/_vendor/haystack/views.py +0 -253
  520. codex/integrity.py +0 -492
  521. codex/librarian/search/merge.py +0 -86
  522. codex/librarian/search/version.py +0 -63
  523. codex/search/__init__.py +0 -1
  524. codex/search/backend.py +0 -524
  525. codex/search/backend_search.py +0 -229
  526. codex/search/engine.py +0 -33
  527. codex/search/indexes.py +0 -79
  528. codex/search/query.py +0 -67
  529. codex/search/writing.py +0 -180
  530. codex/serializers/choices.py +0 -196
  531. codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js +0 -1
  532. codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.br +0 -0
  533. codex/static_root/assets/VCheckbox-Cko1sQK-.170726b3b8a1.js.gz +0 -0
  534. codex/static_root/assets/VCheckbox-Cko1sQK-.js +0 -1
  535. codex/static_root/assets/VCheckbox-Cko1sQK-.js.br +0 -0
  536. codex/static_root/assets/VCheckbox-Cko1sQK-.js.gz +0 -0
  537. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js +0 -1
  538. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.br +0 -0
  539. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.c9b8062fd66f.js.gz +0 -0
  540. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js +0 -1
  541. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.br +0 -0
  542. codex/static_root/assets/VCheckboxBtn-CO3-aqQL.js.gz +0 -0
  543. codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js +0 -1
  544. codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.br +0 -0
  545. codex/static_root/assets/VCombobox-Bjp6lffE.caf59fba486e.js.gz +0 -0
  546. codex/static_root/assets/VCombobox-Bjp6lffE.js +0 -1
  547. codex/static_root/assets/VCombobox-Bjp6lffE.js.br +0 -0
  548. codex/static_root/assets/VCombobox-Bjp6lffE.js.gz +0 -0
  549. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css +0 -1
  550. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.br +0 -0
  551. codex/static_root/assets/VDialog-38np3TSA.5a0cb7a9e56d.css.gz +0 -0
  552. codex/static_root/assets/VDialog-38np3TSA.css +0 -1
  553. codex/static_root/assets/VDialog-38np3TSA.css.br +0 -0
  554. codex/static_root/assets/VDialog-38np3TSA.css.gz +0 -0
  555. codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js +0 -1
  556. codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.br +0 -0
  557. codex/static_root/assets/VDialog-BkpVGB70.6eea3ca762a5.js.gz +0 -0
  558. codex/static_root/assets/VDialog-BkpVGB70.js +0 -1
  559. codex/static_root/assets/VDialog-BkpVGB70.js.br +0 -0
  560. codex/static_root/assets/VDialog-BkpVGB70.js.gz +0 -0
  561. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js +0 -1
  562. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.br +0 -0
  563. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.9c55db4ef49e.js.gz +0 -0
  564. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js +0 -1
  565. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.br +0 -0
  566. codex/static_root/assets/VExpansionPanels-Ci2-j8XX.js.gz +0 -0
  567. codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js +0 -1
  568. codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.br +0 -0
  569. codex/static_root/assets/VRadioGroup-D3Py5BfQ.a4f25edb86d3.js.gz +0 -0
  570. codex/static_root/assets/VRadioGroup-D3Py5BfQ.js +0 -1
  571. codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.br +0 -0
  572. codex/static_root/assets/VRadioGroup-D3Py5BfQ.js.gz +0 -0
  573. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css +0 -1
  574. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.br +0 -0
  575. codex/static_root/assets/VSelect-Ca2bIOso.6613989e4c45.css.gz +0 -0
  576. codex/static_root/assets/VSelect-Ca2bIOso.css +0 -1
  577. codex/static_root/assets/VSelect-Ca2bIOso.css.br +0 -0
  578. codex/static_root/assets/VSelect-Ca2bIOso.css.gz +0 -0
  579. codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js +0 -1
  580. codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.br +0 -0
  581. codex/static_root/assets/VSelect-DtvZsYZz.e98745d858eb.js.gz +0 -0
  582. codex/static_root/assets/VSelect-DtvZsYZz.js +0 -1
  583. codex/static_root/assets/VSelect-DtvZsYZz.js.br +0 -0
  584. codex/static_root/assets/VSelect-DtvZsYZz.js.gz +0 -0
  585. codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js +0 -1
  586. codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.br +0 -0
  587. codex/static_root/assets/VSelectionControl-D6kmykQW.1e1fda62ceba.js.gz +0 -0
  588. codex/static_root/assets/VSelectionControl-D6kmykQW.js +0 -1
  589. codex/static_root/assets/VSelectionControl-D6kmykQW.js.br +0 -0
  590. codex/static_root/assets/VSelectionControl-D6kmykQW.js.gz +0 -0
  591. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css +0 -1
  592. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.br +0 -0
  593. codex/static_root/assets/VSlideGroup-Dk_1upHt.2c836070ec75.css.gz +0 -0
  594. codex/static_root/assets/VSlideGroup-Dk_1upHt.css +0 -1
  595. codex/static_root/assets/VSlideGroup-Dk_1upHt.css.br +0 -0
  596. codex/static_root/assets/VSlideGroup-Dk_1upHt.css.gz +0 -0
  597. codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js +0 -1
  598. codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.br +0 -0
  599. codex/static_root/assets/VSlideGroup-UGZWxpF5.360bdefcd215.js.gz +0 -0
  600. codex/static_root/assets/VSlideGroup-UGZWxpF5.js +0 -1
  601. codex/static_root/assets/VSlideGroup-UGZWxpF5.js.br +0 -0
  602. codex/static_root/assets/VSlideGroup-UGZWxpF5.js.gz +0 -0
  603. codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js +0 -1
  604. codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.br +0 -0
  605. codex/static_root/assets/VTable-DXH_yQ0o.1149f56ad566.js.gz +0 -0
  606. codex/static_root/assets/VTable-DXH_yQ0o.js +0 -1
  607. codex/static_root/assets/VTable-DXH_yQ0o.js.br +0 -0
  608. codex/static_root/assets/VTable-DXH_yQ0o.js.gz +0 -0
  609. codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js +0 -1
  610. codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.br +0 -0
  611. codex/static_root/assets/VTextField-Blqy56_L.26d8761e0155.js.gz +0 -0
  612. codex/static_root/assets/VTextField-Blqy56_L.js +0 -1
  613. codex/static_root/assets/VTextField-Blqy56_L.js.br +0 -0
  614. codex/static_root/assets/VTextField-Blqy56_L.js.gz +0 -0
  615. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css +0 -1
  616. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.br +0 -0
  617. codex/static_root/assets/VTextField-Crc3gdNh.39995ccf4d52.css.gz +0 -0
  618. codex/static_root/assets/VTextField-Crc3gdNh.css +0 -1
  619. codex/static_root/assets/VTextField-Crc3gdNh.css.br +0 -0
  620. codex/static_root/assets/VTextField-Crc3gdNh.css.gz +0 -0
  621. codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js +0 -1
  622. codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.br +0 -0
  623. codex/static_root/assets/VWindowItem-C8_ovV7e.afbc08414765.js.gz +0 -0
  624. codex/static_root/assets/VWindowItem-C8_ovV7e.js +0 -1
  625. codex/static_root/assets/VWindowItem-C8_ovV7e.js.br +0 -0
  626. codex/static_root/assets/VWindowItem-C8_ovV7e.js.gz +0 -0
  627. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.br +0 -0
  628. codex/static_root/assets/admin-CWEjaFr5.7d4523387384.css.gz +0 -0
  629. codex/static_root/assets/admin-CWEjaFr5.css.br +0 -0
  630. codex/static_root/assets/admin-CWEjaFr5.css.gz +0 -0
  631. codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js +0 -1
  632. codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.br +0 -0
  633. codex/static_root/assets/admin-DTRn8bBs.d031ea42d8d0.js.gz +0 -0
  634. codex/static_root/assets/admin-DTRn8bBs.js +0 -1
  635. codex/static_root/assets/admin-DTRn8bBs.js.br +0 -0
  636. codex/static_root/assets/admin-DTRn8bBs.js.gz +0 -0
  637. codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js +0 -30
  638. codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.br +0 -0
  639. codex/static_root/assets/admin-drawer-panel-BaXFOcru.b1a44b05f482.js.gz +0 -0
  640. codex/static_root/assets/admin-drawer-panel-BaXFOcru.js +0 -30
  641. codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.br +0 -0
  642. codex/static_root/assets/admin-drawer-panel-BaXFOcru.js.gz +0 -0
  643. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css +0 -1
  644. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.br +0 -0
  645. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.3cb33056e994.css.gz +0 -0
  646. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css +0 -1
  647. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.br +0 -0
  648. codex/static_root/assets/admin-drawer-panel-Bs2wgKj3.css.gz +0 -0
  649. codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js +0 -1
  650. codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.br +0 -0
  651. codex/static_root/assets/browser-BBpUyGmz.c1cb9e2d463b.js.gz +0 -0
  652. codex/static_root/assets/browser-BBpUyGmz.js +0 -1
  653. codex/static_root/assets/browser-BBpUyGmz.js.br +0 -0
  654. codex/static_root/assets/browser-BBpUyGmz.js.gz +0 -0
  655. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css +0 -1
  656. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.br +0 -0
  657. codex/static_root/assets/browser-Binc3n9H.313722d27ca3.css.gz +0 -0
  658. codex/static_root/assets/browser-Binc3n9H.css +0 -1
  659. codex/static_root/assets/browser-Binc3n9H.css.br +0 -0
  660. codex/static_root/assets/browser-Binc3n9H.css.gz +0 -0
  661. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css +0 -1
  662. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.br +0 -0
  663. codex/static_root/assets/change-password-dialog-Cebek1-c.aff2975e31f8.css.gz +0 -0
  664. codex/static_root/assets/change-password-dialog-Cebek1-c.css +0 -1
  665. codex/static_root/assets/change-password-dialog-Cebek1-c.css.br +0 -0
  666. codex/static_root/assets/change-password-dialog-Cebek1-c.css.gz +0 -0
  667. codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js +0 -1
  668. codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.br +0 -0
  669. codex/static_root/assets/change-password-dialog-DlHoLhDF.76ab4a29bb9b.js.gz +0 -0
  670. codex/static_root/assets/change-password-dialog-DlHoLhDF.js +0 -1
  671. codex/static_root/assets/change-password-dialog-DlHoLhDF.js.br +0 -0
  672. codex/static_root/assets/change-password-dialog-DlHoLhDF.js.gz +0 -0
  673. codex/static_root/assets/confirm-dialog-BmhedLCx.7c2103d319d4.css +0 -1
  674. codex/static_root/assets/confirm-dialog-BmhedLCx.css +0 -1
  675. codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js +0 -1
  676. codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.br +0 -0
  677. codex/static_root/assets/confirm-dialog-D3_s2DQy.9dde66036c72.js.gz +0 -0
  678. codex/static_root/assets/confirm-dialog-D3_s2DQy.js +0 -1
  679. codex/static_root/assets/confirm-dialog-D3_s2DQy.js.br +0 -0
  680. codex/static_root/assets/confirm-dialog-D3_s2DQy.js.gz +0 -0
  681. codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js +0 -1
  682. codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.br +0 -0
  683. codex/static_root/assets/datetime-column-BxC1Li9e.3dc314b3295d.js.gz +0 -0
  684. codex/static_root/assets/datetime-column-BxC1Li9e.js +0 -1
  685. codex/static_root/assets/datetime-column-BxC1Li9e.js.br +0 -0
  686. codex/static_root/assets/datetime-column-BxC1Li9e.js.gz +0 -0
  687. codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js +0 -1
  688. codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.br +0 -0
  689. codex/static_root/assets/filter-Bzk7x841.d0c3b602e62d.js.gz +0 -0
  690. codex/static_root/assets/filter-Bzk7x841.js +0 -1
  691. codex/static_root/assets/filter-Bzk7x841.js.br +0 -0
  692. codex/static_root/assets/filter-Bzk7x841.js.gz +0 -0
  693. codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js +0 -1
  694. codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.br +0 -0
  695. codex/static_root/assets/flag-tab-C2bI5zq3.8f8c297600a6.js.gz +0 -0
  696. codex/static_root/assets/flag-tab-C2bI5zq3.js +0 -1
  697. codex/static_root/assets/flag-tab-C2bI5zq3.js.br +0 -0
  698. codex/static_root/assets/flag-tab-C2bI5zq3.js.gz +0 -0
  699. codex/static_root/assets/flag-tab-DBqpLKi7.css +0 -1
  700. codex/static_root/assets/flag-tab-DBqpLKi7.css.br +0 -0
  701. codex/static_root/assets/flag-tab-DBqpLKi7.css.gz +0 -0
  702. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css +0 -1
  703. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.br +0 -0
  704. codex/static_root/assets/flag-tab-DBqpLKi7.dcfef39f541e.css.gz +0 -0
  705. codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js +0 -1
  706. codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.br +0 -0
  707. codex/static_root/assets/group-tab-5esH4lwX.a81fdd258117.js.gz +0 -0
  708. codex/static_root/assets/group-tab-5esH4lwX.js +0 -1
  709. codex/static_root/assets/group-tab-5esH4lwX.js.br +0 -0
  710. codex/static_root/assets/group-tab-5esH4lwX.js.gz +0 -0
  711. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css +0 -1
  712. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.br +0 -0
  713. codex/static_root/assets/group-tab-CBKuS3Iv.7e4746634782.css.gz +0 -0
  714. codex/static_root/assets/group-tab-CBKuS3Iv.css +0 -1
  715. codex/static_root/assets/group-tab-CBKuS3Iv.css.br +0 -0
  716. codex/static_root/assets/group-tab-CBKuS3Iv.css.gz +0 -0
  717. codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js +0 -1
  718. codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.br +0 -0
  719. codex/static_root/assets/http-error-BTOBfcGY.52ced6f28420.js.gz +0 -0
  720. codex/static_root/assets/http-error-BTOBfcGY.js +0 -1
  721. codex/static_root/assets/http-error-BTOBfcGY.js.br +0 -0
  722. codex/static_root/assets/http-error-BTOBfcGY.js.gz +0 -0
  723. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.br +0 -2
  724. codex/static_root/assets/library-tab-Bh7ErAk7.7c44200ee727.css.gz +0 -0
  725. codex/static_root/assets/library-tab-Bh7ErAk7.css.br +0 -2
  726. codex/static_root/assets/library-tab-Bh7ErAk7.css.gz +0 -0
  727. codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js +0 -1
  728. codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.br +0 -0
  729. codex/static_root/assets/library-tab-DiPEdOF7.ece2d642a9dc.js.gz +0 -0
  730. codex/static_root/assets/library-tab-DiPEdOF7.js +0 -1
  731. codex/static_root/assets/library-tab-DiPEdOF7.js.br +0 -0
  732. codex/static_root/assets/library-tab-DiPEdOF7.js.gz +0 -0
  733. codex/static_root/assets/main-BPIPjzsc.66def8991e38.js +0 -6
  734. codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.br +0 -0
  735. codex/static_root/assets/main-BPIPjzsc.66def8991e38.js.gz +0 -0
  736. codex/static_root/assets/main-BPIPjzsc.js +0 -6
  737. codex/static_root/assets/main-BPIPjzsc.js.br +0 -0
  738. codex/static_root/assets/main-BPIPjzsc.js.gz +0 -0
  739. codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js +0 -1
  740. codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.br +0 -0
  741. codex/static_root/assets/pager-full-pdf-CiyC6Al9.278b9978f36f.js.gz +0 -0
  742. codex/static_root/assets/pager-full-pdf-CiyC6Al9.js +0 -1
  743. codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.br +0 -0
  744. codex/static_root/assets/pager-full-pdf-CiyC6Al9.js.gz +0 -0
  745. codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.br +0 -0
  746. codex/static_root/assets/pagination-toolbar-C45-onwe.4722195bb60b.css.gz +0 -0
  747. codex/static_root/assets/pagination-toolbar-C45-onwe.css.br +0 -0
  748. codex/static_root/assets/pagination-toolbar-C45-onwe.css.gz +0 -0
  749. codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js +0 -1
  750. codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.br +0 -0
  751. codex/static_root/assets/pagination-toolbar-CkIzuyx1.00c354a82e6e.js.gz +0 -0
  752. codex/static_root/assets/pagination-toolbar-CkIzuyx1.js +0 -1
  753. codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.br +0 -0
  754. codex/static_root/assets/pagination-toolbar-CkIzuyx1.js.gz +0 -0
  755. codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.br +0 -0
  756. codex/static_root/assets/pdf-doc-BjlRPuf4.0f056fa321b6.js.gz +0 -0
  757. codex/static_root/assets/pdf-doc-BjlRPuf4.js.br +0 -0
  758. codex/static_root/assets/pdf-doc-BjlRPuf4.js.gz +0 -0
  759. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css +0 -1
  760. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.br +0 -0
  761. codex/static_root/assets/reader-_stAWL-4.5089e9144795.css.gz +0 -0
  762. codex/static_root/assets/reader-_stAWL-4.css +0 -1
  763. codex/static_root/assets/reader-_stAWL-4.css.br +0 -0
  764. codex/static_root/assets/reader-_stAWL-4.css.gz +0 -0
  765. codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js +0 -2
  766. codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.br +0 -0
  767. codex/static_root/assets/reader-ucxnLi2K.d0fbce0f03c1.js.gz +0 -0
  768. codex/static_root/assets/reader-ucxnLi2K.js +0 -2
  769. codex/static_root/assets/reader-ucxnLi2K.js.br +0 -0
  770. codex/static_root/assets/reader-ucxnLi2K.js.gz +0 -0
  771. codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js +0 -1
  772. codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.br +0 -0
  773. codex/static_root/assets/relation-chips-CUSatZET.a58031ff8141.js.gz +0 -0
  774. codex/static_root/assets/relation-chips-CUSatZET.js +0 -1
  775. codex/static_root/assets/relation-chips-CUSatZET.js.br +0 -0
  776. codex/static_root/assets/relation-chips-CUSatZET.js.gz +0 -0
  777. codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js +0 -2
  778. codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.br +0 -0
  779. codex/static_root/assets/settings-drawer-BSHTAsJ9.1d07ce084fc9.js.gz +0 -0
  780. codex/static_root/assets/settings-drawer-BSHTAsJ9.js +0 -2
  781. codex/static_root/assets/settings-drawer-BSHTAsJ9.js.br +0 -0
  782. codex/static_root/assets/settings-drawer-BSHTAsJ9.js.gz +0 -0
  783. codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.br +0 -0
  784. codex/static_root/assets/stats-tab-BXGisn5N.9b7a71e7fe28.css.gz +0 -0
  785. codex/static_root/assets/stats-tab-BXGisn5N.css.br +0 -0
  786. codex/static_root/assets/stats-tab-BXGisn5N.css.gz +0 -0
  787. codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js +0 -1
  788. codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.br +0 -0
  789. codex/static_root/assets/stats-tab-DKXVB2Cc.0a3de58bf5ee.js.gz +0 -0
  790. codex/static_root/assets/stats-tab-DKXVB2Cc.js +0 -1
  791. codex/static_root/assets/stats-tab-DKXVB2Cc.js.br +0 -0
  792. codex/static_root/assets/stats-tab-DKXVB2Cc.js.gz +0 -0
  793. codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css +0 -1
  794. codex/static_root/assets/task-tab-1XiWn9xP.06cb8e1b794d.css.gz +0 -0
  795. codex/static_root/assets/task-tab-1XiWn9xP.css +0 -1
  796. codex/static_root/assets/task-tab-1XiWn9xP.css.gz +0 -0
  797. codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js +0 -1
  798. codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.br +0 -0
  799. codex/static_root/assets/task-tab-NORFAa9p.97e07e366ddd.js.gz +0 -0
  800. codex/static_root/assets/task-tab-NORFAa9p.js +0 -1
  801. codex/static_root/assets/task-tab-NORFAa9p.js.br +0 -0
  802. codex/static_root/assets/task-tab-NORFAa9p.js.gz +0 -0
  803. codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js +0 -1
  804. codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.br +0 -0
  805. codex/static_root/assets/unauthorized-RHaQZRfr.a234e9403f60.js.gz +0 -0
  806. codex/static_root/assets/unauthorized-RHaQZRfr.js +0 -1
  807. codex/static_root/assets/unauthorized-RHaQZRfr.js.br +0 -0
  808. codex/static_root/assets/unauthorized-RHaQZRfr.js.gz +0 -0
  809. codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.br +0 -0
  810. codex/static_root/assets/unauthorized-muDsv-rC.2166ec8b3a32.css.gz +0 -0
  811. codex/static_root/assets/unauthorized-muDsv-rC.css.br +0 -0
  812. codex/static_root/assets/unauthorized-muDsv-rC.css.gz +0 -0
  813. codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js +0 -1
  814. codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.br +0 -0
  815. codex/static_root/assets/user-tab-CHbyN1EW.e7206db08680.js.gz +0 -0
  816. codex/static_root/assets/user-tab-CHbyN1EW.js +0 -1
  817. codex/static_root/assets/user-tab-CHbyN1EW.js.br +0 -0
  818. codex/static_root/assets/user-tab-CHbyN1EW.js.gz +0 -0
  819. codex/static_root/js/choices-admin.1a20a5648f20.json +0 -1
  820. codex/static_root/js/choices-admin.1a20a5648f20.json.br +0 -0
  821. codex/static_root/js/choices-admin.1a20a5648f20.json.gz +0 -0
  822. codex/static_root/js/choices-admin.json +0 -1
  823. codex/static_root/js/choices-admin.json.br +0 -0
  824. codex/static_root/js/choices-admin.json.gz +0 -0
  825. codex/static_root/js/choices.2f6b0359e1ef.json +0 -1
  826. codex/static_root/js/choices.2f6b0359e1ef.json.br +0 -0
  827. codex/static_root/js/choices.2f6b0359e1ef.json.gz +0 -0
  828. codex/static_root/js/choices.json +0 -1
  829. codex/static_root/js/choices.json.br +0 -0
  830. codex/static_root/js/choices.json.gz +0 -0
  831. codex/static_root/manifest.0fe31a6cb99f.json +0 -635
  832. codex/static_root/manifest.0fe31a6cb99f.json.br +0 -0
  833. codex/static_root/manifest.0fe31a6cb99f.json.gz +0 -0
  834. codex/templates/search/indexes/codex/comic_text.txt +0 -47
  835. codex/views/browser/annotations.py +0 -296
  836. codex/views/browser/base.py +0 -140
  837. codex/views/browser/filters/annotations.py +0 -103
  838. codex/views/browser/filters/search.py +0 -196
  839. codex/views/browser/metadata.py +0 -332
  840. {codex-1.6.19.dist-info → codex-1.7.0.dist-info}/LICENSE +0 -0
  841. {codex-1.6.19.dist-info → codex-1.7.0.dist-info}/WHEEL +0 -0
  842. {codex-1.6.19.dist-info → codex-1.7.0.dist-info}/entry_points.txt +0 -0
codex/exceptions.py CHANGED
@@ -11,8 +11,8 @@ from django.utils.http import urlencode
11
11
  from rest_framework.exceptions import APIException
12
12
  from rest_framework.status import HTTP_303_SEE_OTHER
13
13
 
14
+ from codex.choices import DEFAULT_BROWSER_ROUTE
14
15
  from codex.logger.logging import get_logger
15
- from codex.serializers.choices import DEFAULTS
16
16
  from codex.serializers.fields import BreadcrumbsField
17
17
  from codex.serializers.route import RouteSerializer
18
18
  from codex.views.util import pop_name
@@ -24,7 +24,6 @@ if TYPE_CHECKING:
24
24
  LOG = get_logger(__name__)
25
25
  _OPDS_REDIRECT_SETTINGS_KEYS = ("order_by", "top_group")
26
26
  _REDIRECT_SETTINGS_KEYS = ("breadcrumbs", *_OPDS_REDIRECT_SETTINGS_KEYS)
27
- _DEFAULT_ROUTE = DEFAULTS["breadcrumbs"][0]
28
27
 
29
28
 
30
29
  class SeeOtherRedirectError(APIException):
@@ -41,7 +40,7 @@ class SeeOtherRedirectError(APIException):
41
40
 
42
41
  # Get route params
43
42
  route = detail.get("route", {})
44
- params = route.get("params", _DEFAULT_ROUTE)
43
+ params = route.get("params", DEFAULT_BROWSER_ROUTE)
45
44
  params = pop_name(params)
46
45
  route = deepcopy(route)
47
46
  route["params"] = params
@@ -0,0 +1 @@
1
+ """Bookmark Thread."""
@@ -0,0 +1,38 @@
1
+ """Sends notifications to connections, reading from a queue."""
2
+
3
+ from codex.librarian.bookmark.tasks import BookmarkUpdateTask
4
+ from codex.librarian.bookmark.update import BookmarkUpdate
5
+ from codex.threads import AggregateMessageQueuedThread
6
+
7
+
8
+ class BookmarkThread(AggregateMessageQueuedThread, BookmarkUpdate):
9
+ """Aggregates Bookmark updates preventing floods updates db in batches.."""
10
+
11
+ FLOOD_DELAY = 3.0
12
+ MAX_DELAY = 5.0
13
+
14
+ def aggregate_items(self, item):
15
+ """Aggregate bookmark updates."""
16
+ if isinstance(item, BookmarkUpdateTask):
17
+ key = (tuple(item.auth_filter.items()), tuple(item.comic_filter.items()))
18
+ if key not in self.cache:
19
+ self.cache[key] = {}
20
+ self.cache[key].update(item.updates)
21
+ else:
22
+ self.log.warning(f"Unknown Bookmark task {item}")
23
+
24
+ def send_all_items(self):
25
+ """Run the task method."""
26
+ cleanup = set()
27
+ for filters, updates in self.cache.items():
28
+ try:
29
+ auth_filter, comic_filter = filters
30
+ auth_filter = dict(auth_filter)
31
+ comic_filter = dict(comic_filter)
32
+ self.update_bookmarks(auth_filter, comic_filter, updates)
33
+ self.log.debug(f"updated {auth_filter | comic_filter}")
34
+ cleanup.add(filters)
35
+ except Exception:
36
+ self.log.exception("Updating bookmarks")
37
+
38
+ self.cleanup_cache(cleanup)
@@ -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 = (
@@ -45,7 +45,7 @@ class CoverPurgeMixin(CoverPathMixin):
45
45
  except FileNotFoundError:
46
46
  status.decrement_total()
47
47
  cover_dirs.add(cover_path.parent)
48
- self.status_controller.update(status)
48
+ self.status_controller.update(status, notify=False)
49
49
  for cover_dir in cover_dirs:
50
50
  self._cleanup_cover_dirs(cover_dir, cover_root)
51
51
  self.log.info(f"Removed {status.complete} cover thumbnails.")
@@ -115,7 +115,13 @@ DICT_MODEL_REL_LINK_MAP = MappingProxyType(
115
115
  # BULK UPDATE #
116
116
  ###############
117
117
 
118
- _EXCLUDEBULK_UPDATE_COMIC_FIELDS = {"bookmark", "created_at", "id", "library"}
118
+ _EXCLUDEBULK_UPDATE_COMIC_FIELDS = {
119
+ "bookmark",
120
+ "created_at",
121
+ "id",
122
+ "library",
123
+ "comicfts",
124
+ }
119
125
  GROUP_BASE_FIELDS = ("name", "sort_name")
120
126
  BULK_UPDATE_COMIC_FIELDS = tuple(
121
127
  sorted(
@@ -213,8 +213,11 @@ class CreateForeignKeysImporter(CreateCoversImporter):
213
213
  # create each depth level first to ensure we can assign parents
214
214
  for depth_level in sorted(folder_path_dict):
215
215
  paths = sorted(folder_path_dict[depth_level])
216
- count += self._bulk_folders_create_depth_level(paths, status)
217
- self.log.debug(f"Created {count} Folders at depth level {depth_level}")
216
+ level_count = self._bulk_folders_create_depth_level(paths, status)
217
+ count += level_count
218
+ self.log.debug(
219
+ f"Created {level_count} Folders at depth level {depth_level}"
220
+ )
218
221
 
219
222
  if count:
220
223
  self.log.info(f"Created {count} Folders.")
@@ -12,6 +12,7 @@ from codex.librarian.importer.tasks import (
12
12
  LazyImportComicsTask,
13
13
  UpdateGroupsTask,
14
14
  )
15
+ from codex.librarian.janitor.tasks import JanitorAdoptOrphanFoldersFinishedTask
15
16
  from codex.librarian.notifier.tasks import LIBRARY_CHANGED_TASK
16
17
  from codex.models import Comic, Folder, Library
17
18
  from codex.status import Status
@@ -88,20 +89,24 @@ class ComicImporterThread(QueuedThread):
88
89
  importer.bulk_folders_moved()
89
90
  return True
90
91
 
91
- def _adopt_orphan_folders(self):
92
+ def _adopt_orphan_folders(self, janitor=False):
92
93
  """Find orphan folders and move them into their correct place."""
93
94
  status = Status(ImportStatusTypes.ADOPT_FOLDERS)
94
95
  moved_status = Status(ImportStatusTypes.DIRS_MOVED)
95
- self.status_controller.start(status)
96
- self.status_controller.start(moved_status)
97
- libraries = Library.objects.filter(covers_only=False).only("path")
98
- for library in libraries.iterator():
99
- folders_left = True
100
- while folders_left:
101
- # Run until there are no orphan folders
102
- folders_left = self._adopt_orphan_folders_for_library(library)
103
-
104
- self.status_controller.finish_many((moved_status, status))
96
+ try:
97
+ self.status_controller.start(status)
98
+ self.status_controller.start(moved_status)
99
+ libraries = Library.objects.filter(covers_only=False).only("path")
100
+ for library in libraries.iterator():
101
+ folders_left = True
102
+ while folders_left:
103
+ # Run until there are no orphan folders
104
+ folders_left = self._adopt_orphan_folders_for_library(library)
105
+ finally:
106
+ self.status_controller.finish_many((moved_status, status))
107
+ if janitor:
108
+ next_task = JanitorAdoptOrphanFoldersFinishedTask()
109
+ self.librarian_queue.put(next_task)
105
110
 
106
111
  def process_item(self, item):
107
112
  """Run the updater."""
@@ -113,6 +118,6 @@ class ComicImporterThread(QueuedThread):
113
118
  elif isinstance(task, UpdateGroupsTask):
114
119
  self._update_groups(task)
115
120
  elif isinstance(task, AdoptOrphanFoldersTask):
116
- self._adopt_orphan_folders()
121
+ self._adopt_orphan_folders(task.janitor)
117
122
  else:
118
123
  self.log.warning(f"Bad task sent to library updater {task}")
@@ -164,17 +164,23 @@ class MovedImporter(AggregateMetadataImporter):
164
164
  self.status_controller.finish(status)
165
165
 
166
166
  def _bulk_move_folders(
167
- self, src_folder_paths, dest_parent_folders_map, dirs_moved: bidict, status
167
+ self,
168
+ src_folder_paths_with_existing_dest_parents,
169
+ dest_parent_folders_map,
170
+ dirs_moved: bidict[str, str],
171
+ status,
168
172
  ):
169
173
  """Bulk move folders."""
174
+ # Move collisions removed before this
170
175
  folders_to_move = (
171
- Folder.objects.filter(library=self.library, path__in=src_folder_paths)
176
+ Folder.objects.filter(
177
+ library=self.library,
178
+ path__in=src_folder_paths_with_existing_dest_parents,
179
+ )
172
180
  .only(*MOVED_BULK_FOLDER_UPDATE_FIELDS)
173
181
  .order_by("path")
174
182
  )
175
183
 
176
- # TODO what if a folder with that path already exists in the db?
177
-
178
184
  new_paths = set()
179
185
  update_folders = []
180
186
  for folder in folders_to_move.iterator():
@@ -201,7 +207,7 @@ class MovedImporter(AggregateMetadataImporter):
201
207
  self.status_controller.update(status, notify=False)
202
208
 
203
209
  def _bulk_move_folders_under_existing_parents(
204
- self, dest_parent_folder_paths_map, dirs_moved, status
210
+ self, dest_parent_folder_paths_map, dirs_moved: bidict, status
205
211
  ):
206
212
  """Move folders under existing folders."""
207
213
  while True:
@@ -214,25 +220,32 @@ class MovedImporter(AggregateMetadataImporter):
214
220
 
215
221
  # Create a list of folders than can be moved under existing folders.
216
222
  dest_parent_folders_map = {}
217
- src_folder_paths = []
223
+ src_folder_paths_with_existing_dest_parents = []
218
224
  for extant_parent_folder in extant_parent_folders:
219
225
  dest_parent_folders_map[extant_parent_folder.path] = (
220
226
  extant_parent_folder
221
227
  )
222
228
  if dest_folder_paths := dest_parent_folder_paths_map.pop(
223
- extant_parent_folder.path
229
+ extant_parent_folder.path, None
224
230
  ):
225
231
  for dest_folder_path in dest_folder_paths:
226
232
  src_path = dirs_moved.inverse[dest_folder_path]
227
- src_folder_paths.append(src_path)
233
+ src_folder_paths_with_existing_dest_parents.append(src_path)
228
234
 
229
- if not src_folder_paths:
235
+ if not src_folder_paths_with_existing_dest_parents:
230
236
  return
231
- src_folder_paths = sorted(src_folder_paths)
232
- self.log.debug(f"Moving folders under existing parents: {src_folder_paths}")
237
+ src_folder_paths_with_existing_dest_parents = sorted(
238
+ src_folder_paths_with_existing_dest_parents
239
+ )
240
+ self.log.debug(
241
+ f"Moving folders under existing parents: {src_folder_paths_with_existing_dest_parents}"
242
+ )
233
243
 
234
244
  self._bulk_move_folders(
235
- src_folder_paths, dest_parent_folders_map, dirs_moved, status
245
+ src_folder_paths_with_existing_dest_parents,
246
+ dest_parent_folders_map,
247
+ dirs_moved,
248
+ status,
236
249
  )
237
250
 
238
251
  def _get_move_create_folders_one_layer(self, dest_parent_folder_paths_map):
@@ -260,12 +273,30 @@ class MovedImporter(AggregateMetadataImporter):
260
273
 
261
274
  return frozenset(create_folder_paths_one_layer)
262
275
 
276
+ def _remove_move_collisions(self, dirs_moved: bidict[str, str]):
277
+ """Remove moves that would collide with an existing Folder."""
278
+ dest_paths = set(dirs_moved.values())
279
+ collision_dest_paths = Folder.objects.filter(
280
+ library=self.library, path__in=dest_paths
281
+ ).values_list("path", flat=True)
282
+ if not collision_dest_paths:
283
+ return
284
+ collision_dest_paths = sorted(set(collision_dest_paths))
285
+ for collision_dest_path in collision_dest_paths:
286
+ dirs_moved.inverse.pop(collision_dest_path, None)
287
+ self.log.warning(
288
+ f"Not moving folders to destinations that would collide with existing database folders: {collision_dest_paths}"
289
+ )
290
+
263
291
  def _bulk_move_folders_and_create_parents(self, status):
264
292
  """Find folders that can be moved without creating parents."""
265
293
  dirs_moved = bidict(self.task.dirs_moved)
266
294
 
295
+ self._remove_move_collisions(dirs_moved)
296
+
297
+ dest_paths = sorted(set(dirs_moved.values()))
267
298
  dest_parent_folder_paths_map = {}
268
- for dest_path in sorted(set(dirs_moved.values())):
299
+ for dest_path in dest_paths:
269
300
  parent = str(Path(dest_path).parent)
270
301
  if parent not in dest_parent_folder_paths_map:
271
302
  dest_parent_folder_paths_map[parent] = set()
@@ -45,6 +45,8 @@ class LazyImportComicsTask(ImportTask):
45
45
  class AdoptOrphanFoldersTask(ImportTask):
46
46
  """Move orphaned folders into a correct tree position."""
47
47
 
48
+ janitor: bool = False
49
+
48
50
 
49
51
  @dataclass
50
52
  class UpdateGroupsTask(ImportTask):
@@ -35,6 +35,7 @@ from codex.models import (
35
35
  Team,
36
36
  Volume,
37
37
  )
38
+ from codex.models.bookmark import Bookmark
38
39
  from codex.models.paths import CustomCover
39
40
  from codex.status import Status
40
41
  from codex.worker_base import WorkerBaseMixin
@@ -84,15 +85,15 @@ CLEANUP_MAP = MappingProxyType(
84
85
  class CleanupMixin(WorkerBaseMixin):
85
86
  """Cleanup methods for Janitor."""
86
87
 
87
- def _bulk_cleanup_fks(self, classes, field_name, status):
88
+ def _bulk_cleanup_fks(self, models, field_name, status):
88
89
  """Remove foreign keys that aren't used anymore."""
89
- for cls in classes:
90
+ for model in models:
90
91
  filter_dict = {f"{field_name}__isnull": True}
91
- query = cls.objects.filter(**filter_dict)
92
- count = query.count()
93
- query.delete()
92
+ qs = model.objects.filter(**filter_dict)
93
+ count = qs.count()
94
+ qs.delete()
94
95
  if count:
95
- self.log.info(f"Deleted {count} orphan {cls.__name__}s")
96
+ self.log.info(f"Deleted {count} orphan {model.__name__}s")
96
97
  status.complete += count
97
98
  self.status_controller.update(status)
98
99
 
@@ -102,8 +103,8 @@ class CleanupMixin(WorkerBaseMixin):
102
103
  try:
103
104
  self.status_controller.start(status)
104
105
  self.log.debug("Cleaning up unused foreign keys...")
105
- for field_name, classes in CLEANUP_MAP.items():
106
- self._bulk_cleanup_fks(classes, field_name, status)
106
+ for field_name, models in CLEANUP_MAP.items():
107
+ self._bulk_cleanup_fks(models, field_name, status)
107
108
  level = logging.INFO if status.complete else logging.DEBUG
108
109
  self.log.log(level, f"Cleaned up {status.complete} unused foreign keys.")
109
110
  finally:
@@ -152,3 +153,15 @@ class CleanupMixin(WorkerBaseMixin):
152
153
  finally:
153
154
  until = time() + 1
154
155
  self.status_controller.finish(status, until=until)
156
+
157
+ def cleanup_orphan_bookmarks(self):
158
+ """Delete bookmarks without users or sessions."""
159
+ status = Status(JanitorStatusTypes.CLEANUP_BOOKMARKS)
160
+ try:
161
+ self.status_controller.start(status)
162
+ orphan_bms = Bookmark.objects.filter(session=None, user=None)
163
+ if count := orphan_bms.count():
164
+ orphan_bms.delete()
165
+ self.log.info(f"Deleted {count} orphan bookmarks.")
166
+ finally:
167
+ self.status_controller.finish(status)