[baobab] Fold back ThreadedScanner in Scanner



commit 28e663bc6a485353e83f411698255a204811554a
Author: Paolo Borelli <pborelli gnome org>
Date:   Mon Apr 9 15:00:54 2012 +0200

    Fold back ThreadedScanner in Scanner
    
    Since we decided to drop the sync scanner, we also remove the
    Scanner abstract class and merge it with the concrete ThreadedScanner
    class

 src/Makefile.am                  |    2 -
 src/baobab-scanner.vala          |  278 +++++++++++++++++++++++++++++++++++---
 src/baobab-threaded-scanner.vala |  270 ------------------------------------
 src/baobab-window.vala           |    2 +-
 4 files changed, 259 insertions(+), 293 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 9d78727..9ce4692 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -34,7 +34,6 @@ baobab_SOURCES = \
 	baobab-location.vala		\
 	baobab-location-widget.vala	\
 	baobab-scanner.vala		\
-	baobab-threaded-scanner.vala	\
 	baobab-window.vala		\
 	main.vala			\
 	$(BUILT_SOURCES)
@@ -63,7 +62,6 @@ MAINTAINERCLEANFILES = \
 	baobab-location.c		\
 	baobab-location-widget.c	\
 	baobab-scanner.c		\
-	baobab-threaded-scanner.c	\
 	baobab-window.c			\
 	baobab_vala.stamp		\
 	main.c				\
diff --git a/src/baobab-scanner.vala b/src/baobab-scanner.vala
index db3c146..b33a60f 100644
--- a/src/baobab-scanner.vala
+++ b/src/baobab-scanner.vala
@@ -28,7 +28,7 @@ namespace Baobab {
         EXCLUDE_MOUNTS
     }
 
-    abstract class Scanner : Gtk.TreeStore {
+    class Scanner : Gtk.TreeStore {
         public enum Columns {
             DISPLAY_NAME,
             PARSE_NAME,
@@ -49,41 +49,274 @@ namespace Baobab {
             DONE
         }
 
-        protected struct HardLink {
-            uint64 inode;
-            uint32 device;
+        public File directory { get; private set; }
 
-            public HardLink (FileInfo info) {
-                this.inode = info.get_attribute_uint64 (FileAttribute.UNIX_INODE);
-                this.device = info.get_attribute_uint32 (FileAttribute.UNIX_DEVICE);
-            }
-        }
+        public ScanFlags scan_flags { get; private set; }
 
-        protected Cancellable cancellable;
-        protected HashTable<File, unowned File> excluded_locations;
-        protected HardLink[] hardlinks;
-        protected Error? scan_error;
+        public int max_depth { get; protected set; }
 
-        protected static const string ATTRIBUTES =
+        public signal void completed();
+
+        static const string ATTRIBUTES =
             FileAttribute.STANDARD_NAME + "," +
             FileAttribute.STANDARD_DISPLAY_NAME + "," +
             FileAttribute.STANDARD_TYPE + "," +
             FileAttribute.STANDARD_SIZE +  "," +
-            FileAttribute.STANDARD_ALLOCATED_SIZE +     "," +
+            FileAttribute.STANDARD_ALLOCATED_SIZE + "," +
             FileAttribute.UNIX_NLINK + "," +
             FileAttribute.UNIX_INODE + "," +
             FileAttribute.UNIX_DEVICE + "," +
             FileAttribute.ACCESS_CAN_READ;
 
-        public File directory { get; private set; }
+        struct HardLink {
+            uint64 inode;
+            uint32 device;
 
-        public ScanFlags scan_flags { get; private set; }
+            public HardLink (FileInfo info) {
+                this.inode = info.get_attribute_uint64 (FileAttribute.UNIX_INODE);
+                this.device = info.get_attribute_uint32 (FileAttribute.UNIX_DEVICE);
+            }
+        }
 
-        public int max_depth { get; protected set; }
+        HardLink[] hardlinks;
+        HashTable<File, unowned File> excluded_locations;
 
-        public signal void completed();
+        /* 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.
+         */
+
+        AsyncQueue<ResultsArray> results_queue;
+        Scanner? self;
+        Cancellable cancellable;
+        Error? scan_error;
+
+        [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;
 
-        public abstract void scan ();
+                        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 void scan () {
+            new GLib2.Thread ("scanner", scan_in_thread);
+            Timeout.add (100, process_results);
+        }
 
         public virtual void cancel () {
             cancellable.cancel ();
@@ -121,6 +354,11 @@ namespace Baobab {
             }
 
             excluded_locations.remove (directory);
+
+            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 0c42b83..30c2bfa 100644
--- a/src/baobab-window.vala
+++ b/src/baobab-window.vala
@@ -535,7 +535,7 @@ namespace Baobab {
                 return;
             }
 
-            scanner = new ThreadedScanner (directory, flags);
+            scanner = new Scanner (directory, flags);
             set_model (scanner);
 
             scanner.completed.connect(() => {



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