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,30 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fileutils'
4
-
5
- require_relative '../../core/services/base_service.rb'
6
- require_relative '../../adapters/storage/cache_paths'
7
- require_relative '../../adapters/storage/recent_files'
8
- require_relative '../state/actions/update_config_action.rb'
9
- require_relative '../selectors/config_selectors'
10
-
11
3
  module Shoko
12
- module Application::UseCases
4
+ module Application
5
+ module UseCases
13
6
  # Centralises configuration toggles and cache maintenance for menu settings flows.
14
- class SettingsService < BaseService
7
+ class SettingsService
15
8
  WIPE_CACHE_MESSAGE = "All caches wiped. Use 'Find Book' to rescan"
16
9
 
17
- def initialize(dependencies)
18
- super
19
- @state_store = resolve(:state_store)
20
- @terminal_service = resolve(:terminal_service)
21
- @wrapping_service = resolve(:wrapping_service) if registered?(:wrapping_service)
22
- @recent_repository = Adapters::Storage::RecentFiles
10
+ def initialize(config_reader:, state_writer:, cache_manager:, dictionary_availability:,
11
+ dictionary_storage:, data_cleanup:,
12
+ wrapping_service: nil, recent_files_repository: nil, dictionary_service: nil,
13
+ catalog_service: nil, config_storage: nil, logger: nil)
14
+ @config_reader = config_reader
15
+ @state_writer = state_writer
16
+ @cache_manager = cache_manager
17
+ @dictionary_availability = dictionary_availability
18
+ @dictionary_storage = dictionary_storage
19
+ @data_cleanup = data_cleanup
20
+ @wrapping_service = wrapping_service
21
+ @recent_repository = recent_files_repository
22
+ @dictionary_service = dictionary_service
23
+ @catalog_service_ref = catalog_service
24
+ @config_storage = config_storage
25
+ @logger = logger
23
26
  end
24
27
 
25
28
  # Toggle split/single view mode and persist the change.
26
29
  def toggle_view_mode
27
- current = Shoko::Application::Selectors::ConfigSelectors.view_mode(@state_store) || :split
30
+ current = @config_reader.view_mode || :single
28
31
  new_mode = current == :split ? :single : :split
29
32
  dispatch_config(view_mode: new_mode)
30
33
  new_mode
@@ -32,14 +35,14 @@ module Shoko
32
35
 
33
36
  # Toggle whether page numbers are displayed.
34
37
  def toggle_page_numbers
35
- current = Shoko::Application::Selectors::ConfigSelectors.show_page_numbers(@state_store)
38
+ current = @config_reader.show_page_numbers
36
39
  dispatch_config(show_page_numbers: !current)
37
40
  end
38
41
 
39
- # Cycle through line spacing options (compact normal relaxed ...).
42
+ # Cycle through line spacing options (compact -> normal -> relaxed -> ...).
40
43
  def cycle_line_spacing
41
44
  modes = Shoko::Core::Models::ReaderSettings::LINE_SPACING_VALUES
42
- current = Shoko::Application::Selectors::ConfigSelectors.line_spacing(@state_store) || Shoko::Core::Models::ReaderSettings::DEFAULT_LINE_SPACING
45
+ current = @config_reader.line_spacing || Shoko::Core::Models::ReaderSettings::DEFAULT_LINE_SPACING
43
46
  next_mode = modes[(modes.index(current) || 1) + 1] || modes.first
44
47
  dispatch_config(line_spacing: next_mode)
45
48
  next_mode
@@ -47,77 +50,192 @@ module Shoko
47
50
 
48
51
  # Toggle quote highlighting preference.
49
52
  def toggle_highlight_quotes
50
- current = Shoko::Application::Selectors::ConfigSelectors.highlight_quotes(@state_store)
53
+ current = @config_reader.highlight_quotes
51
54
  dispatch_config(highlight_quotes: !current)
52
55
  end
53
56
 
