[baobab] Consistenly convert to space indent



commit 1251e74399d6bbbda0d5a4f40b03efb0cf25678d
Author: Paolo Borelli <pborelli gnome org>
Date:   Fri Apr 6 18:37:29 2012 +0200

    Consistenly convert to space indent
    
    Let's make indentation sane and consistent before it is too late

 src/baobab-application.vala      |  214 ++++----
 src/baobab-cellrenderers.vala    |  142 +++---
 src/baobab-connect-server.vala   |  104 ++--
 src/baobab-location-monitor.vala |    1 +
 src/baobab-location-widget.vala  |    1 +
 src/baobab-location.vala         |    1 +
 src/baobab-scanner.vala          |  305 ++++++------
 src/baobab-sync-scanner.vala     |  248 +++++-----
 src/baobab-threaded-scanner.vala |  492 +++++++++---------
 src/baobab-window.vala           | 1077 +++++++++++++++++++-------------------
 src/main.vala                    |   11 +-
 11 files changed, 1307 insertions(+), 1289 deletions(-)
---
diff --git a/src/baobab-application.vala b/src/baobab-application.vala
index 08832bf..305bfa6 100644
--- a/src/baobab-application.vala
+++ b/src/baobab-application.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Ryan Lortie <desrt desrt ca>
@@ -20,110 +21,111 @@
  */
 
 namespace Baobab {
-	public class Application : Gtk.Application {
-		static Application baobab;
-
-		private const GLib.ActionEntry[] action_entries = {
-			{ "quit", on_quit_activate }
-		};
-
-		Settings desktop_settings;
-		Settings prefs_settings;
-		Settings ui_settings;
-
-		protected override void activate () {
-			new Window (this);
-		}
-
-		protected override void open (File[] files, string hint) {
-			foreach (var file in files) {
-				var window = new Window (this);
-				window.scan_directory (file);
-			}
-		}
-
-		public static HashTable<File, unowned File> get_excluded_locations () {
-			var app = baobab;
-
-			var excluded_locations = new HashTable<File, unowned File> (File.hash, File.equal);
-			excluded_locations.add (File.new_for_path ("/proc"));
-			excluded_locations.add (File.new_for_path ("/sys"));
-			excluded_locations.add (File.new_for_path ("/selinux"));
-
-			var home = File.new_for_path (Environment.get_home_dir ());
-			excluded_locations.add (home.get_child (".gvfs"));
-
-			var root = File.new_for_path ("/");
-			foreach (var uri in app.prefs_settings.get_value ("excluded-uris")) {
-				var file = File.new_for_uri ((string) uri);
-				if (!file.equal (root)) {
-					excluded_locations.add (file);
-				}
-			}
-
-			return excluded_locations;
-		}
-
-		protected override void startup () {
-			base.startup ();
-
-			baobab = this;
-
-			// Settings
-			ui_settings = new Settings ("org.gnome.baobab.ui");
-			prefs_settings = new Settings ("org.gnome.baobab.preferences");
-			desktop_settings = new Settings ("org.gnome.desktop.interface");
-
-			// Menus: in gnome shell we just use the app menu, since the remaining
-			// items are too few to look ok in a menubar and they are not essential
-			var gtk_settings = Gtk.Settings.get_default ();
-			var builder = new Gtk.Builder ();
-			try {
-				builder.add_from_resource ("/org/gnome/baobab/ui/baobab-menu.ui");
-			} catch (Error e) {
-				error ("loading menu builder file: %s", e.message);
-			}
-			if (gtk_settings.gtk_shell_shows_app_menu) {
-				var app_menu = builder.get_object ("appmenu") as MenuModel;
-				set_app_menu (app_menu);
-			} else {
-				var menubar = builder.get_object ("menubar") as MenuModel;
-				set_menubar (menubar);
-			}
-		}
-
-		protected override bool local_command_line ([CCode (array_length = false, array_null_terminated = true)] ref unowned string[] arguments, out int exit_status) {
-			if (arguments[1] == "-v") {
-				print ("%s %s\n", Environment.get_application_name (), Config.VERSION);
-				exit_status = 0;
-				return true;
-			}
-
-			return base.local_command_line (ref arguments, out exit_status);
-		}
-
-		public Application () {
-			Object (application_id: "org.gnome.baobab", flags: ApplicationFlags.HANDLES_OPEN);
-
-			add_action_entries (action_entries, this);
-		}
-
-		public static Settings get_desktop_settings () {
-			var app = baobab;
-			return app.desktop_settings;
-		}
-
-		public static Settings get_prefs_settings () {
-			var app = baobab;
-			return app.prefs_settings;
-		}
-
-		public static Settings get_ui_settings () {
-			var app = baobab;
-			return app.ui_settings;
-		}
-
-		void on_quit_activate () {
-		}
-	}
+
+    public class Application : Gtk.Application {
+        static Application baobab;
+
+        private const GLib.ActionEntry[] action_entries = {
+            { "quit", on_quit_activate }
+        };
+
+        Settings desktop_settings;
+        Settings prefs_settings;
+        Settings ui_settings;
+
+        protected override void activate () {
+            new Window (this);
+        }
+
+        protected override void open (File[] files, string hint) {
+            foreach (var file in files) {
+                var window = new Window (this);
+                window.scan_directory (file);
+            }
+        }
+
+        public static HashTable<File, unowned File> get_excluded_locations () {
+            var app = baobab;
+
+            var excluded_locations = new HashTable<File, unowned File> (File.hash, File.equal);
+            excluded_locations.add (File.new_for_path ("/proc"));
+            excluded_locations.add (File.new_for_path ("/sys"));
+            excluded_locations.add (File.new_for_path ("/selinux"));
+
+            var home = File.new_for_path (Environment.get_home_dir ());
+            excluded_locations.add (home.get_child (".gvfs"));
+
+            var root = File.new_for_path ("/");
+            foreach (var uri in app.prefs_settings.get_value ("excluded-uris")) {
+                var file = File.new_for_uri ((string) uri);
+                if (!file.equal (root)) {
+                    excluded_locations.add (file);
+                }
+            }
+
+            return excluded_locations;
+        }
+
+        protected override void startup () {
+            base.startup ();
+
+            baobab = this;
+
+            // Settings
+            ui_settings = new Settings ("org.gnome.baobab.ui");
+            prefs_settings = new Settings ("org.gnome.baobab.preferences");
+            desktop_settings = new Settings ("org.gnome.desktop.interface");
+
+            // Menus: in gnome shell we just use the app menu, since the remaining
+            // items are too few to look ok in a menubar and they are not essential
+            var gtk_settings = Gtk.Settings.get_default ();
+            var builder = new Gtk.Builder ();
+            try {
+                builder.add_from_resource ("/org/gnome/baobab/ui/baobab-menu.ui");
+            } catch (Error e) {
+                error ("loading menu builder file: %s", e.message);
+            }
+            if (gtk_settings.gtk_shell_shows_app_menu) {
+                var app_menu = builder.get_object ("appmenu") as MenuModel;
+                set_app_menu (app_menu);
+            } else {
+                var menubar = builder.get_object ("menubar") as MenuModel;
+                set_menubar (menubar);
+            }
+        }
+
+        protected override bool local_command_line ([CCode (array_length = false, array_null_terminated = true)] ref unowned string[] arguments, out int exit_status) {
+            if (arguments[1] == "-v") {
+                print ("%s %s\n", Environment.get_application_name (), Config.VERSION);
+                exit_status = 0;
+                return true;
+            }
+
+            return base.local_command_line (ref arguments, out exit_status);
+        }
+
+        public Application () {
+            Object (application_id: "org.gnome.baobab", flags: ApplicationFlags.HANDLES_OPEN);
+
+            add_action_entries (action_entries, this);
+        }
+
+        public static Settings get_desktop_settings () {
+            var app = baobab;
+            return app.desktop_settings;
+        }
+
+        public static Settings get_prefs_settings () {
+            var app = baobab;
+            return app.prefs_settings;
+        }
+
+        public static Settings get_ui_settings () {
+            var app = baobab;
+            return app.ui_settings;
+        }
+
+        void on_quit_activate () {
+        }
+    }
 }
diff --git a/src/baobab-cellrenderers.vala b/src/baobab-cellrenderers.vala
index 3a86405..0fe987c 100644
--- a/src/baobab-cellrenderers.vala
+++ b/src/baobab-cellrenderers.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Ryan Lortie <desrt desrt ca>
@@ -18,82 +19,83 @@
  */
 
 namespace Baobab {
-	public class CellRendererPercent : Gtk.CellRendererText {
-		public double percent {
-			set {
-				text = "%.1f %%".printf (value);
-			}
-		}
-	}
 
-	public class CellRendererSize : Gtk.CellRendererText {
-		public new uint64 size {
-			set {
-				if (!show_allocated_size) {
-					text = format_size (value);
-				}
-			}
-		}
+    public class CellRendererPercent : Gtk.CellRendererText {
+        public double percent {
+            set {
+                text = "%.1f %%".printf (value);
+            }
+        }
+    }
 
-		public uint64 alloc_size {
-			set {
-				if (show_allocated_size) {
-					text = format_size (value);
-				}
-			}
-		}
+    public class CellRendererSize : Gtk.CellRendererText {
+        public new uint64 size {
+            set {
+                if (!show_allocated_size) {
+                    text = format_size (value);
+                }
+            }
+        }
 
-		public bool show_allocated_size { private get; set; }
-	}
+        public uint64 alloc_size {
+            set {
+                if (show_allocated_size) {
+                    text = format_size (value);
+                }
+            }
+        }
 
-	public class CellRendererItems : Gtk.CellRendererText {
-		public int items {
-			set {
-				text = value >= 0 ? ngettext ("%d item", "%d items", value).printf (value) : "";
-			}
-		}
-	}
+        public bool show_allocated_size { private get; set; }
+    }
 
-	public class CellRendererProgress : Gtk.CellRendererProgress {
-		public override void render (Cairo.Context cr, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
-			int xpad;
-			int ypad;
-			get_padding (out xpad, out ypad);
+    public class CellRendererItems : Gtk.CellRendererText {
+        public int items {
+            set {
+                text = value >= 0 ? ngettext ("%d item", "%d items", value).printf (value) : "";
+            }
+        }
+    }
 
-			// fill entire drawing area with black
-			var x = cell_area.x + xpad;
-			var y = cell_area.y + ypad;
-			var w = cell_area.width - xpad * 2;
-			var h = cell_area.height - ypad * 2;
-			cr.rectangle (x, y, w, h);
-			cr.set_source_rgb (0, 0, 0);
-			cr.fill ();
+    public class CellRendererProgress : Gtk.CellRendererProgress {
+        public override void render (Cairo.Context cr, Gtk.Widget widget, Gdk.Rectangle background_area, Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
+            int xpad;
+            int ypad;
+            get_padding (out xpad, out ypad);
 
-			// draw a smaller white rectangle on top, leaving a black outline
-			var style = widget.get_style ();
-			x += style.xthickness;
-			y += style.xthickness;
-			w -= style.xthickness * 2;
-			h -= style.xthickness * 2;
-			cr.rectangle (x, y, w, h);
-			cr.set_source_rgb (1, 1, 1);
-			cr.fill ();
+            // fill entire drawing area with black
+            var x = cell_area.x + xpad;
+            var y = cell_area.y + ypad;
+            var w = cell_area.width - xpad * 2;
+            var h = cell_area.height - ypad * 2;
+            cr.rectangle (x, y, w, h);
+            cr.set_source_rgb (0, 0, 0);
+            cr.fill ();
 
-			// fill in remaining area according to percentage value
-			var percent = value;
-			var perc_w = (w * percent) / 100;
-			if (widget.get_direction () == Gtk.TextDirection.RTL) {
-				x += w - perc_w;
-			}
-			cr.rectangle (x, y, perc_w, h);
-			if (percent <= 33) {
-				cr.set_source_rgb (0x73 / 255.0, 0xd2 / 255.0, 0x16 / 255.0);
-			} else if (percent <= 66) {
-				cr.set_source_rgb (0xed / 255.0, 0xd4 / 255.0, 0x00 / 255.0);
-			} else {
-				cr.set_source_rgb (0xcc / 255.0, 0x00 / 255.0, 0x00 / 255.0);
-			}
-			cr.fill ();
-		}
-	}
+            // draw a smaller white rectangle on top, leaving a black outline
+            var style = widget.get_style ();
+            x += style.xthickness;
+            y += style.xthickness;
+            w -= style.xthickness * 2;
+            h -= style.xthickness * 2;
+            cr.rectangle (x, y, w, h);
+            cr.set_source_rgb (1, 1, 1);
+            cr.fill ();
+
+            // fill in remaining area according to percentage value
+            var percent = value;
+            var perc_w = (w * percent) / 100;
+            if (widget.get_direction () == Gtk.TextDirection.RTL) {
+                x += w - perc_w;
+            }
+            cr.rectangle (x, y, perc_w, h);
+            if (percent <= 33) {
+                cr.set_source_rgb (0x73 / 255.0, 0xd2 / 255.0, 0x16 / 255.0);
+            } else if (percent <= 66) {
+                cr.set_source_rgb (0xed / 255.0, 0xd4 / 255.0, 0x00 / 255.0);
+            } else {
+                cr.set_source_rgb (0xcc / 255.0, 0x00 / 255.0, 0x00 / 255.0);
+            }
+            cr.fill ();
+        }
+    }
 }
