shoko 0.1.3 → 0.1.5

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.
Files changed (777) hide show
  1. checksums.yaml +4 -4
  2. data/.bundle/config +0 -1
  3. data/.rubocop.yml +35 -4
  4. data/Gemfile +2 -0
  5. data/README.md +100 -1
  6. data/Rakefile +80 -1
  7. data/Shoko.gemspec +34 -0
  8. data/bin/shoko +11 -5
  9. data/bin/start +11 -5
  10. data/docs/architecture/hexagonal-adherence-checklist.md +173 -0
  11. data/lib/shoko/adapters/base_adapter.rb +30 -0
  12. data/lib/{zip.rb → shoko/adapters/book_sources/archive/zip_reader.rb} +46 -32
  13. data/lib/shoko/adapters/book_sources/book_document.rb +177 -0
  14. data/lib/shoko/adapters/book_sources/book_file_probe.rb +24 -0
  15. data/lib/shoko/adapters/book_sources/book_finder/directory_scanner.rb +152 -0
  16. data/lib/shoko/adapters/book_sources/book_finder/scanner_context.rb +30 -0
  17. data/lib/shoko/adapters/book_sources/{epub_finder.rb → book_finder.rb} +62 -46
  18. data/lib/shoko/adapters/book_sources/cache_import_adapter.rb +30 -0
  19. data/lib/shoko/adapters/book_sources/document_loader_adapter.rb +72 -0
  20. data/lib/shoko/adapters/book_sources/document_service.rb +133 -169
  21. data/lib/shoko/adapters/book_sources/download_service.rb +22 -21
  22. data/lib/shoko/adapters/book_sources/epub/epub_importer.rb +266 -0
  23. data/lib/shoko/adapters/book_sources/epub/epub_resource_loader.rb +138 -127
  24. data/lib/shoko/adapters/book_sources/fb2/fb2_importer.rb +336 -0
  25. data/lib/shoko/adapters/book_sources/folder_scanner.rb +92 -0
  26. data/lib/shoko/adapters/book_sources/gutendex_client.rb +137 -110
  27. data/lib/shoko/adapters/book_sources/kindle/kindle_importer.rb +422 -0
  28. data/lib/shoko/adapters/book_sources/library_scanner.rb +144 -71
  29. data/lib/shoko/adapters/book_sources/metadata_reader_adapter.rb +72 -0
  30. data/lib/shoko/adapters/book_sources/pdf/pdf_importer.rb +364 -0
  31. data/lib/shoko/adapters/book_sources/rtf/rtf_importer.rb +303 -0
  32. data/lib/shoko/adapters/input/annotations/mouse_handler.rb +130 -126
  33. data/lib/shoko/adapters/input/cli.rb +439 -0
  34. data/lib/shoko/adapters/input/command_factory.rb +276 -201
  35. data/lib/shoko/adapters/input/commands.rb +77 -47
  36. data/lib/shoko/adapters/input/controllers/annotation_overlay_controller.rb +362 -0
  37. data/lib/shoko/adapters/input/controllers/dependencies/menu_controller_dependencies.rb +134 -0
  38. data/lib/shoko/adapters/input/controllers/dependencies/reader_controller_dependencies.rb +390 -0
  39. data/lib/shoko/adapters/input/controllers/dictionary/constants.rb +80 -0
  40. data/lib/shoko/adapters/input/controllers/dictionary/controller_support.rb +117 -0
  41. data/lib/shoko/adapters/input/controllers/dictionary/display_mode_support.rb +65 -0
  42. data/lib/shoko/adapters/input/controllers/dictionary/index.rb +7 -0
  43. data/lib/shoko/adapters/input/controllers/dictionary/language_pair_support.rb +190 -0
  44. data/lib/shoko/adapters/input/controllers/dictionary/setup_flow/download_support.rb +97 -0
  45. data/lib/shoko/adapters/input/controllers/dictionary/setup_flow/interaction_handlers.rb +106 -0
  46. data/lib/shoko/adapters/input/controllers/dictionary/setup_flow/lookup_flow.rb +106 -0
  47. data/lib/shoko/adapters/input/controllers/dictionary/setup_flow/popup_state_support.rb +99 -0
  48. data/lib/shoko/adapters/input/controllers/dictionary/setup_flow/submission_flow.rb +100 -0
  49. data/lib/shoko/adapters/input/controllers/dictionary/setup_flow_support.rb +26 -0
  50. data/lib/shoko/adapters/input/controllers/dictionary_controller.rb +304 -0
  51. data/lib/shoko/adapters/input/controllers/in_book_search_controller.rb +202 -0
  52. data/lib/shoko/adapters/input/controllers/menu/actions/dictionary_actions.rb +121 -0
  53. data/lib/shoko/adapters/input/controllers/menu/actions/download_actions.rb +74 -0
  54. data/lib/shoko/adapters/input/controllers/menu/actions/lifecycle_actions.rb +157 -0
  55. data/lib/shoko/adapters/input/controllers/menu/actions/navigation_actions.rb +141 -0
  56. data/lib/shoko/adapters/input/controllers/menu/actions/settings_actions.rb +128 -0
  57. data/lib/shoko/adapters/input/controllers/menu/controller.rb +430 -0
  58. data/lib/shoko/adapters/input/controllers/menu/input_controller.rb +210 -0
  59. data/lib/shoko/adapters/input/controllers/menu/intent_executor_bridge.rb +115 -0
  60. data/lib/shoko/adapters/input/controllers/menu/menu_workflow_bridges.rb +100 -0
  61. data/lib/shoko/adapters/input/controllers/menu/reader_launch_bridges.rb +78 -0
  62. data/lib/shoko/adapters/input/controllers/menu/state_controller.rb +123 -0
  63. data/lib/shoko/adapters/input/controllers/mouseable_reader.rb +243 -0
  64. data/lib/shoko/adapters/input/controllers/reader/event_loop.rb +100 -0
  65. data/lib/shoko/adapters/input/controllers/reader/input_router.rb +68 -0
  66. data/lib/shoko/adapters/input/controllers/reader/intent_executor_bridge.rb +107 -0
  67. data/lib/shoko/adapters/input/controllers/reader/lifecycle_runner.rb +117 -0
  68. data/lib/shoko/adapters/input/controllers/reader/render_metrics.rb +54 -0
  69. data/lib/shoko/adapters/input/controllers/reader/render_requester_bridge.rb +35 -0
  70. data/lib/shoko/adapters/input/controllers/reader/startup_loader.rb +61 -0
  71. data/lib/shoko/adapters/input/controllers/reader/startup_sequence.rb +64 -0
  72. data/lib/shoko/adapters/input/controllers/reader_controller.rb +519 -0
  73. data/lib/shoko/adapters/input/controllers/selection_mouse_handler.rb +212 -0
  74. data/lib/shoko/adapters/input/controllers/sidebar/anchor_resolver.rb +88 -0
  75. data/lib/shoko/adapters/input/controllers/sidebar/tab_state_orchestrator.rb +151 -0
  76. data/lib/shoko/adapters/input/controllers/sidebar/toc_facade.rb +71 -0
  77. data/lib/shoko/adapters/input/controllers/sidebar/toc_navigation.rb +77 -0
  78. data/lib/shoko/adapters/input/controllers/sidebar_controller.rb +309 -0
  79. data/lib/shoko/adapters/input/controllers/sidebar_mouse_handler.rb +201 -0
  80. data/lib/shoko/adapters/input/controllers/state_controller/annotation_actions.rb +119 -0
  81. data/lib/shoko/adapters/input/controllers/state_controller/bookmark_actions.rb +154 -0
  82. data/lib/shoko/adapters/input/controllers/state_controller/progress_actions.rb +149 -0
  83. data/lib/shoko/adapters/input/controllers/state_controller.rb +97 -0
  84. data/lib/shoko/adapters/input/controllers/support/message_notifier.rb +29 -0
  85. data/lib/shoko/adapters/input/controllers/ui_controller/delegation_facade.rb +325 -0
  86. data/lib/shoko/adapters/input/controllers/ui_controller/mode_switching.rb +62 -0
  87. data/lib/shoko/adapters/input/controllers/ui_controller/popup_actions.rb +84 -0
  88. data/lib/shoko/adapters/input/controllers/ui_controller.rb +109 -0
  89. data/lib/shoko/adapters/input/dispatcher.rb +50 -48
  90. data/lib/shoko/adapters/input/input_system_factory_adapter.rb +33 -0
  91. data/lib/shoko/adapters/input/key_classifier_adapter.rb +64 -0
  92. data/lib/shoko/adapters/input/reader_input_controller.rb +342 -0
  93. data/lib/shoko/adapters/input/validators/file_path_validator.rb +70 -63
  94. data/lib/shoko/adapters/input/validators/terminal_size_validator.rb +65 -60
  95. data/lib/shoko/adapters/monitoring/logger_adapter.rb +161 -0
  96. data/lib/shoko/adapters/monitoring/perf_tracer.rb +146 -142
  97. data/lib/shoko/adapters/monitoring/performance_monitor.rb +19 -24
  98. data/lib/shoko/adapters/output/clipboard/clipboard_service.rb +80 -90
  99. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/image_builder.rb +118 -118
  100. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/table_renderer.rb +365 -0
  101. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/text_wrapper.rb +167 -103
  102. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler/tokenizer.rb +213 -61
  103. data/lib/shoko/adapters/output/formatting/formatting_service/line_assembler.rb +288 -109
  104. data/lib/shoko/adapters/output/formatting/formatting_service/plain_lines_builder.rb +48 -40
  105. data/lib/shoko/adapters/output/formatting/formatting_service.rb +243 -196
  106. data/lib/shoko/adapters/output/formatting/wrapped_lines_provider_adapter.rb +43 -0
  107. data/lib/shoko/adapters/output/formatting/wrapping_service.rb +203 -180
  108. data/lib/shoko/adapters/output/instrumentation_service.rb +58 -23
  109. data/lib/shoko/adapters/output/kitty/display_capabilities.rb +21 -0
  110. data/lib/shoko/adapters/output/kitty/image_transcoder.rb +53 -49
  111. data/lib/shoko/adapters/output/kitty/kitty_graphics.rb +96 -92
  112. data/lib/shoko/adapters/output/kitty/kitty_image_renderer.rb +215 -226
  113. data/lib/shoko/adapters/output/kitty/kitty_unicode_placeholders.rb +4 -129
  114. data/lib/shoko/adapters/output/kitty/resource_loader.rb +34 -0
  115. data/lib/shoko/adapters/output/layout/layout_metrics_adapter.rb +68 -0
  116. data/lib/shoko/adapters/output/notification_service.rb +19 -17
  117. data/lib/shoko/adapters/output/terminal/buffer.rb +364 -218
  118. data/lib/shoko/adapters/output/terminal/cli_progress_renderer.rb +104 -0
  119. data/lib/shoko/adapters/output/terminal/constants/terminal_defaults.rb +9 -5
  120. data/lib/shoko/adapters/output/terminal/input/decoder.rb +375 -373
  121. data/lib/shoko/adapters/output/terminal/input.rb +204 -123
  122. data/lib/shoko/adapters/output/terminal/null_runtime_config.rb +23 -0
  123. data/lib/shoko/adapters/output/terminal/output.rb +43 -96
  124. data/lib/shoko/adapters/output/terminal/terminal.rb +202 -144
  125. data/lib/shoko/adapters/output/terminal/terminal_sanitizer.rb +7 -232
  126. data/lib/shoko/adapters/output/terminal/terminal_service.rb +121 -105
  127. data/lib/shoko/adapters/output/terminal/terminal_session_adapter.rb +32 -0
  128. data/lib/shoko/adapters/output/terminal/text_metrics.rb +4 -263
  129. data/lib/shoko/adapters/output/terminal/text_metrics_port_adapter.rb +41 -0
  130. data/lib/shoko/adapters/output/terminal/text_sanitizer_adapter.rb +23 -0
  131. data/lib/shoko/adapters/output/terminal_capabilities_adapter.rb +23 -0
  132. data/lib/shoko/adapters/runtime/app_mode_runner_adapter.rb +27 -0
  133. data/lib/shoko/adapters/runtime/env_runtime_config_adapter.rb +169 -0
  134. data/lib/shoko/adapters/runtime/monotonic_clock_adapter.rb +18 -0
  135. data/lib/shoko/adapters/runtime/null_runtime_config.rb +21 -0
  136. data/lib/shoko/adapters/runtime/process_control_adapter.rb +18 -0
  137. data/lib/shoko/adapters/runtime/rexml_security_limits_adapter.rb +35 -0
  138. data/lib/shoko/adapters/runtime/session_state/actions/base_action.rb +28 -0
  139. data/lib/shoko/adapters/runtime/session_state/actions/quit_to_menu_action.rb +20 -0
  140. data/lib/shoko/adapters/runtime/session_state/actions/switch_reader_mode_action.rb +26 -0
  141. data/lib/shoko/adapters/runtime/session_state/actions/toggle_view_mode_action.rb +35 -0
  142. data/lib/shoko/adapters/runtime/session_state/actions/update_config_action.rb +30 -0
  143. data/lib/shoko/adapters/runtime/session_state/actions/update_field_helpers.rb +30 -0
  144. data/lib/shoko/adapters/runtime/session_state/actions/update_menu_action.rb +21 -0
  145. data/lib/shoko/adapters/runtime/session_state/actions/update_message_action.rb +26 -0
  146. data/lib/shoko/adapters/runtime/session_state/actions/update_pagination_state_action.rb +23 -0
  147. data/lib/shoko/adapters/runtime/session_state/actions/update_reader_meta_action.rb +23 -0
  148. data/lib/shoko/adapters/runtime/session_state/actions/update_rendered_lines_action.rb +34 -0
  149. data/lib/shoko/adapters/runtime/session_state/actions/update_sidebar_action.rb +42 -0
  150. data/lib/shoko/adapters/runtime/session_state/actions/update_state_action.rb +76 -0
  151. data/lib/shoko/adapters/runtime/session_state/actions/update_ui_loading_action.rb +23 -0
  152. data/lib/shoko/adapters/runtime/session_state/config_reader_adapter.rb +82 -0
  153. data/lib/shoko/adapters/runtime/session_state/event_bus.rb +85 -0
  154. data/lib/shoko/adapters/runtime/session_state/event_publisher_adapter.rb +24 -0
  155. data/lib/shoko/adapters/runtime/session_state/menu_launch_state_adapter.rb +32 -0
  156. data/lib/shoko/adapters/runtime/session_state/menu_state_reader_adapter.rb +246 -0
  157. data/lib/shoko/adapters/runtime/session_state/menu_state_writer_adapter.rb +157 -0
  158. data/lib/shoko/adapters/runtime/session_state/notification_writer_adapter.rb +39 -0
  159. data/lib/shoko/adapters/runtime/session_state/observer_registry_adapter.rb +32 -0
  160. data/lib/shoko/adapters/runtime/session_state/observer_state_store.rb +129 -0
  161. data/lib/shoko/adapters/runtime/session_state/reader_launch_state_adapter.rb +45 -0
  162. data/lib/shoko/adapters/runtime/session_state/reader_state_reader_adapter.rb +162 -0
  163. data/lib/shoko/adapters/runtime/session_state/render_state_writer_adapter.rb +58 -0
  164. data/lib/shoko/adapters/runtime/session_state/rendered_content_reader_adapter.rb +29 -0
  165. data/lib/shoko/adapters/runtime/session_state/selectors/config_selectors.rb +82 -0
  166. data/lib/shoko/adapters/runtime/session_state/selectors/menu_selectors.rb +90 -0
  167. data/lib/shoko/adapters/runtime/session_state/selectors/reader_selectors.rb +191 -0
  168. data/lib/shoko/adapters/runtime/session_state/sidebar_state_reader_adapter.rb +67 -0
  169. data/lib/shoko/adapters/runtime/session_state/state_store/change_event_builder.rb +35 -0
  170. data/lib/shoko/adapters/runtime/session_state/state_store/config_persistence.rb +91 -0
  171. data/lib/shoko/adapters/runtime/session_state/state_store/initial_state_builder.rb +113 -0
  172. data/lib/shoko/adapters/runtime/session_state/state_store/transition_validator.rb +83 -0
  173. data/lib/shoko/adapters/runtime/session_state/state_store.rb +328 -0
  174. data/lib/shoko/adapters/runtime/session_state/state_writer_adapter.rb +134 -0
  175. data/lib/shoko/adapters/runtime/session_state/ui_state_reader_adapter.rb +50 -0
  176. data/lib/shoko/adapters/runtime/system_wall_clock_adapter.rb +18 -0
  177. data/lib/shoko/adapters/runtime/uuid_generator_adapter.rb +19 -0
  178. data/lib/shoko/adapters/storage/atomic_file_writer.rb +52 -24
  179. data/lib/shoko/adapters/storage/background_worker.rb +49 -43
  180. data/lib/shoko/adapters/storage/background_worker_builder_adapter.rb +24 -0
  181. data/lib/shoko/adapters/storage/book_cache_pipeline/cache_integrity_checker.rb +36 -0
  182. data/lib/shoko/adapters/storage/book_cache_pipeline/cache_session.rb +150 -0
  183. data/lib/shoko/adapters/storage/book_cache_pipeline/cache_status.rb +34 -0
  184. data/lib/shoko/adapters/storage/book_cache_pipeline/fingerprint_filter.rb +52 -0
  185. data/lib/shoko/adapters/storage/book_cache_pipeline/load_error_handler.rb +25 -0
  186. data/lib/shoko/adapters/storage/book_cache_pipeline/manifest_row.rb +74 -0
  187. data/lib/shoko/adapters/storage/book_cache_pipeline/manifest_sha_finder.rb +169 -0
  188. data/lib/shoko/adapters/storage/book_cache_pipeline/payload_context.rb +21 -0
  189. data/lib/shoko/adapters/storage/book_cache_pipeline/pointer_file_ensurer.rb +49 -0
  190. data/lib/shoko/adapters/storage/book_cache_pipeline.rb +122 -596
  191. data/lib/shoko/adapters/storage/book_cache_pipeline_factory_adapter.rb +23 -0
  192. data/lib/shoko/adapters/storage/cache/epub/memory_cache.rb +73 -71
  193. data/lib/shoko/adapters/storage/cache/epub/persistence.rb +124 -122
  194. data/lib/shoko/adapters/storage/cache/epub/serializer/deserialize.rb +195 -191
  195. data/lib/shoko/adapters/storage/cache/epub/serializer/helpers.rb +54 -47
  196. data/lib/shoko/adapters/storage/cache/epub/serializer/serialize.rb +65 -62
  197. data/lib/shoko/adapters/storage/cache/epub/source_reference.rb +43 -41
  198. data/lib/shoko/adapters/storage/cache_availability_adapter.rb +105 -0
  199. data/lib/shoko/adapters/storage/cache_manager_adapter.rb +28 -0
  200. data/lib/shoko/adapters/storage/cache_paths.rb +15 -13
  201. data/lib/shoko/adapters/storage/cache_pointer_manager.rb +42 -40
  202. data/lib/shoko/adapters/storage/cache_pointer_resolver.rb +35 -0
  203. data/lib/shoko/adapters/storage/config_paths.rb +22 -20
  204. data/lib/shoko/adapters/storage/config_storage_adapter.rb +73 -0
  205. data/lib/shoko/adapters/storage/data_cleanup_adapter.rb +83 -0
  206. data/lib/shoko/adapters/storage/dictionary_availability_adapter.rb +23 -0
  207. data/lib/shoko/adapters/storage/dictionary_catalog_service.rb +148 -0
  208. data/lib/shoko/adapters/storage/dictionary_storage_adapter.rb +84 -0
  209. data/lib/shoko/adapters/storage/epub_cache.rb +137 -152
  210. data/lib/shoko/adapters/storage/file_probe_adapter.rb +26 -0
  211. data/lib/shoko/adapters/storage/file_writer_service.rb +18 -22
  212. data/lib/shoko/adapters/storage/json_cache_store/chapters.rb +104 -102
  213. data/lib/shoko/adapters/storage/json_cache_store/layouts.rb +52 -49
  214. data/lib/shoko/adapters/storage/json_cache_store/manifest.rb +125 -30
  215. data/lib/shoko/adapters/storage/json_cache_store/payload_helpers.rb +109 -106
  216. data/lib/shoko/adapters/storage/json_cache_store/resources.rb +62 -60
  217. data/lib/shoko/adapters/storage/json_cache_store.rb +138 -134
  218. data/lib/shoko/adapters/storage/lazy_file_string.rb +40 -51
  219. data/lib/shoko/adapters/storage/pagination_cache.rb +107 -97
  220. data/lib/shoko/adapters/storage/path_ops_adapter.rb +30 -0
  221. data/lib/shoko/adapters/storage/recent_files.rb +96 -58
  222. data/lib/shoko/adapters/storage/recent_files_repository.rb +27 -0
  223. data/lib/shoko/adapters/storage/repositories/annotation_repository.rb +144 -156
  224. data/lib/shoko/adapters/storage/repositories/base_repository.rb +68 -63
  225. data/lib/shoko/adapters/storage/repositories/bookmark_repository.rb +111 -106
  226. data/lib/shoko/adapters/storage/repositories/cached_library_repository.rb +100 -97
  227. data/lib/shoko/adapters/storage/repositories/progress_repository.rb +128 -142
  228. data/lib/shoko/adapters/storage/repositories/storage/annotation_file_store.rb +92 -118
  229. data/lib/shoko/adapters/storage/repositories/storage/base_file_store.rb +40 -0
  230. data/lib/shoko/adapters/storage/repositories/storage/bookmark_file_store.rb +77 -92
  231. data/lib/shoko/adapters/storage/repositories/storage/file_store_utils.rb +13 -9
  232. data/lib/shoko/adapters/storage/repositories/storage/progress_file_store.rb +34 -50
  233. data/lib/shoko/adapters/storage/sqlite_dictionary_adapter.rb +301 -0
  234. data/lib/shoko/adapters/support/lifecycle_helpers.rb +73 -0
  235. data/lib/shoko/adapters/ui/builders/page_setup_builder.rb +51 -0
  236. data/lib/shoko/adapters/ui/component_factory.rb +103 -0
  237. data/lib/shoko/adapters/ui/components/annotation_editor_overlay/footer_renderer.rb +84 -0
  238. data/lib/shoko/adapters/ui/components/annotation_editor_overlay/geometry.rb +65 -0
  239. data/lib/shoko/adapters/ui/components/annotation_editor_overlay/note_renderer.rb +91 -0
  240. data/lib/shoko/adapters/ui/components/annotation_editor_overlay_component.rb +415 -0
  241. data/lib/shoko/adapters/ui/components/annotations_overlay/list_renderer.rb +148 -0
  242. data/lib/shoko/adapters/ui/components/annotations_overlay_component.rb +210 -0
  243. data/lib/shoko/adapters/ui/components/base_component.rb +118 -0
  244. data/lib/shoko/adapters/ui/components/content_component.rb +67 -0
  245. data/lib/shoko/adapters/ui/components/dictionary/entry_formatter.rb +232 -0
  246. data/lib/shoko/adapters/ui/components/dictionary_panel_component.rb +290 -0
  247. data/lib/shoko/adapters/ui/components/dictionary_popup/results_flow.rb +122 -0
  248. data/lib/shoko/adapters/ui/components/dictionary_popup/setup_flow.rb +479 -0
  249. data/lib/shoko/adapters/ui/components/dictionary_popup_component.rb +280 -0
  250. data/lib/shoko/adapters/ui/components/enhanced_popup_menu.rb +345 -0
  251. data/lib/shoko/adapters/ui/components/footer_component.rb +126 -0
  252. data/lib/shoko/adapters/ui/components/header_component.rb +48 -0
  253. data/lib/shoko/adapters/ui/components/in_book_search_popup_component.rb +509 -0
  254. data/lib/shoko/adapters/ui/components/layouts/horizontal.rb +67 -0
  255. data/lib/shoko/adapters/ui/components/layouts/horizontal_three.rb +94 -0
  256. data/lib/shoko/adapters/ui/components/layouts/vertical.rb +77 -0
  257. data/lib/shoko/adapters/ui/components/main_menu_component.rb +130 -0
  258. data/lib/shoko/adapters/ui/components/menu_design/frame_renderer.rb +93 -0
  259. data/lib/shoko/adapters/ui/components/menu_design/icon_set.rb +69 -0
  260. data/lib/shoko/adapters/ui/components/menu_design/layout.rb +33 -0
  261. data/lib/shoko/adapters/ui/components/menu_design/progress_renderer.rb +36 -0
  262. data/lib/shoko/adapters/ui/components/menu_design/search_field_renderer.rb +41 -0
  263. data/lib/shoko/adapters/ui/components/menu_design/status_renderer.rb +49 -0
  264. data/lib/shoko/adapters/ui/components/menu_design/table_renderer.rb +59 -0
  265. data/lib/shoko/adapters/ui/components/menu_design/theme_tokens.rb +73 -0
  266. data/lib/shoko/adapters/ui/components/rect.rb +19 -0
  267. data/lib/shoko/adapters/ui/components/render_style.rb +125 -0
  268. data/lib/shoko/adapters/ui/components/screen_component.rb +28 -0
  269. data/lib/shoko/adapters/ui/components/screens/annotation_detail_screen_component.rb +193 -0
  270. data/lib/shoko/adapters/ui/components/screens/annotation_edit_screen_component.rb +298 -0
  271. data/lib/shoko/adapters/ui/components/screens/annotation_editor_screen_component.rb +268 -0
  272. data/lib/shoko/adapters/ui/components/screens/annotation_rendering_helpers.rb +462 -0
  273. data/lib/shoko/adapters/ui/components/screens/annotations_screen_component.rb +398 -0
  274. data/lib/shoko/adapters/ui/components/screens/base_screen_component.rb +53 -0
  275. data/lib/shoko/adapters/ui/components/screens/browse_screen_component.rb +384 -0
  276. data/lib/shoko/adapters/ui/components/screens/dictionary_settings_screen_component.rb +501 -0
  277. data/lib/shoko/adapters/ui/components/screens/download_books_screen_component.rb +352 -0
  278. data/lib/shoko/adapters/ui/components/screens/library_screen_component.rb +383 -0
  279. data/lib/shoko/adapters/ui/components/screens/loading_overlay_component.rb +63 -0
  280. data/lib/shoko/adapters/ui/components/screens/menu_screen_component.rb +103 -0
  281. data/lib/shoko/adapters/ui/components/screens/settings_screen_component.rb +324 -0
  282. data/lib/shoko/adapters/ui/components/sidebar/annotations_tab_renderer.rb +170 -0
  283. data/lib/shoko/adapters/ui/components/sidebar/bookmarks_tab_renderer.rb +160 -0
  284. data/lib/shoko/adapters/ui/components/sidebar/tab_header_component.rb +168 -0
  285. data/lib/shoko/adapters/ui/components/sidebar/toc/constants.rb +23 -0
  286. data/lib/shoko/adapters/ui/components/sidebar/toc/context.rb +101 -0
  287. data/lib/shoko/adapters/ui/components/sidebar/toc/document_model.rb +59 -0
  288. data/lib/shoko/adapters/ui/components/sidebar/toc/entries_calculator.rb +99 -0
  289. data/lib/shoko/adapters/ui/components/sidebar/toc/entry_rendering.rb +185 -0
  290. data/lib/shoko/adapters/ui/components/sidebar/toc/index.rb +11 -0
  291. data/lib/shoko/adapters/ui/components/sidebar/toc/layout/entries_list_layout.rb +81 -0
  292. data/lib/shoko/adapters/ui/components/sidebar/toc/layout/entry_layout_helper.rb +48 -0
  293. data/lib/shoko/adapters/ui/components/sidebar/toc/layout/line_index.rb +63 -0
  294. data/lib/shoko/adapters/ui/components/sidebar/toc/layout/visible_entry_item.rb +97 -0
  295. data/lib/shoko/adapters/ui/components/sidebar/toc/layout/visible_items_calculator.rb +87 -0
  296. data/lib/shoko/adapters/ui/components/sidebar/toc/layout.rb +7 -0
  297. data/lib/shoko/adapters/ui/components/sidebar/toc/renderers/component_orchestrator.rb +27 -0
  298. data/lib/shoko/adapters/ui/components/sidebar/toc/renderers/empty_state_renderer.rb +51 -0
  299. data/lib/shoko/adapters/ui/components/sidebar/toc/renderers/entries_list_renderer.rb +28 -0
  300. data/lib/shoko/adapters/ui/components/sidebar/toc/renderers/filter_input_renderer.rb +52 -0
  301. data/lib/shoko/adapters/ui/components/sidebar/toc/renderers/header_renderer.rb +176 -0
  302. data/lib/shoko/adapters/ui/components/sidebar/toc/renderers/scrollbar_renderer.rb +51 -0
  303. data/lib/shoko/adapters/ui/components/sidebar/toc/renderers.rb +8 -0
  304. data/lib/shoko/adapters/ui/components/sidebar/toc/scroll_metrics.rb +152 -0
  305. data/lib/shoko/adapters/ui/components/sidebar/toc/tree_formatting.rb +202 -0
  306. data/lib/shoko/adapters/ui/components/sidebar/toc_tab_renderer.rb +132 -0
  307. data/lib/shoko/adapters/ui/components/sidebar_panel_component.rb +229 -0
  308. data/lib/shoko/adapters/ui/components/surface.rb +92 -0
  309. data/lib/shoko/adapters/ui/components/tooltip_overlay_component.rb +251 -0
  310. data/lib/shoko/adapters/ui/components/ui/annotation_list_input.rb +102 -0
  311. data/lib/shoko/adapters/ui/components/ui/annotation_markup.rb +456 -0
  312. data/lib/shoko/adapters/ui/components/ui/box_drawer.rb +37 -0
  313. data/lib/shoko/adapters/ui/components/ui/cursor_blink.rb +104 -0
  314. data/lib/shoko/adapters/ui/components/ui/list_helpers.rb +37 -0
  315. data/lib/shoko/adapters/ui/components/ui/overlay_layout.rb +92 -0
  316. data/lib/shoko/adapters/ui/components/ui/text_utils.rb +50 -0
  317. data/lib/shoko/adapters/ui/constants/highlighting.rb +23 -0
  318. data/lib/shoko/adapters/ui/constants/messages.rb +14 -0
  319. data/lib/shoko/adapters/ui/constants/themes.rb +81 -0
  320. data/lib/shoko/adapters/ui/constants/ui_constants.rb +155 -0
  321. data/lib/shoko/adapters/ui/dependency_sets.rb +49 -0
  322. data/lib/shoko/adapters/ui/menu_visual_profile.rb +10 -0
  323. data/lib/shoko/adapters/ui/render_registry.rb +42 -0
  324. data/lib/shoko/adapters/ui/rendering/frame_coordinator.rb +55 -0
  325. data/lib/shoko/adapters/ui/rendering/line/config_helpers.rb +58 -0
  326. data/lib/shoko/adapters/ui/rendering/line/inline_segment_highlighter.rb +163 -0
  327. data/lib/shoko/adapters/ui/rendering/line/kitty_image_line_renderer.rb +256 -0
  328. data/lib/shoko/adapters/ui/rendering/line/line_content_composer.rb +253 -0
  329. data/lib/shoko/adapters/ui/rendering/line/line_drawer.rb +96 -0
  330. data/lib/shoko/adapters/ui/rendering/line/line_geometry_builder.rb +133 -0
  331. data/lib/shoko/adapters/ui/rendering/line/render_dependencies.rb +30 -0
  332. data/lib/shoko/adapters/ui/rendering/line/rendered_lines_recorder.rb +73 -0
  333. data/lib/shoko/adapters/ui/rendering/line/wrapped_lines_fetcher.rb +140 -0
  334. data/lib/shoko/adapters/{output → ui}/rendering/models/line_geometry.rb +4 -4
  335. data/lib/shoko/adapters/{output → ui}/rendering/models/page_rendering_context.rb +1 -1
  336. data/lib/shoko/adapters/{output → ui}/rendering/models/render_params.rb +1 -1
  337. data/lib/shoko/adapters/ui/rendering/models/rendering_context.rb +67 -0
  338. data/lib/shoko/adapters/ui/rendering/reader_render_coordinator.rb +272 -0
  339. data/lib/shoko/adapters/ui/rendering/render_pipeline.rb +75 -0
  340. data/lib/shoko/adapters/ui/rendering/views/base_view_renderer.rb +218 -0
  341. data/lib/shoko/adapters/ui/rendering/views/help_renderer.rb +67 -0
  342. data/lib/shoko/adapters/ui/rendering/views/single_view_renderer.rb +190 -0
  343. data/lib/shoko/adapters/ui/rendering/views/split_view_renderer.rb +261 -0
  344. data/lib/shoko/adapters/ui/rendering/views/view_renderer_factory.rb +27 -0
  345. data/lib/shoko/adapters/ui/rendering_factory.rb +48 -0
  346. data/lib/shoko/adapters/ui/sessions/annotation_editor_launcher_adapter.rb +29 -0
  347. data/lib/shoko/adapters/ui/sessions/annotation_overlay_ui_session_adapter.rb +294 -0
  348. data/lib/shoko/adapters/ui/sessions/dictionary_ui_session_adapter.rb +342 -0
  349. data/lib/shoko/adapters/ui/sessions/in_book_search_ui_session_adapter.rb +189 -0
  350. data/lib/shoko/adapters/ui/view_models/reader_view_model.rb +179 -0
  351. data/lib/shoko/adapters/ui/view_models/reader_view_model_builder.rb +64 -0
  352. data/lib/shoko/application/cli_progress_presenter.rb +48 -0
  353. data/lib/shoko/application/pending_jump_handler.rb +55 -71
  354. data/lib/shoko/application/services/pagination/page_info_calculator.rb +262 -0
  355. data/lib/shoko/application/services/pagination/pagination_cache_preloader.rb +232 -0
  356. data/lib/shoko/application/services/pagination/pagination_coordinator.rb +279 -0
  357. data/lib/shoko/application/services/pagination/pagination_orchestrator.rb +399 -0
  358. data/lib/shoko/application/services/popup_position_service.rb +35 -0
  359. data/lib/shoko/application/services/reader/annotation_state_service.rb +53 -0
  360. data/lib/shoko/application/services/reader/bookmark_service.rb +318 -0
  361. data/lib/shoko/application/services/reader/navigation/absolute_change_applier.rb +98 -0
  362. data/lib/shoko/application/services/reader/navigation/absolute_layout.rb +128 -0
  363. data/lib/shoko/application/services/reader/navigation/absolute_strategy.rb +181 -0
  364. data/lib/shoko/application/services/reader/navigation/context_builder.rb +77 -0
  365. data/lib/shoko/application/services/reader/navigation/context_helpers.rb +77 -0
  366. data/lib/shoko/application/services/reader/navigation/dynamic_change_applier.rb +49 -0
  367. data/lib/shoko/application/services/reader/navigation/dynamic_strategy.rb +53 -0
  368. data/lib/shoko/application/services/reader/navigation/image_offset_snapper.rb +183 -0
  369. data/lib/shoko/application/services/reader/navigation/nav_context.rb +29 -0
  370. data/lib/shoko/application/services/reader/navigation/state_updater.rb +26 -0
  371. data/lib/shoko/application/services/reader/navigation/strategy_factory.rb +22 -0
  372. data/lib/shoko/application/services/reader/navigation_service.rb +169 -0
  373. data/lib/shoko/application/unified_application.rb +134 -18
  374. data/lib/shoko/application/use_cases/catalog_service.rb +52 -38
  375. data/lib/shoko/application/use_cases/command_bus.rb +170 -0
  376. data/lib/shoko/application/use_cases/commands/base_command.rb +125 -135
  377. data/lib/shoko/application/use_cases/commands/bookmark_commands.rb +70 -84
  378. data/lib/shoko/application/use_cases/commands/input_command_payload.rb +13 -0
  379. data/lib/shoko/application/use_cases/commands/menu_intent_command.rb +51 -0
  380. data/lib/shoko/application/use_cases/commands/navigation_commands.rb +151 -134
  381. data/lib/shoko/application/use_cases/commands/reader_intent_command.rb +51 -0
  382. data/lib/shoko/application/use_cases/commands/shared_intent_command.rb +55 -0
  383. data/lib/shoko/application/use_cases/intents/menu_intent_handler.rb +47 -0
  384. data/lib/shoko/application/use_cases/intents/reader_intent_handler.rb +47 -0
  385. data/lib/shoko/application/use_cases/settings_service.rb +173 -55
  386. data/lib/shoko/application/workflows/cli/folder_import_workflow.rb +164 -0
  387. data/lib/shoko/application/workflows/menu/annotation_workflow.rb +132 -0
  388. data/lib/shoko/application/workflows/menu/dictionary_workflow.rb +203 -0
  389. data/lib/shoko/application/workflows/menu/download_workflow.rb +181 -0
  390. data/lib/shoko/application/workflows/menu/menu_progress_presenter.rb +85 -0
  391. data/lib/shoko/application/workflows/menu/null_progress_presenter.rb +24 -0
  392. data/lib/shoko/application/workflows/menu/reader_launch/contracts.rb +70 -0
  393. data/lib/shoko/application/workflows/menu/reader_launch/document_preparation.rb +137 -0
  394. data/lib/shoko/application/workflows/menu/reader_launch/path_resolution.rb +76 -0
  395. data/lib/shoko/application/workflows/menu/reader_launch/progress_orchestration.rb +203 -0
  396. data/lib/shoko/application/workflows/menu/reader_launch/runtime_execution.rb +106 -0
  397. data/lib/shoko/application/workflows/menu/reader_launch_service.rb +161 -0
  398. data/lib/shoko/bootstrap/container_factory/controller_composition/menu_builder.rb +163 -0
  399. data/lib/shoko/bootstrap/container_factory/controller_composition/menu_state_controller_composer.rb +174 -0
  400. data/lib/shoko/bootstrap/container_factory/controller_composition/reader_builder.rb +400 -0
  401. data/lib/shoko/bootstrap/container_factory/controller_composition/reader_runtime_assembler.rb +267 -0
  402. data/lib/shoko/bootstrap/container_factory/controller_composition.rb +19 -0
  403. data/lib/shoko/bootstrap/container_factory/domain_application_registration.rb +340 -0
  404. data/lib/shoko/bootstrap/container_factory/infrastructure_registration.rb +123 -0
  405. data/lib/shoko/bootstrap/container_factory/port_and_repository_registration.rb +241 -0
  406. data/lib/shoko/bootstrap/container_factory/test_container_registration.rb +198 -0
  407. data/lib/shoko/bootstrap/container_factory.rb +174 -0
  408. data/lib/shoko/bootstrap/dependency_container.rb +142 -0
  409. data/lib/shoko/bootstrap/format_registry_bootstrap.rb +137 -0
  410. data/lib/shoko/bootstrap/runtime_bootstrap.rb +86 -0
  411. data/lib/shoko/core/book_formats/epub/html_processor.rb +146 -0
  412. data/lib/shoko/core/book_formats/epub/metadata_extractor.rb +65 -0
  413. data/lib/shoko/core/book_formats/epub/opf/entry_reader.rb +120 -0
  414. data/lib/shoko/core/book_formats/epub/opf/metadata_extractor.rb +71 -0
  415. data/lib/shoko/core/book_formats/epub/opf/navigation_context.rb +90 -0
  416. data/lib/shoko/core/book_formats/epub/opf/navigation_document_index.rb +79 -0
  417. data/lib/shoko/core/book_formats/epub/opf/navigation_document_scanner.rb +51 -0
  418. data/lib/shoko/core/book_formats/epub/opf/navigation_extractor.rb +50 -0
  419. data/lib/shoko/core/book_formats/epub/opf/navigation_label_resolver.rb +91 -0
  420. data/lib/shoko/core/book_formats/epub/opf/navigation_list_item.rb +59 -0
  421. data/lib/shoko/core/book_formats/epub/opf/navigation_result.rb +12 -0
  422. data/lib/shoko/core/book_formats/epub/opf/navigation_selector.rb +104 -0
  423. data/lib/shoko/core/book_formats/epub/opf/navigation_source_locator.rb +97 -0
  424. data/lib/shoko/core/book_formats/epub/opf/navigation_traversal.rb +108 -0
  425. data/lib/shoko/core/book_formats/epub/opf/navigation_walker.rb +60 -0
  426. data/lib/shoko/core/book_formats/epub/opf_processor.rb +115 -0
  427. data/lib/shoko/core/book_formats/epub/rexml_safe_parser.rb +22 -0
  428. data/lib/shoko/core/book_formats/epub/xhtml_content_parser.rb +803 -0
  429. data/lib/shoko/core/book_formats/epub/xml_text_normalizer.rb +45 -0
  430. data/lib/shoko/core/book_formats/fb2/fb2_content_parser.rb +324 -0
  431. data/lib/shoko/core/book_formats/fb2/fb2_inline_parser.rb +117 -0
  432. data/lib/shoko/core/book_formats/fb2/fb2_metadata_extractor.rb +68 -0
  433. data/lib/shoko/core/book_formats/fb2/fb2_section_flattener.rb +133 -0
  434. data/lib/shoko/core/book_formats/fb2/metadata_parser.rb +96 -0
  435. data/lib/shoko/core/book_formats/format_registry.rb +157 -0
  436. data/lib/shoko/core/book_formats/kindle/exth_parser.rb +169 -0
  437. data/lib/shoko/core/book_formats/kindle/kindle_content_parser.rb +31 -0
  438. data/lib/shoko/core/book_formats/kindle/kindle_metadata_extractor.rb +79 -0
  439. data/lib/shoko/core/book_formats/kindle/metadata_parser.rb +61 -0
  440. data/lib/shoko/core/book_formats/kindle/mobi_header_parser.rb +224 -0
  441. data/lib/shoko/core/book_formats/kindle/palmdoc_decompressor.rb +142 -0
  442. data/lib/shoko/core/book_formats/kindle/pdb_header_parser.rb +103 -0
  443. data/lib/shoko/core/book_formats/pdf/metadata_parser.rb +57 -0
  444. data/lib/shoko/core/book_formats/pdf/pdf_content_parser.rb +687 -0
  445. data/lib/shoko/core/book_formats/pdf/pdf_metadata_extractor.rb +64 -0
  446. data/lib/shoko/core/book_formats/pdf/pdf_reader.rb +485 -0
  447. data/lib/shoko/core/book_formats/pdf/pdf_text_extractor.rb +629 -0
  448. data/lib/shoko/core/book_formats/rtf/metadata_parser.rb +121 -0
  449. data/lib/shoko/core/book_formats/rtf/rtf_content_parser.rb +29 -0
  450. data/lib/shoko/core/book_formats/rtf/rtf_metadata_extractor.rb +73 -0
  451. data/lib/shoko/core/book_formats/rtf/rtf_parser.rb +767 -0
  452. data/lib/shoko/core/errors/dictionary_failure.rb +30 -0
  453. data/lib/shoko/core/events/base_domain_event.rb +30 -10
  454. data/lib/shoko/core/events/domain_event_bus.rb +15 -21
  455. data/lib/shoko/core/events/event_factory.rb +23 -0
  456. data/lib/shoko/core/models/annotation_selection.rb +59 -0
  457. data/lib/shoko/core/models/block_type.rb +51 -0
  458. data/lib/shoko/core/models/book_data.rb +29 -0
  459. data/lib/shoko/core/models/chapter.rb +5 -1
  460. data/lib/shoko/core/models/dictionary_catalog_entry.rb +52 -0
  461. data/lib/shoko/core/models/dictionary_entry.rb +163 -0
  462. data/lib/shoko/core/models/menu_book.rb +36 -0
  463. data/lib/shoko/core/models/pending_jump_payload.rb +58 -0
  464. data/lib/shoko/core/models/reader_settings.rb +8 -8
  465. data/lib/shoko/core/models/reading_progress.rb +30 -0
  466. data/lib/shoko/core/models/selection_anchor.rb +58 -53
  467. data/lib/shoko/core/ports/inbound/command_bus.rb +39 -0
  468. data/lib/shoko/core/ports/inbound/input_command_payload.rb +55 -0
  469. data/lib/shoko/core/ports/inbound/intent_dispatch_context.rb +24 -0
  470. data/lib/shoko/core/ports/inbound/menu_intent_handler.rb +82 -0
  471. data/lib/shoko/core/ports/inbound/reader_bookmark_command_context.rb +16 -0
  472. data/lib/shoko/core/ports/inbound/reader_intent_handler.rb +70 -0
  473. data/lib/shoko/core/ports/inbound/reader_navigation_command_context.rb +20 -0
  474. data/lib/shoko/core/ports/outbound/annotation_editor_launcher.rb +16 -0
  475. data/lib/shoko/core/ports/outbound/annotation_repository.rb +96 -0
  476. data/lib/shoko/core/ports/outbound/annotation_selection_reader.rb +17 -0
  477. data/lib/shoko/core/ports/outbound/annotation_view_refresher.rb +16 -0
  478. data/lib/shoko/core/ports/outbound/app_mode_runner.rb +20 -0
  479. data/lib/shoko/core/ports/outbound/async_executor.rb +26 -0
  480. data/lib/shoko/core/ports/outbound/background_worker_builder.rb +19 -0
  481. data/lib/shoko/core/ports/outbound/book_cache_pipeline_factory.rb +16 -0
  482. data/lib/shoko/core/ports/outbound/bookmark_repository.rb +79 -0
  483. data/lib/shoko/core/ports/outbound/cache_availability.rb +20 -0
  484. data/lib/shoko/core/ports/outbound/cache_manager.rb +28 -0
  485. data/lib/shoko/core/ports/outbound/cache_pointer_resolver.rb +29 -0
  486. data/lib/shoko/core/ports/outbound/chapter_formatter.rb +38 -0
  487. data/lib/shoko/core/ports/outbound/clock.rb +16 -0
  488. data/lib/shoko/core/ports/outbound/config_reader.rb +60 -0
  489. data/lib/shoko/core/ports/outbound/config_storage.rb +71 -0
  490. data/lib/shoko/core/ports/outbound/data_cleanup.rb +34 -0
  491. data/lib/shoko/core/ports/outbound/dictionary_availability.rb +21 -0
  492. data/lib/shoko/core/ports/outbound/dictionary_repository.rb +78 -0
  493. data/lib/shoko/core/ports/outbound/dictionary_storage.rb +47 -0
  494. data/lib/shoko/core/ports/outbound/display_capabilities.rb +20 -0
  495. data/lib/shoko/core/ports/outbound/document_loader.rb +20 -0
  496. data/lib/shoko/core/ports/outbound/event_publisher.rb +18 -0
  497. data/lib/shoko/core/ports/outbound/file_probe.rb +24 -0
  498. data/lib/shoko/core/ports/outbound/folder_importer.rb +17 -0
  499. data/lib/shoko/core/ports/outbound/folder_scanner.rb +18 -0
  500. data/lib/shoko/core/ports/outbound/id_generator.rb +16 -0
  501. data/lib/shoko/core/ports/outbound/instrumentation.rb +33 -0
  502. data/lib/shoko/core/ports/outbound/layout_metrics.rb +80 -0
  503. data/lib/shoko/core/ports/outbound/library_scanner.rb +48 -0
  504. data/lib/shoko/core/ports/outbound/line_wrapper.rb +36 -0
  505. data/lib/shoko/core/ports/outbound/logging.rb +67 -0
  506. data/lib/shoko/core/ports/outbound/menu_book_selection.rb +22 -0
  507. data/lib/shoko/core/ports/outbound/menu_intent_executor.rb +16 -0
  508. data/lib/shoko/core/ports/outbound/menu_launch_state.rb +24 -0
  509. data/lib/shoko/core/ports/outbound/menu_mode_switcher.rb +16 -0
  510. data/lib/shoko/core/ports/outbound/menu_progress_presenters.rb +16 -0
  511. data/lib/shoko/core/ports/outbound/menu_reader_runtime.rb +24 -0
  512. data/lib/shoko/core/ports/outbound/menu_workflow_runtime.rb +20 -0
  513. data/lib/shoko/core/ports/outbound/menu_workflow_state_reader.rb +36 -0
  514. data/lib/shoko/core/ports/outbound/menu_workflow_state_writer.rb +28 -0
  515. data/lib/shoko/core/ports/outbound/metadata_reader.rb +22 -0
  516. data/lib/shoko/core/ports/outbound/notification_writer.rb +20 -0
  517. data/lib/shoko/core/ports/outbound/observer_registry.rb +32 -0
  518. data/lib/shoko/core/ports/outbound/pagination_state_writer.rb +24 -0
  519. data/lib/shoko/core/ports/outbound/path_ops.rb +28 -0
  520. data/lib/shoko/core/ports/outbound/process_control.rb +16 -0
  521. data/lib/shoko/core/ports/outbound/progress_repository.rb +57 -0
  522. data/lib/shoko/core/ports/outbound/reader_chapter.rb +24 -0
  523. data/lib/shoko/core/ports/outbound/reader_document.rb +32 -0
  524. data/lib/shoko/core/ports/outbound/reader_intent_executor.rb +16 -0
  525. data/lib/shoko/core/ports/outbound/reader_launch_state.rb +36 -0
  526. data/lib/shoko/core/ports/outbound/reader_navigation_reader.rb +56 -0
  527. data/lib/shoko/core/ports/outbound/reader_render_requester.rb +18 -0
  528. data/lib/shoko/core/ports/outbound/reader_runner.rb +16 -0
  529. data/lib/shoko/core/ports/outbound/reader_state_writer.rb +48 -0
  530. data/lib/shoko/core/ports/outbound/recent_files_repository.rb +27 -0
  531. data/lib/shoko/core/ports/outbound/render_state_writer.rb +22 -0
  532. data/lib/shoko/core/ports/outbound/rendered_content_reader.rb +34 -0
  533. data/lib/shoko/core/ports/outbound/runtime_config.rb +113 -0
  534. data/lib/shoko/core/ports/outbound/sidebar_state_reader.rb +48 -0
  535. data/lib/shoko/core/ports/outbound/terminal_capabilities.rb +35 -0
  536. data/lib/shoko/core/ports/outbound/terminal_session.rb +24 -0
  537. data/lib/shoko/core/ports/outbound/text_metrics.rb +21 -0
  538. data/lib/shoko/core/ports/outbound/text_sanitizer.rb +24 -0
  539. data/lib/shoko/core/ports/outbound/ui_loading_writer.rb +16 -0
  540. data/lib/shoko/core/ports/outbound/ui_state_reader.rb +32 -0
  541. data/lib/shoko/core/ports/outbound/wall_clock.rb +16 -0
  542. data/lib/shoko/core/ports/outbound/wrapped_lines_provider.rb +25 -0
  543. data/lib/shoko/core/services/annotation_service.rb +39 -52
  544. data/lib/shoko/core/services/base_service.rb +7 -46
  545. data/lib/shoko/core/services/coordinate_service.rb +7 -34
  546. data/lib/shoko/core/services/default_display_capabilities.rb +18 -0
  547. data/lib/shoko/core/services/default_layout_metrics.rb +65 -0
  548. data/lib/shoko/core/services/default_terminal_capabilities.rb +23 -0
  549. data/lib/shoko/core/services/default_text_metrics.rb +44 -0
  550. data/lib/shoko/core/services/dictionary_service.rb +232 -0
  551. data/lib/shoko/core/services/document_path_resolver.rb +62 -0
  552. data/lib/shoko/core/services/in_book_search_service.rb +188 -0
  553. data/lib/shoko/core/services/inline_executor.rb +24 -0
  554. data/lib/shoko/core/services/layout_service.rb +34 -9
  555. data/lib/shoko/core/services/null_instrumentation.rb +24 -0
  556. data/lib/shoko/core/services/null_logger.rb +39 -0
  557. data/lib/shoko/core/services/page_calculator_service.rb +330 -106
  558. data/lib/shoko/core/services/pagination/internal/absolute_page_map_builder.rb +53 -21
  559. data/lib/shoko/core/services/pagination/internal/chapter_cache.rb +56 -45
  560. data/lib/shoko/core/services/pagination/internal/dynamic_page_map_builder.rb +165 -110
  561. data/lib/shoko/core/services/pagination/internal/layout_metrics_calculator.rb +51 -58
  562. data/lib/shoko/core/services/pagination/internal/page_hydrator.rb +115 -120
  563. data/lib/shoko/core/services/pagination/internal/pagination_workflow.rb +141 -128
  564. data/lib/shoko/core/services/progress_helper.rb +10 -10
  565. data/lib/shoko/core/services/selection_service.rb +62 -53
  566. data/lib/shoko/core/services/toc_tree_service.rb +196 -0
  567. data/lib/shoko/shared/contracts/session_outcome.rb +18 -0
  568. data/lib/shoko/shared/errors.rb +101 -3
  569. data/lib/shoko/{adapters/input → shared}/key_definitions.rb +18 -22
  570. data/lib/shoko/shared/menu_definitions.rb +76 -0
  571. data/lib/shoko/shared/optional_dependency.rb +70 -0
  572. data/lib/shoko/shared/runtime/null_runtime_config.rb +44 -0
  573. data/lib/shoko/{adapters/book_sources → shared}/source_fingerprint.rb +5 -5
  574. data/lib/shoko/shared/terminal/ansi.rb +73 -0
  575. data/lib/shoko/shared/terminal/kitty_unicode_placeholders.rb +137 -0
  576. data/lib/shoko/shared/terminal/text_metrics.rb +490 -0
  577. data/lib/shoko/shared/terminal/text_sanitizer.rb +12 -0
  578. data/lib/shoko/shared/text_sanitizer.rb +239 -0
  579. data/lib/shoko/shared/ui_constraints.rb +11 -0
  580. data/lib/shoko/shared/unicode_display_width/display_width.marshal.gz +0 -0
  581. data/lib/shoko/shared/unicode_display_width.rb +147 -0
  582. data/lib/shoko/shared/version.rb +1 -1
  583. data/lib/shoko/test_support/terminal_double.rb +1 -1
  584. data/lib/shoko/test_support/test_mode.rb +5 -19
  585. data/lib/shoko.rb +4 -275
  586. data/script/architecture/fallback_report.rb +105 -0
  587. data/script/bench/sidebar_toggle_layout_benchmark.rb +194 -0
  588. data/script/bench/snappiness_benchmark.rb +443 -0
  589. data/script/bench/startup_menu_benchmark.rb +114 -0
  590. data/tmp/rspec-results/fixtures.json +1 -0
  591. data/tmp/rspec-results/full-seed-10101.log +10 -0
  592. data/tmp/rspec-results/full-seed-1559.log +10 -0
  593. data/tmp/rspec-results/full-seed-20202.log +10 -0
  594. data/tmp/rspec-results/full-seed-30303.log +10 -0
  595. data/tmp/rspec-results/full-seed-49446.log +10 -0
  596. data/tmp/rspec-results/full-seed-52407.log +10 -0
  597. data/tmp/rspec-results/full-seed-5517.log +10 -0
  598. data/tmp/rspec-results/full-seed-56072.log +10 -0
  599. data/tmp/rspec-results/full-seed-61968.log +10 -0
  600. data/tmp/rspec-results/full-seed-63851.log +10 -0
  601. data/tmp/rspec-results/full-seed-65033.log +10 -0
  602. data/tmp/rspec-results/full-seed-87095.log +10 -0
  603. data/tmp/rspec-results/full-seed-97342.log +10 -0
  604. data/tmp/rspec-results/guardrails.json +1 -0
  605. data/tmp/rspec-results/required-seed-10101.json +1 -0
  606. data/tmp/rspec-results/required-seed-20202.json +1 -0
  607. data/tmp/rspec-results/required-seed-30303.json +1 -0
  608. data/tmp/rubocop-metrics.json +1 -0
  609. metadata +507 -227
  610. data/lib/shoko/adapters/book_sources/epub/parsers/html_processor.rb +0 -151
  611. data/lib/shoko/adapters/book_sources/epub/parsers/metadata_extractor.rb +0 -53
  612. data/lib/shoko/adapters/book_sources/epub/parsers/opf/entry_reader.rb +0 -77
  613. data/lib/shoko/adapters/book_sources/epub/parsers/opf/metadata_extractor.rb +0 -67
  614. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_context.rb +0 -86
  615. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_index.rb +0 -75
  616. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_document_scanner.rb +0 -47
  617. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_extractor.rb +0 -46
  618. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_label_resolver.rb +0 -83
  619. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_list_item.rb +0 -55
  620. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_result.rb +0 -8
  621. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_selector.rb +0 -100
  622. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_source_locator.rb +0 -93
  623. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_traversal.rb +0 -103
  624. data/lib/shoko/adapters/book_sources/epub/parsers/opf/navigation_walker.rb +0 -56
  625. data/lib/shoko/adapters/book_sources/epub/parsers/opf_processor.rb +0 -102
  626. data/lib/shoko/adapters/book_sources/epub/parsers/xhtml_content_parser.rb +0 -661
  627. data/lib/shoko/adapters/book_sources/epub/parsers/xml_text_normalizer.rb +0 -41
  628. data/lib/shoko/adapters/book_sources/epub_document.rb +0 -253
  629. data/lib/shoko/adapters/book_sources/epub_finder/directory_scanner.rb +0 -134
  630. data/lib/shoko/adapters/book_sources/epub_finder/scanner_context.rb +0 -28
  631. data/lib/shoko/adapters/book_sources/epub_importer.rb +0 -268
  632. data/lib/shoko/adapters/input/command_bridge.rb +0 -148
  633. data/lib/shoko/adapters/input/input_controller.rb +0 -250
  634. data/lib/shoko/adapters/monitoring/logger.rb +0 -150
  635. data/lib/shoko/adapters/output/render_registry.rb +0 -45
  636. data/lib/shoko/adapters/output/rendering/models/rendering_context.rb +0 -58
  637. data/lib/shoko/adapters/output/ui/builders/page_setup_builder.rb +0 -47
  638. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/footer_renderer.rb +0 -80
  639. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/geometry.rb +0 -61
  640. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay/note_renderer.rb +0 -86
  641. data/lib/shoko/adapters/output/ui/components/annotation_editor_overlay_component.rb +0 -234
  642. data/lib/shoko/adapters/output/ui/components/annotations_overlay/list_renderer.rb +0 -142
  643. data/lib/shoko/adapters/output/ui/components/annotations_overlay_component.rb +0 -185
  644. data/lib/shoko/adapters/output/ui/components/base_component.rb +0 -110
  645. data/lib/shoko/adapters/output/ui/components/component_interface.rb +0 -80
  646. data/lib/shoko/adapters/output/ui/components/content_component.rb +0 -61
  647. data/lib/shoko/adapters/output/ui/components/enhanced_popup_menu.rb +0 -191
  648. data/lib/shoko/adapters/output/ui/components/footer_component.rb +0 -120
  649. data/lib/shoko/adapters/output/ui/components/header_component.rb +0 -46
  650. data/lib/shoko/adapters/output/ui/components/layouts/horizontal.rb +0 -63
  651. data/lib/shoko/adapters/output/ui/components/layouts/vertical.rb +0 -73
  652. data/lib/shoko/adapters/output/ui/components/main_menu_component.rb +0 -103
  653. data/lib/shoko/adapters/output/ui/components/reading/base_view_renderer.rb +0 -199
  654. data/lib/shoko/adapters/output/ui/components/reading/config_helpers.rb +0 -42
  655. data/lib/shoko/adapters/output/ui/components/reading/help_renderer.rb +0 -62
  656. data/lib/shoko/adapters/output/ui/components/reading/inline_segment_highlighter.rb +0 -144
  657. data/lib/shoko/adapters/output/ui/components/reading/kitty_image_line_renderer.rb +0 -262
  658. data/lib/shoko/adapters/output/ui/components/reading/line_content_composer.rb +0 -114
  659. data/lib/shoko/adapters/output/ui/components/reading/line_drawer.rb +0 -87
  660. data/lib/shoko/adapters/output/ui/components/reading/line_geometry_builder.rb +0 -41
  661. data/lib/shoko/adapters/output/ui/components/reading/rendered_lines_recorder.rb +0 -64
  662. data/lib/shoko/adapters/output/ui/components/reading/single_view_renderer.rb +0 -156
  663. data/lib/shoko/adapters/output/ui/components/reading/split_view_renderer.rb +0 -221
  664. data/lib/shoko/adapters/output/ui/components/reading/view_renderer_factory.rb +0 -20
  665. data/lib/shoko/adapters/output/ui/components/reading/wrapped_lines_fetcher.rb +0 -139
  666. data/lib/shoko/adapters/output/ui/components/rect.rb +0 -15
  667. data/lib/shoko/adapters/output/ui/components/render_style.rb +0 -84
  668. data/lib/shoko/adapters/output/ui/components/screen_component.rb +0 -24
  669. data/lib/shoko/adapters/output/ui/components/screens/annotation_detail_screen_component.rb +0 -175
  670. data/lib/shoko/adapters/output/ui/components/screens/annotation_edit_screen_component.rb +0 -221
  671. data/lib/shoko/adapters/output/ui/components/screens/annotation_editor_screen_component.rb +0 -205
  672. data/lib/shoko/adapters/output/ui/components/screens/annotation_rendering_helpers.rb +0 -190
  673. data/lib/shoko/adapters/output/ui/components/screens/annotations_screen_component.rb +0 -266
  674. data/lib/shoko/adapters/output/ui/components/screens/base_screen_component.rb +0 -49
  675. data/lib/shoko/adapters/output/ui/components/screens/browse_screen_component.rb +0 -319
  676. data/lib/shoko/adapters/output/ui/components/screens/download_books_screen_component.rb +0 -340
  677. data/lib/shoko/adapters/output/ui/components/screens/library_screen_component.rb +0 -205
  678. data/lib/shoko/adapters/output/ui/components/screens/loading_overlay_component.rb +0 -49
  679. data/lib/shoko/adapters/output/ui/components/screens/menu_screen_component.rb +0 -107
  680. data/lib/shoko/adapters/output/ui/components/screens/settings_screen_component.rb +0 -238
  681. data/lib/shoko/adapters/output/ui/components/sidebar/annotations_tab_renderer.rb +0 -159
  682. data/lib/shoko/adapters/output/ui/components/sidebar/bookmarks_tab_renderer.rb +0 -139
  683. data/lib/shoko/adapters/output/ui/components/sidebar/tab_header_component.rb +0 -157
  684. data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_renderer.rb +0 -111
  685. data/lib/shoko/adapters/output/ui/components/sidebar/toc_tab_support.rb +0 -1606
  686. data/lib/shoko/adapters/output/ui/components/sidebar_panel_component.rb +0 -217
  687. data/lib/shoko/adapters/output/ui/components/surface.rb +0 -88
  688. data/lib/shoko/adapters/output/ui/components/tooltip_overlay_component.rb +0 -224
  689. data/lib/shoko/adapters/output/ui/components/ui/box_drawer.rb +0 -32
  690. data/lib/shoko/adapters/output/ui/components/ui/list_helpers.rb +0 -33
  691. data/lib/shoko/adapters/output/ui/components/ui/overlay_layout.rb +0 -79
  692. data/lib/shoko/adapters/output/ui/components/ui/text_utils.rb +0 -46
  693. data/lib/shoko/adapters/output/ui/constants/highlighting.rb +0 -21
  694. data/lib/shoko/adapters/output/ui/constants/messages.rb +0 -12
  695. data/lib/shoko/adapters/output/ui/constants/themes.rb +0 -79
  696. data/lib/shoko/adapters/output/ui/constants/ui_constants.rb +0 -85
  697. data/lib/shoko/adapters/output/ui/rendering/frame_coordinator.rb +0 -42
  698. data/lib/shoko/adapters/output/ui/rendering/reader_render_coordinator.rb +0 -169
  699. data/lib/shoko/adapters/output/ui/rendering/render_pipeline.rb +0 -55
  700. data/lib/shoko/adapters/storage/repositories/config_repository.rb +0 -262
  701. data/lib/shoko/application/annotation_editor_overlay_session.rb +0 -138
  702. data/lib/shoko/application/cli.rb +0 -134
  703. data/lib/shoko/application/controllers/menu/input_controller.rb +0 -189
  704. data/lib/shoko/application/controllers/menu/state_controller.rb +0 -642
  705. data/lib/shoko/application/controllers/menu_controller.rb +0 -469
  706. data/lib/shoko/application/controllers/mouseable_reader.rb +0 -421
  707. data/lib/shoko/application/controllers/reader_controller.rb +0 -449
  708. data/lib/shoko/application/controllers/state_controller.rb +0 -410
  709. data/lib/shoko/application/controllers/ui_controller.rb +0 -782
  710. data/lib/shoko/application/dependency_container.rb +0 -301
  711. data/lib/shoko/application/infrastructure/event_bus.rb +0 -80
  712. data/lib/shoko/application/infrastructure/observer_state_store.rb +0 -136
  713. data/lib/shoko/application/infrastructure/state_store.rb +0 -413
  714. data/lib/shoko/application/main_menu/menu_progress_presenter.rb +0 -83
  715. data/lib/shoko/application/reader_lifecycle.rb +0 -65
  716. data/lib/shoko/application/reader_startup_orchestrator.rb +0 -113
  717. data/lib/shoko/application/selectors/config_selectors.rb +0 -62
  718. data/lib/shoko/application/selectors/menu_selectors.rb +0 -62
  719. data/lib/shoko/application/selectors/reader_selectors.rb +0 -190
  720. data/lib/shoko/application/state/actions/base_action.rb +0 -24
  721. data/lib/shoko/application/state/actions/quit_to_menu_action.rb +0 -16
  722. data/lib/shoko/application/state/actions/switch_reader_mode_action.rb +0 -22
  723. data/lib/shoko/application/state/actions/toggle_view_mode_action.rb +0 -31
  724. data/lib/shoko/application/state/actions/update_annotation_editor_overlay_action.rb +0 -27
  725. data/lib/shoko/application/state/actions/update_annotations_action.rb +0 -20
  726. data/lib/shoko/application/state/actions/update_annotations_overlay_action.rb +0 -27
  727. data/lib/shoko/application/state/actions/update_bookmarks_action.rb +0 -20
  728. data/lib/shoko/application/state/actions/update_chapter_action.rb +0 -24
  729. data/lib/shoko/application/state/actions/update_config_action.rb +0 -22
  730. data/lib/shoko/application/state/actions/update_field_helpers.rb +0 -26
  731. data/lib/shoko/application/state/actions/update_menu_action.rb +0 -21
  732. data/lib/shoko/application/state/actions/update_message_action.rb +0 -35
  733. data/lib/shoko/application/state/actions/update_page_action.rb +0 -21
  734. data/lib/shoko/application/state/actions/update_pagination_state_action.rb +0 -21
  735. data/lib/shoko/application/state/actions/update_popup_menu_action.rb +0 -27
  736. data/lib/shoko/application/state/actions/update_reader_meta_action.rb +0 -21
  737. data/lib/shoko/application/state/actions/update_reader_mode_action.rb +0 -20
  738. data/lib/shoko/application/state/actions/update_rendered_lines_action.rb +0 -40
  739. data/lib/shoko/application/state/actions/update_selection_action.rb +0 -27
  740. data/lib/shoko/application/state/actions/update_selections_action.rb +0 -21
  741. data/lib/shoko/application/state/actions/update_sidebar_action.rb +0 -34
  742. data/lib/shoko/application/state/actions/update_ui_loading_action.rb +0 -23
  743. data/lib/shoko/application/ui/reader_view_model_builder.rb +0 -74
  744. data/lib/shoko/application/ui/view_models/reader_view_model.rb +0 -177
  745. data/lib/shoko/application/use_cases/commands/annotation_editor_commands.rb +0 -105
  746. data/lib/shoko/application/use_cases/commands/application_commands.rb +0 -208
  747. data/lib/shoko/application/use_cases/commands/conditional_navigation_commands.rb +0 -57
  748. data/lib/shoko/application/use_cases/commands/menu_commands.rb +0 -170
  749. data/lib/shoko/application/use_cases/commands/reader_commands.rb +0 -46
  750. data/lib/shoko/application/use_cases/commands/sidebar_commands.rb +0 -55
  751. data/lib/shoko/core/ports/annotation_repository.rb +0 -0
  752. data/lib/shoko/core/ports/book_repository.rb +0 -0
  753. data/lib/shoko/core/ports/book_source.rb +0 -0
  754. data/lib/shoko/core/ports/bookmark_repository.rb +0 -0
  755. data/lib/shoko/core/ports/cache.rb +0 -0
  756. data/lib/shoko/core/ports/input_handler.rb +0 -0
  757. data/lib/shoko/core/ports/renderer.rb +0 -0
  758. data/lib/shoko/core/ports/storage.rb +0 -0
  759. data/lib/shoko/core/services/bookmark_service.rb +0 -267
  760. data/lib/shoko/core/services/navigation/absolute_change_applier.rb +0 -96
  761. data/lib/shoko/core/services/navigation/absolute_layout.rb +0 -101
  762. data/lib/shoko/core/services/navigation/absolute_strategy.rb +0 -179
  763. data/lib/shoko/core/services/navigation/context_builder.rb +0 -52
  764. data/lib/shoko/core/services/navigation/context_helpers.rb +0 -63
  765. data/lib/shoko/core/services/navigation/dynamic_change_applier.rb +0 -50
  766. data/lib/shoko/core/services/navigation/dynamic_strategy.rb +0 -51
  767. data/lib/shoko/core/services/navigation/image_offset_snapper.rb +0 -150
  768. data/lib/shoko/core/services/navigation/nav_context.rb +0 -27
  769. data/lib/shoko/core/services/navigation/state_updater.rb +0 -29
  770. data/lib/shoko/core/services/navigation/strategy_factory.rb +0 -20
  771. data/lib/shoko/core/services/navigation_service.rb +0 -150
  772. data/lib/shoko/core/services/pagination/page_info_calculator.rb +0 -247
  773. data/lib/shoko/core/services/pagination/pagination_cache_preloader.rb +0 -173
  774. data/lib/shoko/core/services/pagination/pagination_coordinator.rb +0 -202
  775. data/lib/shoko/core/services/pagination/pagination_orchestrator.rb +0 -291
  776. data/zip.rb +0 -5
  777. /data/lib/shoko/{adapters/output/kitty → shared/terminal}/kitty_unicode_placeholders_diacritic_codepoints.txt +0 -0
