[ease] [editor] Added threaded saving with progress.



commit 7edd2a366c5bf069f440e3f57f6839fed96bb74e
Author: Nate Stedman <natesm gmail com>
Date:   Sat Aug 7 05:58:19 2010 -0400

    [editor] Added threaded saving with progress.
    
    - New Dialogs.Progress class
    - New Archiver class with modified code removed from temp
    - Saving is threaded if supported, falls back if not
    - Progress bar is a little glitchy - some times does not
      advance.

 data/ui/progress-dialog.ui          |   76 ++++++++++++++++
 ease-core/Makefile.am               |    2 +
 ease-core/ease-archiver.vala        |  161 +++++++++++++++++++++++++++++++++++
 ease-core/ease-dialog-progress.vala |  129 ++++++++++++++++++++++++++++
 ease-core/ease-document.vala        |    4 +-
 ease-core/ease-temp.vala            |   69 ---------------
 ease/ease-editor-window.vala        |    2 +-
 7 files changed, 371 insertions(+), 72 deletions(-)
---
diff --git a/data/ui/progress-dialog.ui b/data/ui/progress-dialog.ui
new file mode 100644
index 0000000..ac3216d
--- /dev/null
+++ b/data/ui/progress-dialog.ui
@@ -0,0 +1,76 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkDialog" id="dialog">
+    <property name="width_request">350</property>
+    <property name="border_width">5</property>
+    <property name="resizable">False</property>
+    <property name="modal">True</property>
+    <property name="type_hint">normal</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="vbox">
+        <property name="visible">True</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkVBox" id="vbox-top">
+            <property name="visible">True</property>
+            <property name="spacing">4</property>
+            <child>
+              <object class="GtkLabel" id="label">
+                <property name="xalign">0</property>
+                <property name="label" translatable="yes">label</property>
+                <property name="use_markup">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkProgressBar" id="progress">
+                <property name="height_request">30</property>
+                <property name="visible">True</property>
+              </object>
+              <packing>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="action-area">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="0">cancel</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/ease-core/Makefile.am b/ease-core/Makefile.am
index 490b206..2c8921a 100644
--- a/ease-core/Makefile.am
+++ b/ease-core/Makefile.am
@@ -14,11 +14,13 @@ AM_CPPFLAGS = \
 libease_core_0_3_la_SOURCES = \
 	ease-actor.vala \
 	ease-animated-zoom-slider.vala \
+	ease-archiver.vala \
 	ease-background.vala \
 	ease-background-widget.vala \
 	ease-cairo-actor.vala \
 	ease-cairo-element.vala \
 	ease-color.vala \
+	ease-dialog-progress.vala \
 	ease-dialogs.vala \
 	ease-document.vala \
 	ease-element.vala \
