[baobab/wip/vala] Implement volume handling



commit c2bf3725cf8620f59f39d81787139eb03152798b
Author: Stefano Facchini <stefano facchini gmail com>
Date:   Tue Mar 6 16:05:17 2012 +0100

    Implement volume handling
    
    https://bugzilla.gnome.org/show_bug.cgi?id=671829

 configure.ac                          |    3 +
 src/Makefile.am                       |   12 +++-
 src/baobab-application.vala           |    9 ++-
 src/baobab-main-window.ui             |   10 +-
 src/baobab-menu.ui                    |   12 ++--
 src/baobab-scanner.vala               |    8 ++-
 src/baobab-sync-scanner.vala          |    4 +-
 src/baobab-threaded-scanner.vala      |    4 +-
 src/baobab-volume-chooser-dialog.vala |   79 +++++++++++++++++
 src/baobab-volume-list.vala           |  155 +++++++++++++++++++++++++++++++++
 src/baobab-window.vala                |   39 ++++++---
 11 files changed, 302 insertions(+), 33 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index bece5eb..6cd2290 100644
--- a/configure.ac
+++ b/configure.ac
@@ -46,6 +46,9 @@ YELP_HELP_INIT
 
 PKG_CHECK_MODULES(gtk, gtk+-3.0 >= 3.3.6)
 PKG_CHECK_MODULES(gio, gio-2.0 >= 2.30.0)