57
+ def toggle_dictionary_backend
58
+ current = @config_reader.dictionary_backend
59
+ backend_name = current.to_s.downcase
60
+ auto_enabled = backend_name.empty? && dictionary_auto_available?
61
+ new_backend = case backend_name
62
+ when 'sqlite'
63
+ :disabled
64
+ when 'disabled'
65
+ :sqlite
66
+ else
67
+ auto_enabled ? :disabled : :sqlite
68
+ end
69
+ dispatch_config(dictionary_backend: new_backend)
70
+ new_backend
71
+ end
72
+
73
+ def cycle_dictionary_pair
74
+ pairs = available_dictionary_pairs
75
+ source = @config_reader.dictionary_source_lang
76
+ target = @config_reader.dictionary_target_lang
77
+
78
+ auto = dictionary_auto_setting?(source)
79
+ indexed_pairs = pairs.map { |pair| [pair[:source], pair[:target]] }
80
+ current_index = auto ? -1 : indexed_pairs.index([source, target]) || -1
81
+ next_index = (current_index + 1) % (indexed_pairs.length + 1)
82
+
83
+ if next_index.zero?
84
+ dispatch_config(dictionary_source_lang: 'auto')
85
+ { source: 'auto', target: target }
86
+ else
87
+ next_pair = indexed_pairs[next_index - 1]
88
+ dispatch_config(dictionary_source_lang: next_pair[0], dictionary_target_lang: next_pair[1])
89
+ { source: next_pair[0], target: next_pair[1] }
90
+ end
91
+ end
92
+
54
93
  def toggle_kitty_images
55
- current = Shoko::Application::Selectors::ConfigSelectors.kitty_images(@state_store)
94
+ current = @config_reader.kitty_images
56
95
  dispatch_config(kitty_images: !current)
57
96
  end
58
97
 
59
98
  # Toggle dynamic/absolute page numbering mode.
60
99
  def toggle_page_numbering_mode
61
- current = Shoko::Application::Selectors::ConfigSelectors.page_numbering_mode(@state_store) || :dynamic
100
+ current = @config_reader.page_numbering_mode || :dynamic
62
101
  next_mode = current == :absolute ? :dynamic : :absolute
63
102
  dispatch_config(page_numbering_mode: next_mode)
64
103
  next_mode
65
104
  end
66
105
 
67
- # Wipe cached EPUB data, recent file history, and wrapping caches.
106
+ # Wipe selected cached data and/or downloaded books.
68
107
  # Returns the status message applied to the catalog.
69
- def wipe_cache(catalog: nil)
70
- Shoko::Adapters::BookSources::EPUBFinder.clear_cache
71
- remove_epub_cache_on_disk
72
- @recent_repository.clear
73
- @wrapping_service&.clear_cache
108
+ def wipe_cache(catalog: nil, cached: nil, downloads: nil, nuke: nil,
109
+ annotations: nil, bookmarks: nil, progress: nil, config_file: nil)
110
+ cached = cached.nil? ? true : !!cached
111
+ downloads = !!downloads
112
+ nuke = !!nuke
113
+ dictionary = false
114
+ annotations = !!annotations
115
+ bookmarks = !!bookmarks
116
+ progress = !!progress
117
+ config_file = !!config_file
118
+
119
+ if nuke
120
+ cached = true
121
+ downloads = true
122
+ dictionary = true
123
+ annotations = true
124
+ bookmarks = true
125
+ progress = true
126
+ config_file = true
127
+ end
74
128
 
75
- target_catalog = catalog || resolve(:catalog_service)
129
+ wipe_cached_data if cached
130
+ remove_downloads_on_disk if downloads
131
+ remove_dictionary_databases if dictionary
132
+ remove_user_data_files(annotations: annotations, bookmarks: bookmarks,
133
+ progress: progress, config_file: config_file)
134
+
135
+ target_catalog = catalog || @catalog_service_ref
76
136
  if target_catalog
77
- target_catalog.update_entries([])
78
- target_catalog.scan_status = :idle
79
- target_catalog.scan_message = WIPE_CACHE_MESSAGE
137
+ target_catalog.reset_after_wipe(
138
+ message: wipe_cache_message(cached: cached, downloads: downloads, nuke: nuke,
139
+ annotations: annotations, bookmarks: bookmarks,
140
+ progress: progress, config_file: config_file)
141
+ )
80
142
  end
81
143
 
82
- WIPE_CACHE_MESSAGE
144
+ wipe_cache_message(cached: cached, downloads: downloads, nuke: nuke,
145
+ annotations: annotations, bookmarks: bookmarks,
146
+ progress: progress, config_file: config_file)
83
147
  end