diff --git a/ease-core/ease-archiver.vala b/ease-core/ease-archiver.vala
new file mode 100644
index 0000000..b45b123
--- /dev/null
+++ b/ease-core/ease-archiver.vala
@@ -0,0 +1,161 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+internal class Ease.Archiver : GLib.Object
+{
+	private string temp_path;
+	private string filename;
+	private Dialogs.Progress dialog;
+	private unowned Thread thread;
+	private bool async = true;
+	private int total_size = 0;
+	
+	private static GLib.List<Archiver> archivers = new GLib.List<Archiver>();
+	
+	private const int ARCHIVE_BUFFER = 4096;
+	private const string LABEL_MARKUP = "<large>%s</large>".printf(LABEL_TEXT);
+	private const string LABEL_TEXT = _("Saving %s");
+	
+	internal Archiver(string temp, string fname, Dialogs.Progress dlog)
+	{
+		temp_path = temp;
+		filename = fname;
+		dialog = dlog;
+		archivers.append(this);
+		
+		if (!Thread.supported())
+		{
+			// fall back on non-async archiving
+			async = false;
+			archive_real();
+			return;
+		}
+		
+		// this is a little redundant, probably not a huge perf hit though
+		recursive_directory(temp_path, null, (path, full_path) => {
+			Posix.Stat st;
+			Posix.stat(full_path, out st);
+			total_size += (int)st.st_size;
+		});
+		
+		dialog.set_label("Hello World");
+		dialog.show();
+		thread = Thread.create(archive_real, true);
+	}
+	
+	/**
+	 * Does the actual archiving of a directory.
+	 */
+	private void* archive_real()
+	{	
+		// create a writable archive
+		var archive = new Archive.Write();
+		var buffer = new char[ARCHIVE_BUFFER];
+		
+		// set archive format
+		archive.set_format_pax_restricted();
+		archive.set_compression_none();
+		
+		// open file
+		if (archive.open_filename(filename) == Archive.Result.FAILED)
+		{
+			throw new Error(0, 0, "Error opening %s", filename);
+		}
+		
+		// open the temporary directory
+		var dir = GLib.Dir.open(temp_path, 0);
+		
+		// error if the temporary directory has disappeared
+		if (dir == null)
+		{
+			throw new FileError.NOENT(
+				_("Temporary directory doesn't exist: %s"), temp_path);
+		}
+		
+		// add files
+		recursive_directory(temp_path, null, (path, full_path) => {
+			// create an archive entry for the file
+			var entry = new Archive.Entry();
+			entry.set_pathname(path);
+			entry.set_perm(0644);
+			Posix.Stat st;
+			Posix.stat(full_path, out st);
+			entry.copy_stat(st);
+			arc_fail(archive.write_header(entry), archive);
+			
+			double size = (double)st.st_size;
+			double size_frac = size / total_size;
+			
+			// write the file
+			var fd = Posix.open(full_path, Posix.O_RDONLY);
+			var len = Posix.read(fd, buffer, sizeof(char) * ARCHIVE_BUFFER);
+			while(len > 0)
+			{
+				archive.write_data(buffer, len);
+				len = Posix.read(fd, buffer, sizeof(char) * ARCHIVE_BUFFER);
+				lock (dialog) dialog.add_fraction(size_frac * (len / size));
+			}
+			Posix.close(fd);
+			arc_fail(archive.finish_entry(), archive);
+		});
+		
+		// close the archive
+		arc_fail(archive.close(), archive);
+		
+		// destroy the progress dialog in async mode
+		lock (dialog) if (async) dialog.destroy();
+		
+		// stop tracking this archiver
+		lock (archivers) { archivers.remove(this); }
+		
+		return null;
+	}
+	
+	/**
+	 * Produces an error if a libarchive error occurs.
+	 */
+	private static void arc_fail(Archive.Result result, Archive.Archive archive)
+	{
+		if (result != Archive.Result.OK) critical(archive.error_string());
+	}
+}
+
+namespace Ease
+{
+	/**
+	 * Asynchronously (if supported) creates an archive from a temporary
+	 * directory. Otherwise, falls back on synchronous archiving.
+	 *
+	 * archive() uses libarchive to create a tarball of the temporary directory.
+	 *
+	 * @param temp_path The path of the temporary directory.
+	 * @param filename The filename of the archive to save to.
+	 * @param title The title of the progress dialog.
+	 * @param win The window to display a progress dialog modal for.
+	 */
+	internal static void archive(string temp_path,
+		                         string filename,
+		                         string title,
+		                         Gtk.Window? win) throws Error
+	{
+		// create a progress dialog
+		var dialog = new Dialogs.Progress(title, false, 1, win);
+	
+		// archive away!
+		var arc = new Archiver(temp_path, filename, dialog);
+	}
+}
diff --git a/ease-core/ease-dialog-progress.vala b/ease-core/ease-dialog-progress.vala
new file mode 100644
index 0000000..3f5fc0f
--- /dev/null
+++ b/ease-core/ease-dialog-progress.vala
@@ -0,0 +1,129 @@
+/*  Ease, a GTK presentation application
+    Copyright (C) 2010 Nate Stedman
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/**
+ * Controls a dialog displaying a progress bar, and optionally a cancel button
+ * and label.
+ *
+ * Note that this class is not a subclass of Gtk.Dialog.
+ */
+public class Ease.Dialogs.Progress : GLib.Object
+{
+	private const string UI_FILE = "progress-dialog.ui";
+	
+	private Gtk.Dialog dialog;
+	private Gtk.Button cancel;
+	private Gtk.Label label;
+	private Gtk.ProgressBar progress;
+	private double max_val;
+	
+	/**
+	 * Creates a progress dialog.
+	 *
+	 * @param title The title of the dialog.
+	 * @param cancellable If the dialog should display a cancel button.
+	 * @param max The maximum value of the dialog.
+	 * @param modal The window the dialog should be modal for, or null.
+	 */
+	public Progress(string title, bool cancellable, double max,
+	                Gtk.Window? modal)
+	{
+		max_val = max;
+		
+		var builder = new Gtk.Builder();
+		try
+		{
+			builder.add_from_file(data_path(Path.build_filename(Temp.UI_DIR,
+				                                                UI_FILE)));
+		}
+		catch (Error e) { error("Error loading UI: %s", e.message); }
+		
+		// get builder objects
+		dialog = builder.get_object("dialog") as Gtk.Dialog;
+		cancel = builder.get_object("cancel") as Gtk.Button;
+		label = builder.get_object("label") as Gtk.Label;
+		progress = builder.get_object("progress") as Gtk.ProgressBar;
+		
+		// set basic stuff
+		dialog.title = title;
+		cancel.visible = cancellable;
+	}
+	
+	/**
+	 * Shows the progress dialog.
+	 */
+	public void show()
+	{
+		dialog.show();
+	}
+	
+	/**
+	 * Hides the progress dialog.
+	 */
+	public void destroy()
+	{
+		dialog.destroy();
+	}
+	
+	/**
+	 * Sets (or unsets with null) the label of this dialog. Markup allowed.
+	 */
+	public void set_label(string? str)
+	{
+		if (str == null)
+		{
+			label.hide();
+			return;
+		}
+		label.set_markup(str);
+		label.show_all();
+	}
+	
+	/**
+	 * Sets the dialog's progress to a value relative to the maximum, specified
+	 * in the constructor.
+	 */
+	public void set(double val)
+	{
+		progress.set_fraction(val / max_val);
+	}
+	
+	/**
+	 * Sets the dialog's progress to a fraction between 0 and 1.
+	 */
+	public void set_fraction(double val)
+	{
+		progress.set_fraction(val);
+	}
+	
+	/**
+	 * Adds a value, relative to the maximum specified in the constructor, to
+	 * the progress bar.
+	 */
+	public void add(double val)
+	{
+		progress.set_fraction(progress.get_fraction() + val / max_val);
+	}
+	
+	/**
+	 * Adds a fractional value to the progress bar.
+	 */
+	public void add_fraction(double val)
+	{
+		progress.set_fraction(progress.get_fraction() + val);
+	}
+}
diff --git a/ease-core/ease-document.vala b/ease-core/ease-document.vala
index 39a2495..58e8886 100644
--- a/ease-core/ease-document.vala
+++ b/ease-core/ease-document.vala
@@ -194,7 +194,7 @@ public class Ease.Document : GLib.Object, UndoSource
 		append_slide(slide);
 	}
 	