@@ -1,652 +1,178 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../../shared/errors.rb'
3
+ require_relative '../../shared/errors'
4
4
  require_relative 'epub_cache'
5
- require_relative '../book_sources/epub_importer.rb'
6
5
  require_relative 'json_cache_store'
7
6
  require_relative 'cache_pointer_manager'
8
- require_relative '../monitoring/logger.rb'
9
- require_relative '../book_sources/source_fingerprint.rb'
7
+ require_relative '../../shared/source_fingerprint'
8
+ require_relative '../../core/book_formats/format_registry'
9
+ require_relative 'book_cache_pipeline/manifest_row'
10
+ require_relative 'book_cache_pipeline/fingerprint_filter'
11
+ require_relative 'book_cache_pipeline/manifest_sha_finder'
12
+ require_relative 'book_cache_pipeline/pointer_file_ensurer'
13
+ require_relative 'book_cache_pipeline/cache_integrity_checker'
14
+ require_relative 'book_cache_pipeline/cache_status'
15
+ require_relative 'book_cache_pipeline/payload_context'
16
+ require_relative 'book_cache_pipeline/cache_session'
17
+ require_relative 'book_cache_pipeline/load_error_handler'
18
+ require_relative '../support/lifecycle_helpers'
10
19
  require 'fileutils'
