codex 1.4.0a1__py3-none-any.whl → 1.4.1__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 (259) hide show
  1. codex/config_default.yaml +12 -4
  2. codex/db_functions.py +4 -2
  3. codex/integrity.py +17 -6
  4. codex/librarian/covers/create.py +6 -8
  5. codex/librarian/importer/aggregate_metadata.py +75 -41
  6. codex/librarian/importer/clean_metadata.py +30 -7
  7. codex/librarian/importer/create_fks.py +154 -55
  8. codex/librarian/importer/deleted.py +11 -2
  9. codex/librarian/importer/failed_imports.py +41 -5
  10. codex/librarian/importer/importerd.py +34 -11
  11. codex/librarian/importer/link_comics.py +54 -31
  12. codex/librarian/importer/moved.py +55 -11
  13. codex/librarian/importer/query_fks.py +210 -48
  14. codex/librarian/importer/tasks.py +7 -7
  15. codex/librarian/janitor/cleanup.py +17 -5
  16. codex/librarian/librariand.py +10 -0
  17. codex/librarian/watchdog/events.py +11 -14
  18. codex/librarian/watchdog/observers.py +5 -1
  19. codex/logger/loggerd.py +7 -3
  20. codex/logger/logging.py +1 -1
  21. codex/migrations/0024_comic_gtin_comic_story_arc_number.py +24 -0
  22. codex/migrations/0025_add_story_arc_number.py +83 -0
  23. codex/models.py +21 -11
  24. codex/search/backend.py +1 -1
  25. codex/search/indexes.py +1 -1
  26. codex/serializers/browser.py +1 -0
  27. codex/serializers/metadata.py +5 -1
  28. codex/serializers/models.py +16 -1
  29. codex/serializers/opds/v1.py +1 -0
  30. codex/serializers/opds/v2.py +5 -2
  31. codex/serializers/reader.py +55 -16
  32. codex/settings/settings.py +1 -1
  33. codex/static_root/assets/admin-12749881.ef0f50bac290.js +41 -0
  34. codex/static_root/assets/admin-12749881.ef0f50bac290.js.br +0 -0
  35. codex/static_root/assets/admin-12749881.ef0f50bac290.js.gz +0 -0
  36. codex/static_root/assets/admin-12749881.js +41 -0
  37. codex/static_root/assets/admin-12749881.js.br +0 -0
  38. codex/static_root/assets/admin-12749881.js.gz +0 -0
  39. codex/static_root/assets/admin-beda768d.a614eee46307.css +1 -0
  40. codex/static_root/assets/admin-beda768d.a614eee46307.css.br +0 -0
  41. codex/static_root/assets/admin-beda768d.a614eee46307.css.gz +0 -0
  42. codex/static_root/assets/admin-beda768d.css +1 -0
  43. codex/static_root/assets/admin-beda768d.css.br +0 -0
  44. codex/static_root/assets/admin-beda768d.css.gz +0 -0
  45. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css +1 -0
  46. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.br +0 -0
  47. codex/static_root/assets/admin-drawer-panel-41c225cc.3f84583b435b.css.gz +0 -0
  48. codex/static_root/assets/admin-drawer-panel-41c225cc.css +1 -0
  49. codex/static_root/assets/admin-drawer-panel-41c225cc.css.br +0 -0
  50. codex/static_root/assets/admin-drawer-panel-41c225cc.css.gz +0 -0
  51. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js +1 -0
  52. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.br +0 -0
  53. codex/static_root/assets/admin-drawer-panel-522f1e6c.089d70878270.js.gz +0 -0
  54. codex/static_root/assets/admin-drawer-panel-522f1e6c.js +1 -0
  55. codex/static_root/assets/admin-drawer-panel-522f1e6c.js.br +0 -0
  56. codex/static_root/assets/admin-drawer-panel-522f1e6c.js.gz +0 -0
  57. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css +1 -0
  58. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.br +0 -0
  59. codex/static_root/assets/browser-7f7d7134.0fe3749b0f2f.css.gz +0 -0
  60. codex/static_root/assets/browser-7f7d7134.css +1 -0
  61. codex/static_root/assets/browser-7f7d7134.css.br +0 -0
  62. codex/static_root/assets/browser-7f7d7134.css.gz +0 -0
  63. codex/static_root/assets/browser-af622672.d51aca96d64d.js +1 -0
  64. codex/static_root/assets/browser-af622672.d51aca96d64d.js.br +0 -0
  65. codex/static_root/assets/browser-af622672.d51aca96d64d.js.gz +0 -0
  66. codex/static_root/assets/browser-af622672.js +1 -0
  67. codex/static_root/assets/browser-af622672.js.br +0 -0
  68. codex/static_root/assets/browser-af622672.js.gz +0 -0
  69. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js +1 -0
  70. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.br +0 -0
  71. codex/static_root/assets/http-error-5e17b794.77ceeb2d4641.js.gz +0 -0
  72. codex/static_root/assets/http-error-5e17b794.js +1 -0
  73. codex/static_root/assets/http-error-5e17b794.js.br +0 -0
  74. codex/static_root/assets/http-error-5e17b794.js.gz +0 -0
  75. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js +1 -0
  76. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.br +0 -0
  77. codex/static_root/assets/main-9e76a4c3.6844a407d14c.js.gz +0 -0
  78. codex/static_root/assets/main-9e76a4c3.js +1 -0
  79. codex/static_root/assets/main-9e76a4c3.js.br +0 -0
  80. codex/static_root/assets/main-9e76a4c3.js.gz +0 -0
  81. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js +1 -0
  82. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.br +0 -0
  83. codex/static_root/assets/metadata-dialog-62c29ce0.8418785c0453.js.gz +0 -0
  84. codex/static_root/assets/metadata-dialog-62c29ce0.js +1 -0
  85. codex/static_root/assets/metadata-dialog-62c29ce0.js.br +0 -0
  86. codex/static_root/assets/metadata-dialog-62c29ce0.js.gz +0 -0
  87. codex/static_root/assets/{metadata-dialog-785c4cfc.694a251cda37.css → metadata-dialog-cb306ffd.cc304996d7bb.css} +1 -1
  88. codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.br +0 -0
  89. codex/static_root/assets/metadata-dialog-cb306ffd.cc304996d7bb.css.gz +0 -0
  90. codex/static_root/assets/{metadata-dialog-785c4cfc.css → metadata-dialog-cb306ffd.css} +1 -1
  91. codex/static_root/assets/metadata-dialog-cb306ffd.css.br +0 -0
  92. codex/static_root/assets/metadata-dialog-cb306ffd.css.gz +0 -0
  93. codex/static_root/assets/{page-pdf-c603e996.ab2d147c9ae1.js → page-pdf-157ba97e.613d7c2beb77.js} +61 -51
  94. codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.br +0 -0
  95. codex/static_root/assets/page-pdf-157ba97e.613d7c2beb77.js.gz +0 -0
  96. codex/static_root/assets/{page-pdf-c603e996.js → page-pdf-157ba97e.js} +61 -51
  97. codex/static_root/assets/page-pdf-157ba97e.js.br +0 -0
  98. codex/static_root/assets/page-pdf-157ba97e.js.gz +0 -0
  99. codex/static_root/assets/reader-36266549.0b2cf1291f27.js +1 -0
  100. codex/static_root/assets/reader-36266549.0b2cf1291f27.js.br +0 -0
  101. codex/static_root/assets/reader-36266549.0b2cf1291f27.js.gz +0 -0
  102. codex/static_root/assets/reader-36266549.js +1 -0
  103. codex/static_root/assets/reader-36266549.js.br +0 -0
  104. codex/static_root/assets/reader-36266549.js.gz +0 -0
  105. codex/static_root/assets/reader-7f004141.506eecc6954b.css +1 -0
  106. codex/static_root/assets/reader-7f004141.506eecc6954b.css.br +0 -0
  107. codex/static_root/assets/reader-7f004141.506eecc6954b.css.gz +0 -0
  108. codex/static_root/assets/reader-7f004141.css +1 -0
  109. codex/static_root/assets/reader-7f004141.css.br +0 -0
  110. codex/static_root/assets/reader-7f004141.css.gz +0 -0
  111. codex/static_root/js/choices.8c58714cf5b2.json +1 -0
  112. codex/static_root/js/choices.8c58714cf5b2.json.br +5 -0
  113. codex/static_root/js/choices.8c58714cf5b2.json.gz +0 -0
  114. codex/static_root/js/choices.json +1 -1
  115. codex/static_root/js/choices.json.br +0 -0
  116. codex/static_root/js/choices.json.gz +0 -0
  117. codex/static_root/{manifest.c0e270b2e6b6.json → manifest.d2f93a519ada.json} +32 -32
  118. codex/static_root/manifest.d2f93a519ada.json.br +0 -0
  119. codex/static_root/manifest.d2f93a519ada.json.gz +0 -0
  120. codex/static_root/manifest.json +32 -32
  121. codex/static_root/manifest.json.br +0 -0
  122. codex/static_root/manifest.json.gz +0 -0
  123. codex/static_root/staticfiles.json +1 -1
  124. codex/templates/headers-script-globals.html +1 -1
  125. codex/templates/{opds → opds_v1}/index.xml +3 -1
  126. codex/templates/{opds/opensearch.xml → opds_v1/opensearch_v1.xml} +1 -1
  127. codex/templates/search/indexes/codex/comic_text.txt +2 -2
  128. codex/urls/converters.py +1 -1
  129. codex/urls/opds/authentication.py +1 -1
  130. codex/urls/opds/root.py +8 -12
  131. codex/urls/opds/v1.py +12 -5
  132. codex/urls/opds/v2.py +2 -2
  133. codex/views/bookmark.py +2 -2
  134. codex/views/browser/base.py +23 -7
  135. codex/views/browser/browser.py +51 -41
  136. codex/views/browser/browser_annotations.py +159 -50
  137. codex/views/browser/browser_order_by.py +50 -106
  138. codex/views/browser/choices.py +75 -38
  139. codex/views/browser/filters/bookmark.py +6 -9
  140. codex/views/browser/filters/field.py +9 -6
  141. codex/views/browser/filters/group.py +12 -27
  142. codex/views/browser/filters/search.py +5 -10
  143. codex/views/browser/metadata.py +44 -19
  144. codex/views/download.py +1 -1
  145. codex/views/frontend.py +2 -3
  146. codex/views/mixins.py +15 -2
  147. codex/views/opds/const.py +8 -1
  148. codex/views/opds/util.py +37 -1
  149. codex/views/opds/v1/__init__.py +1 -1
  150. codex/views/opds/v1/data.py +21 -0
  151. codex/views/opds/v1/entry/__init__.py +1 -0
  152. codex/views/opds/v1/entry/data.py +23 -0
  153. codex/views/opds/v1/entry/entry.py +151 -0
  154. codex/views/opds/v1/entry/links.py +135 -0
  155. codex/views/opds/v1/facets.py +190 -0
  156. codex/views/opds/v1/feed.py +199 -0
  157. codex/views/opds/v1/links.py +198 -0
  158. codex/views/opds/{opensearch.py → v1/opensearch_v1.py} +3 -3
  159. codex/views/opds/v2/__init__.py +1 -1
  160. codex/views/opds/v2/const.py +10 -2
  161. codex/views/opds/v2/feed.py +82 -21
  162. codex/views/opds/v2/links.py +1 -1
  163. codex/views/opds/v2/publications.py +1 -1
  164. codex/views/opds/v2/top_links.py +1 -1
  165. codex/views/reader/page.py +6 -7
  166. codex/views/reader/reader.py +191 -61
  167. codex/views/session.py +2 -1
  168. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/METADATA +10 -41
  169. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/RECORD +172 -170
  170. codex/librarian/importer/db_ops.py +0 -251
  171. codex/pdf.py +0 -115
  172. codex/static_root/assets/admin-75c007ce.199fccf24c8d.js +0 -48
  173. codex/static_root/assets/admin-75c007ce.199fccf24c8d.js.br +0 -0
  174. codex/static_root/assets/admin-75c007ce.199fccf24c8d.js.gz +0 -0
  175. codex/static_root/assets/admin-75c007ce.js +0 -48
  176. codex/static_root/assets/admin-75c007ce.js.br +0 -0
  177. codex/static_root/assets/admin-75c007ce.js.gz +0 -0
  178. codex/static_root/assets/admin-848d48b1.5de8a0c45636.css +0 -1
  179. codex/static_root/assets/admin-848d48b1.5de8a0c45636.css.br +0 -0
  180. codex/static_root/assets/admin-848d48b1.5de8a0c45636.css.gz +0 -0
  181. codex/static_root/assets/admin-848d48b1.css +0 -1
  182. codex/static_root/assets/admin-848d48b1.css.br +0 -0
  183. codex/static_root/assets/admin-848d48b1.css.gz +0 -0
  184. codex/static_root/assets/admin-drawer-panel-a110c068.edf187333272.js +0 -1
  185. codex/static_root/assets/admin-drawer-panel-a110c068.edf187333272.js.br +0 -0
  186. codex/static_root/assets/admin-drawer-panel-a110c068.edf187333272.js.gz +0 -0
  187. codex/static_root/assets/admin-drawer-panel-a110c068.js +0 -1
  188. codex/static_root/assets/admin-drawer-panel-a110c068.js.br +0 -0
  189. codex/static_root/assets/admin-drawer-panel-a110c068.js.gz +0 -0
  190. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css +0 -1
  191. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.br +0 -2
  192. codex/static_root/assets/admin-drawer-panel-cce8c0aa.2c0814fa2a9b.css.gz +0 -0
  193. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css +0 -1
  194. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.br +0 -2
  195. codex/static_root/assets/admin-drawer-panel-cce8c0aa.css.gz +0 -0
  196. codex/static_root/assets/browser-2c2380fd.8b515af7a743.js +0 -1
  197. codex/static_root/assets/browser-2c2380fd.8b515af7a743.js.br +0 -0
  198. codex/static_root/assets/browser-2c2380fd.8b515af7a743.js.gz +0 -0
  199. codex/static_root/assets/browser-2c2380fd.js +0 -1
  200. codex/static_root/assets/browser-2c2380fd.js.br +0 -0
  201. codex/static_root/assets/browser-2c2380fd.js.gz +0 -0
  202. codex/static_root/assets/browser-7325db61.css +0 -1
  203. codex/static_root/assets/browser-7325db61.css.br +0 -0
  204. codex/static_root/assets/browser-7325db61.css.gz +0 -0
  205. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css +0 -1
  206. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.br +0 -0
  207. codex/static_root/assets/browser-7325db61.ed2cfbf8e8ee.css.gz +0 -0
  208. codex/static_root/assets/http-error-402decbe.9ea8de1df13f.js +0 -1
  209. codex/static_root/assets/http-error-402decbe.9ea8de1df13f.js.br +0 -0
  210. codex/static_root/assets/http-error-402decbe.9ea8de1df13f.js.gz +0 -0
  211. codex/static_root/assets/http-error-402decbe.js +0 -1
  212. codex/static_root/assets/http-error-402decbe.js.br +0 -0
  213. codex/static_root/assets/http-error-402decbe.js.gz +0 -0
  214. codex/static_root/assets/main-a7f327e9.6641fe833335.js +0 -1
  215. codex/static_root/assets/main-a7f327e9.6641fe833335.js.br +0 -0
  216. codex/static_root/assets/main-a7f327e9.6641fe833335.js.gz +0 -0
  217. codex/static_root/assets/main-a7f327e9.js +0 -1
  218. codex/static_root/assets/main-a7f327e9.js.br +0 -0
  219. codex/static_root/assets/main-a7f327e9.js.gz +0 -0
  220. codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.br +0 -0
  221. codex/static_root/assets/metadata-dialog-785c4cfc.694a251cda37.css.gz +0 -0
  222. codex/static_root/assets/metadata-dialog-785c4cfc.css.br +0 -0
  223. codex/static_root/assets/metadata-dialog-785c4cfc.css.gz +0 -0
  224. codex/static_root/assets/metadata-dialog-8a0bd8e1.c213b08d582f.js +0 -1
  225. codex/static_root/assets/metadata-dialog-8a0bd8e1.c213b08d582f.js.br +0 -0
  226. codex/static_root/assets/metadata-dialog-8a0bd8e1.c213b08d582f.js.gz +0 -0
  227. codex/static_root/assets/metadata-dialog-8a0bd8e1.js +0 -1
  228. codex/static_root/assets/metadata-dialog-8a0bd8e1.js.br +0 -0
  229. codex/static_root/assets/metadata-dialog-8a0bd8e1.js.gz +0 -0
  230. codex/static_root/assets/page-pdf-c603e996.ab2d147c9ae1.js.br +0 -0
  231. codex/static_root/assets/page-pdf-c603e996.ab2d147c9ae1.js.gz +0 -0
  232. codex/static_root/assets/page-pdf-c603e996.js.br +0 -0
  233. codex/static_root/assets/page-pdf-c603e996.js.gz +0 -0
  234. codex/static_root/assets/reader-c2965a5f.b011260169f7.js +0 -1
  235. codex/static_root/assets/reader-c2965a5f.b011260169f7.js.br +0 -0
  236. codex/static_root/assets/reader-c2965a5f.b011260169f7.js.gz +0 -0
  237. codex/static_root/assets/reader-c2965a5f.js +0 -1
  238. codex/static_root/assets/reader-c2965a5f.js.br +0 -0
  239. codex/static_root/assets/reader-c2965a5f.js.gz +0 -0
  240. codex/static_root/assets/reader-d8534888.2821de925986.css +0 -1
  241. codex/static_root/assets/reader-d8534888.2821de925986.css.br +0 -0
  242. codex/static_root/assets/reader-d8534888.2821de925986.css.gz +0 -0
  243. codex/static_root/assets/reader-d8534888.css +0 -1
  244. codex/static_root/assets/reader-d8534888.css.br +0 -0
  245. codex/static_root/assets/reader-d8534888.css.gz +0 -0
  246. codex/static_root/js/choices.6bfc2a3d293f.json +0 -1
  247. codex/static_root/js/choices.6bfc2a3d293f.json.br +0 -0
  248. codex/static_root/js/choices.6bfc2a3d293f.json.gz +0 -0
  249. codex/static_root/manifest.c0e270b2e6b6.json.br +0 -0
  250. codex/static_root/manifest.c0e270b2e6b6.json.gz +0 -0
  251. codex/urls/opds/opensearch.py +0 -18
  252. codex/views/opds/v1/browser.py +0 -346
  253. codex/views/opds/v1/entry.py +0 -278
  254. codex/views/opds/v1/start.py +0 -28
  255. codex/views/opds/v1/util.py +0 -162
  256. codex/views/opds/v2/start.py +0 -28
  257. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/LICENSE +0 -0
  258. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/WHEEL +0 -0
  259. {codex-1.4.0a1.dist-info → codex-1.4.1.dist-info}/entry_points.txt +0 -0