84
148
 
85
149
  private
86
150
 
87
- def required_dependencies
88
- %i[state_store terminal_service]
151
+ def dispatch_config(**payload)
152
+ @state_writer.update_config(**payload)
153
+ end
154
+
155
+ def available_dictionary_pairs
156
+ pairs = @dictionary_service&.available_language_pairs || []
157
+ pairs.filter_map do |pair|
158
+ normalized = normalize_language_pair(pair)
159
+ source = normalized[:source].to_s.strip
160
+ target = normalized[:target].to_s.strip
161
+ next if source.empty? || target.empty?
162
+
163
+ { source: source, target: target }
164
+ end.uniq.sort_by { |pair| [pair[:source].to_s, pair[:target].to_s] }
165
+ end
166
+
167
+ def dictionary_auto_setting?(value)
168
+ return true if value.nil?
169
+
170
+ str = value.to_s.strip
171
+ str.empty? || str.casecmp('auto').zero?
89
172
  end
90
173
 
91
- def dispatch_config(payload)
92
- @state_store.dispatch(Shoko::Application::Actions::UpdateConfigAction.new(payload))
93
- @state_store.save_config if @state_store.respond_to?(:save_config)
174
+ def dictionary_auto_available?
175
+ return false unless @dictionary_availability&.sqlite3_available?
176
+
177
+ @dictionary_storage&.databases_present?(@config_reader.dictionary_path)
94
178
  end
95
179
 
96
180
  def remove_epub_cache_on_disk
97
- cache_root = Adapters::Storage::CachePaths.cache_root
98
- return unless cache_root && File.directory?(cache_root)
181
+ @data_cleanup&.remove_cache_root(@cache_manager.cache_root)
182
+ end
183
+
184
+ def remove_downloads_on_disk
185
+ @data_cleanup&.remove_downloads_root(configured_config_root)
186
+ end
187
+
188
+ def remove_dictionary_databases
189
+ @dictionary_storage&.remove_databases_path(@config_reader.dictionary_path)
190
+ end
191
+
192
+ def wipe_cached_data
193
+ @cache_manager.clear_epub_cache
194
+ remove_epub_cache_on_disk
195
+ @recent_repository&.clear
196
+ @wrapping_service&.clear_cache
197
+ end
198
+
199
+ def wipe_cache_message(cached:, downloads:, nuke:, annotations:, bookmarks:, progress:, config_file:)
200
+ return "All data wiped. Use 'Find Book' to rescan" if nuke
99
201
 
100
- cache_real = safe_realpath(cache_root)
101
- return unless cache_real
202
+ data = annotations || bookmarks || progress || config_file
203
+ return "Caches + downloads + data wiped. Use 'Find Book' to rescan" if cached && downloads && data
204
+ return "Caches + downloads wiped. Use 'Find Book' to rescan" if cached && downloads
205
+ return "Caches + data wiped. Use 'Find Book' to rescan" if cached && data
206
+ return "Downloads + data wiped. Use 'Find Book' to rescan" if downloads && data
207
+ return WIPE_CACHE_MESSAGE if cached
208
+ return "Downloads deleted. Use 'Find Book' to rescan" if downloads
209
+ return 'User data wiped.' if data
102
210
 
103
- FileUtils.rm_rf(cache_real)
104
- rescue StandardError
105
- nil
211
+ 'Nothing selected to wipe'
106
212
  end
107
213
 
108
- def safe_realpath(path)
109
- root_real = File.realpath(File.dirname(path))
110
- cache_real = File.realpath(path)
111
- return cache_real if cache_real.start_with?(root_real) && allowed_cache_dir?(File.basename(cache_real))
214
+ def remove_user_data_files(annotations:, bookmarks:, progress:, config_file:)
215
+ @data_cleanup&.remove_user_data_files(
216
+ config_root: configured_config_root,
217
+ annotations: annotations,
218
+ bookmarks: bookmarks,
219
+ progress: progress,
220
+ config_file: config_file
221
+ )
222
+ end
112
223
 
113
- nil
114
- rescue StandardError
115
- allowed_cache_dir?(File.basename(path)) ? path : nil
224
+ def configured_config_root
225
+ @config_storage&.config_dir
116
226
  end
