summaryrefslogtreecommitdiff
path: root/src/Exporter.vala
diff options
context:
space:
mode:
Diffstat (limited to 'src/Exporter.vala')
-rw-r--r--src/Exporter.vala343
1 files changed, 343 insertions, 0 deletions
diff --git a/src/Exporter.vala b/src/Exporter.vala
new file mode 100644
index 0000000..a930c4c
--- /dev/null
+++ b/src/Exporter.vala
@@ -0,0 +1,343 @@
+/* Copyright 2010-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+public enum ExportFormatMode {
+ UNMODIFIED,
+ CURRENT,
+ SPECIFIED, /* use an explicitly specified format like PNG or JPEG */
+ LAST /* use whatever format was used in the previous export operation */
+}
+
+public struct ExportFormatParameters {
+ public ExportFormatMode mode;
+ public PhotoFileFormat specified_format;
+ public Jpeg.Quality quality;
+ public bool export_metadata;
+
+ private ExportFormatParameters(ExportFormatMode mode, PhotoFileFormat specified_format,
+ Jpeg.Quality quality) {
+ this.mode = mode;
+ this.specified_format = specified_format;
+ this.quality = quality;
+ this.export_metadata = true;
+ }
+
+ public static ExportFormatParameters current() {
+ return ExportFormatParameters(ExportFormatMode.CURRENT,
+ PhotoFileFormat.get_system_default_format(), Jpeg.Quality.HIGH);
+ }
+
+ public static ExportFormatParameters unmodified() {
+ return ExportFormatParameters(ExportFormatMode.UNMODIFIED,
+ PhotoFileFormat.get_system_default_format(), Jpeg.Quality.HIGH);
+ }
+
+ public static ExportFormatParameters for_format(PhotoFileFormat format) {
+ return ExportFormatParameters(ExportFormatMode.SPECIFIED, format, Jpeg.Quality.HIGH);
+ }
+
+ public static ExportFormatParameters last() {
+ return ExportFormatParameters(ExportFormatMode.LAST,
+ PhotoFileFormat.get_system_default_format(), Jpeg.Quality.HIGH);
+ }
+
+ public static ExportFormatParameters for_JPEG(Jpeg.Quality quality) {
+ return ExportFormatParameters(ExportFormatMode.SPECIFIED, PhotoFileFormat.JFIF,
+ quality);
+ }
+}
+
+public class Exporter : Object {
+ public enum Overwrite {
+ YES,
+ NO,
+ CANCEL,
+ REPLACE_ALL
+ }
+
+ public delegate void CompletionCallback(Exporter exporter, bool is_cancelled);
+
+ public delegate Overwrite OverwriteCallback(Exporter exporter, File file);
+
+ public delegate bool ExportFailedCallback(Exporter exporter, File file, int remaining,
+ Error err);
+
+ private class ExportJob : BackgroundJob {
+ public MediaSource media;
+ public File dest;
+ public Scaling? scaling;
+ public Jpeg.Quality? quality;
+ public PhotoFileFormat? format;
+ public Error? err = null;
+ public bool direct_copy_unmodified = false;
+ public bool export_metadata = true;
+
+ public ExportJob(Exporter owner, MediaSource media, File dest, Scaling? scaling,
+ Jpeg.Quality? quality, PhotoFileFormat? format, Cancellable cancellable,
+ bool direct_copy_unmodified = false, bool export_metadata = true) {
+ base (owner, owner.on_exported, cancellable, owner.on_export_cancelled);
+
+ assert(media is Photo || media is Video);
+
+ this.media = media;
+ this.dest = dest;
+ this.scaling = scaling;
+ this.quality = quality;
+ this.format = format;
+ this.direct_copy_unmodified = direct_copy_unmodified;
+ this.export_metadata = export_metadata;
+ }
+
+ public override void execute() {
+ try {
+ if (media is Photo) {
+ ((Photo) media).export(dest, scaling, quality, format, direct_copy_unmodified, export_metadata);
+ } else if (media is Video) {
+ ((Video) media).export(dest);
+ }
+ } catch (Error err) {
+ this.err = err;
+ }
+ }
+ }
+
+ private Gee.Collection<MediaSource> to_export = new Gee.ArrayList<MediaSource>();
+ private File[] exported_files;
+ private File? dir;
+ private Scaling scaling;
+ private int completed_count = 0;
+ private Workers workers = new Workers(Workers.threads_per_cpu(1, 4), false);
+ private unowned CompletionCallback? completion_callback = null;
+ private unowned ExportFailedCallback? error_callback = null;
+ private unowned OverwriteCallback? overwrite_callback = null;
+ private unowned ProgressMonitor? monitor = null;
+ private Cancellable cancellable;
+ private bool replace_all = false;
+ private bool aborted = false;
+ private ExportFormatParameters export_params;
+
+ public Exporter(Gee.Collection<MediaSource> to_export, File? dir, Scaling scaling,
+ ExportFormatParameters export_params, bool auto_replace_all = false) {
+ this.to_export.add_all(to_export);
+ this.dir = dir;
+ this.scaling = scaling;
+ this.export_params = export_params;
+ this.replace_all = auto_replace_all;
+ }
+
+ public Exporter.for_temp_file(Gee.Collection<MediaSource> to_export, Scaling scaling,
+ ExportFormatParameters export_params) {
+ this.to_export.add_all(to_export);
+ this.dir = null;
+ this.scaling = scaling;
+ this.export_params = export_params;
+ }
+
+ // This should be called only once; the object does not reset its internal state when completed.
+ public void export(CompletionCallback completion_callback, ExportFailedCallback error_callback,
+ OverwriteCallback overwrite_callback, Cancellable? cancellable, ProgressMonitor? monitor) {
+ this.completion_callback = completion_callback;
+ this.error_callback = error_callback;
+ this.overwrite_callback = overwrite_callback;
+ this.monitor = monitor;
+ this.cancellable = cancellable ?? new Cancellable();
+
+ if (!process_queue())
+ export_completed(true);
+ }
+
+ private void on_exported(BackgroundJob j) {
+ ExportJob job = (ExportJob) j;
+
+ completed_count++;
+
+ // because the monitor spins the event loop, and so it's possible this function will be
+ // re-entered, decide now if this is the last job
+ bool completed = completed_count == to_export.size;
+
+ if (!aborted && job.err != null) {
+ if (!error_callback(this, job.dest, to_export.size - completed_count, job.err)) {
+ aborted = true;
+
+ if (!completed)
+ return;
+ }
+ }
+
+ if (!aborted && monitor != null) {
+ if (!monitor(completed_count, to_export.size, false)) {
+ aborted = true;
+
+ if (!completed)
+ return;
+ } else {
+ exported_files += job.dest;
+ }
+ }
+
+ if (completed)
+ export_completed(false);
+ }
+
+ private void on_export_cancelled(BackgroundJob j) {
+ if (++completed_count == to_export.size)
+ export_completed(true);
+ }
+
+ public File[] get_exported_files() {
+ return exported_files;
+ }
+
+ private bool process_queue() {
+ int submitted = 0;
+ foreach (MediaSource source in to_export) {
+ File? use_source_file = null;
+ PhotoFileFormat real_export_format = PhotoFileFormat.get_system_default_format();
+ string? basename = null;
+ if (source is Photo) {
+ Photo photo = (Photo) source;
+ real_export_format = photo.get_export_format_for_parameters(export_params);
+ basename = photo.get_export_basename_for_parameters(export_params);
+ } else if (source is Video) {
+ basename = ((Video) source).get_basename();
+ }
+ assert(basename != null);
+
+ if (use_source_file != null) {
+ exported_files += use_source_file;
+
+ completed_count++;
+ if (monitor != null) {
+ if (!monitor(completed_count, to_export.size)) {
+ cancellable.cancel();
+
+ return false;
+ }
+ }
+
+ continue;
+ }
+
+ File? export_dir = dir;
+ File? dest = null;
+
+ if (export_dir == null) {
+ try {
+ bool collision;
+ dest = generate_unique_file(AppDirs.get_temp_dir(), basename, out collision);
+ } catch (Error err) {
+ AppWindow.error_message(_("Unable to generate a temporary file for %s: %s").printf(
+ source.get_file().get_basename(), err.message));
+
+ break;
+ }
+ } else {
+ dest = dir.get_child(basename);
+
+ if (!replace_all && dest.query_exists(null)) {
+ switch (overwrite_callback(this, dest)) {
+ case Overwrite.YES:
+ // continue
+ break;
+
+ case Overwrite.REPLACE_ALL:
+ replace_all = true;
+ break;
+
+ case Overwrite.CANCEL:
+ cancellable.cancel();
+
+ return false;
+
+ case Overwrite.NO:
+ default:
+ completed_count++;
+ if (monitor != null) {
+ if (!monitor(completed_count, to_export.size)) {
+ cancellable.cancel();
+
+ return false;
+ }
+ }
+
+ continue;
+ }
+ }
+ }
+
+ workers.enqueue(new ExportJob(this, source, dest, scaling, export_params.quality,
+ real_export_format, cancellable, export_params.mode == ExportFormatMode.UNMODIFIED, export_params.export_metadata));
+ submitted++;
+ }
+
+ return submitted > 0;
+ }
+
+ private void export_completed(bool is_cancelled) {
+ completion_callback(this, is_cancelled);
+ }
+}
+
+public class ExporterUI {
+ private Exporter exporter;
+ private Cancellable cancellable = new Cancellable();
+ private ProgressDialog? progress_dialog = null;
+ private unowned Exporter.CompletionCallback? completion_callback = null;
+
+ public ExporterUI(Exporter exporter) {
+ this.exporter = exporter;
+ }
+
+ public void export(Exporter.CompletionCallback completion_callback) {
+ this.completion_callback = completion_callback;
+
+ AppWindow.get_instance().set_busy_cursor();
+
+ progress_dialog = new ProgressDialog(AppWindow.get_instance(), _("Exporting"), cancellable);
+ exporter.export(on_export_completed, on_export_failed, on_export_overwrite, cancellable,
+ progress_dialog.monitor);
+ }
+
+ private void on_export_completed(Exporter exporter, bool is_cancelled) {
+ if (progress_dialog != null) {
+ progress_dialog.close();
+ progress_dialog = null;
+ }
+
+ AppWindow.get_instance().set_normal_cursor();
+
+ completion_callback(exporter, is_cancelled);
+ }
+
+ private Exporter.Overwrite on_export_overwrite(Exporter exporter, File file) {
+ progress_dialog.set_modal(false);
+ string question = _("File %s already exists. Replace?").printf(file.get_basename());
+ Gtk.ResponseType response = AppWindow.negate_affirm_all_cancel_question(question,
+ _("_Skip"), _("_Replace"), _("Replace _All"), _("Export"));
+
+ progress_dialog.set_modal(true);
+
+ switch (response) {
+ case Gtk.ResponseType.APPLY:
+ return Exporter.Overwrite.REPLACE_ALL;
+
+ case Gtk.ResponseType.YES:
+ return Exporter.Overwrite.YES;
+
+ case Gtk.ResponseType.CANCEL:
+ return Exporter.Overwrite.CANCEL;
+
+ case Gtk.ResponseType.NO:
+ default:
+ return Exporter.Overwrite.NO;
+ }
+ }
+
+ private bool on_export_failed(Exporter exporter, File file, int remaining, Error err) {
+ return export_error_dialog(file, remaining > 0) != Gtk.ResponseType.CANCEL;
+ }
+}
+