diff --git a/qt/aqt/exporting.py b/qt/aqt/exporting.py index 0ad18c3d5..a3eeb96a9 100644 --- a/qt/aqt/exporting.py +++ b/qt/aqt/exporting.py @@ -196,6 +196,7 @@ class ExportDialog(QDialog): else: self.on_export_finished() + gui_hooks.legacy_exporter_will_export(self.exporter) if self.isVerbatim: gui_hooks.collection_will_temporarily_close(self.mw.col) self.mw.progress.start() @@ -213,6 +214,7 @@ class ExportDialog(QDialog): msg = tr.exporting_note_exported(count=self.exporter.count) else: msg = tr.exporting_card_exported(count=self.exporter.count) + gui_hooks.legacy_exporter_did_export(self.exporter) tooltip(msg, period=3000) QDialog.reject(self) diff --git a/qt/aqt/import_export/exporting.py b/qt/aqt/import_export/exporting.py index aed2e3c54..f4d132ab0 100644 --- a/qt/aqt/import_export/exporting.py +++ b/qt/aqt/import_export/exporting.py @@ -42,21 +42,21 @@ class ExportDialog(QDialog): self.col = mw.col.weakref() self.frm = aqt.forms.exporting.Ui_ExportDialog() self.frm.setupUi(self) - self.exporter: Type[Exporter] = None + self.exporter: Exporter self.nids = nids disable_help_button(self) self.setup(did) self.open() def setup(self, did: DeckId | None) -> None: - self.exporters: list[Type[Exporter]] = [ + self.exporter_classes: list[Type[Exporter]] = [ ApkgExporter, ColpkgExporter, NoteCsvExporter, CardCsvExporter, ] self.frm.format.insertItems( - 0, [f"{e.name()} (.{e.extension})" for e in self.exporters] + 0, [f"{e.name()} (.{e.extension})" for e in self.exporter_classes] ) qconnect(self.frm.format.activated, self.exporter_changed) if self.nids is None and not did: @@ -86,7 +86,7 @@ class ExportDialog(QDialog): self.frm.includeSched.setChecked(False) def exporter_changed(self, idx: int) -> None: - self.exporter = self.exporters[idx] + self.exporter = self.exporter_classes[idx]() self.frm.includeSched.setVisible(self.exporter.show_include_scheduling) self.frm.includeMedia.setVisible(self.exporter.show_include_media) self.frm.includeTags.setVisible(self.exporter.show_include_tags) @@ -125,14 +125,14 @@ class ExportDialog(QDialog): break return path - def options(self, out_path: str) -> Options: + def options(self, out_path: str) -> ExportOptions: limit: ExportLimit = None if self.nids: limit = NoteIdsLimit(self.nids) elif current_deck_id := self.current_deck_id(): limit = DeckIdLimit(current_deck_id) - return Options( + return ExportOptions( out_path=out_path, include_scheduling=self.frm.includeSched.isChecked(), include_media=self.frm.includeMedia.isChecked(), @@ -165,7 +165,7 @@ class ExportDialog(QDialog): @dataclass -class Options: +class ExportOptions: out_path: str include_scheduling: bool include_media: bool @@ -190,9 +190,8 @@ class Exporter(ABC): show_include_notetype = False show_include_guid = False - @staticmethod @abstractmethod - def export(mw: aqt.main.AnkiQt, options: Options) -> None: + def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None: pass @staticmethod @@ -210,10 +209,12 @@ class ColpkgExporter(Exporter): def name() -> str: return tr.exporting_anki_collection_package() - @staticmethod - def export(mw: aqt.main.AnkiQt, options: Options) -> None: + def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None: + options = gui_hooks.exporter_will_export(options, self) + def on_success(_: None) -> None: mw.reopen() + gui_hooks.exporter_did_export(options, self) tooltip(tr.exporting_collection_exported(), parent=mw) def on_failure(exception: Exception) -> None: @@ -245,8 +246,13 @@ class ApkgExporter(Exporter): def name() -> str: return tr.exporting_anki_deck_package() - @staticmethod - def export(mw: aqt.main.AnkiQt, options: Options) -> None: + def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None: + options = gui_hooks.exporter_will_export(options, self) + + def on_success(count: int) -> None: + gui_hooks.exporter_did_export(options, self) + tooltip(tr.exporting_note_exported(count=count), parent=mw) + QueryOp( parent=mw, op=lambda col: col.export_anki_package( @@ -256,9 +262,7 @@ class ApkgExporter(Exporter): with_media=options.include_media, legacy_support=options.legacy_support, ), - success=lambda count: tooltip( - tr.exporting_note_exported(count=count), parent=mw - ), + success=on_success, ).with_backend_progress(export_progress_update).run_in_background() @@ -275,8 +279,13 @@ class NoteCsvExporter(Exporter): def name() -> str: return tr.exporting_notes_in_plain_text() - @staticmethod - def export(mw: aqt.main.AnkiQt, options: Options) -> None: + def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None: + options = gui_hooks.exporter_will_export(options, self) + + def on_success(count: int) -> None: + gui_hooks.exporter_did_export(options, self) + tooltip(tr.exporting_note_exported(count=count), parent=mw) + QueryOp( parent=mw, op=lambda col: col.export_note_csv( @@ -288,9 +297,7 @@ class NoteCsvExporter(Exporter): with_notetype=options.include_notetype, with_guid=options.include_guid, ), - success=lambda count: tooltip( - tr.exporting_note_exported(count=count), parent=mw - ), + success=on_success, ).with_backend_progress(export_progress_update).run_in_background() @@ -303,8 +310,13 @@ class CardCsvExporter(Exporter): def name() -> str: return tr.exporting_cards_in_plain_text() - @staticmethod - def export(mw: aqt.main.AnkiQt, options: Options) -> None: + def export(self, mw: aqt.main.AnkiQt, options: ExportOptions) -> None: + options = gui_hooks.exporter_will_export(options, self) + + def on_success(count: int) -> None: + gui_hooks.exporter_did_export(options, self) + tooltip(tr.exporting_card_exported(count=count), parent=mw) + QueryOp( parent=mw, op=lambda col: col.export_card_csv( @@ -312,9 +324,7 @@ class CardCsvExporter(Exporter): limit=options.limit, with_html=options.include_html, ), - success=lambda count: tooltip( - tr.exporting_card_exported(count=count), parent=mw - ), + success=on_success, ).with_backend_progress(export_progress_update).run_in_background() diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 6efe0125f..6a3a139ab 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -816,6 +816,46 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest) `output` provides access to the unused/missing file lists and the text output that will be shown in the Check Media screen.""", ), + # Importing/exporting data + ################### + Hook( + name="exporter_will_export", + args=[ + "export_options: aqt.import_export.exporting.ExportOptions", + "exporter: aqt.import_export.exporting.Exporter", + ], + return_type="aqt.import_export.exporting.ExportOptions", + doc="""Called before collection and deck exports. + + Allows add-ons to be notified of impending deck exports and potentially + modify the export options. To perform the export unaltered, please return + `export_options` as is, e.g.: + + def on_exporter_will_export(export_options: ExportOptions, exporter: Exporter): + if not isinstance(exporter, ApkgExporter): + return export_options + export_options.limit = ... + return export_options + """, + ), + Hook( + name="exporter_did_export", + args=[ + "export_options: aqt.import_export.exporting.ExportOptions", + "exporter: aqt.import_export.exporting.Exporter", + ], + doc="""Called after collection and deck exports.""", + ), + Hook( + name="legacy_exporter_will_export", + args=["legacy_exporter: anki.exporting.Exporter"], + doc="""Called before collection and deck exports performed by legacy exporters.""", + ), + Hook( + name="legacy_exporter_did_export", + args=["legacy_exporter: anki.exporting.Exporter"], + doc="""Called after collection and deck exports performed by legacy exporters.""", + ), # Dialog Manager ################### Hook(