11
20
  require 'time'
12
21
 
13
22
  module Shoko
14
- module Adapters::Storage
15
- # Coordinates importing EPUB files and storing/loading JSON-backed caches.
16
- # Provides a single entry point that ensures cache integrity, re-importing
17
- # whenever a cache is missing, corrupted, or outdated.
18
- class BookCachePipeline
19
- # Result payload returned by cache pipeline loads.
20
- Result = Struct.new(
21
- :book,
22
- :cache_path,
23
- :source_path,
24
- :loaded_from_cache,
25
- :payload,
26
- keyword_init: true
27
- )
28
-
29
- # Wraps manifest row hashes with typed accessors.
30
- class ManifestRow
31
- def self.from(row)
32
- return nil unless row.is_a?(Hash)
33
-
34
- new(row)
35
- end
36
-
37
- def initialize(row)
38
- source_path, source_mtime, source_size, source_fingerprint, updated_at, source_sha =
39
- row.values_at(
40
- 'source_path',
41
- 'source_mtime',
42
- 'source_size_bytes',
43
- 'source_fingerprint',
44
- 'updated_at',
45
- 'source_sha'
46
- )
47
- fingerprint_value = source_fingerprint.to_s.strip
48
- fingerprint_value = nil if fingerprint_value.empty?
49
-
50
- @data = {
51
- source_path: source_path.to_s,
52
- source_mtime: source_mtime,
53
- source_size_bytes: source_size,
54
- source_fingerprint: fingerprint_value,
55
- updated_at: updated_at.to_f,
56
- source_sha: source_sha.to_s.strip,
57
- }
58
- end
59
-
60
- def path_match?(source_path)
61
- @data[:source_path] == source_path
62
- end
63
-
64
- def mtime_match?(source_mtime)
65
- raw_mtime = @data[:source_mtime]
66
- return false unless raw_mtime
67
-
68
- (raw_mtime.to_f - source_mtime.to_f).abs <= 1.0
69
- end
70
-
71
- def size_match?(source_size_bytes)
72
- raw_size = @data[:source_size_bytes]
73
- return true unless raw_size
74
-
75
- raw_size.to_i == source_size_bytes.to_i
76
- end
77
-
78
- def fingerprint_value
79
- @data[:source_fingerprint]
80
- end
81
-
82
- def updated_at
83
- @data[:updated_at]
84
- end
85
-
86
- def source_sha
87
- sha = @data[:source_sha]
88
- sha.empty? ? nil : sha
89
- end
90
- end
91
- private_constant :ManifestRow
92
-
93
- # Filters manifest rows by source fingerprint, keeping untagged rows.
94
- class FingerprintFilter
95
- def initialize(source_path)
96
- @source_path = source_path
97
- @fingerprint = nil
98
- @fingerprint_blank = false
99
- end
100
-
101
- def call(rows)
102
- matches = rows.select { |row| include_row?(row) }
103
- [matches, applied?, blank?]
104
- end
105
-
106
- private
107
-
108
- def include_row?(row)
109
- value = row.fingerprint_value
110
- return true unless value
111
-
112
- ensure_fingerprint
113
- return false if @fingerprint_blank
114
-
115
- value == @fingerprint
116
- end
117
-
118
- def ensure_fingerprint
119
- return if @fingerprint
120
-
121
- @fingerprint = Shoko::Adapters::BookSources::SourceFingerprint.compute(@source_path).to_s
122
- @fingerprint_blank = @fingerprint.empty?
123
- end
124
-
125
- def applied?
126
- !!@fingerprint
127
- end
128
-
129
- def blank?
130
- @fingerprint_blank
131
- end
132
- end
133
- private_constant :FingerprintFilter
134
-
135
- # Finds the best manifest SHA match for a source file.
136
- class ManifestShaFinder
137
- def initialize(rows:, source_path:, source_mtime:, source_size_bytes:)
138
- @rows = rows.each_with_object([]) do |row, acc|
139
- wrapper = ManifestRow.from(row)
140
- acc << wrapper if wrapper
141
- end
142
- @source_path = source_path
143
- @source_mtime = source_mtime
144
- @source_size_bytes = source_size_bytes
145
- end
146
-
147
- def sha
148
- best = best_match
149
- best&.source_sha
150
- rescue StandardError
151
- nil
152
- end
153
-
154
- private
155
-
156
- def best_match
157
- filtered_rows.max_by(&:updated_at)
158
- end
159
-
160
- def filtered_rows
161
- fingerprint_matches(size_matches(mtime_matches(path_matches)))
162
- end
163
-
164
- def path_matches
165
- @rows.select { |row| row.path_match?(@source_path) }
166
- end
167
-
168
- def mtime_matches(rows)
169
- rows.select { |row| row.mtime_match?(@source_mtime) }
170
- end
171
-
172
- def size_matches(rows)
173
- rows.select { |row| row.size_match?(@source_size_bytes) }
174
- end
175
-
176
- def fingerprint_matches(rows)
177
- matches, applied, blank = FingerprintFilter.new(@source_path).call(rows)
178
- return rows unless applied
179
- return [] if blank
180
-
181
- matches
182
- end
183
- end
184
- private_constant :ManifestShaFinder
185
-
186
- # Removes stale pointer cache files after a rebuild.
187
- class PointerCacheCleaner
188
- def initialize(cache_path, rebuilt_path)
189
- @cache_path = cache_path
190
- @rebuilt_path = rebuilt_path
191
- end
192
-
193
- def call
194
- return unless @rebuilt_path
195
- return if same_path?
196
-
197
- FileUtils.rm_f(@cache_path)
198
- rescue StandardError
199
- nil
200
- end
201
-
202
- private
203
-
204
- def same_path?
205
- File.expand_path(@cache_path) == File.expand_path(@rebuilt_path)
206
- end
207
- end
208
- private_constant :PointerCacheCleaner
23
+ module Adapters
24
+ module Storage
25
+ # Coordinates importing EPUB files and storing/loading JSON-backed caches.
26
+ # Provides a single entry point that ensures cache integrity, re-importing
27
+ # whenever a cache is missing, corrupted, or outdated.
28
+ class BookCachePipeline
29
+ include Shoko::Adapters::Support::LifecycleHelpers
30
+
31
+ # Result payload returned by cache pipeline loads.
32
+ Result = Struct.new(
33
+ :book,
34
+ :cache_path,
35
+ :source_path,
36
+ :loaded_from_cache,
37
+ :payload,
38
+ keyword_init: true
39
+ )
209
40
 