+PKG_CHECK_MODULES(udisks2, udisks2)
+
+VALA_CHECK_PACKAGES([udisks2])
 
 AC_CONFIG_FILES([
 Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index 792df96..11beb6c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,13 +9,19 @@ INCLUDES = \
 	-DPKGDATADIR=\""$(datadir)/baobab"\"			\
 	-DBAOBAB_PIX_DIR=\""$(datadir)/baobab/pixmaps/"\" 	\
 	-DGNOMELOCALEDIR=\""$(prefix)/$(DATADIRNAME)/locale"\" 	\
+	-DUDISKS_API_IS_SUBJECT_TO_CHANGE			\
 	$(NULL)
 
 bin_PROGRAMS = baobab
 
 BUILT_SOURCES = baobab-resources.c
 
-baobab_VALAFLAGS = --pkg gtk+-3.0
+baobab_VALAFLAGS = \
+	--pkg gtk+-3.0		\
+	--pkg gio-2.0		\
+	--pkg gio-unix-2.0	\
+	--pkg udisks2
+
 baobab_SOURCES = \
 	fixes.vapi			\
 	baobab.vapi			\
@@ -30,6 +36,8 @@ baobab_SOURCES = \
 	baobab-application.vala		\
 	baobab-window.vala		\
 	baobab-connect-server.vala	\
+	baobab-volume-chooser-dialog.vala	\
+	baobab-volume-list.vala		\
 	main.vala			\
 	$(BUILT_SOURCES)
 
@@ -38,6 +46,7 @@ baobab-resources.c: baobab.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --gen
 
 AM_CFLAGS = \
 	$(gtk_CFLAGS)			\
+	$(udisks2_CFLAGS)		\
 	-Wall				\
 	-Wno-unused-but-set-variable	\
 	-Wno-unused-variable		\
@@ -47,6 +56,7 @@ baobab_LDFLAGS = -export-dynamic
 baobab_LDADD = \
 	-lm				\
 	$(gtk_LIBS)			\
+	$(udisks2_LIBS)			\
 	$(NULL)
 
 MAINTAINERCLEANFILES = \
diff --git a/src/baobab-application.vala b/src/baobab-application.vala
index b8981d1..9543e68 100644
--- a/src/baobab-application.vala
+++ b/src/baobab-application.vala
@@ -18,11 +18,11 @@ namespace Baobab {
 		protected override void open (File[] files, string hint) {
 			foreach (var file in files) {
 				var window = new Window (this);
-				window.scan_directory (file);
+				window.scan_directory (file, false);
 			}
 		}
 
-		public static HashTable<File, unowned File> get_excluded_locations () {
+		public static HashTable<File, unowned File> get_excluded_locations (bool exclude_mounts) {
 			var app = baobab;
 
 			var excluded_locations = new HashTable<File, unowned File> (File.hash, File.equal);
@@ -41,6 +41,11 @@ namespace Baobab {
 				}
 			}
 
+			if (exclude_mounts) {
+				foreach (unowned UnixMountEntry mount in UnixMountEntry.get (null))
+					excluded_locations.add (File.new_for_path (mount.get_mount_path ()));
+			}
+
 			return excluded_locations;
 		}
 
diff --git a/src/baobab-main-window.ui b/src/baobab-main-window.ui
index 0e5895e..763e3ee 100644
--- a/src/baobab-main-window.ui
+++ b/src/baobab-main-window.ui
@@ -38,16 +38,16 @@
           </packing>
         </child>
         <child>
-          <object class="GtkToolButton" id="scan-filesystem-button">
+          <object class="GtkToolButton" id="scan-volume-button">
             <property name="use_action_appearance">False</property>
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="has_tooltip">True</property>
-            <property name="tooltip_markup" translatable="yes">Scan the file system</property>
-            <property name="tooltip_text" translatable="yes">Scan the file system</property>
+            <property name="tooltip_markup" translatable="yes">Scan a volume</property>
+            <property name="tooltip_text" translatable="yes">Scan a volume</property>
             <property name="use_action_appearance">False</property>
-            <property name="action_name">win.scan-filesystem</property>
-            <property name="label" translatable="yes">Scan File System</property>
+            <property name="action_name">win.scan-volume</property>
+            <property name="label" translatable="yes">Scan Volume</property>
             <property name="use_underline">True</property>
             <property name="stock_id">gtk-harddisk</property>
           </object>
diff --git a/src/baobab-menu.ui b/src/baobab-menu.ui
index 41e139b..cdbe16d 100644
--- a/src/baobab-menu.ui
+++ b/src/baobab-menu.ui
@@ -9,9 +9,9 @@
         <attribute name="accel">&lt;Primary&gt;h</attribute>
       </item>
       <item>
-        <attribute name="label" translatable="yes">Scan _Filesystem</attribute>
-        <attribute name="action">win.scan-filesystem</attribute>
-        <attribute name="accel">&lt;Primary&gt;f</attribute>
+        <attribute name="label" translatable="yes">Scan _Volumeâ</attribute>
+        <attribute name="action">win.scan-volume</attribute>
+        <attribute name="accel">&lt;Primary&gt;v</attribute>
       </item>
       <item>
         <attribute name="label" translatable="yes">Scan F_olderâ</attribute>
@@ -63,9 +63,9 @@
           <attribute name="accel">&lt;Primary&gt;h</attribute>
         </item>
         <item>
-          <attribute name="label" translatable="yes">Scan _Filesystem</attribute>
-          <attribute name="action">win.scan-filesystem</attribute>
-          <attribute name="accel">&lt;Primary&gt;f</attribute>
+          <attribute name="label" translatable="yes">Scan _Volumeâ</attribute>
+          <attribute name="action">win.scan-volume</attribute>
+          <attribute name="accel">&lt;Primary&gt;v</attribute>
         </item>
         <item>
           <attribute name="label" translatable="yes">Scan F_olderâ</attribute>
diff --git a/src/baobab-scanner.vala b/src/baobab-scanner.vala
index 071f6da..50f90bb 100644
--- a/src/baobab-scanner.vala
+++ b/src/baobab-scanner.vala
@@ -48,6 +48,8 @@ namespace Baobab {
 
 		public File directory { get; private set; }
 
+		public bool exclude_mounts { get; private set; }
+
 		public int max_depth { get; protected set; }
 
 		public signal void completed();
@@ -115,8 +117,9 @@ namespace Baobab {
 			     Columns.ERROR, null);
 		}
 
-		public Scanner (File directory) {
+		public Scanner (File directory, bool exclude_mounts) {
 			this.directory = directory;
+			this.exclude_mounts = exclude_mounts;
 			cancellable = new Cancellable();
 			scan_error = null;
 			set_column_types (new Type[] {
@@ -129,7 +132,8 @@ namespace Baobab {
 			                  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 ();
+			excluded_locations = Application.get_excluded_locations (exclude_mounts);
+			excluded_locations.remove (directory);
 		}
 	}
 }
diff --git a/src/baobab-sync-scanner.vala b/src/baobab-sync-scanner.vala
index f5b4e8c..3344ab8 100644
--- a/src/baobab-sync-scanner.vala
+++ b/src/baobab-sync-scanner.vala
@@ -118,8 +118,8 @@ namespace Baobab {
 			} catch { }
 		}
 
-		public SyncScanner (File directory) {
-			base (directory);
+		public SyncScanner (File directory, bool exclude_mounts) {
+			base (directory, exclude_mounts);
 		}
 	}
 }
diff --git a/src/baobab-threaded-scanner.vala b/src/baobab-threaded-scanner.vala
index b7dd3dd..6fb920e 100644
--- a/src/baobab-threaded-scanner.vala
+++ b/src/baobab-threaded-scanner.vala
@@ -235,8 +235,8 @@ namespace Baobab {
 			Timeout.add (100, process_results);
 		}
 
-		public ThreadedScanner (File directory) {
-			base (directory);
+		public ThreadedScanner (File directory, bool exclude_mounts) {
+			base (directory, exclude_mounts);
 
 			results_queue = new AsyncQueue<ResultsArray> ();
 
diff --git a/src/baobab-volume-chooser-dialog.vala b/src/baobab-volume-chooser-dialog.vala
new file mode 100644
index 0000000..bf5dcdb
--- /dev/null
+++ b/src/baobab-volume-chooser-dialog.vala
@@ -0,0 +1,79 @@
+namespace Baobab {
+        public class VolumeChooserDialog : Gtk.Dialog {
+                private VolumeList model;
+                private Gtk.TreeView tree_view;
+
+                public VolumeChooserDialog (Gtk.Window parent) {
+                        Object ();
+
+                        title = _("Select volume");
+                        transient_for = parent;
+                        add_buttons (Gtk.Stock.CANCEL, Gtk.ResponseType.CANCEL,
+                                     Gtk.Stock.OPEN, Gtk.ResponseType.ACCEPT);
+                        modal = true;
+
+                        model = new VolumeList ();
+
+                        setup_treeview ();
+                        tree_view.show ();
+
+                        get_content_area ().add (tree_view);
+                }
+
+                public File get_file () throws Error {
+                        Gtk.TreePath path = tree_view.get_selection ().get_selected_rows (null).data;
+                        Gtk.TreeIter iter;
+
+                        model.get_iter (out iter, path);
+
+                        string? mount_point = null;
+                        model.get (iter, VolumeList.Columns.MOUNT_POINT, out mount_point);
+
+
+                        if (mount_point == null) {
+                                UDisks.Object volume;
+                                model.get (iter, VolumeList.Columns.UDISKS_OBJECT, out volume);
+                                var fs = volume.get_filesystem ();
+                                fs.call_mount_sync (new GLib.Variant ("a{sv}", null), out mount_point, null);
+                        }
+
+                        return File.new_for_path (mount_point);
+                }
+
+                private void setup_treeview () {
+                        tree_view = new Gtk.TreeView.with_model (model);
+
+                        Gtk.CellRenderer renderer;
+
+                        renderer = new Gtk.CellRendererPixbuf ();
+                        renderer.set ("stock-size", Gtk.IconSize.DIALOG);
+                        var column = new Gtk.TreeViewColumn.with_attributes (null, renderer, "gicon", VolumeList.Columns.ICON);
+                        tree_view.append_column (column);
+
+                        renderer = new Gtk.CellRendererText ();
+                        column = new Gtk.TreeViewColumn.with_attributes (_("Volume"), renderer, "text", VolumeList.Columns.NAME);
+                        tree_view.append_column (column);
+
+                        renderer = new Gtk.CellRendererToggle ();
+                        column = new Gtk.TreeViewColumn.with_attributes (_("Mounted"), renderer);
+                        column.set_cell_data_func (renderer, (layout, cell, model, iter) => {
+                                        string? mount_point = null;
+                                        model.get (iter, VolumeList.Columns.MOUNT_POINT, out mount_point);
+                                        ((Gtk.CellRendererToggle) cell).active = mount_point != null;
+                                });
+                        tree_view.append_column (column);
+
+                        renderer = new Gtk.CellRendererText ();
+                        column = new Gtk.TreeViewColumn.with_attributes (_("Size"), renderer, "text", VolumeList.Columns.SIZE_LABEL);
+                        tree_view.append_column (column);
+
+                        renderer = new Gtk.CellRendererProgress ();
+                        renderer.ypad = 10;
+                        renderer.width = 150;
+                        column = new Gtk.TreeViewColumn.with_attributes (_("Usage"), renderer, "value", VolumeList.Columns.USAGE_PERCENT, "text", VolumeList.Columns.USAGE_LABEL);
+                        tree_view.append_column (column);
+
+                        tree_view.vexpand = true;
+                }
+        }
+}
diff --git a/src/baobab-volume-list.vala b/src/baobab-volume-list.vala
new file mode 100644
index 0000000..6d043bd
--- /dev/null
+++ b/src/baobab-volume-list.vala
@@ -0,0 +1,155 @@
+namespace Baobab {
+        public class VolumeList : Gtk.ListStore {
+                public enum Columns {
+                        NAME,
+                        MOUNT_POINT,
+                        SIZE_LABEL,
+                        USAGE_PERCENT,
+                        USAGE_LABEL,
+                        ICON,
+                        UDISKS_OBJECT,
+                        COLUMNS
+                }
+
+                private UDisks.Client client = null;
+                private VolumeMonitor monitor;
+
+                public VolumeList () {
+                        set_column_types (new Type[] {
+                                          typeof (string),   // NAME
+                                          typeof (string),   // MOUNT_POINT
+                                          typeof (string),   // SIZE_LABEL
+                                          typeof (int),      // USAGE_PERCENT
+                                          typeof (string),   // USAGE_LABEL
+                                          typeof (Icon),     // ICON
+                                          typeof (Object)}); // UDISKS_OBJECT
+
+                        try {
+                                client = new UDisks.Client.sync (null);
+                                client.changed.connect(() => { update (); });
+                        } catch (Error e) {
+                        }
+
+                        monitor = VolumeMonitor.get ();
+                        monitor.mount_changed.connect ((mount) => { update (); });
+                        monitor.mount_removed.connect ((mount) => { update (); });
+                        monitor.mount_added.connect ((mount) => { update (); });
+
+                        update ();
+                }
+
+                private void compute_usage (File file, ref uint64? size, ref int percent) {
+                        try {
+                                var info = file.query_filesystem_info (FileAttribute.FILESYSTEM_SIZE + "," +
+                                                                       FileAttribute.FILESYSTEM_USED,
+                                                                       null);
+                                if (size == null && info.has_attribute (FileAttribute.FILESYSTEM_SIZE))
+                                        size = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_SIZE);
+                                if (size > 0 && info.has_attribute (FileAttribute.FILESYSTEM_USED)) {
+                                        var used = info.get_attribute_uint64 (FileAttribute.FILESYSTEM_USED);
+                                        percent = (int) ((double) used / size * 100);
+                                }
+                        } catch (Error e) {
+                        }
+                }
+
+                private void update () {
+                        clear ();
+
+                        var pretty_names = new HashTable<string, string> (str_hash, str_equal);
+                        var icons = new HashTable<string, Icon> (str_hash, str_equal);
+
+                        foreach (unowned UnixMountEntry mount in UnixMountEntry.get (null)) {
+                                var device = mount.get_device_path ();
+                                pretty_names.insert (device, mount.guess_name ());
+                                icons.insert (device, mount.guess_icon ());
+                        }
+
+                        foreach (var volume in monitor.get_volumes ()) {
+                                var device = volume.get_identifier (VolumeIdentifier.UNIX_DEVICE);
+                                if (device != null) {
+                                        pretty_names.insert (device, volume.get_name ());
+                                        icons.insert (device, volume.get_icon ());
+                                }
+                        }
+
+                        Gtk.TreeIter iter;
+
+                        var dbus_objects = client.get_object_manager ().get_objects ();
+                        foreach (var dbus_object in dbus_objects) {
+                                UDisks.Object volume = (UDisks.Object) dbus_object;
+
+                                var block = volume.get_block ();
+                                if (block == null ||
+                                    block.hint_ignore ||
+                                    volume.get_filesystem () == null)
+                                        continue;
+
+                                append (out iter);
+
+                                uint64? size = block.size;
+
+                                var mount_point = volume.get_filesystem ().mount_points[0];
+                                int percent = -1;
+                                if (mount_point != null) {
+                                        var file = File.new_for_path (mount_point);
+                                        compute_usage (file, ref size, ref percent);
+                                }
+
+                                if (mount_point != null && percent != -1)
+                                        set (iter, Columns.USAGE_PERCENT, percent);
+                                else
+                                        set (iter, Columns.USAGE_LABEL,  _("Unknown"));
+
+
+                                var name = block.device;
+                                var icon = icons.lookup (name);
+                                name = pretty_names.lookup (name);
+                                if (mount_point == "/") {
+                                        name = _("Main volume");
+                                        icon = new ThemedIcon ("drive-harddisk-system");
+                                }
+
+                                set (iter,
+                                     Columns.NAME,          name,
+                                     Columns.SIZE_LABEL,    format_size (size),
+                                     Columns.MOUNT_POINT,   mount_point,
+                                     Columns.ICON,          icon,
+                                     Columns.UDISKS_OBJECT, volume);
+                        }
+
+                        foreach (var mount in monitor.get_mounts ()) {
+                                bool found = false;
+                                var mount_point = mount.get_root ().get_path ();
+
+                                get_iter_first (out iter);
+                                do {
+                                        string other_mount;
+                                        get (iter, Columns.MOUNT_POINT, out other_mount);
+                                        if (mount_point == other_mount) {
+                                                found = true;
+                                                break;
+                                        }
+                                } while (iter_next (ref iter));
+
+                                if (!found) {
+                                        append (out iter);
+                                        set (iter,
+                                             Columns.NAME,        mount.get_name (),
+                                             Columns.MOUNT_POINT, mount_point,
+                                             Columns.ICON,        mount.get_icon ());
+
+                                        int percent = -1;
+                                        uint64? size = null;
+                                        compute_usage (mount.get_root (), ref size, ref percent);
+                                        if (size != null)
+                                                set (iter, Columns.SIZE_LABEL, format_size (size));
+                                        if (percent != -1)
+                                                set (iter, Columns.USAGE_PERCENT, percent);
+                                        else
+                                                set (iter, Columns.USAGE_LABEL, _("Unknown"));
+                                }
+                        }
+                }
+        }
+}
\ No newline at end of file
diff --git a/src/baobab-window.vala b/src/baobab-window.vala
index 0c7140c..50d67e0 100644
--- a/src/baobab-window.vala
+++ b/src/baobab-window.vala
@@ -20,7 +20,7 @@ namespace Baobab {
 		private const GLib.ActionEntry[] action_entries = {
 			{ "active-chart", radio_activate, "s", "'rings'", on_chart_type_changed },
 			{ "scan-home", on_scan_home_activate },
-			{ "scan-filesystem", on_scan_filesystem_activate },
+			{ "scan-volume", on_scan_volume_activate },
 			{ "scan-folder", on_scan_folder_activate },
 			{ "scan-remote", on_scan_remote_activate },
 			{ "stop", on_stop_activate },
@@ -40,7 +40,7 @@ namespace Baobab {
 
 		private const ActionState[] actions_while_scanning = {
 			{ "scan-home", false },
-			{ "scan-filesystem", false },
+			{ "scan-volume", false },
 			{ "scan-folder", false },
 			{ "scan-remote", false },
 			{ "stop", true },
@@ -128,12 +128,25 @@ namespace Baobab {
 
 		void on_scan_home_activate () {
 			var dir = File.new_for_path (GLib.Environment.get_home_dir ());
-			scan_directory (dir);
+			scan_directory (dir, false);
 		}
 
-		void on_scan_filesystem_activate () {
-			var dir = File.new_for_uri ("file:///");
-			scan_directory (dir);
+		void on_scan_volume_activate () {
+			var volume_chooser = new VolumeChooserDialog (this);
+
+			volume_chooser.response.connect ((response) => {
+				if (response == Gtk.ResponseType.ACCEPT) {
+					try {
+                                                var dir = volume_chooser.get_file ();
+                                                scan_directory (dir, true);
+                                        } catch (Error e) {
+                                                message (_("Could not analyze volume."), e.message, Gtk.MessageType.ERROR);
+                                        }
+				}
+				volume_chooser.destroy ();
+			});
+
+			volume_chooser.show ();
 		}
 
 		void on_scan_folder_activate () {
@@ -147,7 +160,7 @@ namespace Baobab {
 			file_chooser.response.connect ((response) => {
 				if (response == Gtk.ResponseType.ACCEPT) {
 					var dir = file_chooser.get_file ();
-					scan_directory (dir);
+					scan_directory (dir, false);
 				}
 				file_chooser.destroy ();
 			});
@@ -161,7 +174,7 @@ namespace Baobab {
 			connect_server.selected.connect ((uri) => {
 				if (uri != null) {
 					var dir = File.new_for_uri (uri);
-					scan_directory (dir);
+					scan_directory (dir, false);
 				}
 			});
 
@@ -176,7 +189,7 @@ namespace Baobab {
 
 		void on_reload_activate () {
 			if (scanner != null) {
-				scan_directory (scanner.directory);
+				scan_directory (scanner.directory, scanner.exclude_mounts);
 			}
 		}
 
@@ -251,7 +264,7 @@ namespace Baobab {
 			if (dir != null) {
 				// finish drop before scanning, since the it can time out
 				Gtk.drag_finish (context, true, false, time);
-				scan_directory (dir);
+				scan_directory (dir, false);
 			} else {
 				Gtk.drag_finish (context, false, false, time);
 			}
@@ -439,7 +452,7 @@ namespace Baobab {
 		public void show_filesystem_usage () {
 			var dir = File.new_for_uri ("file:///");
 
-			scanner = new ThreadedScanner (dir);
+			scanner = new ThreadedScanner (dir, false);
 			set_model (scanner);
 
 			try {
@@ -451,12 +464,12 @@ namespace Baobab {
 			treeview.set_headers_visible (false);
 		}
 
-		public void scan_directory (File directory) {
+		public void scan_directory (File directory, bool exclude_mounts) {
 			if (!check_dir (directory)) {
 				return;
 			}
 
-			scanner = new ThreadedScanner (directory);
+			scanner = new ThreadedScanner (directory, exclude_mounts);
 			set_model (scanner);
 
 			scanner.completed.connect(() => {



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