-	public void to_json() throws GLib.Error
+	public void to_json(Gtk.Window? window) throws GLib.Error
 	{
 		var root = new Json.Node(Json.NodeType.OBJECT);
 		var obj = new Json.Object();
@@ -223,7 +223,7 @@ public class Ease.Document : GLib.Object, UndoSource
 		generator.to_file(Path.build_filename(path, JSON_FILE));
 		
 		// archive
-		Temp.archive(path, filename);
+		archive(path, filename, _("Saving Document"), window);
 	}
 	
 	/**
diff --git a/ease-core/ease-temp.vala b/ease-core/ease-temp.vala
index cc35ad5..0d197cb 100644
--- a/ease-core/ease-temp.vala
+++ b/ease-core/ease-temp.vala
@@ -177,75 +177,6 @@ public static class Ease.Temp : Object
 	}
 	
 	/**
-	 * Creates an archive from a temporary directory.
-	 *
-	 * archive() uses libarchive to create a tarball of the temporary directory.
-	 *
-	 * @param temp_path The path of the temporary directory.
-	 * @param filename The filename of the archive to save to.
-	 */
-	internal static void archive(string temp_path, string filename) throws Error
-	{
-		// create a writable archive
-		var archive = new Archive.Write();
-		var buffer = new char[ARCHIVE_BUFFER];
-		
-		// set archive format
-		archive.set_format_pax_restricted();
-		archive.set_compression_none();
-		
-		// open file
-		if (archive.open_filename(filename) == Archive.Result.FAILED)
-		{
-			throw new Error(0, 0, "Error opening %s", filename);
-		}
-		
-		// open the temporary directory
-		var dir = GLib.Dir.open(temp_path, 0);
-		
-		// error if the temporary directory has disappeared
-		if (dir == null)
-		{
-			throw new FileError.NOENT(
-				_("Temporary directory doesn't exist: %s"), temp_path);
-		}
-		
-		// add files
-		recursive_directory(temp_path, null, (path, full_path) => {
-			// create an archive entry for the file
-			var entry = new Archive.Entry();
-			entry.set_pathname(path);
-			entry.set_perm(0644);
-			Posix.Stat st;
-			Posix.stat(full_path, out st);
-			entry.copy_stat(st);
-			arc_fail(archive.write_header(entry), archive);
-			
-			// write the file
-			var fd = Posix.open(full_path, Posix.O_RDONLY);
-			var len = Posix.read(fd, buffer, sizeof(char) * ARCHIVE_BUFFER);
-			while(len > 0)
-			{
-				archive.write_data(buffer, len);
-				len = Posix.read(fd, buffer, sizeof(char) * ARCHIVE_BUFFER);
-			}
-			Posix.close(fd);
-			arc_fail(archive.finish_entry(), archive);
-		});
-		
-		// close the archive
-		arc_fail(archive.close(), archive);
-	}
-	
-	/**
-	 * Produces an error if a libarchive error occurs.
-	 */
-	private static void arc_fail(Archive.Result result, Archive.Archive archive)
-	{
-		if (result != Archive.Result.OK) error(archive.error_string());
-	}
-	
-	/**
 	 * Deletes all temporary directories created by this instance of Ease.
 	 * Call when exiting.
 	 */
diff --git a/ease/ease-editor-window.vala b/ease/ease-editor-window.vala
index 702b6fd..521cd7d 100644
--- a/ease/ease-editor-window.vala
+++ b/ease/ease-editor-window.vala
@@ -560,7 +560,7 @@ internal class Ease.EditorWindow : Gtk.Window
 	
 		try
 		{
-			document.to_json();
+			document.to_json(this);
 			last_saved = 0;
 		}
 		catch (GLib.Error e)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]