210
- # Rebuilds pointer caches by loading the original source file.
211
- class PointerRebuilder
212
- def initialize(cache:, formatting_service:, load_callback:)
213
- @cache = cache
214
- @formatting_service = formatting_service
215
- @load_callback = load_callback
216
- @cache_class = cache.class
41
+ def initialize(cache_class: EpubCache, cache_root: CachePaths.cache_root,
42
+ default_importer_class: nil, progress_reporter: nil, logger: nil, runtime_config: nil)
43
+ @cache_class = cache_class
44
+ @cache_root = cache_root
45
+ @default_importer_class = default_importer_class
46
+ @pointer_manager_class = CachePointerManager
47
+ @progress_reporter = progress_reporter
48
+ @logger = logger
49
+ @runtime_config = runtime_config
217
50
  end
218
51
 
219
- def call
220
- return nil unless pointer_source_valid?
221
-
222
- rebuild
52
+ def load(path, formatting_service: nil)
53
+ perform_load(path, formatting_service)
223
54
  rescue StandardError => e
224
- log_failure(e)
225
- nil
55
+ raise if e.is_a?(Shoko::Error)
56
+ raise_load_error(path, e)
226
57
  end
227
58
 
228
59
  private
229
60
 
230
- def pointer_source_valid?
231
- path = source_path
232
- return false if path.empty?
233
- return false if @cache_class.cache_file?(path)
234
-
235
- File.file?(path)
236
- rescue StandardError
237
- false
238
- end
239
-
240
- def rebuild
241
- rebuilt = @load_callback.call(source_path, formatting_service: @formatting_service)
242
- PointerCacheCleaner.new(cache_path, rebuilt&.cache_path).call
243
- rebuilt
244
- end
245
-
246
- def cache_path
247
- @cache.cache_path
248
- end
61
+ def perform_load(path, formatting_service)
62
+ report('Checking cache...')
63
+ expanded = File.expand_path(path)
64
+ fast = fast_load_if_available(expanded, formatting_service)
65
+ return fast if fast
249
66
 