117
227
 
118
- def allowed_cache_dir?(name)
119
- %w[shoko reader].include?(name)
228
+ def normalize_language_pair(pair)
229
+ unless pair.is_a?(Hash)
230
+ raise ArgumentError, "dictionary language pair must be a Hash, got #{pair.class}"
231
+ end
232
+
233
+ pair.each_with_object({}) do |(key, value), acc|
234
+ normalized_key = key.is_a?(String) ? key.to_sym : key
235
+ acc[normalized_key] = value
236
+ end
120
237
  end
121
238
  end
239
+ end
122
240
  end
123
241
  end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../core/ports/outbound/folder_scanner'
4
+ require_relative '../../../core/ports/outbound/folder_importer'
5
+ require_relative '../../../core/ports/outbound/clock'
6
+ require_relative '../../../core/ports/outbound/path_ops'
7
+
8
+ module Shoko
9
+ module Application
10
+ module Workflows
11
+ module Cli
12
+ # Coordinates directory discovery and batch cache imports for CLI usage.
13
+ class FolderImportWorkflow
14
+ GROUP_ORDER = %i[epub pdf fb2 kindle rtf].freeze
15
+
16
+ DocumentCandidate = Shoko::Core::Ports::Outbound::FolderScanner::Entry
17
+ DiscoveryReport = Data.define(:directory_path, :documents, :counts_by_group, :total_count)
18
+ ImportFailure = Data.define(:path, :error_class, :error_message)
19
+ ImportReport = Data.define(:total_count, :imported_count, :skipped_count, :failed_count, :failures,
20
+ :elapsed_seconds)
21
+
22
+ def initialize(scanner:, importer:, clock:, path_ops:, logger: nil)
23
+ unless scanner.is_a?(Shoko::Core::Ports::Outbound::FolderScanner)
24
+ raise ArgumentError, 'scanner must implement Core::Ports::Outbound::FolderScanner'
25
+ end
26
+ unless importer.is_a?(Shoko::Core::Ports::Outbound::FolderImporter)
27
+ raise ArgumentError, 'importer must implement Core::Ports::Outbound::FolderImporter'
28
+ end
29
+ unless clock.is_a?(Shoko::Core::Ports::Outbound::Clock)
30
+ raise ArgumentError, 'clock must implement Core::Ports::Outbound::Clock'
31
+ end
32
+ unless path_ops.is_a?(Shoko::Core::Ports::Outbound::PathOps)
33
+ raise ArgumentError, 'path_ops must implement Core::Ports::Outbound::PathOps'
34
+ end
35
+
36
+ @scanner = scanner
37
+ @importer = importer
38
+ @clock = clock
39
+ @path_ops = path_ops
40
+ @logger = logger
41
+ end
42
+
43
+ def discover(directory_path, recursive: true, skip_hidden: true)
44
+ root = @path_ops.expand_path(directory_path.to_s)
45
+ raw_documents = Array(@scanner.scan(root, recursive: recursive, skip_hidden: skip_hidden))
46
+ documents = raw_documents.map { |entry| normalize_candidate(entry) }
47
+ .sort_by { |entry| entry.path.to_s.downcase }
48
+
49
+ counts = default_counts
50
+ documents.each do |document|
51
+ group = normalize_group(document.format_group)
52
+ counts[group] ||= 0
53
+ counts[group] += 1
54
+ end
55
+
56
+ DiscoveryReport.new(
57
+ directory_path: root,
58
+ documents: documents,
59
+ counts_by_group: counts,
60
+ total_count: documents.length
61
+ )
62
+ end
63
+
64
+ def import(documents)
65
+ selected = Array(documents).map { |entry| normalize_candidate(entry) }
66
+ total = selected.length
67
+ imported_count = 0
68
+ skipped_count = 0
69
+ started_at = monotonic_now
70
+
71
+ selected.each_with_index do |document, index|
72
+ path = document.path
73
+
74
+ status = normalize_import_status(@importer.import(path))
75
+ if status == :skipped
76
+ skipped_count += 1
77
+ else
78
+ imported_count += 1
79
+ end
80
+ yield(done: index + 1, total: total, path: path, status: status) if block_given?
81
+ end
82
+
83
+ ImportReport.new(
84
+ total_count: total,
85
+ imported_count: imported_count,
86
+ skipped_count: skipped_count,
87
+ failed_count: 0,
88
+ failures: [],
89
+ elapsed_seconds: monotonic_now - started_at
90
+ )
91
+ end
92
+
93
+ private
94
+
95
+ def normalize_candidate(entry)
96
+ unless entry.is_a?(DocumentCandidate)
97
+ raise ArgumentError, "Expected #{DocumentCandidate}, got #{entry.class}"
98
+ end
99
+
100
+ path = entry.path
101
+ if path.to_s.strip.empty?
102
+ raise ArgumentError, 'candidate path cannot be blank'
103
+ end
104
+
105
+ extension = normalize_extension(entry.format_extension, path)
106
+ group = normalize_group(entry.format_group || group_for_extension(extension))
107
+
108
+ DocumentCandidate.new(
109
+ path: path,
110
+ format_group: group,
111
+ format_extension: extension || ''
112
+ )
113
+ end
114
+
115
+ def group_for_extension(extension)
116
+ case extension.to_s.downcase
117
+ when '.epub' then :epub
118
+ when '.pdf' then :pdf
119
+ when '.fb2', '.fb2.zip' then :fb2
120
+ when '.mobi', '.azw', '.azw3' then :kindle
121
+ when '.rtf' then :rtf
122
+ else
123
+ nil
124
+ end
125
+ end
126
+
127
+ def normalize_group(group)
128
+ value = group.to_s.strip.downcase
129
+ return :unknown if value.empty?
130
+
131
+ value.tr('-', '_').to_sym
132
+ end
133
+
134
+ def normalize_extension(extension, path)
135
+ value = extension.to_s.strip.downcase
136
+ return value unless value.empty?
137
+
138
+ basename = @path_ops.basename(path.to_s).downcase
139
+ return '.fb2.zip' if basename.end_with?('.fb2.zip')
140
+
141
+ @path_ops.extname(path.to_s).downcase
142
+ end
143
+
144
+ def normalize_import_status(result)
145
+ case result
146
+ when :imported, :skipped
147
+ result
148
+ else
149
+ raise ArgumentError, "Invalid folder import status: #{result.inspect}"
150
+ end
151
+ end
152
+
153
+ def default_counts
154
+ GROUP_ORDER.each_with_object({}) { |group, acc| acc[group] = 0 }
155
+ end
156
+
157
+ def monotonic_now
158
+ @clock.monotonic_now.to_f
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../core/ports/outbound/menu_mode_switcher'
4
+ require_relative '../../../core/ports/outbound/annotation_selection_reader'
5
+ require_relative '../../../core/ports/outbound/annotation_view_refresher'
6
+ require_relative '../../../core/ports/outbound/reader_runner'
7
+ require_relative '../../../core/ports/outbound/menu_workflow_state_reader'
8
+ require_relative '../../../core/ports/outbound/menu_workflow_state_writer'
9
+ require_relative '../../../core/models/annotation_selection'
10
+ require_relative '../../../core/models/pending_jump_payload'
11
+
12
+ module Shoko
13
+ module Application
14
+ module Workflows
15
+ module Menu
16
+ class AnnotationWorkflow
17
+ def initialize(mode_switcher:, menu_state_reader:, menu_state_writer:, state_writer:, annotation_service:,
18
+ logger:, selected_annotation_reader:, annotations_view_refresher:, reader_runner:)
19
+ unless mode_switcher.is_a?(Shoko::Core::Ports::Outbound::MenuModeSwitcher)
20
+ raise ArgumentError, 'mode_switcher must implement Core::Ports::Outbound::MenuModeSwitcher'
21
+ end
22
+ unless selected_annotation_reader.is_a?(Shoko::Core::Ports::Outbound::AnnotationSelectionReader)
23
+ raise ArgumentError, 'selected_annotation_reader must implement Core::Ports::Outbound::AnnotationSelectionReader'
24
+ end
25
+ unless annotations_view_refresher.is_a?(Shoko::Core::Ports::Outbound::AnnotationViewRefresher)
26
+ raise ArgumentError, 'annotations_view_refresher must implement Core::Ports::Outbound::AnnotationViewRefresher'
27
+ end
28
+ unless reader_runner.is_a?(Shoko::Core::Ports::Outbound::ReaderRunner)
29
+ raise ArgumentError, 'reader_runner must implement Core::Ports::Outbound::ReaderRunner'
30
+ end
31
+ unless menu_state_reader.is_a?(Shoko::Core::Ports::Outbound::MenuWorkflowStateReader)
32
+ raise ArgumentError, 'menu_state_reader must implement Core::Ports::Outbound::MenuWorkflowStateReader'
33
+ end
34
+ unless menu_state_writer.is_a?(Shoko::Core::Ports::Outbound::MenuWorkflowStateWriter)
35
+ raise ArgumentError, 'menu_state_writer must implement Core::Ports::Outbound::MenuWorkflowStateWriter'
36
+ end
37
+ raise ArgumentError, 'annotation_service is required' if annotation_service.nil?
38
+
39
+ @mode_switcher = mode_switcher
40
+ @menu_state_reader = menu_state_reader
41
+ @menu_state_writer = menu_state_writer
42
+ @state_writer = state_writer
43
+ @annotation_service = annotation_service
44
+ @logger = logger
45
+ @selected_annotation_reader = selected_annotation_reader
46
+ @annotations_view_refresher = annotations_view_refresher
47
+ @reader_runner = reader_runner
48
+ end
49
+
50
+ def open_selected_annotation
51
+ selection = selected_annotation
52
+ return unless selection
53
+
54
+ @state_writer.update_reader_meta(book_path: selection.book_path)
55
+ pending_payload = Shoko::Core::Models::PendingJumpPayload.new(
56
+ chapter_index: selection.chapter_index,
57
+ selection_range: selection.range,
58
+ annotation: selection,
59
+ edit: false
60
+ )
61
+ @state_writer.update_selections(pending_jump: pending_payload)
62
+
63
+ @reader_runner.run_reader(selection.book_path)
64
+ end
65
+
66
+ def open_selected_annotation_for_edit
67
+ selection = selected_annotation
68
+ return unless selection
69
+
70
+ note_text = selection.note.to_s
71
+ @menu_state_writer.set_annotation_state(
72
+ selected_annotation: selection.to_annotation_h,
73
+ selected_annotation_book: selection.book_path,
74
+ annotation_edit_text: note_text,
75
+ annotation_edit_cursor: note_text.length
76
+ )
77
+ @mode_switcher.switch_mode(:annotation_editor)
78
+ end
79
+
80
+ def delete_selected_annotation
81
+ selection = selected_annotation
82
+ return unless selection
83
+
84
+ ann_id = selection.id
85
+ return unless ann_id
86
+
87
+ @annotation_service.delete(selection.book_path, ann_id)
88
+ @menu_state_writer.set_annotation_state(annotations_all: @annotation_service.list_all)
89
+
90
+ @annotations_view_refresher.refresh_annotations_view
91
+ end
92
+
93
+ def save_current_annotation_edit
94
+ context = current_annotation_edit_context
95
+ return unless context
96
+
97
+ @annotation_service.update(context[:path], context[:id], context[:text])
98
+ @menu_state_writer.set_annotation_state(annotations_all: @annotation_service.list_all)
99
+
100
+ @mode_switcher.switch_mode(:annotations)
101
+ @annotations_view_refresher.refresh_annotations_view
102
+ end
103
+
104
+ private
105
+
106
+ def selected_annotation
107
+ @selected_annotation_reader.selected_annotation
108
+ end
109
+
110
+ def current_annotation_edit_context
111
+ annotation = @menu_state_reader.selected_annotation_record || {}
112
+ path = @menu_state_reader.selected_annotation_book_path
113
+ text = @menu_state_reader.annotation_editor_text
114
+ return unless path
115
+ unless annotation.is_a?(Hash)
116
+ raise ArgumentError, "selected_annotation_record must be Hash, got #{annotation.class}"
117
+ end
118
+
119
+ ann_id = if annotation.key?(:id)
120
+ annotation[:id]
121
+ elsif annotation.key?('id')
122
+ raise ArgumentError, 'selected_annotation_record must use symbol keys'
123
+ end
124
+ return unless ann_id
125
+
126
+ { path: path, id: ann_id, text: text }
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end