diff --git a/src/baobab-connect-server.vala b/src/baobab-connect-server.vala
index 14d7a76..8c86178 100644
--- a/src/baobab-connect-server.vala
+++ b/src/baobab-connect-server.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Paolo Borelli <pborelli gnome org>
@@ -18,65 +19,66 @@
  */
 
 namespace Baobab {
-	class ConnectServer : Object {
-		string[] argv = {
-			"nautilus-connect-server",
-			"--print-uri"
-		};
 
-		public signal void selected(string? uri);
+    class ConnectServer : Object {
+        string[] argv = {
+            "nautilus-connect-server",
+            "--print-uri"
+        };
 
-		void on_child_watch (Pid pid, int status) {
-			Process.close_pid (pid);
-		}
+        public signal void selected(string? uri);
 
-		bool on_out_watch (IOChannel channel, IOCondition cond) {
-			if (IOCondition.HUP in cond) {
-				selected(null);
-				return false;
-			}
+        void on_child_watch (Pid pid, int status) {
+            Process.close_pid (pid);
+        }
 
-			string? uri = null;
-			try {
-				size_t len;
-				size_t lineend;
-				channel.read_line(out uri, out len, out lineend);
-				if (len > 0) {
-					uri = uri[0:(int)lineend];
-				}
-			} catch {
-			} finally {
-				selected(uri);
-			}
+        bool on_out_watch (IOChannel channel, IOCondition cond) {
+            if (IOCondition.HUP in cond) {
+                selected(null);
+                return false;
+            }
 
-			return true;
-		}
+            string? uri = null;
+            try {
+                size_t len;
+                size_t lineend;
+                channel.read_line(out uri, out len, out lineend);
+                if (len > 0) {
+                    uri = uri[0:(int)lineend];
+                }
+            } catch {
+            } finally {
+                selected(uri);
+            }
 
-		public void show () {
+            return true;
+        }
 
-			Pid pid;
-			int out_fd;
+        public void show () {
 
-			try {
-				Process.spawn_async_with_pipes (
-				    null,
-				    argv,
-				    null, // envp
-				    SpawnFlags.SEARCH_PATH | SpawnFlags.STDERR_TO_DEV_NULL,
-				    null, // child_setup
-				    out pid,
-				    null, // stdin
-				    out out_fd,
-				    null // stderr
-				);
-			} catch (SpawnError e) {
-				warning ("Failed to run nautilus-connect-server: %s", e.message);
-			}
+            Pid pid;
+            int out_fd;
 
-			var out_channel = new IOChannel.unix_new (out_fd);
-			out_channel.add_watch (IOCondition.IN | IOCondition.HUP, on_out_watch);
+            try {
+                Process.spawn_async_with_pipes (
+                    null,
+                    argv,
+                    null, // envp
+                    SpawnFlags.SEARCH_PATH | SpawnFlags.STDERR_TO_DEV_NULL,
+                    null, // child_setup
+                    out pid,
+                    null, // stdin
+                    out out_fd,
+                    null // stderr
+                );
+            } catch (SpawnError e) {
+                warning ("Failed to run nautilus-connect-server: %s", e.message);
+            }
 
-			ChildWatch.add (pid, on_child_watch);
-		}
-	}
+            var out_channel = new IOChannel.unix_new (out_fd);
+            out_channel.add_watch (IOCondition.IN | IOCondition.HUP, on_out_watch);
+
+            ChildWatch.add (pid, on_child_watch);
+        }
+    }
 }
diff --git a/src/baobab-location-monitor.vala b/src/baobab-location-monitor.vala
index 23f41d5..47c45e1 100644
--- a/src/baobab-location-monitor.vala
+++ b/src/baobab-location-monitor.vala
@@ -19,6 +19,7 @@
  */
 
 namespace Baobab {
+
     public class LocationMonitor {
         private static LocationMonitor? instance = null;
 
diff --git a/src/baobab-location-widget.vala b/src/baobab-location-widget.vala
index a68e351..632597c 100644
--- a/src/baobab-location-widget.vala
+++ b/src/baobab-location-widget.vala
@@ -19,6 +19,7 @@
  */
 
 namespace Baobab {
+
     public class LocationWidget : Gtk.Grid {
         private static Gtk.SizeGroup name_size_group = null;
         private static Gtk.SizeGroup mount_point_size_group = null;
diff --git a/src/baobab-location.vala b/src/baobab-location.vala
index 8f384c3..1cf399a 100644
--- a/src/baobab-location.vala
+++ b/src/baobab-location.vala
@@ -19,6 +19,7 @@
  */
 
 namespace Baobab {
+
     public class Location {
         public string name { get; private set; }
         public string? mount_point { get; private set; }
diff --git a/src/baobab-scanner.vala b/src/baobab-scanner.vala
index 2605039..429b1e4 100644
--- a/src/baobab-scanner.vala
+++ b/src/baobab-scanner.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Ryan Lortie <desrt desrt ca>
@@ -20,155 +21,157 @@
  */
 
 namespace Baobab {
-	[Flags]
-	public enum ScanFlags {
-		NONE,
-		EXCLUDE_MOUNTS
-	}
-
-	abstract class Scanner : Gtk.TreeStore {
-		public enum Columns {
-			DISPLAY_NAME,
-			PARSE_NAME,
-			PERCENT,
-			SIZE,
-			ALLOC_SIZE,
-			ELEMENTS,
-			STATE,
-			ERROR,
-			COLUMNS
-		}
-
-		public enum State {
-			SCANNING,
-			CANCELLED,
-			NEED_PERCENT,
-			ERROR,
-			DONE
-		}
-
-		protected struct HardLink {
-			uint64 inode;
-			uint32 device;
-
-			public HardLink (FileInfo info) {
-				this.inode = info.get_attribute_uint64 (FileAttribute.UNIX_INODE);
-				this.device = info.get_attribute_uint32 (FileAttribute.UNIX_DEVICE);
-			}
-		}
-
-		protected Cancellable cancellable;
-		protected HashTable<File, unowned File> excluded_locations;
-		protected HardLink[] hardlinks;
-		protected Error? scan_error;
-
-		protected static const string ATTRIBUTES =
-			FileAttribute.STANDARD_NAME + "," +
-			FileAttribute.STANDARD_DISPLAY_NAME + "," +
-			FileAttribute.STANDARD_TYPE + "," +
-			FileAttribute.STANDARD_SIZE +  "," +
-			FileAttribute.STANDARD_ALLOCATED_SIZE +	 "," +
-			FileAttribute.UNIX_NLINK + "," +
-			FileAttribute.UNIX_INODE + "," +
-			FileAttribute.UNIX_DEVICE + "," +
-			FileAttribute.ACCESS_CAN_READ;
-
-		public File directory { get; private set; }
-
-		public ScanFlags scan_flags { get; private set; }
-
-		public int max_depth { get; protected set; }
-
-		public signal void completed();
-
-		public abstract void scan ();
-
-		public virtual void cancel () {
-			cancellable.cancel ();
-		}
-
-		public virtual void finish () throws Error {
-			if (scan_error != null) {
-				throw scan_error;
-			}
-		}
-
-		protected static const string FS_ATTRIBUTES =
-			FileAttribute.FILESYSTEM_SIZE + "," +
-			FileAttribute.FILESYSTEM_USED + "," +
-			FileAttribute.FILESYSTEM_FREE;
-
-		public void get_filesystem_usage () throws Error {
-			var info = directory.query_filesystem_info (FS_ATTRIBUTES, cancellable);
-
-			var size = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_SIZE);
-			var used = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_USED);
-			var free = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_FREE);
-			var reserved = size - free - used;
-
-			var used_perc = 100 * ((double) used) / ((double) size);
-			var reserved_perc = 100 * ((double) reserved) / ((double) size);
-
-			Gtk.TreeIter? root_iter, iter;
-			append (out root_iter, null);
-			set (root_iter,
-			     Columns.STATE, State.DONE,
-			     Columns.DISPLAY_NAME, _("Total filesystem capacity"),
-			     Columns.PARSE_NAME, "",
-			     Columns.SIZE, size,
-			     Columns.ALLOC_SIZE, size,
-			     Columns.PERCENT, 100.0,
-			     Columns.ELEMENTS, -1,
-			     Columns.ERROR, null);
-
-			append (out iter, root_iter);
-			set (iter,
-			     Columns.STATE, State.DONE,
-			     Columns.DISPLAY_NAME, _("Used"),
-			     Columns.PARSE_NAME, "",
-			     Columns.SIZE, used,
-			     Columns.ALLOC_SIZE, used,
-			     Columns.PERCENT, used_perc,
-			     Columns.ELEMENTS, -1,
-			     Columns.ERROR, null);
-
-			append (out iter, root_iter);
-			set (iter,
-			     Columns.STATE, State.DONE,
-			     Columns.DISPLAY_NAME, _("Reserved"),
-			     Columns.PARSE_NAME, "",
-			     Columns.SIZE, reserved,
-			     Columns.ALLOC_SIZE, reserved,
-			     Columns.PERCENT, reserved_perc,
-			     Columns.ELEMENTS, -1,
-			     Columns.ERROR, null);
-		}
-
-		public Scanner (File directory, ScanFlags flags) {
-			this.directory = directory;
-			this.scan_flags = flags;
-			cancellable = new Cancellable();
-			scan_error = null;
-			set_column_types (new Type[] {
-					  typeof (string),  // DIR_NAME
-					  typeof (string),  // PARSE_NAME
-					  typeof (double),  // PERCENT
-					  typeof (uint64),  // SIZE
-					  typeof (uint64),  // ALLOC_SIZE
-					  typeof (int),	    // ELEMENTS
-					  typeof (State),   // STATE
-					  typeof (Error)}); // ERROR (if STATE is ERROR)
-			set_sort_column_id (Columns.SIZE, Gtk.SortType.DESCENDING);
-
-			excluded_locations = Application.get_excluded_locations ();
-
-			if (ScanFlags.EXCLUDE_MOUNTS in flags) {
-				foreach (unowned UnixMountEntry mount in UnixMountEntry.get (null)) {
-					excluded_locations.add (File.new_for_path (mount.get_mount_path ()));
-				}
-			}
-
-			excluded_locations.remove (directory);
-		}
-	}
+
+    [Flags]
+    public enum ScanFlags {
+        NONE,
+        EXCLUDE_MOUNTS
+    }
+
+    abstract class Scanner : Gtk.TreeStore {
+        public enum Columns {
+            DISPLAY_NAME,
+            PARSE_NAME,
+            PERCENT,
+            SIZE,
+            ALLOC_SIZE,
+            ELEMENTS,
+            STATE,
+            ERROR,
+            COLUMNS
+        }
+
+        public enum State {
+            SCANNING,
+            CANCELLED,
+            NEED_PERCENT,
+            ERROR,
+            DONE
+        }
+
+        protected struct HardLink {
+            uint64 inode;
+            uint32 device;
+
+            public HardLink (FileInfo info) {
+                this.inode = info.get_attribute_uint64 (FileAttribute.UNIX_INODE);
+                this.device = info.get_attribute_uint32 (FileAttribute.UNIX_DEVICE);
+            }
+        }
+
+        protected Cancellable cancellable;
+        protected HashTable<File, unowned File> excluded_locations;
+        protected HardLink[] hardlinks;
+        protected Error? scan_error;
+
+        protected static const string ATTRIBUTES =
+            FileAttribute.STANDARD_NAME + "," +
+            FileAttribute.STANDARD_DISPLAY_NAME + "," +
+            FileAttribute.STANDARD_TYPE + "," +
+            FileAttribute.STANDARD_SIZE +  "," +
+            FileAttribute.STANDARD_ALLOCATED_SIZE +     "," +
+            FileAttribute.UNIX_NLINK + "," +
+            FileAttribute.UNIX_INODE + "," +
+            FileAttribute.UNIX_DEVICE + "," +
+            FileAttribute.ACCESS_CAN_READ;
+
+        public File directory { get; private set; }
+
+        public ScanFlags scan_flags { get; private set; }
+
+        public int max_depth { get; protected set; }
+
+        public signal void completed();
+
+        public abstract void scan ();
+
+        public virtual void cancel () {
+            cancellable.cancel ();
+        }
+
+        public virtual void finish () throws Error {
+            if (scan_error != null) {
+                throw scan_error;
+            }
+        }
+
+        protected static const string FS_ATTRIBUTES =
+            FileAttribute.FILESYSTEM_SIZE + "," +
+            FileAttribute.FILESYSTEM_USED + "," +
+            FileAttribute.FILESYSTEM_FREE;
+
+        public void get_filesystem_usage () throws Error {
+            var info = directory.query_filesystem_info (FS_ATTRIBUTES, cancellable);
+
+            var size = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_SIZE);
+            var used = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_USED);
+            var free = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_FREE);
+            var reserved = size - free - used;
+
+            var used_perc = 100 * ((double) used) / ((double) size);
+            var reserved_perc = 100 * ((double) reserved) / ((double) size);
+
+            Gtk.TreeIter? root_iter, iter;
+            append (out root_iter, null);
+            set (root_iter,
+                 Columns.STATE, State.DONE,
+                 Columns.DISPLAY_NAME, _("Total filesystem capacity"),
+                 Columns.PARSE_NAME, "",
+                 Columns.SIZE, size,
+                 Columns.ALLOC_SIZE, size,
+                 Columns.PERCENT, 100.0,
+                 Columns.ELEMENTS, -1,
+                 Columns.ERROR, null);
+
+            append (out iter, root_iter);
+            set (iter,
+                 Columns.STATE, State.DONE,
+                 Columns.DISPLAY_NAME, _("Used"),
+                 Columns.PARSE_NAME, "",
+                 Columns.SIZE, used,
+                 Columns.ALLOC_SIZE, used,
+                 Columns.PERCENT, used_perc,
+                 Columns.ELEMENTS, -1,
+                 Columns.ERROR, null);
+
+            append (out iter, root_iter);
+            set (iter,
+                 Columns.STATE, State.DONE,
+                 Columns.DISPLAY_NAME, _("Reserved"),
+                 Columns.PARSE_NAME, "",
+                 Columns.SIZE, reserved,
+                 Columns.ALLOC_SIZE, reserved,
+                 Columns.PERCENT, reserved_perc,
+                 Columns.ELEMENTS, -1,
+                 Columns.ERROR, null);
+        }
+
+        public Scanner (File directory, ScanFlags flags) {
+            this.directory = directory;
+            this.scan_flags = flags;
+            cancellable = new Cancellable();
+            scan_error = null;
+            set_column_types (new Type[] {
+                typeof (string),  // DIR_NAME
+                typeof (string),  // PARSE_NAME
+                typeof (double),  // PERCENT
+                typeof (uint64),  // SIZE
+                typeof (uint64),  // ALLOC_SIZE
+                typeof (int),     // ELEMENTS
+                typeof (State),   // STATE
+                typeof (Error)    // ERROR (if STATE is ERROR)
+            });
+            set_sort_column_id (Columns.SIZE, Gtk.SortType.DESCENDING);
+
+            excluded_locations = Application.get_excluded_locations ();
+
+            if (ScanFlags.EXCLUDE_MOUNTS in flags) {
+                foreach (unowned UnixMountEntry mount in UnixMountEntry.get (null)) {
+                    excluded_locations.add (File.new_for_path (mount.get_mount_path ()));
+                }
+            }
+
+            excluded_locations.remove (directory);
+        }
+    }
 }