250
- def source_path
251
- @source_path ||= @cache.source_path.to_s
67
+ cache = build_cache(expanded)
68
+ cache_session(cache, formatting_service).load
252
69
  end
253
70
 
254
- def log_failure(error)
255
- Shoko::Adapters::Monitoring::Logger.error(
256
- 'Pointer cache rebuild failed',
257
- cache: cache_path,
258
- source: source_path,
259
- error: error.message
260
- )
261
- end
262
- end
263
- private_constant :PointerRebuilder
71
+ def fast_load_if_available(expanded, formatting_service)
72
+ return nil unless fast_source_path?(expanded)
264
73
 
265
- # Ensures pointer metadata exists on disk for a cached source.
266
- class PointerFileEnsurer
267
- def initialize(pointer_path:, sha:, source_path:, manager_class:)
268
- @pointer_path = pointer_path
269
- @sha = sha
270
- @source_path = source_path
271
- @manager_class = manager_class
74
+ fast_load_for_source(expanded, formatting_service)
272
75
  end
273
76
 
274
- def call
275
- manager = @manager_class.new(@pointer_path)
276
- existing = manager.read
277
- return if current?(existing)
278
-
279
- manager.write(metadata)
280
- rescue StandardError
281
- nil
282
- end
283
-
284
- private
285
-
286
- def current?(existing)
287
- existing && existing['sha256'] == @sha && existing['source_path'].to_s == @source_path
288
- end
289
-
290
- def metadata
291
- {
292
- 'format' => @manager_class::POINTER_FORMAT,
293
- 'version' => @manager_class::POINTER_VERSION,
294
- 'sha256' => @sha,
295
- 'source_path' => @source_path,
296
- 'generated_at' => Time.now.utc.iso8601,
297
- 'engine' => JsonCacheStore::ENGINE,
77
+ def build_cache(path)
78
+ kwargs = {
79
+ cache_root: @cache_root,
80
+ logger: @logger
298
81
  }
299
- end
300
- end
301
- private_constant :PointerFileEnsurer
302
-
303
- # Validates cached payload completeness.
304
- class CacheIntegrityChecker
305
- def initialize(cache:, payload:)
306
- @cache = cache
307
- @payload = payload
308
- end
309
-
310
- def incomplete?
311
- chapters = Array(book&.chapters)
312
- return true if chapters.empty? || chapters.any?(&:nil?)
313
-
314
- !@cache.chapters_complete?(chapters.length, generation: chapters_generation)
315
- rescue StandardError
316
- true
317
- end
318
-
319
- private
320
-
321
- def book
322
- @payload&.book
323
- end
324
-
325
- def chapters_generation
326
- book&.chapters_generation
327
- rescue NoMethodError
328
- nil
329
- end
330
- end
331
- private_constant :CacheIntegrityChecker
332
-
333
- # Tracks whether a payload originated from cache.
334
- class CacheStatus
335
- def self.hit(cache_marker)
336
- new(cache_marker)
337
- end
338
-
339
- def self.miss
340
- new(nil)
82
+ kwargs[:runtime_config] = @runtime_config if cache_supports_runtime_config?
83
+ @cache_class.new(path, **kwargs)
84
+ end
85
+
86
+ def cache_session(cache, formatting_service)
87
+ CacheSession.new(
88
+ cache: cache,
89
+ formatting_service: formatting_service,
90
+ importer_class: @default_importer_class,
91
+ load_callback: method(:load),
92
+ progress_reporter: @progress_reporter,
93
+ runtime_config: @runtime_config,
94
+ logger: @logger
95
+ )
341
96
  end
342
97
 
343
- def initialize(cache_marker)
344
- @cache_marker = cache_marker
345
- end
98
+ def fast_source_path?(expanded)
99
+ return false if @cache_class.cache_file?(expanded)
346
100
 
347
- def mark_rebuilt
348
- @cache_marker = nil
101
+ File.file?(expanded)
102
+ rescue Shoko::Error
103
+ raise
349
104
  end
350
105
 
351
- def loaded_from_cache?
352
- !!@cache_marker
106
+ def fast_load_for_source(source_path, formatting_service)
107
+ perform_fast_load(source_path, formatting_service)
353
108
  end
354
- end
355
- private_constant :CacheStatus
356
109
 
357
- # Bundles payload and cache status for cache session operations.
358
- class PayloadContext
359
- attr_reader :payload, :cache_status
110
+ def perform_fast_load(source_path, formatting_service)
111
+ report('Looking up cached data...')
112
+ pointer_path, sha = pointer_for_source(source_path)
113
+ return nil unless pointer_path
360
114
 
361
- def initialize(payload:, cache_status:)
362
- @payload = payload
363
- @cache_status = cache_status
364
- end
365
- end
366
- private_constant :PayloadContext
115
+ ensure_pointer_file(pointer_path, sha, source_path)
367
116
 
368
- # Handles cache loading/rebuilding for a specific cache instance.
369
- class CacheSession
370
- def initialize(cache:, formatting_service:, importer_class:, load_callback:, progress_reporter: nil)
371
- @cache = cache
372
- @formatting_service = formatting_service
373
- @importer_class = importer_class
374
- @load_callback = load_callback
375
- @progress_reporter = progress_reporter
117
+ cache = build_cache(pointer_path)
118
+ cache_session(cache, formatting_service).fast_load
376
119
  end
377
120
 
378
- def load
379
- cache_file = @cache.cache_file?
380
- report('Loading cached book...') if cache_file
381
- result = result_from_initial_payload
382
- return result if result
383
-
384
- return rebuild_from_pointer_or_raise if cache_file
121
+ def pointer_for_source(source_path)
122
+ sha = sha_for_source(source_path)
123
+ return nil unless sha
385
124
 
386
- import_and_result
387
- end
125
+ pointer_path = pointer_path_for_sha(sha)
126
+ return nil unless pointer_path
388
127
 
389
- def fast_load
390
- report('Loading cached book...')
391
- payload, cache_status = payload_from_cache
392
- context = PayloadContext.new(payload: payload, cache_status: cache_status)
393
- result_from_payload_or_nil(context)
128
+ [pointer_path, sha]
394
129
  end
395
130
 
396
- private
397
-
398
- def initial_payload
399
- payload = @cache.cache_file? ? @cache.read_cache(strict: true) : payload_from_source
400
- payload || payload_from_source
131
+ def sha_for_source(source_path)
132
+ source_mtime = File.mtime(source_path).utc
133
+ source_size_bytes = File.size(source_path)
134
+ sha_from_manifest(source_path, source_mtime, source_size_bytes)
401
135
  end
402
136
 
403
- def payload_from_cache
404
- payload = @cache.read_cache(strict: true)
405
- cache_status = CacheStatus.hit(payload)
406
- payload ||= rebuild_cache
407
- [payload, cache_status]
137
+ def pointer_path_for_sha(sha)
138
+ @cache_class.cache_path_for_sha(sha, cache_root: @cache_root)
408
139
  end
409
140
 
410
- def payload_from_source
411
- @cache.load_for_source(strict: true) || @cache.load_for_source(strict: false)
412
- end
141
+ def sha_from_manifest(source_path, source_mtime, source_size_bytes)
142
+ rows = JsonCacheStore.manifest_rows(@cache_root, runtime_config: @runtime_config)
143
+ return nil if rows.empty?
413
144
 
414
- def result_from_initial_payload
415
- payload = initial_payload
416
- payload && result_from_payload(
417
- PayloadContext.new(payload: payload, cache_status: CacheStatus.hit(payload))
418
- )
419
- end
420
-
421
- def result_from_payload(context)
422
- payload = rebuild_if_incomplete(context)
423
- result_for(payload, loaded_from_cache: context.cache_status.loaded_from_cache?)
424
- end
425
-
426
- def result_from_payload_or_nil(context)
427
- context.payload && begin
428
- payload = rebuild_if_incomplete(context)
429
- payload && result_for(payload, loaded_from_cache: context.cache_status.loaded_from_cache?)
430
- end
431
- end
432
-
433
- def rebuild_if_incomplete(context)
434
- payload = context.payload
435
- checker = CacheIntegrityChecker.new(cache: @cache, payload: payload)
436
- return payload unless checker.incomplete?
437
-
438
- context.cache_status.mark_rebuilt
439
- rebuild_cache
440
- end
441
-
442
- def result_for(payload, loaded_from_cache:)
443
- book = payload.book
444
- cache_path = @cache.cache_path
445
- source_path = payload.source_path || @cache.source_path
446
- Result.new(
447
- book: book,
448
- cache_path: cache_path,
145
+ ManifestShaFinder.new(
146
+ rows: rows,
449
147
  source_path: source_path,
450
- loaded_from_cache: loaded_from_cache,
451
- payload: payload
452
- )
453
- end
454
-
455
- def import_and_result
456
- context = PayloadContext.new(payload: rebuild_cache, cache_status: CacheStatus.miss)
457
- result = result_from_payload_or_nil(context)
458
- return result if result
459
-
460
- raise Shoko::CacheLoadError.new(@cache.cache_path, 'cache write failed')
148
+ source_mtime: source_mtime,
149
+ source_size_bytes: source_size_bytes,
150
+ runtime_config: @runtime_config
151
+ ).sha
152
+ rescue Shoko::Error
153
+ raise
461
154
  end
462
155
 
463
- def rebuild_cache
464
- importer = @importer_class.new(
465
- formatting_service: @formatting_service,
466
- progress_reporter: @progress_reporter
467
- )
468
- book_data = importer.import(@cache.source_path)
469
- report('Creating JSON cache...', progress: 0.0)
470
- @cache.write_book!(book_data)
471
- report('Finalizing cache...', progress: 1.0)
472
- payload_from_source
156
+ def cache_supports_runtime_config?
157
+ parameters = @cache_class.instance_method(:initialize).parameters
158
+ parameters.any? { |kind, name| (kind == :key || kind == :keyreq) && name == :runtime_config }
159
+ rescue Shoko::Error
160
+ raise
473
161
  end
474
162
 
475
- def rebuild_from_pointer_or_raise
476
- rebuilt = rebuild_from_pointer
477
- return rebuilt if rebuilt
478
-
479
- raise Shoko::CacheLoadError, @cache.cache_path
480
- end
481
-
482
- def rebuild_from_pointer
483
- report('Rebuilding cache from source...')
484
- PointerRebuilder.new(
485
- cache: @cache,
486
- formatting_service: @formatting_service,
487
- load_callback: @load_callback
163
+ def ensure_pointer_file(pointer_path, sha, source_path)
164
+ PointerFileEnsurer.new(
165
+ pointer_path: pointer_path,
166
+ sha: sha,
167
+ source_path: source_path,
168
+ manager_class: @pointer_manager_class,
169
+ logger: @logger
488
170
  ).call
489
171
  end
490
172
 
491
- def report(message, progress: nil)
492
- reporter = @progress_reporter
493
- return unless reporter
494
- return if message.nil? || message.to_s.strip.empty?
495
-
496
- if reporter.respond_to?(:call)
497
- reporter.call(message: message, progress: progress)
498
- elsif reporter.respond_to?(:update_status)
499
- reporter.update_status(message: message, progress: progress)
500
- end
501
- rescue StandardError
502
- nil
503
- end
504
- end
505
- private_constant :CacheSession
506
-
507
- # Raises standardized load errors for pipeline failures.
508
- class LoadErrorHandler
509
- def initialize(path)
510
- @path = path
511
- end
512
-
513
- def call(error)
514
- message = error.message
515
- Shoko::Adapters::Monitoring::Logger.error('Book cache pipeline failed', path: @path, error: message)
516
- raise Shoko::EPUBParseError.new(message, @path)
517
- end
518
- end
519
- private_constant :LoadErrorHandler
520
-
521
- def initialize(cache_class: EpubCache, cache_root: CachePaths.cache_root, progress_reporter: nil)
522
- @cache_class = cache_class
523
- @cache_root = cache_root
524
- @importer_class = Shoko::Adapters::BookSources::EpubImporter
525
- @pointer_manager_class = CachePointerManager
526
- @progress_reporter = progress_reporter
527
- end
528
-
529
- def load(path, formatting_service: nil)
530
- perform_load(path, formatting_service)
531
- rescue StandardError => e
532
- raise if e.is_a?(Shoko::Error)
533
-
534
- LoadErrorHandler.new(path).call(e)
535
- end
536
-
537
- private
538
-
539
- def perform_load(path, formatting_service)
540
- report('Checking cache...')
541
- expanded = File.expand_path(path)
542
- fast = fast_load_if_available(expanded, formatting_service)
543
- return fast if fast
544
-
545
- cache = build_cache(expanded)
546
- cache_session(cache, formatting_service).load
547
- end
548
-
549
- def fast_load_if_available(expanded, formatting_service)
550
- return nil unless fast_source_path?(expanded)
551
-
552
- fast_load_for_source(expanded, formatting_service)
553
- end
554
-
555
- def build_cache(path)
556
- @cache_class.new(path, cache_root: @cache_root)
557
- end
558
-
559
- def cache_session(cache, formatting_service)
560
- CacheSession.new(
561
- cache: cache,
562
- formatting_service: formatting_service,
563
- importer_class: @importer_class,
564
- load_callback: method(:load),
565
- progress_reporter: @progress_reporter
566
- )
567
- end
568
-
569
- def fast_source_path?(expanded)
570
- return false if @cache_class.cache_file?(expanded)
571
-
572
- File.file?(expanded)
573
- rescue StandardError
574
- false
575
- end
576
-
577
- def fast_load_for_source(source_path, formatting_service)
578
- perform_fast_load(source_path, formatting_service)
579
- rescue StandardError => e
580
- Shoko::Adapters::Monitoring::Logger.debug('Fast cache load failed', path: source_path, error: e.message)
581
- nil
582
- end
583
-
584
- def perform_fast_load(source_path, formatting_service)
585
- report('Looking up cached data...')
586
- pointer_path, sha = pointer_for_source(source_path)
587
- return nil unless pointer_path
588
-
589
- ensure_pointer_file(pointer_path, sha, source_path)
590
-
591
- cache = build_cache(pointer_path)
592
- cache_session(cache, formatting_service).fast_load
593
- end
594
-
595
- def pointer_for_source(source_path)
596
- sha = sha_for_source(source_path)
597
- return nil unless sha
598
-
599
- pointer_path = pointer_path_for_sha(sha)
600
- return nil unless pointer_path
601
-
602
- [pointer_path, sha]
603
- end
604
-
605
- def sha_for_source(source_path)
606
- source_mtime = File.mtime(source_path).utc
607
- source_size_bytes = File.size(source_path)
608
- sha_from_manifest(source_path, source_mtime, source_size_bytes)
609
- end
610
-
611
- def pointer_path_for_sha(sha)
612
- @cache_class.cache_path_for_sha(sha, cache_root: @cache_root)
613
- end
614
-
615
- def sha_from_manifest(source_path, source_mtime, source_size_bytes)
616
- rows = JsonCacheStore.manifest_rows(@cache_root)
617
- return nil if rows.empty?
618
-
619
- ManifestShaFinder.new(
620
- rows: rows,
621
- source_path: source_path,
622
- source_mtime: source_mtime,
623
- source_size_bytes: source_size_bytes
624
- ).sha
625
- rescue StandardError
626
- nil
627
- end
628
-
629
- def ensure_pointer_file(pointer_path, sha, source_path)
630
- PointerFileEnsurer.new(
631
- pointer_path: pointer_path,
632
- sha: sha,
633
- source_path: source_path,
634
- manager_class: @pointer_manager_class
635
- ).call
636
- end
637
-
638
- def report(message, progress: nil)
639
- reporter = @progress_reporter
640
- return unless reporter
641
- return if message.nil? || message.to_s.strip.empty?
642
-
643
- if reporter.respond_to?(:call)
644
- reporter.call(message: message, progress: progress)
645
- elsif reporter.respond_to?(:update_status)
646
- reporter.update_status(message: message, progress: progress)
173
+ def raise_load_error(path, error)
174
+ LoadErrorHandler.new(path, logger: @logger).call(error)
647
175
  end
648
- rescue StandardError
649
- nil
650
176
  end
651
177
  end
652
178
  end