@@ -1,251 +0,0 @@
1
- """Wrap jobs in statuses."""
2
- from itertools import chain
3
-
4
- from codex.librarian.importer.aggregate_metadata import AggregateMetadataMixin
5
- from codex.librarian.importer.deleted import DeletedMixin
6
- from codex.librarian.importer.failed_imports import FailedImportsMixin
7
- from codex.librarian.importer.moved import MovedMixin
8
- from codex.librarian.importer.status import ImportStatusTypes
9
- from codex.librarian.importer.update_comics import UpdateComicsMixin
10
- from codex.models import Comic, Creator
11
- from codex.status import Status
12
-
13
- _CREATOR_FK_NAMES = ("role", "person")
14
-
15
-
16
- class ApplyDBOpsMixin(
17
- AggregateMetadataMixin,
18
- DeletedMixin,
19
- UpdateComicsMixin,
20
- FailedImportsMixin,
21
- MovedMixin,
22
- ):
23
- """Apply db ops, wrapped in statuses."""
24
-
25
- def read_metadata(self, library_path, all_paths, metadata):
26
- """Aggregate Metadata."""
27
- return self.get_aggregate_metadata(all_paths, library_path, metadata)
28
-
29
- def move_and_modify_dirs(self, library, task):
30
- """Move files and dirs and modify dirs."""
31
- changed = 0
32
- changed += self.bulk_folders_moved(task.dirs_moved, library)
33
- task.dirs_moved = None
34
-
35
- changed += self.bulk_comics_moved(task.files_moved, library)
36
- task.files_moved = None
37
-
38
- changed += self.bulk_folders_modified(task.dirs_modified, library)
39
- task.dirs_modified = None
40
-
41
- return changed
42
-
43
- @staticmethod
44
- def _get_query_fks_totals(fks):
45
- """Get the query foreign keys totals."""
46
- fks_total = 0
47
- for key, objs in fks.items():
48
- if key == "group_trees":
49
- for groups in objs.values():
50
- fks_total += len(groups)
51
- else:
52
- fks_total += len(objs)
53
- return fks_total
54
-
55
- def _query_one_simple_model(self, fk_field, names, create_fks, status):
56
- """Batch query one simple model name."""
57
- base_cls = Creator if fk_field in _CREATOR_FK_NAMES else Comic
58
- fk_data = create_fks, base_cls, fk_field, "name"
59
- status.complete += self.query_missing_simple_models(
60
- names,
61
- fk_data,
62
- status=status,
63
- )
64
-
65
- def query_all_missing_fks(self, library_path, fks):
66
- """Get objects to create by querying existing objects for the proposed fks."""
67
- create_creators = set()
68
- create_groups = {}
69
- update_groups = {}
70
- create_folder_paths = set()
71
- create_fks = {}
72
- self.log.debug(f"Querying existing foreign keys for comics in {library_path}")
73
- fks_total = self._get_query_fks_totals(fks)
74
- status = Status(ImportStatusTypes.QUERY_MISSING_FKS, 0, fks_total)
75
- try:
76
- self.status_controller.start(status)
77
-
78
- self.query_missing_creators( # type: ignore
79
- fks.pop("creators", {}),
80
- create_creators,
81
- status=status,
82
- )
83
-
84
- create_and_update_groups = {
85
- "create_groups": create_groups,
86
- "update_groups": update_groups,
87
- }
88
- for group_class, groups in fks.pop("group_trees", {}).items():
89
- self.query_missing_group(
90
- groups,
91
- group_class,
92
- create_and_update_groups,
93
- status=status,
94
- )
95
-
96
- self.query_missing_folder_paths(
97
- fks.pop("comic_paths", ()),
98
- library_path,
99
- create_folder_paths,
100
- status=status,
101
- )
102
-
103
- for fk_field in sorted(fks.keys()):
104
- self._query_one_simple_model(
105
- fk_field, fks.pop(fk_field), create_fks, status
106
- )
107
- finally:
108
- self.status_controller.finish(status)
109
-
110
- return (
111
- create_groups,
112
- update_groups,
113
- create_folder_paths,
114
- create_fks,
115
- create_creators,
116
- )
117
-
118
- @staticmethod
119
- def _get_create_fks_totals(create_data):
120
- (
121
- create_groups,
122
- update_groups,
123
- create_folder_paths,
124
- create_fks,
125
- create_creators,
126
- ) = create_data
127
- total_fks = 0
128
- for data_group in chain(
129
- create_groups.values(), update_groups.values(), create_fks.values()
130
- ):
131
- total_fks += len(data_group)
132
- total_fks += len(create_folder_paths) + len(create_creators)
133
- return total_fks
134
-
135
- def create_all_fks(self, library, create_data):
136
- """Bulk create all foreign keys."""
137
- total_fks = self._get_create_fks_totals(create_data)
138
- status = Status(ImportStatusTypes.CREATE_FKS, 0, total_fks)
139
- try:
140
- self.status_controller.start(status)
141
- (
142
- create_groups,
143
- update_groups,
144
- create_folder_paths,
145
- create_fks,
146
- create_creators,
147
- ) = create_data
148
-
149
- for group_class, group_tree_counts in create_groups.items():
150
- status.complete += self.bulk_group_creator( # type: ignore
151
- group_tree_counts,
152
- group_class,
153
- status=status,
154
- )
155
-
156
- for group_class, group_tree_counts in update_groups.items():
157
- status.complete += self.bulk_group_updater( # type: ignore
158
- group_tree_counts,
159
- group_class,
160
- status=status,
161
- )
162
-
163
- status.complete += self.bulk_folders_create( # type: ignore
164
- sorted(create_folder_paths),
165
- library,
166
- status=status,
167
- )
168
-
169
- for named_class, names in create_fks.items():
170
- status.complete += self.bulk_create_named_models(
171
- names,
172
- named_class,
173
- status=status,
174
- )
175
-
176
- # This must happen after creator_fks created by create_named_models
177
- status.complete += self.bulk_create_creators(
178
- create_creators,
179
- status=status,
180
- )
181
- finally:
182
- self.status_controller.finish(status)
183
- return status.complete
184
-
185
- def create_comic_relations(self, library, fks):
186
- """Query all foreign keys to determine what needs creating, then create them."""
187
- if not fks:
188
- return 0
189
- create_data = self.query_all_missing_fks(library.path, fks)
190
- return self.create_all_fks(library, create_data)
191
-
192
- def update_create_and_link_comics(
193
- self, library, modified_paths, created_paths, metadata
194
- ):
195
- """Update, create and link comics."""
196
- imported_count = self.bulk_update_comics(
197
- modified_paths,
198
- library,
199
- created_paths,
200
- metadata["mds"],
201
- )
202
- imported_count += self.bulk_create_comics(
203
- created_paths, library, metadata["mds"]
204
- )
205
- self.bulk_query_and_link_comic_m2m_fields(metadata["m2m_mds"])
206
-
207
- return imported_count
208
-
209
- def fail_imports(self, library, failed_imports, is_files_deleted):
210
- """Handle failed imports."""
211
- created_count = 0
212
- try:
213
- fis = {"update_fis": {}, "create_fis": {}, "delete_fi_paths": set()}
214
- if is_files_deleted:
215
- # if any files were deleted. Run the failed import check
216
- failed_imports["files_deleted"] = True
217
- status = Status(ImportStatusTypes.FAILED_IMPORTS, 0, len(failed_imports))
218
- status.total = self.query_failed_imports(
219
- failed_imports,
220
- library,
221
- fis,
222
- status=status,
223
- )
224
-
225
- self.bulk_update_failed_imports(
226
- fis["update_fis"],
227
- library,
228
- status=status,
229
- )
230
-
231
- created_count = self.bulk_create_failed_imports(
232
- fis["create_fis"],
233
- library,
234
- status=status,
235
- )
236
-
237
- self.bulk_cleanup_failed_imports(
238
- fis["delete_fi_paths"], library, status=status
239
- )
240
- except Exception:
241
- self.log.exception("Processing failed imports")
242
- return bool(created_count)
243
-
244
- def delete(self, library, task):
245
- """Delete files and folders."""
246
- count = self.bulk_folders_deleted(task.dirs_deleted, library)
247
- task.dirs_deleted = None
248
-
249
- count += self.bulk_comics_deleted(task.files_deleted, library)
250
- task.files_deleted = None
251
- return count
codex/pdf.py DELETED
@@ -1,115 +0,0 @@
1
- """Mimic comicbox.ComicArchive functions for PDFs."""
2
- from io import BytesIO
3
- from pathlib import Path
4
- from typing import Union
5
-
6
- import fitz
7
- from comicbox.metadata.filename import FilenameMetadata
8
- from filetype import guess
9
-
10
- from codex.logger.logging import get_logger
11
-
12
- LOG = get_logger(__name__)
13
-
14
-
15
- class PDF:
16
- """PDF class."""
17
-
18
- COVER_PAGE_INDEX = 1
19
- _SUFFIX = ".pdf"
20
- _FILE_TYPE = "PDF"
21
- MIME_TYPE = "application/pdf"
22
- _METADATA_KEY_MAP = {
23
- "tags": "keywords",
24
- "name": "title",
25
- "": "subject",
26
- }
27
-
28
- @classmethod
29
- def is_pdf(cls, path):
30
- """Is the path a pdf."""
31
- if Path(path).suffix.lower() == cls._SUFFIX:
32
- return True
33
- kind = guess(path)
34
- return kind and kind.mime == cls.MIME_TYPE
35
-
36
- def __init__(self, path: Union[Path, str], **_kwargs):
37
- """Initialize."""
38
- self._path: Path = Path(path)
39
- self._doc = None
40
- self._metadata: dict = {}
41
-
42
- def __enter__(self):
43
- """Context enter."""
44
- return self
45
-
46
- def __exit__(self, *_args):
47
- """Context exit."""
48
- self.close()
49
-
50
- def _get_doc(self):
51
- if not self._doc:
52
- self._doc = fitz.Document(self._path)
53
- return self._doc
54
-
55
- def _set_metadata_key(self, comicbox_key, pdf_key):
56
- metadata = self._get_doc().metadata
57
- if not metadata:
58
- return
59
- value = metadata.get(pdf_key)
60
- if value:
61
- if comicbox_key == "writer":
62
- if "creators" not in self._metadata:
63
- self._metadata["creators"] = []
64
- creator = {
65
- "role": comicbox_key,
66
- "person": value,
67
- }
68
- self._metadata["creators"].append(creator)
69
- else:
70
- self._metadata[comicbox_key] = value
71
-
72
- def get_file_type(self):
73
- """Return the file type like comicbox."""
74
- return self._FILE_TYPE
75
-
76
- def get_metadata(self) -> dict:
77
- """Get metadata from pdf."""
78
- if not self._metadata:
79
- try:
80
- self._metadata = FilenameMetadata(
81
- path=self._path, string=self._path.name
82
- ).metadata
83
- except Exception as exc:
84
- LOG.warning(f"Error reading filename metadata for {self._path}: {exc}")
85
- try:
86
- doc = self._get_doc()
87
- self._metadata["page_count"] = doc.page_count
88
- for comicbox_key, pdf_key in self._METADATA_KEY_MAP.items():
89
- self._set_metadata_key(comicbox_key, pdf_key)
90
- except Exception as exc:
91
- LOG.warning(f"Error reading metadata for {self._path}: {exc}")
92
- self._metadata["page_count"] = 100
93
-
94
- return self._metadata
95
-
96
- def get_page_by_index(self, index) -> bytes:
97
- """Get the page bytestream by index."""
98
- doc = self._get_doc()
99
- doc.select((index,))
100
- buffer = BytesIO()
101
- doc.save(buffer)
102
- self._doc = None
103
- return buffer.getvalue()
104
-
105
- def get_cover_image(self) -> bytes:
106
- """Get the first page as a image data."""
107
- # image_data = b""
108
- doc = self._get_doc()
109
- page = doc.load_page(0)
110
- pix = page.get_pixmap()
111
- return pix.tobytes(output="ppm")
112
-
113
- def close(self):
114
- """Get rid of the reader."""
115
- self._reader = None