diff --git a/src/baobab-sync-scanner.vala b/src/baobab-sync-scanner.vala
index c585ab5..f119602 100644
--- a/src/baobab-sync-scanner.vala
+++ b/src/baobab-sync-scanner.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Ryan Lortie <desrt desrt ca>
@@ -20,127 +21,128 @@
  */
 
 namespace Baobab {
-	class SyncScanner : Scanner {
-		struct Results {
-			uint64 size;
-			uint64 alloc_size;
-			uint64 elements;
-			int max_depth;
-		}
-
-		Results add_directory (File directory, FileInfo info, Gtk.TreeIter? parent_iter = null) {
-			var results = Results ();
-			Gtk.TreeIter iter;
-
-			if (directory in excluded_locations) {
-				return results;
-			}
-
-			var display_name = info.get_display_name ();
-			var parse_name = directory.get_parse_name ();
-
-			append (out iter, parent_iter);
-			set (iter,
-			     Columns.DISPLAY_NAME, display_name,
-			     Columns.PARSE_NAME,   parse_name);
-
-			results.size = info.get_size ();
-			if (info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
-				results.alloc_size = info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
-			}
-			results.elements = 1;
-
-			try {
-				var children = directory.enumerate_children (ATTRIBUTES, FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
-				FileInfo? child_info;
-				while ((child_info = children.next_file (cancellable)) != null) {
-					if (cancellable.is_cancelled ()) {
-						break;
-					}
-
-					switch (child_info.get_file_type ()) {
-						case FileType.DIRECTORY:
-							var child = directory.get_child (child_info.get_name ());
-							var child_results = add_directory (child, child_info, iter);
-
-							results.size += child_results.size;
-							results.alloc_size += child_results.alloc_size;
-							results.elements += child_results.elements;
-							results.max_depth = int.max (results.max_depth, child_results.max_depth + 1);
-							break;
-
-						case FileType.REGULAR:
-							if (child_info.has_attribute (FileAttribute.UNIX_NLINK)) {
-								if (child_info.get_attribute_uint32 (FileAttribute.UNIX_NLINK) > 1) {
-									var hl = HardLink (child_info);
-
-									/* check if we've already encountered this file */
-									if (hl in hardlinks) {
-										continue;
-									}
-
-									hardlinks += hl;
-								}
-							}
-
-							results.size += child_info.get_size ();
-							if (child_info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
-								results.alloc_size += child_info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
-							}
-							results.elements++;
-							break;
-
-						default:
-							/* ignore other types (symlinks, sockets, devices, etc) */
-							break;
-					}
-				}
-			} catch (IOError.PERMISSION_DENIED e) {
-			} catch (Error e) {
-				warning ("couldn't iterate %s: %s", parse_name, e.message);
-			}
-
-			add_percent (results.size, iter);
-
-			if (!cancellable.is_cancelled ()) {
-				set (iter,
-				     Columns.SIZE,       results.size,
-				     Columns.ALLOC_SIZE, results.alloc_size,
-				     Columns.ELEMENTS,   results.elements,
-				     Columns.STATE,      State.NEED_PERCENT);
-			} else {
-				set (iter,
-				     Columns.STATE,      State.CANCELLED);
-			}
-
-			return results;
-		}
-
-		void add_percent (uint64 parent_size, Gtk.TreeIter? parent = null) {
-			Gtk.TreeIter iter;
-
-			if (iter_children (out iter, parent)) {
-				do {
-					uint64 size;
-					get (iter, Columns.SIZE, out size);
-					set (iter,
-					     Columns.PERCENT, 100 * ((double) size) / ((double) parent_size),
-					     Columns.STATE,   State.DONE);
-				} while (iter_next (ref iter));
-			}
-		}
-
-		public override void scan () {
-			try {
-				var info = directory.query_info (ATTRIBUTES, 0, cancellable);
-				var results = add_directory (directory, info);
-				add_percent (results.size);
-				max_depth = results.max_depth;
-			} catch { }
-		}
-
-		public SyncScanner (File directory, ScanFlags flags) {
-			base (directory, flags);
-		}
-	}
+
+    class SyncScanner : Scanner {
+        struct Results {
+            uint64 size;
+            uint64 alloc_size;
+            uint64 elements;
+            int max_depth;
+        }
+
+        Results add_directory (File directory, FileInfo info, Gtk.TreeIter? parent_iter = null) {
+            var results = Results ();
+            Gtk.TreeIter iter;
+
+            if (directory in excluded_locations) {
+                return results;
+            }
+
+            var display_name = info.get_display_name ();
+            var parse_name = directory.get_parse_name ();
+
+            append (out iter, parent_iter);
+            set (iter,
+                 Columns.DISPLAY_NAME, display_name,
+                 Columns.PARSE_NAME,   parse_name);
+
+            results.size = info.get_size ();
+            if (info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
+                results.alloc_size = info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
+            }
+            results.elements = 1;
+
+            try {
+                var children = directory.enumerate_children (ATTRIBUTES, FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
+                FileInfo? child_info;
+                while ((child_info = children.next_file (cancellable)) != null) {
+                    if (cancellable.is_cancelled ()) {
+                        break;
+                    }
+
+                    switch (child_info.get_file_type ()) {
+                        case FileType.DIRECTORY:
+                            var child = directory.get_child (child_info.get_name ());
+                            var child_results = add_directory (child, child_info, iter);
+
+                            results.size += child_results.size;
+                            results.alloc_size += child_results.alloc_size;
+                            results.elements += child_results.elements;
+                            results.max_depth = int.max (results.max_depth, child_results.max_depth + 1);
+                            break;
+
+                        case FileType.REGULAR:
+                            if (child_info.has_attribute (FileAttribute.UNIX_NLINK)) {
+                                if (child_info.get_attribute_uint32 (FileAttribute.UNIX_NLINK) > 1) {
+                                    var hl = HardLink (child_info);
+
+                                    /* check if we've already encountered this file */
+                                    if (hl in hardlinks) {
+                                        continue;
+                                    }
+
+                                    hardlinks += hl;
+                                }
+                            }
+
+                            results.size += child_info.get_size ();
+                            if (child_info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
+                                results.alloc_size += child_info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
+                            }
+                            results.elements++;
+                            break;
+
+                        default:
+                            /* ignore other types (symlinks, sockets, devices, etc) */
+                            break;
+                    }
+                }
+            } catch (IOError.PERMISSION_DENIED e) {
+            } catch (Error e) {
+                warning ("couldn't iterate %s: %s", parse_name, e.message);
+            }
+
+            add_percent (results.size, iter);
+
+            if (!cancellable.is_cancelled ()) {
+                set (iter,
+                     Columns.SIZE,       results.size,
+                     Columns.ALLOC_SIZE, results.alloc_size,
+                     Columns.ELEMENTS,   results.elements,
+                     Columns.STATE,      State.NEED_PERCENT);
+            } else {
+                set (iter,
+                     Columns.STATE,      State.CANCELLED);
+            }
+
+            return results;
+        }
+
+        void add_percent (uint64 parent_size, Gtk.TreeIter? parent = null) {
+            Gtk.TreeIter iter;
+
+            if (iter_children (out iter, parent)) {
+                do {
+                    uint64 size;
+                    get (iter, Columns.SIZE, out size);
+                    set (iter,
+                         Columns.PERCENT, 100 * ((double) size) / ((double) parent_size),
+                         Columns.STATE,   State.DONE);
+                } while (iter_next (ref iter));
+            }
+        }
+
+        public override void scan () {
+            try {
+                var info = directory.query_info (ATTRIBUTES, 0, cancellable);
+                var results = add_directory (directory, info);
+                add_percent (results.size);
+                max_depth = results.max_depth;
+            } catch { }
+        }
+
+        public SyncScanner (File directory, ScanFlags flags) {
+            base (directory, flags);
+        }
+    }
 }
diff --git a/src/baobab-threaded-scanner.vala b/src/baobab-threaded-scanner.vala
index e0927cf..42b0572 100644
--- a/src/baobab-threaded-scanner.vala
+++ b/src/baobab-threaded-scanner.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Ryan Lortie <desrt desrt ca>
@@ -20,249 +21,250 @@
  */
 
 namespace Baobab {
-	class ThreadedScanner : Scanner {
-		AsyncQueue<ResultsArray> results_queue;
-		ThreadedScanner? self;
-
-		/* General overview:
-		 *
-		 * We cannot directly modify the treemodel from the worker thread, so we have to have a way to dispatch
-		 * the results back to the main thread.
-		 *
-		 * Each scanned directory gets a 'Results' struct created for it.  If the directory has a parent
-		 * directory, then the 'parent' pointer is set.  The 'display_name' and 'parse_name' fields are filled
-		 * in as soon as the struct is created.  This part is done as soon as the directory is encountered.
-		 *
-		 * In order to determine all of the information for a particular directory (and finish filling in the
-		 * results structure), we must scan it and all of its children.  We must also scan all of the siblings
-		 * of the directory so that we know what percentage of the total size of the parent directory the
-		 * directory in question is responsible for.
-		 *
-		 * After a directory, all of its children and all of its siblings have been scanned, we can do the
-		 * percentage calculation.  We do this from the iteration that takes care of the parent directory: we
-		 * collect an array of all of the child directory result structs and when we have them all, we assign
-		 * the proper percentage to each.  At this point we can report this array of result structs back to the
-		 * main thread to be added to the treemodel.
-		 *
-		 * Back in the main thread, we receive a Results object.  If the results object has not yet had a
-		 * TreeIter assigned to it, we create it one.  We use the parent results object to determine the correct
-		 * place in the tree (assigning the parent an iter if required, recursively).  When we create the iter,
-		 * we fill in the data that existed from the start (ie: display name and parse name) and mark the status
-		 * of the iter as 'scanning'.
-		 *
-		 * For the iter that was actually directly reported (ie: the one that's ready) we record the information
-		 * into the treemodel and free the results structure (or Vala does it for us).
-		 *
-		 * We can be sure that the 'parent' field always points to valid memory because of the nature of the
-		 * recursion and the queue.  At the time we queue a Results struct for dispatch back to the main thread,
-		 * its 'parent' is held on the stack by a higher invocation of add_directory().  This invocation will
-		 * never finish without first pushing its own Results struct onto the queue -- after ours.  It is
-		 * therefore guaranteed that the 'parent' Results object will not be freed before each child.
-		 */
-		[Compact]
-		class ResultsArray {
-			internal Results[] results;
-		}
-
-		[Compact]
-		class Results {
-			// written in the worker thread on creation
-			// read from the main thread at any time
-			internal unowned Results? parent;
-			internal string display_name;
-			internal string parse_name;
-
-			// written in the worker thread before dispatch
-			// read from the main thread only after dispatch
-			internal uint64 size;
-			internal uint64 alloc_size;
-			internal uint64 elements;
-			internal double percent;
-			internal int max_depth;
-			internal Error? error;
-
-			// accessed only by the main thread
-			internal Gtk.TreeIter iter;
-			internal bool iter_is_set;
-		}
-
-		Results? add_directory (File directory, FileInfo info, Results? parent = null) {
-			var results_array = new ResultsArray ();
-
-			if (directory in excluded_locations) {
-				return null;
-			}
-
-			var results = new Results ();
-			results.display_name = info.get_display_name ();
-			results.parse_name = directory.get_parse_name ();
-			results.parent = parent;
-
-			results.size = info.get_size ();
-			if (info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
-				results.alloc_size = info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
-			}
-			results.elements = 1;
-			results.error = null;
-
-			try {
-				var children = directory.enumerate_children (ATTRIBUTES, FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
-				FileInfo? child_info;
-				while ((child_info = children.next_file (cancellable)) != null) {
-					switch (child_info.get_file_type ()) {
-						case FileType.DIRECTORY:
-							var child = directory.get_child (child_info.get_name ());
-							var child_results = add_directory (child, child_info, results);
-
-							if (child_results != null) {
-								results.size += child_results.size;
-								results.alloc_size += child_results.alloc_size;
-								results.elements += child_results.elements;
-								results.max_depth = int.max (results.max_depth, child_results.max_depth + 1);
-								results_array.results += (owned) child_results;
-							}
-							break;
-
-						case FileType.REGULAR:
-							if (child_info.has_attribute (FileAttribute.UNIX_NLINK)) {
-								if (child_info.get_attribute_uint32 (FileAttribute.UNIX_NLINK) > 1) {
-									var hl = HardLink (child_info);
-
-									// check if we've already encountered this file
-									if (hl in hardlinks) {
-										continue;
-									}
-
-									hardlinks += hl;
-								}
-							}
-
-							results.size += child_info.get_size ();
-							if (child_info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
-								results.alloc_size += child_info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
-							}
-							results.elements++;
-							break;
-
-						default:
-							// ignore other types (symlinks, sockets, devices, etc)
-							break;
-					}
-				}
-			} catch (Error e) {
-				results.error = e;
-			}
-
-			foreach (unowned Results child_results in results_array.results) {
-				child_results.percent = 100 * ((double) child_results.size) / ((double) results.size);
-			}
-
-			/* No early exit.  In order to avoid a potential crash, we absolutely *must* push this onto the
-			 * queue after having passed it to any recursive invocation of add_directory() above.  See the large
-			 * comment at the top of this class for why.
-			 */
-			results_queue.push ((owned) results_array);
-
-			return results;
-		}
-
-		void* scan_in_thread () {
-			try {
-				var array = new ResultsArray ();
-				var info = directory.query_info (ATTRIBUTES, 0, cancellable);
-				var results = add_directory (directory, info);
-				results.percent = 100.0;
-				array.results += (owned) results;
-				results_queue.push ((owned) array);
-			} catch {
-			}
-
-			// drop the thread's reference on the Scanner object
-			this.self = null;
-			return null;
-		}
-
-		void ensure_iter_exists (Results results) {
-			Gtk.TreeIter? parent_iter;
-
-			if (results.iter_is_set) {
-				return;
-			}
-
-			if (results.parent != null) {
-				ensure_iter_exists (results.parent);
-				parent_iter = results.parent.iter;
-			} else {
-				parent_iter = null;
-			}
-
-			append (out results.iter, parent_iter);
-			set (results.iter,
-			     Columns.STATE,        State.SCANNING,
-			     Columns.DISPLAY_NAME, results.display_name,
-			     Columns.PARSE_NAME,   results.parse_name);
-			results.iter_is_set = true;
-		}
-
-		bool process_results () {
-			while (true) {
-				var results_array = results_queue.try_pop ();
-
-				if (results_array == null) {
-					break;
-				}
-
-				foreach (unowned Results results in results_array.results) {
-					ensure_iter_exists (results);
-
-					set (results.iter,
-					     Columns.SIZE,       results.size,
-					     Columns.ALLOC_SIZE, results.alloc_size,
-					     Columns.PERCENT,    results.percent,
-					     Columns.ELEMENTS,   results.elements,
-					     Columns.STATE,      results.error == null ? State.DONE : State.ERROR,
-					     Columns.ERROR,      results.error);
-
-					if (results.max_depth > max_depth) {
-						max_depth = results.max_depth;
-					}
-
-					// If the user cancelled abort the scan and
-					// report CANCELLED as the error, otherwise
-					// consider the error not fatal and report the
-					// first error we encountered
-					if (results.error != null) {
-						if (results.error is IOError.CANCELLED) {
-							scan_error = results.error;
-							completed ();
-							return false;
-						} else if (scan_error == null) {
-							scan_error = results.error;
-						}
-					}
-
-					if (results.parent == null) {
-						completed ();
-						return false;
-					}
-				}
-			}
-
-			return this.self != null;
-		}
-
-		public override void scan () {
-			new GLib2.Thread ("scanner", scan_in_thread);
-			Timeout.add (100, process_results);
-		}
-
-		public ThreadedScanner (File directory, ScanFlags flags) {
-			base (directory, flags);
-
-			results_queue = new AsyncQueue<ResultsArray> ();
-
-			// the thread owns a reference on the Scanner object
-			this.self = this;
-		}
-	}
+
+    class ThreadedScanner : Scanner {
+        AsyncQueue<ResultsArray> results_queue;
+        ThreadedScanner? self;
+
+        /* General overview:
+         *
+         * We cannot directly modify the treemodel from the worker thread, so we have to have a way to dispatch
+         * the results back to the main thread.
+         *
+         * Each scanned directory gets a 'Results' struct created for it.  If the directory has a parent
+         * directory, then the 'parent' pointer is set.  The 'display_name' and 'parse_name' fields are filled
+         * in as soon as the struct is created.  This part is done as soon as the directory is encountered.
+         *
+         * In order to determine all of the information for a particular directory (and finish filling in the
+         * results structure), we must scan it and all of its children.  We must also scan all of the siblings
+         * of the directory so that we know what percentage of the total size of the parent directory the
+         * directory in question is responsible for.
+         *
+         * After a directory, all of its children and all of its siblings have been scanned, we can do the
+         * percentage calculation.  We do this from the iteration that takes care of the parent directory: we
+         * collect an array of all of the child directory result structs and when we have them all, we assign
+         * the proper percentage to each.  At this point we can report this array of result structs back to the
+         * main thread to be added to the treemodel.
+         *
+         * Back in the main thread, we receive a Results object.  If the results object has not yet had a
+         * TreeIter assigned to it, we create it one.  We use the parent results object to determine the correct
+         * place in the tree (assigning the parent an iter if required, recursively).  When we create the iter,
+         * we fill in the data that existed from the start (ie: display name and parse name) and mark the status
+         * of the iter as 'scanning'.
+         *
+         * For the iter that was actually directly reported (ie: the one that's ready) we record the information
+         * into the treemodel and free the results structure (or Vala does it for us).
+         *
+         * We can be sure that the 'parent' field always points to valid memory because of the nature of the
+         * recursion and the queue.  At the time we queue a Results struct for dispatch back to the main thread,
+         * its 'parent' is held on the stack by a higher invocation of add_directory().  This invocation will
+         * never finish without first pushing its own Results struct onto the queue -- after ours.  It is
+         * therefore guaranteed that the 'parent' Results object will not be freed before each child.
+         */
+        [Compact]
+        class ResultsArray {
+            internal Results[] results;
+        }
+
+        [Compact]
+        class Results {
+            // written in the worker thread on creation
+            // read from the main thread at any time
+            internal unowned Results? parent;
+            internal string display_name;
+            internal string parse_name;
+
+            // written in the worker thread before dispatch
+            // read from the main thread only after dispatch
+            internal uint64 size;
+            internal uint64 alloc_size;
+            internal uint64 elements;
+            internal double percent;
+            internal int max_depth;
+            internal Error? error;
+
+            // accessed only by the main thread
+            internal Gtk.TreeIter iter;
+            internal bool iter_is_set;
+        }
+
+        Results? add_directory (File directory, FileInfo info, Results? parent = null) {
+            var results_array = new ResultsArray ();
+
+            if (directory in excluded_locations) {
+                return null;
+            }
+
+            var results = new Results ();
+            results.display_name = info.get_display_name ();
+            results.parse_name = directory.get_parse_name ();
+            results.parent = parent;
+
+            results.size = info.get_size ();
+            if (info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
+                results.alloc_size = info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
+            }
+            results.elements = 1;
+            results.error = null;
+
+            try {
+                var children = directory.enumerate_children (ATTRIBUTES, FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
+                FileInfo? child_info;
+                while ((child_info = children.next_file (cancellable)) != null) {
+                    switch (child_info.get_file_type ()) {
+                        case FileType.DIRECTORY:
+                            var child = directory.get_child (child_info.get_name ());
+                            var child_results = add_directory (child, child_info, results);
+
+                            if (child_results != null) {
+                                results.size += child_results.size;
+                                results.alloc_size += child_results.alloc_size;
+                                results.elements += child_results.elements;
+                                results.max_depth = int.max (results.max_depth, child_results.max_depth + 1);
+                                results_array.results += (owned) child_results;
+                            }
+                            break;
+
+                        case FileType.REGULAR:
+                            if (child_info.has_attribute (FileAttribute.UNIX_NLINK)) {
+                                if (child_info.get_attribute_uint32 (FileAttribute.UNIX_NLINK) > 1) {
+                                    var hl = HardLink (child_info);
+
+                                    // check if we've already encountered this file
+                                    if (hl in hardlinks) {
+                                        continue;
+                                    }
+
+                                    hardlinks += hl;
+                                }
+                            }
+
+                            results.size += child_info.get_size ();
+                            if (child_info.has_attribute (FileAttribute.STANDARD_ALLOCATED_SIZE)) {
+                                results.alloc_size += child_info.get_attribute_uint64 (FileAttribute.STANDARD_ALLOCATED_SIZE);
+                            }
+                            results.elements++;
+                            break;
+
+                        default:
+                            // ignore other types (symlinks, sockets, devices, etc)
+                            break;
+                    }
+                }
+            } catch (Error e) {
+                results.error = e;
+            }
+
+            foreach (unowned Results child_results in results_array.results) {
+                child_results.percent = 100 * ((double) child_results.size) / ((double) results.size);
+            }
+
+            /* No early exit.  In order to avoid a potential crash, we absolutely *must* push this onto the
+             * queue after having passed it to any recursive invocation of add_directory() above.  See the large
+             * comment at the top of this class for why.
+             */
+            results_queue.push ((owned) results_array);
+
+            return results;
+        }
+
+        void* scan_in_thread () {
+            try {
+                var array = new ResultsArray ();
+                var info = directory.query_info (ATTRIBUTES, 0, cancellable);
+                var results = add_directory (directory, info);
+                results.percent = 100.0;
+                array.results += (owned) results;
+                results_queue.push ((owned) array);
+            } catch {
+            }
+
+            // drop the thread's reference on the Scanner object
+            this.self = null;
+            return null;
+        }
+
+        void ensure_iter_exists (Results results) {
+            Gtk.TreeIter? parent_iter;
+
+            if (results.iter_is_set) {
+                return;
+            }
+
+            if (results.parent != null) {
+                ensure_iter_exists (results.parent);
+                parent_iter = results.parent.iter;
+            } else {
+                parent_iter = null;
+            }
+
+            append (out results.iter, parent_iter);
+            set (results.iter,
+                 Columns.STATE,        State.SCANNING,
+                 Columns.DISPLAY_NAME, results.display_name,
+                 Columns.PARSE_NAME,   results.parse_name);
+            results.iter_is_set = true;
+        }
+
+        bool process_results () {
+            while (true) {
+                var results_array = results_queue.try_pop ();
+
+                if (results_array == null) {
+                    break;
+                }
+
+                foreach (unowned Results results in results_array.results) {
+                    ensure_iter_exists (results);
+
+                    set (results.iter,
+                         Columns.SIZE,       results.size,
+                         Columns.ALLOC_SIZE, results.alloc_size,
+                         Columns.PERCENT,    results.percent,
+                         Columns.ELEMENTS,   results.elements,
+                         Columns.STATE,      results.error == null ? State.DONE : State.ERROR,
+                         Columns.ERROR,      results.error);
+
+                    if (results.max_depth > max_depth) {
+                        max_depth = results.max_depth;
+                    }
+
+                    // If the user cancelled abort the scan and
+                    // report CANCELLED as the error, otherwise
+                    // consider the error not fatal and report the
+                    // first error we encountered
+                    if (results.error != null) {
+                        if (results.error is IOError.CANCELLED) {
+                            scan_error = results.error;
+                            completed ();
+                            return false;
+                        } else if (scan_error == null) {
+                            scan_error = results.error;
+                        }
+                    }
+
+                    if (results.parent == null) {
+                        completed ();
+                        return false;
+                    }
+                }
+            }
+
+            return this.self != null;
+        }
+
+        public override void scan () {
+            new GLib2.Thread ("scanner", scan_in_thread);
+            Timeout.add (100, process_results);
+        }
+
+        public ThreadedScanner (File directory, ScanFlags flags) {
+            base (directory, flags);
+
+            results_queue = new AsyncQueue<ResultsArray> ();
+
+            // the thread owns a reference on the Scanner object
+            this.self = this;
+        }
+    }
 }
diff --git a/src/baobab-window.vala b/src/baobab-window.vala
index c4e24df..9932fd1 100644
--- a/src/baobab-window.vala
+++ b/src/baobab-window.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Ryan Lortie <desrt desrt ca>
@@ -20,542 +21,542 @@
  */
 
 namespace Baobab {
-	public class Window : Gtk.ApplicationWindow {
-		Settings ui_settings;
-		Gtk.Notebook main_notebook;
-		Gtk.Toolbar toolbar;
-		Gtk.ToolItem toolbar_home_toolitem;
-		Gtk.ToolButton toolbar_show_home_page;
-		Gtk.ToolButton toolbar_rescan;
-		Gtk.InfoBar infobar;
-		Gtk.Label infobar_primary;
-		Gtk.Label infobar_secondary;
-		Gtk.TreeView treeview;
-		Gtk.Notebook chart_notebook;
-		Gtk.Grid location_view;
-		Chart rings_chart;
-		Chart treemap_chart;
-		Gtk.Spinner spinner;
-		Scanner? scanner;
-		LocationMonitor location_monitor;
-
-		static Gdk.Cursor busy_cursor;
-
-		void radio_activate (SimpleAction action, Variant? parameter) {
-			action.change_state (parameter);
-		}
-
-		private const GLib.ActionEntry[] action_entries = {
-			{ "show-home-page", on_show_home_page_activate },
-			{ "active-chart", radio_activate, "s", "'rings'", on_chart_type_changed },
-			{ "scan-home", on_scan_home_activate },
-			{ "scan-folder", on_scan_folder_activate },
-			{ "scan-remote", on_scan_remote_activate },
-			{ "stop", on_stop_activate },
-			{ "reload", on_reload_activate },
-			{ "show-toolbar", on_show_toolbar },
-			{ "show-allocated", on_show_allocated },
-			{ "expand-all", on_expand_all },
-			{ "collapse-all", on_collapse_all },
-			{ "help", on_help_activate },
-			{ "about", on_about_activate }
-		};
-
-		protected struct ActionState {
-			string name;
-			bool enable;
-		}
-
-		private const ActionState[] actions_while_scanning = {
-			{ "scan-home", false },
-			{ "scan-folder", false },
-			{ "scan-remote", false },
-			{ "stop", true },
-			{ "reload", false },
-			{ "show-allocated", false },
-			{ "expand-all", false },
-			{ "collapse-all", false }
-		};
-
-		private enum UIPage {
-			HOME,
-			RESULT
-		}
-
-		private enum ChartPage {
-			RINGS,
-			TREEMAP,
-			SPINNER
-		}
-
-		private enum DndTargets {
-			URI_LIST
-		}
-
-		private const Gtk.TargetEntry dnd_target_list[1] = {
-		    {"text/uri-list", 0, DndTargets.URI_LIST}
-		};
-
-		public Window (Application app) {
-			Object (application: app);
-
-			busy_cursor = new Gdk.Cursor (Gdk.CursorType.WATCH);
-
-			add_action_entries (action_entries, this);
-
-			// Build ourselves.
-			var builder = new Gtk.Builder ();
-			try {
-				builder.add_from_resource ("/org/gnome/baobab/ui/baobab-main-window.ui");
-			} catch (Error e) {
-				error ("loading main builder file: %s", e.message);
-			}
-
-
-			// Cache some objects from the builder.
-			main_notebook = builder.get_object ("main-notebook") as Gtk.Notebook;
-			toolbar = builder.get_object ("toolbar") as Gtk.Toolbar;
-			toolbar_home_toolitem = builder.get_object ("home-page-toolitem") as Gtk.ToolItem;
-			toolbar_show_home_page = builder.get_object ("show-home-page-button") as Gtk.ToolButton;
-			toolbar_rescan = builder.get_object ("rescan-button") as Gtk.ToolButton;
-			infobar = builder.get_object ("infobar") as Gtk.InfoBar;
-			infobar_primary = builder.get_object ("infobar-primary-label") as Gtk.Label;
-			infobar_secondary = builder.get_object ("infobar-secondary-label") as Gtk.Label;
-			treeview = builder.get_object ("treeview") as Gtk.TreeView;
-			chart_notebook = builder.get_object ("chart-notebook") as Gtk.Notebook;
-			rings_chart = builder.get_object ("rings-chart") as Chart;
-			treemap_chart = builder.get_object ("treemap-chart") as Chart;
-			spinner = builder.get_object ("spinner") as Gtk.Spinner;
-			location_view = builder.get_object ("location-view") as Gtk.Grid;
-
-			setup_home_page ();
-			setup_treeview (builder);
-
-			// To make it draggable like a primary toolbar
-			toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_MENUBAR);
-
-			ui_settings = Application.get_ui_settings ();
-			lookup_action ("active-chart").change_state (ui_settings.get_value ("active-chart"));
-
-			rings_chart.item_activated.connect (on_chart_item_activated);
-			treemap_chart.item_activated.connect (on_chart_item_activated);
-
-			// Setup drag-n-drop
-			drag_data_received.connect (on_drag_data_received);
-			enable_drop ();
-
-			add (builder.get_object ("window-contents") as Gtk.Widget);
-			title = _("Disk Usage Analyzer");
-			set_default_size (800, 500);
-			set_hide_titlebar_when_maximized (true);
-
-			set_ui_page (UIPage.HOME);
-
-			set_busy (false);
-
-			show ();
-		}
-
-		void set_ui_page (UIPage page) {
-			toolbar_home_toolitem.visible = (page == UIPage.HOME);
-			toolbar_show_home_page.visible = (page == UIPage.RESULT);
-			toolbar_rescan.visible = (page == UIPage.RESULT);
-
-			main_notebook.page = page;
-		}
-
-		void on_show_home_page_activate () {
-			if (scanner != null) {
-				scanner.cancel ();
-			}
-
-			set_ui_page (UIPage.HOME);
-		}
-
-		void on_chart_type_changed (SimpleAction action, Variant value) {
-			switch (value as string) {
-				case "rings":
-					chart_notebook.page = ChartPage.RINGS;
-					break;
-				case "treemap":
-					chart_notebook.page = ChartPage.TREEMAP;
-					break;
-				default:
-					return;
-			}
-
-			ui_settings.set_value ("active-chart", value);
-			action.set_state (value);
-		}
-
-		void on_scan_home_activate () {
-			var dir = File.new_for_path (GLib.Environment.get_home_dir ());
-			scan_directory (dir);
-		}
-
-		void on_scan_folder_activate () {
-			var file_chooser = new Gtk.FileChooserDialog (_("Select Folder"), this,
-			                                              Gtk.FileChooserAction.SELECT_FOLDER,
-			                                              Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL,
-			                                              Gtk.Stock.OPEN, Gtk.ResponseType.ACCEPT);
-
-			file_chooser.set_modal (true);
-
-			file_chooser.response.connect ((response) => {
-				if (response == Gtk.ResponseType.ACCEPT) {
-					var dir = file_chooser.get_file ();
-					scan_directory (dir);
-				}
-				file_chooser.destroy ();
-			});
-
-			file_chooser.show ();
-		}
-
-		void on_scan_remote_activate () {
-			var connect_server = new ConnectServer ();
-
-			connect_server.selected.connect ((uri) => {
-				if (uri != null) {
-					var dir = File.new_for_uri (uri);
-					scan_directory (dir);
-				}
-			});
-
-			connect_server.show ();
-		}
-
-		void on_stop_activate () {
-			if (scanner != null) {
-				scanner.cancel ();
-			}
-		}
-
-		void on_reload_activate () {
-			if (scanner != null) {
-				scan_directory (scanner.directory, scanner.scan_flags);
-			}
-		}
-
-		void on_show_toolbar () {
-		}
-
-		void on_show_allocated () {
-		}
-
-		void on_expand_all () {
-			treeview.expand_all ();
-		}
-
-		void on_collapse_all () {
-			treeview.collapse_all ();
-		}
-
-		void on_help_activate () {
-			try {
-				Gtk.show_uri (get_screen (), "help:baobab", Gtk.get_current_event_time ());
-			} catch (Error e) {
-				warning ("Failed to show help: %s", e.message);
-			}
-		}
-
-		void on_about_activate () {
-			const string authors[] = {
-				"Ryan Lortie <desrt desrt ca>",
-				"Fabio Marzocca <thesaltydog gmail com>",
-				"Paolo Borelli <pborelli gnome com>",
-				"BenoÃt Dejean <benoit placenet org>",
-				"Igalia (rings-chart and treemap widget) <www.igalia.com>"
-			};
-
-			const string copyright = "Copyright \xc2\xa9 2005-2011 Fabio Marzocca, Paolo Borelli, BenoÃt Dejean, Igalia\n" +
-			                         "Copyright \xc2\xa9 2011-2012 Ryan Lortie, Paolo Borelli\n";
-
-			Gtk.show_about_dialog (this,
-			                       "program-name", _("Baobab"),
-			                       "logo-icon-name", "baobab",
-			                       "version", Config.VERSION,
-			                       "comments", "A graphical tool to analyze disk usage.",
-			                       "copyright", copyright,
-			                       "license-type", Gtk.License.GPL_2_0,
-			                       "wrap-license", false,
-			                       "authors", authors,
-			                       "translator-credits", _("translator-credits"),
-			                       null);
-		}
-
-		void on_chart_item_activated (Chart chart, Gtk.TreeIter iter) {
-			var path = scanner.get_path (iter);
-
-			if (!treeview.is_row_expanded (path)) {
-				treeview.expand_to_path (path);
-			}
-
-			treeview.set_cursor (path, null, false);
-		}
-
-		void on_drag_data_received (Gtk.Widget widget, Gdk.DragContext context, int x, int y,
-		                            Gtk.SelectionData selection_data, uint target_type, uint time) {
-			File dir = null;
-
-			if ((selection_data != null) && (selection_data.get_length () >= 0) && (target_type == DndTargets.URI_LIST)) {
-				var uris = GLib.Uri.list_extract_uris ((string) selection_data.get_data ());
-				if (uris != null && uris.length == 1) {
-					dir = File.new_for_uri (uris[0]);
-				}
-			}
-
-			if (dir != null) {
-				// finish drop before scanning, since the it can time out
-				Gtk.drag_finish (context, true, false, time);
-				scan_directory (dir);
-			} else {
-				Gtk.drag_finish (context, false, false, time);
-			}
-		}
-
-		void enable_drop () {
-			Gtk.drag_dest_set (this,
-			                   Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT,
-			                   dnd_target_list,
-			                   Gdk.DragAction.COPY);
-		}
-
-		void disable_drop () {
-			Gtk.drag_dest_unset (this);
-		}
-
-		void update_locations () {
-			location_view.foreach ((widget) => { widget.destroy (); });
-
-			foreach (var location in location_monitor.get_locations ()) {
-				LocationWidget loc_widget;
-				if (location.is_home_location) {
-					loc_widget = new LocationWidget (location, (location_) => {
-						on_scan_home_activate ();
-					});
-				} else {
-					loc_widget = new LocationWidget (location, (location_) => {
-						location_.mount_volume.begin ((location__, res) => {
-							try {
-								location_.mount_volume.end (res);
-								scan_directory (File.new_for_path (location_.mount_point), ScanFlags.EXCLUDE_MOUNTS);
-							} catch (Error e) {
-								message (_("Could not analyze volume."), e.message, Gtk.MessageType.ERROR);
-							}
-						});
-					});
-				}
-
-				location_view.add (loc_widget);
-			}
-
-			location_view.show_all ();
-		}
-
-		void setup_home_page () {
-			location_monitor = LocationMonitor.get ();
-			location_monitor.changed.connect (() => { update_locations (); });
-			update_locations ();
-		}
-
-		bool show_treeview_popup (Gtk.Menu popup, Gdk.EventButton? event) {
-			if (event != null) {
-				popup.popup (null, null, null, event.button, event.time);
-			} else {
-				popup.popup (null, null, null, 0, Gtk.get_current_event_time ());
-				popup.select_first (false);
-			}
-			return true;
-		}
-
-		void setup_treeview (Gtk.Builder builder) {
-			var popup = builder.get_object ("treeview-popup-menu") as Gtk.Menu;
-			var open_item = builder.get_object ("treeview-popup-open") as Gtk.MenuItem;
-			var trash_item = builder.get_object ("treeview-popup-trash") as Gtk.MenuItem;
-
-			treeview.button_press_event.connect ((event) => {
-				if (((Gdk.Event) (&event)).triggers_context_menu ()) {
-					return show_treeview_popup (popup, event);
-				}
-
-				return false;
-			});
-
-			treeview.popup_menu.connect (() => {
-				return show_treeview_popup (popup, null);
-			});
-
-			open_item.activate.connect (() => {
-				var selection = treeview.get_selection ();
-				Gtk.TreeIter iter;
-				if (selection.get_selected (null, out iter)) {
-					string parse_name;
-					scanner.get (iter, Scanner.Columns.PARSE_NAME, out parse_name);
-					var file = File.parse_name (parse_name);
-					try {
-						var info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE, 0, null);
-						var content = info.get_content_type ();
-						var appinfo = AppInfo.get_default_for_type (content, true);
-						var files = new List<File>();
-						files.append (file);
-						appinfo.launch(files, null);
-					} catch (Error e) {
-						warning ("Failed open file with application: %s", e.message);
-					}
-				}
-			});
-
-			trash_item.activate.connect (() => {
-				var selection = treeview.get_selection ();
-				Gtk.TreeIter iter;
-				if (selection.get_selected (null, out iter)) {
-					string parse_name;
-					scanner.get (iter, Scanner.Columns.PARSE_NAME, out parse_name);
-					var file = File.parse_name (parse_name);
-					try {
-						file.trash ();
-						scanner.remove (iter);
-					} catch (Error e) {
-						warning ("Failed to move file to the trash: %s", e.message);
-					}
-				}
-			});
-
-			var selection = treeview.get_selection ();
-			selection.changed.connect (() => {
-				Gtk.TreeIter iter;
-				if (selection.get_selected (null, out iter)) {
-					var path = scanner.get_path (iter);
-					rings_chart.set_root (path);
-					treemap_chart.set_root (path);
-				}
-			});
-		}
-
-		void message (string primary_msg, string secondary_msg, Gtk.MessageType type) {
-			infobar.message_type = type;
-			infobar_primary.set_label ("<b>%s</b>".printf (_(primary_msg)));
-			infobar_secondary.set_label ("<small>%s</small>".printf (_(secondary_msg)));
-			infobar.show ();
-		}
-
-		void clear_message () {
-			infobar.hide ();
-		}
-
-		void set_busy (bool busy) {
-			Gdk.Cursor? cursor = null;
-
-			if (busy) {
-				cursor = busy_cursor;
-				disable_drop ();
-				rings_chart.freeze_updates ();
-				treemap_chart.freeze_updates ();
-				(lookup_action ("active-chart") as SimpleAction).set_enabled (false);
-				chart_notebook.page = ChartPage.SPINNER;
-				spinner.start ();
-				toolbar_show_home_page.label = _("Cancel scan");
-			} else {
-				enable_drop ();
-				rings_chart.thaw_updates ();
-				treemap_chart.thaw_updates ();
-				(lookup_action ("active-chart") as SimpleAction).set_enabled (true);
-				spinner.stop ();
-				lookup_action ("active-chart").change_state (ui_settings.get_value ("active-chart"));
-				toolbar_show_home_page.label = _("All locations");
-			}
-
-			var window = get_window ();
-			if (window != null) {
-				window.set_cursor (cursor);
-			}
-
-			foreach (ActionState action_state in actions_while_scanning) {
-				var action = lookup_action (action_state.name) as SimpleAction;
-				action.set_enabled (busy == action_state.enable);
-			}
-		}
-
-		public bool check_dir (File directory) {
-			//if (Application.is_excluded_location (directory)) {
-			//	message("", _("Cannot check an excluded folder!"), Gtk.MessageType.INFO);
-			//	return false;
-			//}
-
-			try {
-				var info = directory.query_info (FileAttribute.STANDARD_TYPE, FileQueryInfoFlags.NONE, null);
-				if (info.get_file_type () != FileType.DIRECTORY/* || is_virtual_filesystem ()*/) {
-					var primary = _("\"%s\" is not a valid folder").printf (directory.get_parse_name ());
-					message (primary, _("Could not analyze disk usage."), Gtk.MessageType.ERROR);
-					return false;
-				}
-				return true;
-			} catch (Error e) {
-				message ("", e.message, Gtk.MessageType.INFO);
-				return false;
-			}
-		}
-
-		void first_row_has_child (Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter) {
-			model.row_has_child_toggled.disconnect (first_row_has_child);
-			treeview.expand_row (path, false);
-		}
-
-		void set_model (Gtk.TreeModel model) {
-			Gtk.TreeIter first;
-
-			treeview.model = model;
-
-			if (model.iter_children (out first, null) && model.iter_has_child (first)) {
-				treeview.expand_row (model.get_path (first), false);
-			} else {
-				model.row_has_child_toggled.connect (first_row_has_child);
-			}
-
-			model.bind_property ("max-depth", rings_chart, "max-depth", BindingFlags.SYNC_CREATE);
-			model.bind_property ("max-depth", treemap_chart, "max-depth", BindingFlags.SYNC_CREATE);
-			treemap_chart.set_model_with_columns (model,
-			                                      Scanner.Columns.DISPLAY_NAME,
-			                                      Scanner.Columns.ALLOC_SIZE,
-			                                      Scanner.Columns.PARSE_NAME,
-			                                      Scanner.Columns.PERCENT,
-			                                      Scanner.Columns.ELEMENTS, null);
-			rings_chart.set_model_with_columns (model,
-			                                    Scanner.Columns.DISPLAY_NAME,
-			                                    Scanner.Columns.ALLOC_SIZE,
-			                                    Scanner.Columns.PARSE_NAME,
-			                                    Scanner.Columns.PERCENT,
-			                                    Scanner.Columns.ELEMENTS, null);
-		}
-
-		public void scan_directory (File directory, ScanFlags flags = ScanFlags.NONE) {
-			if (!check_dir (directory)) {
-				return;
-			}
-
-			scanner = new ThreadedScanner (directory, flags);
-			set_model (scanner);
-
-			scanner.completed.connect(() => {
-				set_busy (false);
-				try {
-					scanner.finish();
-				} catch (IOError.CANCELLED e) {
-					// Handle cancellation silently
-					scanner.clear ();
-				} catch (Error e) {
-					var primary = _("Could not scan folder \"%s\" or some of the folders it contains.").printf (scanner.directory.get_parse_name ());
-					message (primary, e.message, Gtk.MessageType.WARNING);
-				}
-			});
-
-			clear_message ();
-
-			set_ui_page (UIPage.RESULT);
-			set_busy (true);
-
-			scanner.scan ();
-		}
-	}
+
+    public class Window : Gtk.ApplicationWindow {
+        Settings ui_settings;
+        Gtk.Notebook main_notebook;
+        Gtk.Toolbar toolbar;
+        Gtk.ToolItem toolbar_home_toolitem;
+        Gtk.ToolButton toolbar_show_home_page;
+        Gtk.ToolButton toolbar_rescan;
+        Gtk.InfoBar infobar;
+        Gtk.Label infobar_primary;
+        Gtk.Label infobar_secondary;
+        Gtk.TreeView treeview;
+        Gtk.Notebook chart_notebook;
+        Gtk.Grid location_view;
+        Chart rings_chart;
+        Chart treemap_chart;
+        Gtk.Spinner spinner;
+        Scanner? scanner;
+        LocationMonitor location_monitor;
+
+        static Gdk.Cursor busy_cursor;
+
+        void radio_activate (SimpleAction action, Variant? parameter) {
+            action.change_state (parameter);
+        }
+
+        private const GLib.ActionEntry[] action_entries = {
+            { "show-home-page", on_show_home_page_activate },
+            { "active-chart", radio_activate, "s", "'rings'", on_chart_type_changed },
+            { "scan-home", on_scan_home_activate },
+            { "scan-folder", on_scan_folder_activate },
+            { "scan-remote", on_scan_remote_activate },
+            { "stop", on_stop_activate },
+            { "reload", on_reload_activate },
+            { "show-toolbar", on_show_toolbar },
+            { "show-allocated", on_show_allocated },
+            { "expand-all", on_expand_all },
+            { "collapse-all", on_collapse_all },
+            { "help", on_help_activate },
+            { "about", on_about_activate }
+        };
+
+        protected struct ActionState {
+            string name;
+            bool enable;
+        }
+
+        private const ActionState[] actions_while_scanning = {
+            { "scan-home", false },
+            { "scan-folder", false },
+            { "scan-remote", false },
+            { "stop", true },
+            { "reload", false },
+            { "show-allocated", false },
+            { "expand-all", false },
+            { "collapse-all", false }
+        };
+
+        private enum UIPage {
+            HOME,
+            RESULT
+        }
+
+        private enum ChartPage {
+            RINGS,
+            TREEMAP,
+            SPINNER
+        }
+
+        private enum DndTargets {
+            URI_LIST
+        }
+
+        private const Gtk.TargetEntry dnd_target_list[1] = {
+            {"text/uri-list", 0, DndTargets.URI_LIST}
+        };
+
+        public Window (Application app) {
+            Object (application: app);
+
+            busy_cursor = new Gdk.Cursor (Gdk.CursorType.WATCH);
+
+            add_action_entries (action_entries, this);
+
+            // Build ourselves.
+            var builder = new Gtk.Builder ();
+            try {
+                builder.add_from_resource ("/org/gnome/baobab/ui/baobab-main-window.ui");
+            } catch (Error e) {
+                error ("loading main builder file: %s", e.message);
+            }
+
+            // Cache some objects from the builder.
+            main_notebook = builder.get_object ("main-notebook") as Gtk.Notebook;
+            toolbar = builder.get_object ("toolbar") as Gtk.Toolbar;
+            toolbar_home_toolitem = builder.get_object ("home-page-toolitem") as Gtk.ToolItem;
+            toolbar_show_home_page = builder.get_object ("show-home-page-button") as Gtk.ToolButton;
+            toolbar_rescan = builder.get_object ("rescan-button") as Gtk.ToolButton;
+            infobar = builder.get_object ("infobar") as Gtk.InfoBar;
+            infobar_primary = builder.get_object ("infobar-primary-label") as Gtk.Label;
+            infobar_secondary = builder.get_object ("infobar-secondary-label") as Gtk.Label;
+            treeview = builder.get_object ("treeview") as Gtk.TreeView;
+            chart_notebook = builder.get_object ("chart-notebook") as Gtk.Notebook;
+            rings_chart = builder.get_object ("rings-chart") as Chart;
+            treemap_chart = builder.get_object ("treemap-chart") as Chart;
+            spinner = builder.get_object ("spinner") as Gtk.Spinner;
+            location_view = builder.get_object ("location-view") as Gtk.Grid;
+
+            setup_home_page ();
+            setup_treeview (builder);
+
+            // To make it draggable like a primary toolbar
+            toolbar.get_style_context ().add_class (Gtk.STYLE_CLASS_MENUBAR);
+
+            ui_settings = Application.get_ui_settings ();
+            lookup_action ("active-chart").change_state (ui_settings.get_value ("active-chart"));
+
+            rings_chart.item_activated.connect (on_chart_item_activated);
+            treemap_chart.item_activated.connect (on_chart_item_activated);
+
+            // Setup drag-n-drop
+            drag_data_received.connect (on_drag_data_received);
+            enable_drop ();
+
+            add (builder.get_object ("window-contents") as Gtk.Widget);
+            title = _("Disk Usage Analyzer");
+            set_default_size (800, 500);
+            set_hide_titlebar_when_maximized (true);
+
+            set_ui_page (UIPage.HOME);
+
+            set_busy (false);
+
+            show ();
+        }
+
+        void set_ui_page (UIPage page) {
+            toolbar_home_toolitem.visible = (page == UIPage.HOME);
+            toolbar_show_home_page.visible = (page == UIPage.RESULT);
+            toolbar_rescan.visible = (page == UIPage.RESULT);
+
+            main_notebook.page = page;
+        }
+
+        void on_show_home_page_activate () {
+            if (scanner != null) {
+                scanner.cancel ();
+            }
+
+            set_ui_page (UIPage.HOME);
+        }
+
+        void on_chart_type_changed (SimpleAction action, Variant value) {
+            switch (value as string) {
+                case "rings":
+                    chart_notebook.page = ChartPage.RINGS;
+                    break;
+                case "treemap":
+                    chart_notebook.page = ChartPage.TREEMAP;
+                    break;
+                default:
+                    return;
+            }
+
+            ui_settings.set_value ("active-chart", value);
+            action.set_state (value);
+        }
+
+        void on_scan_home_activate () {
+            var dir = File.new_for_path (GLib.Environment.get_home_dir ());
+            scan_directory (dir);
+        }
+
+        void on_scan_folder_activate () {
+            var file_chooser = new Gtk.FileChooserDialog (_("Select Folder"), this,
+                                                          Gtk.FileChooserAction.SELECT_FOLDER,
+                                                          Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL,
+                                                          Gtk.Stock.OPEN, Gtk.ResponseType.ACCEPT);
+
+            file_chooser.set_modal (true);
+
+            file_chooser.response.connect ((response) => {
+                if (response == Gtk.ResponseType.ACCEPT) {
+                    var dir = file_chooser.get_file ();
+                    scan_directory (dir);
+                }
+                file_chooser.destroy ();
+            });
+
+            file_chooser.show ();
+        }
+
+        void on_scan_remote_activate () {
+            var connect_server = new ConnectServer ();
+
+            connect_server.selected.connect ((uri) => {
+                if (uri != null) {
+                    var dir = File.new_for_uri (uri);
+                    scan_directory (dir);
+                }
+            });
+
+            connect_server.show ();
+        }
+
+        void on_stop_activate () {
+            if (scanner != null) {
+                scanner.cancel ();
+            }
+        }
+
+        void on_reload_activate () {
+            if (scanner != null) {
+                scan_directory (scanner.directory, scanner.scan_flags);
+            }
+        }
+
+        void on_show_toolbar () {
+        }
+
+        void on_show_allocated () {
+        }
+
+        void on_expand_all () {
+            treeview.expand_all ();
+        }
+
+        void on_collapse_all () {
+            treeview.collapse_all ();
+        }
+
+        void on_help_activate () {
+            try {
+                Gtk.show_uri (get_screen (), "help:baobab", Gtk.get_current_event_time ());
+            } catch (Error e) {
+                warning ("Failed to show help: %s", e.message);
+            }
+        }
+
+        void on_about_activate () {
+            const string authors[] = {
+                "Ryan Lortie <desrt desrt ca>",
+                "Fabio Marzocca <thesaltydog gmail com>",
+                "Paolo Borelli <pborelli gnome com>",
+                "BenoÃt Dejean <benoit placenet org>",
+                "Igalia (rings-chart and treemap widget) <www.igalia.com>"
+            };
+
+            const string copyright = "Copyright \xc2\xa9 2005-2011 Fabio Marzocca, Paolo Borelli, BenoÃt Dejean, Igalia\n" +
+                                     "Copyright \xc2\xa9 2011-2012 Ryan Lortie, Paolo Borelli\n";
+
+            Gtk.show_about_dialog (this,
+                                   "program-name", _("Baobab"),
+                                   "logo-icon-name", "baobab",
+                                   "version", Config.VERSION,
+                                   "comments", "A graphical tool to analyze disk usage.",
+                                   "copyright", copyright,
+                                   "license-type", Gtk.License.GPL_2_0,
+                                   "wrap-license", false,
+                                   "authors", authors,
+                                   "translator-credits", _("translator-credits"),
+                                   null);
+        }
+
+        void on_chart_item_activated (Chart chart, Gtk.TreeIter iter) {
+            var path = scanner.get_path (iter);
+
+            if (!treeview.is_row_expanded (path)) {
+                treeview.expand_to_path (path);
+            }
+
+            treeview.set_cursor (path, null, false);
+        }
+
+        void on_drag_data_received (Gtk.Widget widget, Gdk.DragContext context, int x, int y,
+                                    Gtk.SelectionData selection_data, uint target_type, uint time) {
+            File dir = null;
+
+            if ((selection_data != null) && (selection_data.get_length () >= 0) && (target_type == DndTargets.URI_LIST)) {
+                var uris = GLib.Uri.list_extract_uris ((string) selection_data.get_data ());
+                if (uris != null && uris.length == 1) {
+                    dir = File.new_for_uri (uris[0]);
+                }
+            }
+
+            if (dir != null) {
+                // finish drop before scanning, since the it can time out
+                Gtk.drag_finish (context, true, false, time);
+                scan_directory (dir);
+            } else {
+                Gtk.drag_finish (context, false, false, time);
+            }
+        }
+
+        void enable_drop () {
+            Gtk.drag_dest_set (this,
+                               Gtk.DestDefaults.DROP | Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT,
+                               dnd_target_list,
+                               Gdk.DragAction.COPY);
+        }
+
+        void disable_drop () {
+            Gtk.drag_dest_unset (this);
+        }
+
+        void update_locations () {
+            location_view.foreach ((widget) => { widget.destroy (); });
+
+            foreach (var location in location_monitor.get_locations ()) {
+                LocationWidget loc_widget;
+                if (location.is_home_location) {
+                    loc_widget = new LocationWidget (location, (location_) => {
+                        on_scan_home_activate ();
+                    });
+                } else {
+                    loc_widget = new LocationWidget (location, (location_) => {
+                        location_.mount_volume.begin ((location__, res) => {
+                            try {
+                                location_.mount_volume.end (res);
+                                scan_directory (File.new_for_path (location_.mount_point), ScanFlags.EXCLUDE_MOUNTS);
+                            } catch (Error e) {
+                                message (_("Could not analyze volume."), e.message, Gtk.MessageType.ERROR);
+                            }
+                        });
+                    });
+                }
+
+                location_view.add (loc_widget);
+            }
+
+            location_view.show_all ();
+        }
+
+        void setup_home_page () {
+            location_monitor = LocationMonitor.get ();
+            location_monitor.changed.connect (() => { update_locations (); });
+            update_locations ();
+        }
+
+        bool show_treeview_popup (Gtk.Menu popup, Gdk.EventButton? event) {
+            if (event != null) {
+                popup.popup (null, null, null, event.button, event.time);
+            } else {
+                popup.popup (null, null, null, 0, Gtk.get_current_event_time ());
+                popup.select_first (false);
+            }
+            return true;
+        }
+
+        void setup_treeview (Gtk.Builder builder) {
+            var popup = builder.get_object ("treeview-popup-menu") as Gtk.Menu;
+            var open_item = builder.get_object ("treeview-popup-open") as Gtk.MenuItem;
+            var trash_item = builder.get_object ("treeview-popup-trash") as Gtk.MenuItem;
+
+            treeview.button_press_event.connect ((event) => {
+                if (((Gdk.Event) (&event)).triggers_context_menu ()) {
+                    return show_treeview_popup (popup, event);
+                }
+
+                return false;
+            });
+
+            treeview.popup_menu.connect (() => {
+                return show_treeview_popup (popup, null);
+            });
+
+            open_item.activate.connect (() => {
+                var selection = treeview.get_selection ();
+                Gtk.TreeIter iter;
+                if (selection.get_selected (null, out iter)) {
+                    string parse_name;
+                    scanner.get (iter, Scanner.Columns.PARSE_NAME, out parse_name);
+                    var file = File.parse_name (parse_name);
+                    try {
+                        var info = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE, 0, null);
+                        var content = info.get_content_type ();
+                        var appinfo = AppInfo.get_default_for_type (content, true);
+                        var files = new List<File>();
+                        files.append (file);
+                        appinfo.launch(files, null);
+                    } catch (Error e) {
+                        warning ("Failed open file with application: %s", e.message);
+                    }
+                }
+            });
+
+            trash_item.activate.connect (() => {
+                var selection = treeview.get_selection ();
+                Gtk.TreeIter iter;
+                if (selection.get_selected (null, out iter)) {
+                    string parse_name;
+                    scanner.get (iter, Scanner.Columns.PARSE_NAME, out parse_name);
+                    var file = File.parse_name (parse_name);
+                    try {
+                        file.trash ();
+                        scanner.remove (iter);
+                    } catch (Error e) {
+                        warning ("Failed to move file to the trash: %s", e.message);
+                    }
+                }
+            });
+
+            var selection = treeview.get_selection ();
+            selection.changed.connect (() => {
+                Gtk.TreeIter iter;
+                if (selection.get_selected (null, out iter)) {
+                    var path = scanner.get_path (iter);
+                    rings_chart.set_root (path);
+                    treemap_chart.set_root (path);
+                }
+            });
+        }
+
+        void message (string primary_msg, string secondary_msg, Gtk.MessageType type) {
+            infobar.message_type = type;
+            infobar_primary.set_label ("<b>%s</b>".printf (_(primary_msg)));
+            infobar_secondary.set_label ("<small>%s</small>".printf (_(secondary_msg)));
+            infobar.show ();
+        }
+
+        void clear_message () {
+            infobar.hide ();
+        }
+
+        void set_busy (bool busy) {
+            Gdk.Cursor? cursor = null;
+
+            if (busy) {
+                cursor = busy_cursor;
+                disable_drop ();
+                rings_chart.freeze_updates ();
+                treemap_chart.freeze_updates ();
+                (lookup_action ("active-chart") as SimpleAction).set_enabled (false);
+                chart_notebook.page = ChartPage.SPINNER;
+                spinner.start ();
+                toolbar_show_home_page.label = _("Cancel scan");
+            } else {
+                enable_drop ();
+                rings_chart.thaw_updates ();
+                treemap_chart.thaw_updates ();
+                (lookup_action ("active-chart") as SimpleAction).set_enabled (true);
+                spinner.stop ();
+                lookup_action ("active-chart").change_state (ui_settings.get_value ("active-chart"));
+                toolbar_show_home_page.label = _("All locations");
+            }
+
+            var window = get_window ();
+            if (window != null) {
+                window.set_cursor (cursor);
+            }
+
+            foreach (ActionState action_state in actions_while_scanning) {
+                var action = lookup_action (action_state.name) as SimpleAction;
+                action.set_enabled (busy == action_state.enable);
+            }
+        }
+
+        public bool check_dir (File directory) {
+            //if (Application.is_excluded_location (directory)) {
+            //    message("", _("Cannot check an excluded folder!"), Gtk.MessageType.INFO);
+            //    return false;
+            //}
+
+            try {
+                var info = directory.query_info (FileAttribute.STANDARD_TYPE, FileQueryInfoFlags.NONE, null);
+                if (info.get_file_type () != FileType.DIRECTORY/* || is_virtual_filesystem ()*/) {
+                    var primary = _("\"%s\" is not a valid folder").printf (directory.get_parse_name ());
+                    message (primary, _("Could not analyze disk usage."), Gtk.MessageType.ERROR);
+                    return false;
+                }
+                return true;
+            } catch (Error e) {
+                message ("", e.message, Gtk.MessageType.INFO);
+                return false;
+            }
+        }
+
+        void first_row_has_child (Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter) {
+            model.row_has_child_toggled.disconnect (first_row_has_child);
+            treeview.expand_row (path, false);
+        }
+
+        void set_model (Gtk.TreeModel model) {
+            Gtk.TreeIter first;
+
+            treeview.model = model;
+
+            if (model.iter_children (out first, null) && model.iter_has_child (first)) {
+                treeview.expand_row (model.get_path (first), false);
+            } else {
+                model.row_has_child_toggled.connect (first_row_has_child);
+            }
+
+            model.bind_property ("max-depth", rings_chart, "max-depth", BindingFlags.SYNC_CREATE);
+            model.bind_property ("max-depth", treemap_chart, "max-depth", BindingFlags.SYNC_CREATE);
+            treemap_chart.set_model_with_columns (model,
+                                                  Scanner.Columns.DISPLAY_NAME,
+                                                  Scanner.Columns.ALLOC_SIZE,
+                                                  Scanner.Columns.PARSE_NAME,
+                                                  Scanner.Columns.PERCENT,
+                                                  Scanner.Columns.ELEMENTS, null);
+            rings_chart.set_model_with_columns (model,
+                                                Scanner.Columns.DISPLAY_NAME,
+                                                Scanner.Columns.ALLOC_SIZE,
+                                                Scanner.Columns.PARSE_NAME,
+                                                Scanner.Columns.PERCENT,
+                                                Scanner.Columns.ELEMENTS, null);
+        }
+
+        public void scan_directory (File directory, ScanFlags flags = ScanFlags.NONE) {
+            if (!check_dir (directory)) {
+                return;
+            }
+
+            scanner = new ThreadedScanner (directory, flags);
+            set_model (scanner);
+
+            scanner.completed.connect(() => {
+                set_busy (false);
+                try {
+                    scanner.finish();
+                } catch (IOError.CANCELLED e) {
+                    // Handle cancellation silently
+                    scanner.clear ();
+                } catch (Error e) {
+                    var primary = _("Could not scan folder \"%s\" or some of the folders it contains.").printf (scanner.directory.get_parse_name ());
+                    message (primary, e.message, Gtk.MessageType.WARNING);
+                }
+            });
+
+            clear_message ();
+
+            set_ui_page (UIPage.RESULT);
+            set_busy (true);
+
+            scanner.scan ();
+        }
+    }
 }
diff --git a/src/main.vala b/src/main.vala
index 070e806..b5081e7 100644
--- a/src/main.vala
+++ b/src/main.vala
@@ -1,3 +1,4 @@
+/* -*- indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* Baobab - disk usage analyzer
  *
  * Copyright (C) 2012  Ryan Lortie <desrt desrt ca>
@@ -18,10 +19,10 @@
  */
 
 int main (string[] args) {
-	Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.GNOMELOCALEDIR);
-	Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
-	Intl.textdomain (Config.GETTEXT_PACKAGE);
+    Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.GNOMELOCALEDIR);
+    Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
+    Intl.textdomain (Config.GETTEXT_PACKAGE);
 
-	var baobab = new Baobab.Application ();
-	return baobab.run (args);
+    var baobab = new Baobab.Application ();
+    return baobab.run (args);
 }



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