Merge lp:~jeremywootten/pantheon-files/various-fixes-part2-load-network-locations-better into lp:~elementary-apps/pantheon-files/trunk

Proposed by Jeremy Wootten
Status: Merged
Merged at revision: 2049
Proposed branch: lp:~jeremywootten/pantheon-files/various-fixes-part2-load-network-locations-better
Merge into: lp:~elementary-apps/pantheon-files/trunk
Diff against target: 1701 lines (+539/-435)
12 files modified
libcore/AbstractSlot.vala (+1/-0)
libcore/CMakeLists.txt (+1/-0)
libcore/FileUtils.vala (+25/-0)
libcore/gof-directory-async.vala (+336/-255)
libcore/gof-file.c (+15/-8)
libwidgets/FileUtils.vala (+0/-7)
libwidgets/View/BreadcrumbsEntry.vala (+18/-13)
src/View/AbstractDirectoryView.vala (+37/-36)
src/View/Miller.vala (+4/-0)
src/View/Slot.vala (+36/-12)
src/View/ViewContainer.vala (+59/-71)
src/View/Window.vala (+7/-33)
To merge this branch: bzr merge lp:~jeremywootten/pantheon-files/various-fixes-part2-load-network-locations-better
Reviewer Review Type Date Requested Status
elementary Apps team Pending
Review via email: mp+283723@code.launchpad.net

Commit message

Make loading and reloading of directories asynchronous.

Description of the change

This branch is the second part of a series aimed at improving the handling of remote locations.
It aims to make the loading of directories fully asynchronous so as to prevent blocking of the interface especially when restoring remote locations that have become unavailable or require a password.

A number of related changes are made to the reloading process and the location bar context menu.

The branch has been tested using ftp:// sftp:// afp:// and smb:// servers on the local network.

TO TEST.
1. Open folders to various servers in different/multiple tabs and windows
2. Navigate within and between folders on each server using all available methods.
3. Close and reopen Files without disconnecting the servers.
4. Close Files, unmount the servers, reopen Files
5. Close Files, break the network link (without unmounting) and reopen Files

In most cases the interface should not block; in the worst case Files will not attempt to load a folder for more than 15 seconds.

To post a comment you must log in.
2046. By Jeremy Wootten

Fix reloading of non-existent folder after creation

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libcore/AbstractSlot.vala'
2--- libcore/AbstractSlot.vala 2015-12-26 19:42:49 +0000
3+++ libcore/AbstractSlot.vala 2016-01-27 19:54:54 +0000
4@@ -76,6 +76,7 @@
5 protected abstract void make_view ();
6 public abstract void cancel ();
7 public abstract void close ();
8+ public abstract FileInfo? lookup_file_info (GLib.File loc);
9
10 public virtual void zoom_out () {}
11 public virtual void zoom_in () {}
12
13=== modified file 'libcore/CMakeLists.txt'
14--- libcore/CMakeLists.txt 2015-12-02 14:34:10 +0000
15+++ libcore/CMakeLists.txt 2016-01-27 19:54:54 +0000
16@@ -20,6 +20,7 @@
17 BookmarkList.vala
18 DndHandler.vala
19 Enums.vala
20+ FileUtils.vala
21 gof-callwhenready.vala
22 gof-directory-async.vala
23 gof-preferences.vala
24
25=== added file 'libcore/FileUtils.vala'
26--- libcore/FileUtils.vala 1970-01-01 00:00:00 +0000
27+++ libcore/FileUtils.vala 2016-01-27 19:54:54 +0000
28@@ -0,0 +1,25 @@
29+/***
30+ Copyright (C) 2015 Elementary Developers
31+
32+ This program is free software: you can redistribute it and/or modify it
33+ under the terms of the GNU Lesser General Public License version 3, as published
34+ by the Free Software Foundation.
35+
36+ This program is distributed in the hope that it will be useful, but
37+ WITHOUT ANY WARRANTY; without even the implied warranties of
38+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
39+ PURPOSE. See the GNU General Public License for more details.
40+
41+ You should have received a copy of the GNU General Public License along
42+ with this program. If not, see <http://www.gnu.org/licenses/>.
43+
44+ Authors : Jeremy Wootten <jeremy@elementaryos.org>
45+***/
46+namespace PF.FileUtils {
47+ const string reserved_chars = (GLib.Uri.RESERVED_CHARS_GENERIC_DELIMITERS + GLib.Uri.RESERVED_CHARS_SUBCOMPONENT_DELIMITERS + " ");
48+
49+ public string? escape_uri (string uri, bool allow_utf8 = true) {
50+ string rc = reserved_chars.replace("#", "").replace ("*","");
51+ return Uri.escape_string ((Uri.unescape_string (uri) ?? uri), rc , allow_utf8);
52+ }
53+}
54
55=== modified file 'libcore/gof-directory-async.vala'
56--- libcore/gof-directory-async.vala 2015-12-28 18:36:55 +0000
57+++ libcore/gof-directory-async.vala 2016-01-27 19:54:54 +0000
58@@ -23,6 +23,10 @@
59 public class GOF.Directory.Async : Object {
60 public delegate void GOFFileLoadedFunc (GOF.File file);
61
62+ private uint load_timeout_id = 0;
63+ private const int ENUMERATE_TIMEOUT_SEC = 10;
64+ private const int QUERY_INFO_TIMEOUT_SEC = 15;
65+
66 public GLib.File location;
67 public GLib.File? selected_file = null;
68 public GOF.File file;
69@@ -40,7 +44,7 @@
70 LOADING,
71 LOADED
72 }
73- public State state = State.NOT_LOADED;
74+ private State state = State.NOT_LOADED;
75
76 private HashTable<GLib.File,GOF.File> file_hash;
77 public uint files_count;
78@@ -49,14 +53,15 @@
79
80 private Cancellable cancellable;
81 private FileMonitor? monitor = null;
82-
83 private List<unowned GOF.File>? sorted_dirs = null;
84
85 public signal void file_loaded (GOF.File file);
86 public signal void file_added (GOF.File file);
87 public signal void file_changed (GOF.File file);
88 public signal void file_deleted (GOF.File file);
89- public signal void icon_changed (GOF.File file);
90+ public signal void icon_changed (GOF.File file); /* Called directly by GOF.File - handled by AbstractDirectoryView
91+ Gets emitted for any kind of file operation */
92+
93 public signal void done_loading ();
94 public signal void thumbs_loaded ();
95 public signal void need_reload ();
96@@ -75,22 +80,24 @@
97 }
98
99 private string scheme;
100- public bool is_local;
101- public bool is_trash;
102- public bool is_network;
103- public bool is_recent;
104- public bool has_mounts;
105- public bool has_trash_dirs;
106- public bool can_load;
107- private bool is_cached = false;
108+ public bool is_local {get; private set;}
109+ public bool is_trash {get; private set;}
110+ public bool is_network {get; private set;}
111+ public bool is_recent {get; private set;}
112+ public bool has_mounts {get; private set;}
113+ public bool has_trash_dirs {get; private set;}
114+ public bool can_load {get; private set;}
115+ private bool is_ready = false;
116
117 public bool is_cancelled {
118 get { return cancellable.is_cancelled (); }
119 }
120
121 private Async (GLib.File _file) {
122- location = _file;
123+ /* Ensure uri is correctly escaped */
124+ location = GLib.File.new_for_uri (PF.FileUtils.escape_uri (_file.get_uri ()));
125 file = GOF.File.get (location);
126+
127 cancellable = new Cancellable ();
128 state = State.NOT_LOADED;
129 can_load = false;
130@@ -99,7 +106,16 @@
131 is_trash = (scheme == "trash");
132 is_recent = (scheme == "recent");
133 is_local = is_trash || is_recent || (scheme == "file");
134- is_network = !is_local && ("ftp ftps afp dav davs".contains (scheme));
135+ is_network = !is_local && ("ftp sftp afp dav davs".contains (scheme));
136+
137+ dir_cache_lock.@lock (); /* will always have been created via call to public static functions from_file () or from_gfile () */
138+ directory_cache.insert (location.dup (), this);
139+ dir_cache_lock.unlock ();
140+
141+ this.add_toggle_ref ((ToggleNotify) toggle_ref_notify);
142+ this.unref ();
143+
144+ file_hash = new HashTable<GLib.File, GOF.File> (GLib.File.hash, GLib.File.equal);
145 }
146
147 ~Async () {
148@@ -108,168 +124,190 @@
149 disconnect_volume_monitor_signals ();
150 }
151
152+ /** Views call the following function with null parameter - file_loaded and done_loading
153+ * signals are emitted and cause the view and view container to update.
154+ *
155+ * LocationBar calls this function, with a callback, on its own Async instances in order
156+ * to perform filename completion.- Emitting a done_loaded signal in that case would cause
157+ * the premature ending of text entry.
158+ **/
159 public void init (GOFFileLoadedFunc? file_loaded_func = null) {
160- if (state == State.LOADING) { /* Could happen reloading multiple windows */
161- return;
162+ if (state == State.LOADING) {
163+ return; /* Do not re-enter */
164 }
165- state = State.LOADING;
166+ var previous_state = state;
167+
168 cancellable.cancel ();
169- cancellable.reset ();
170- if (file_hash != null && file_hash.size () > 0) { /* false on first visit or when reloading */
171- list_cached_files (file_loaded_func); /* will call make ready when done */
172- } else if (!prepare_directory (file_loaded_func)) { /* Returns true if has already called make_ready () or will do so in a callback */
173- make_ready (false);
174+ cancellable = new Cancellable ();
175+
176+ /* If we already have a loaded file cache just list them */
177+ if (previous_state == State.LOADED) {
178+ list_cached_files (file_loaded_func);
179+ /* else fully initialise the directory */
180+ } else {
181+ state = State.LOADING;
182+ prepare_directory.begin (file_loaded_func);
183 }
184- /* Otherwise the directory will be prepared and the done_loaded signal emitted when ready */
185+ /* done_loaded signal is emitted when ready */
186 }
187
188 /* This is also called when reloading the directory so that another attempt to connect to
189 * the network is made
190 */
191- private bool prepare_directory (GOFFileLoadedFunc? file_loaded_func) {
192- if (!get_file_info (file_loaded_func)) {
193- return false;
194- } else if (is_local && !file.is_folder ()) {
195- if (!can_try_parent ()) {
196- return false;
197- } else {
198- return get_file_info (file_loaded_func);
199- }
200- }
201- return true;
202- }
203-
204- private bool can_try_parent () {
205- if (file.is_connected) {
206- GLib.File? parent = location.get_parent ();
207- if (parent != null) {
208- file = GOF.File.get (parent);
209- selected_file = location.dup ();
210- location = parent;
211- return true;
212- }
213- }
214- return false;
215- }
216-
217- private bool get_file_info (GOFFileLoadedFunc? file_loaded_func) {
218- if (!is_local && !check_network ()) {
219- return false;
220- }
221+ private async void prepare_directory (GOFFileLoadedFunc? file_loaded_func) {
222+ bool success = yield get_file_info ();
223+ if (success) {
224+ if (is_local && !file.is_folder ()) {
225+ var parent = file.is_connected ? location.get_parent () : null;
226+ if (parent != null) {
227+ file = GOF.File.get (parent);
228+ selected_file = location.dup ();
229+ location = parent;
230+ success = yield get_file_info ();
231+ } else {
232+ success = false;
233+ }
234+ }
235+ }
236+ make_ready (success, file_loaded_func); /* Only place that should call this function */
237+ }
238+
239+ private async bool get_file_info () {
240 /* Force info to be refreshed - the GOF.File may have been created already by another part of the program
241 * that did not ensure the correct info Aync purposes, and retrieved from cache (bug 1511307).
242 */
243- file.info = null;
244- if (!file.ensure_query_info()) { /* should set file.exists and file.connected appropriately */
245- if (is_local || !file.is_connected || !file.exists) {
246- return false;
247- }
248- }
249-
250- if (!is_local) {
251- mount_mountable.begin ((obj,res) => {
252- bool success = false;
253- try {
254- mount_mountable.end (res);
255- success = true;
256- } catch (Error e) {
257- if (e is IOError.ALREADY_MOUNTED) {
258- success = true;
259- } else {
260- warning ("mount_mountable failed: %s", e.message);
261- if (e is IOError.PERMISSION_DENIED ||
262- e is IOError.FAILED_HANDLED) {
263-
264- permission_denied = true;
265- }
266- }
267- }
268- make_ready (success, file_loaded_func);
269- });
270- } else {
271- make_ready (true, file_loaded_func);
272- }
273- return true;
274- }
275-
276- private void set_confirm_trash () {
277- bool to_confirm = true;
278- if (is_trash) {
279- to_confirm = false;
280- var mounts = VolumeMonitor.get ().get_mounts ();
281- if (mounts != null) {
282- foreach (GLib.Mount m in mounts) {
283- to_confirm |= (m.can_eject () && Marlin.FileOperations.has_trash_files (m));
284- }
285- }
286- }
287- Preferences.get_default ().confirm_trash = to_confirm;
288- }
289-
290- private void connect_volume_monitor_signals () {
291- var vm = VolumeMonitor.get();
292- vm.mount_changed.connect (on_mount_changed);
293- }
294- private void disconnect_volume_monitor_signals () {
295- var vm = VolumeMonitor.get();
296- vm.mount_changed.disconnect (on_mount_changed);
297- }
298-
299- private void on_mount_changed () {
300- need_reload ();
301- }
302-
303-
304- public bool check_network () {
305+ file.info = null;
306+ if (is_local) {
307+ return file.ensure_query_info ();
308+ }
309+
310+ /* Must be non-local */
311+ if (!is_local && !yield check_network ()) {
312+ file.is_connected = false;
313+ return false;
314+ } else {
315+ if (!yield try_query_info ()) { /* may already be mounted */
316+ if (yield mount_mountable ()) {
317+ /* Previously mounted Samba servers still appear mounted even if disconnected
318+ * e.g. by unplugging the network cable. So the following function can block for
319+ * a long time; we therefore use a timeout */
320+ debug ("successful mount %s", file.uri);
321+ return yield try_query_info ();
322+ } else {
323+ return false;
324+ }
325+ } else {
326+ return true;
327+ }
328+ }
329+ }
330+
331+ private async bool try_query_info () {
332+ cancellable = new Cancellable ();
333+ bool querying = true;
334+ assert (load_timeout_id == 0);
335+ load_timeout_id = Timeout.add_seconds (QUERY_INFO_TIMEOUT_SEC, () => {
336+ if (querying) {
337+ warning ("Cancelled after timeout in query info async %s", file.uri);
338+ cancellable.cancel ();
339+ load_timeout_id = 0;
340+ }
341+ return false;
342+ });
343+
344+ bool success = yield query_info_async (file, null, cancellable);
345+ querying = false;
346+ cancel_timeout (ref load_timeout_id);
347+ if (cancellable.is_cancelled ()) {
348+ warning ("Failed to get info - timed out and cancelled");
349+ file.is_connected = false;
350+ return false;
351+ }
352+ if (success) {
353+ debug ("got file info");
354+ file.ensure_query_info ();
355+ return true;
356+ } else {
357+ debug ("Failed to get file info for %s", file.uri);
358+ return false;
359+ }
360+ }
361+
362+ private async bool mount_mountable () {
363+ try {
364+ var mount_op = new Gtk.MountOperation (null);
365+ yield location.mount_enclosing_volume (0, mount_op, cancellable);
366+ var mount = location.find_enclosing_mount ();
367+ debug ("Found enclosing mount %s", mount != null ? mount.get_name () : "null");
368+ return mount != null;
369+ } catch (Error e) {
370+ if (e is IOError.ALREADY_MOUNTED) {
371+ debug ("Already mounted %s", file.uri);
372+ file.is_connected = true;
373+ } else {
374+ file.is_connected = false;
375+ warning ("Mount_mountable failed: %s", e.message);
376+ if (e is IOError.PERMISSION_DENIED || e is IOError.FAILED_HANDLED) {
377+ permission_denied = true;
378+ }
379+ }
380+ return false;
381+ }
382+ }
383+
384+ public async bool check_network () {
385 var net_mon = GLib.NetworkMonitor.get_default ();
386 var net_available = net_mon.get_network_available ();
387
388- if (!net_available && is_network) {
389- SocketConnectable? connectable = null;
390- try {
391- connectable = NetworkAddress.parse_uri (file.uri, 21);
392- }
393- catch (GLib.Error e) {}
394+ bool success = false;
395
396- if (connectable != null) {
397+ if (net_available) {
398+ SocketConnectable? connectable = null;
399+ if (!is_network) { /* e.g. smb:// */
400+ /* TODO: Find a way of verifying samba server still connected; gvfs does not detect
401+ * when network connection is broken - still appears mounted and connected */
402+ success = true;
403+ } else {
404 try {
405- net_mon.can_reach (connectable);
406+ connectable = NetworkAddress.parse_uri (file.uri, 21);
407+ success = true;
408+ /* Try to connect for real. This should time out after about 15 seconds if
409+ * the host is not reachable */
410+ var scl = new SocketClient ();
411+ var sc = yield scl.connect_async (connectable, cancellable);
412+ success = (sc != null && sc.is_connected ());
413+ debug ("Attempt to connect to %s %s", file.uri, success ? "succeeded" : "failed");
414 }
415 catch (GLib.Error e) {
416 warning ("Error connecting to connectable %s - %s", file.uri, e.message);
417- return false;
418 }
419 }
420-
421-
422+ } else {
423+ warning ("No network available");
424 }
425- return true;
426+ return success;
427 }
428+
429
430 private void make_ready (bool ready, GOFFileLoadedFunc? file_loaded_func = null) {
431 can_load = ready;
432 if (!can_load) {
433+ debug ("%s cannot load", file.uri);
434+ state = State.NOT_LOADED; /* ensure state is correct */
435 done_loading ();
436 return;
437- } else if (!is_cached) {
438- assert (directory_cache != null);
439- directory_cache.insert (location, this);
440-
441- this.add_toggle_ref ((ToggleNotify) toggle_ref_notify);
442- this.unref ();
443-
444- debug ("created dir %s ref_count %u", this.file.uri, this.ref_count);
445- file_hash = new HashTable<GLib.File,GOF.File> (GLib.File.hash, GLib.File.equal);
446+ } else if (!is_ready) {
447 uri_contain_keypath_icons = "/icons" in file.uri || "/.icons" in file.uri;
448-
449- try {
450- monitor = location.monitor_directory (0);
451- monitor.rate_limit = 100;
452- monitor.changed.connect (directory_changed);
453- } catch (IOError e) {
454- if (!(e is IOError.NOT_MOUNTED)) {
455- /* Will fail for remote filesystems - not an error */
456- debug ("directory monitor failed: %s %s", e.message, file.uri);
457+ if (file_loaded_func == null && is_local) {
458+ try {
459+ monitor = location.monitor_directory (0);
460+ monitor.rate_limit = 100;
461+ monitor.changed.connect (directory_changed);
462+ } catch (IOError e) {
463+ if (!(e is IOError.NOT_MOUNTED)) {
464+ /* Will fail for remote filesystems - not an error */
465+ debug ("directory monitor failed: %s %s", e.message, file.uri);
466+ }
467 }
468 }
469
470@@ -288,10 +326,37 @@
471 connect_volume_monitor_signals ();
472 }
473
474- is_cached = true;
475+ is_ready = true;
476 }
477 /* May be loading for the first time or reloading after clearing directory info */
478- load (file_loaded_func);
479+ list_directory_async.begin (file_loaded_func);
480+ }
481+
482+ private void set_confirm_trash () {
483+ bool to_confirm = true;
484+ if (is_trash) {
485+ to_confirm = false;
486+ var mounts = VolumeMonitor.get ().get_mounts ();
487+ if (mounts != null) {
488+ foreach (GLib.Mount m in mounts) {
489+ to_confirm |= (m.can_eject () && Marlin.FileOperations.has_trash_files (m));
490+ }
491+ }
492+ }
493+ Preferences.get_default ().confirm_trash = to_confirm;
494+ }
495+
496+ private void connect_volume_monitor_signals () {
497+ var vm = VolumeMonitor.get();
498+ vm.mount_changed.connect (on_mount_changed);
499+ }
500+ private void disconnect_volume_monitor_signals () {
501+ var vm = VolumeMonitor.get();
502+ vm.mount_changed.disconnect (on_mount_changed);
503+ }
504+
505+ private void on_mount_changed () {
506+ need_reload ();
507 }
508
509 private static void toggle_ref_notify (void* data, Object object, bool is_last) {
510@@ -308,8 +373,11 @@
511 }
512
513 public void cancel () {
514+ /* This should only be called when closing the view - it will cancel initialisation of the directory */
515 cancellable.cancel ();
516 cancel_thumbnailing ();
517+ cancel_timeout (ref load_timeout_id);
518+ cancel_timeout (ref idle_consume_changes_id);
519 }
520
521 public void cancel_thumbnailing () {
522@@ -320,101 +388,109 @@
523 }
524 }
525
526+ public void reload () {
527+ clear_directory_info ();
528+ init ();
529+ }
530+
531 /** Called in preparation for a reload **/
532- public void clear_directory_info () {
533- if (state != State.LOADED) { /* Could get called multiple times if multiple windows reload the same directory */
534- return;
535+ private void clear_directory_info () {
536+ if (state == State.LOADING) {
537+ return; /* Do not re-enter */
538 }
539 cancel ();
540-
541- if (idle_consume_changes_id != 0) {
542- Source.remove ((uint) idle_consume_changes_id);
543- idle_consume_changes_id = 0;
544- }
545-
546- if (file_hash != null)
547- file_hash.remove_all ();
548-
549+ file_hash.remove_all ();
550 monitor = null;
551 sorted_dirs = null;
552 files_count = 0;
553 state = State.NOT_LOADED;
554 }
555
556- /** Views call the following function with null parameter - file_loaded and done_loading
557- * signals are emitted and cause the view and view container to update.
558- *
559- * LocationBar calls this function, with a callback, on its own Async instances in order
560- * to perform filename completion.- Emitting a done_loaded signal in that case would cause
561- * the premature ending of text entry.
562- **/
563- private void load (GOFFileLoadedFunc? file_loaded_func = null) {
564+ private void list_cached_files (GOFFileLoadedFunc? file_loaded_func = null) {
565+ if (state != State.LOADED) {
566+ warning ("list cached files called in %s state - not expected to happen", state.to_string ());
567+ return;
568+ }
569+ state = State.LOADING;
570+ bool show_hidden = is_trash || Preferences.get_default ().pref_show_hidden_files;
571+ foreach (GOF.File gof in file_hash.get_values ()) {
572+ if (gof != null) {
573+ after_load_file (gof, show_hidden, file_loaded_func);
574+ }
575+ }
576+ state = State.LOADED;
577+ after_loading (file_loaded_func);
578+ }
579+
580+ private async void list_directory_async (GOFFileLoadedFunc? file_loaded_func) {
581 /* Should only be called after creation and if reloaded */
582- if (!is_cached || file_hash != null && file_hash.size () > 0) {
583+ if (!is_ready || file_hash.size () > 0) {
584 critical ("(Re)load directory called when not cleared");
585 return;
586 }
587+
588 if (!can_load) {
589 warning ("load called when cannot load - not expected to happen");
590- after_loading (file_loaded_func);
591- return;
592- }
593-
594- if (state != State.LOADING) {
595- warning ("load called in loaded or loading state - not expected to happen");
596- return;
597- }
598-
599+ return;
600+ }
601+
602+ if (state == State.LOADED) {
603+ warning ("load called when already loaded - not expected to happen");
604+ return;
605+ }
606+ if (load_timeout_id > 0) {
607+ warning ("load called when timeout already running - not expected to happen");
608+ return;
609+ }
610+
611+ cancellable = new Cancellable ();
612 longest_file_name = "";
613 permission_denied = false;
614-
615- list_directory.begin (file_loaded_func);
616- }
617-
618- private void list_cached_files (GOFFileLoadedFunc? file_loaded_func = null) {
619- if (state == State.NOT_LOADED) {
620- warning ("list cached files called in unloaded state - not expected to happen");
621- return;
622- }
623+ can_load = true;
624+ files_count = 0;
625+ state = State.LOADING;
626 bool show_hidden = is_trash || Preferences.get_default ().pref_show_hidden_files;
627- foreach (GOF.File gof in file_hash.get_values ()) {
628- if (gof != null) {
629- after_load_file (gof, show_hidden, file_loaded_func);
630- }
631- }
632- after_loading (file_loaded_func);
633- }
634
635- private async void list_directory (GOFFileLoadedFunc? file_loaded_func) {
636 try {
637- bool show_hidden = is_trash || Preferences.get_default ().pref_show_hidden_files;
638- var e = yield this.location.enumerate_children_async (gio_attrs, 0, 0, cancellable);
639- while (state == State.LOADING) {
640+ /* This may hang for a long time if the connection was closed but is still mounted so we
641+ * impose a time limit */
642+ load_timeout_id = Timeout.add_seconds (ENUMERATE_TIMEOUT_SEC, () => {
643+ cancellable.cancel ();
644+ load_timeout_id = 0;
645+ return false;
646+ });
647+
648+ var e = yield this.location.enumerate_children_async (gio_attrs, 0, Priority.HIGH, cancellable);
649+ cancel_timeout (ref load_timeout_id);
650+
651+ GOF.File? gof;
652+ GLib.File loc;
653+ while (!cancellable.is_cancelled ()) {
654 var files = yield e.next_files_async (200, 0, cancellable);
655 if (files == null) {
656- state = State.LOADED;
657+ break;
658 } else {
659 foreach (var file_info in files) {
660- GLib.File loc = location.get_child (file_info.get_name ());
661- GOF.File? gof = GOF.File.cache_lookup (loc);
662-
663- if (gof == null)
664- gof = new GOF.File (loc, location);
665-
666+ loc = location.get_child (file_info.get_name ());
667+ assert (loc != null);
668+ gof = GOF.File.cache_lookup (loc);
669+
670+ if (gof == null) {
671+ gof = new GOF.File (loc, location); /*does not add to GOF file cache */
672+ }
673 gof.info = file_info;
674 gof.update ();
675
676 file_hash.insert (gof.location, gof);
677-
678 after_load_file (gof, show_hidden, file_loaded_func);
679-
680 files_count++;
681 }
682 }
683 }
684+ state = State.LOADED;
685 } catch (Error err) {
686 warning ("Listing directory error: %s %s", err.message, file.uri);
687-
688+ can_load = false;
689 if (err is IOError.NOT_FOUND || err is IOError.NOT_DIRECTORY) {
690 file.exists = false;
691 } else if (err is IOError.PERMISSION_DENIED)
692@@ -422,6 +498,7 @@
693 else if (err is IOError.NOT_MOUNTED)
694 file.is_mounted = false;
695 }
696+
697 after_loading (file_loaded_func);
698 }
699
700@@ -438,10 +515,16 @@
701 }
702
703 private void after_loading (GOFFileLoadedFunc? file_loaded_func) {
704- if (file_loaded_func == null && !cancellable.is_cancelled ()) {
705+ /* If loading failed reset */
706+ debug ("after loading state is %s", state.to_string ());
707+ if (state == State.LOADING) {
708+ state = State.NOT_LOADED; /* else clear directory info will fail */
709+ clear_directory_info ();
710+ can_load = false;
711+ }
712+ if (file_loaded_func == null) {
713 done_loading ();
714 }
715- state = State.LOADED;
716 }
717
718 public void block_monitor () {
719@@ -456,8 +539,6 @@
720 monitor_blocked = false;
721 monitor.changed.connect (directory_changed);
722 }
723- if (!is_local)
724- need_reload ();
725 }
726
727 private void update_longest_file_name (GOF.File gof) {
728@@ -470,7 +551,7 @@
729 return;
730 }
731 if (state != State.LOADED) {
732- load ();
733+ list_directory_async.begin (null);
734 } else {
735 list_cached_files ();
736 }
737@@ -495,12 +576,6 @@
738 }
739 }
740
741- public async void mount_mountable () throws Error {
742- /**TODO** pass GtkWindow *parent to Gtk.MountOperation */
743- var mount_op = new Gtk.MountOperation (null);
744- yield location.mount_enclosing_volume (0, mount_op, cancellable);
745- }
746-
747 public GOF.File? file_hash_lookup_location (GLib.File? location) {
748 if (location != null && location is GLib.File) {
749 GOF.File? result = file_hash.lookup (location);
750@@ -512,13 +587,12 @@
751 }
752 }
753
754- public void file_hash_add_file (GOF.File gof) {
755- file_hash.insert (gof.location, gof);
756+ public void file_hash_add_file (GOF.File gof) { /* called directly by GOF.File */
757+ file_hash.insert (gof.location, gof);
758 }
759
760- public GOF.File file_cache_find_or_insert (GLib.File file,
761- bool update_hash = false)
762- {
763+ public GOF.File file_cache_find_or_insert (GLib.File file, bool update_hash = false) {
764+ assert (file != null);
765 GOF.File? result = file_hash.lookup (file);
766 /* Although file_hash.lookup returns an unowned value, Vala will add a reference
767 * as the return value is owned. This matches the behaviour of GOF.File.cache_lookup */
768@@ -539,18 +613,22 @@
769 /**TODO** move this to GOF.File */
770 private delegate void func_query_info (GOF.File gof);
771
772- private async void query_info_async (GOF.File gof, func_query_info? f = null) {
773+ private async bool query_info_async (GOF.File gof, func_query_info? f = null, Cancellable? cancellable = null) {
774+ gof.info = null;
775 try {
776 gof.info = yield gof.location.query_info_async (gio_attrs,
777 FileQueryInfoFlags.NONE,
778- Priority.DEFAULT);
779+ Priority.DEFAULT,
780+ cancellable);
781 if (f != null)
782 f (gof);
783 } catch (Error err) {
784- debug ("query info failed, %s %s", err.message, gof.uri);
785- if (err is IOError.NOT_FOUND)
786+ warning ("query info failed, %s %s", err.message, gof.uri);
787+ if (err is IOError.NOT_FOUND) {
788 gof.exists = false;
789+ }
790 }
791+ return gof.info != null;
792 }
793
794 private void changed_and_refresh (GOF.File gof) {
795@@ -674,19 +752,10 @@
796 if (!value) {
797 if (list_fchanges_count >= FCHANGES_MAX) {
798 need_reload ();
799- } else {
800+ } else if (list_fchanges_count > 0) {
801 list_fchanges.reverse ();
802-
803- /* do not autosize during multiple changes */
804- bool tln = track_longest_name;
805- track_longest_name = false;
806-
807- foreach (var fchange in list_fchanges)
808+ foreach (var fchange in list_fchanges) {
809 real_directory_changed (fchange.file, null, fchange.event);
810-
811- if (tln) {
812- track_longest_name = true;
813- list_cached_files ();
814 }
815 }
816 }
817@@ -698,6 +767,7 @@
818
819 public static void notify_files_changed (List<GLib.File> files) {
820 foreach (var loc in files) {
821+ assert (loc != null);
822 Async? parent_dir = cache_lookup_parent (loc);
823 GOF.File? gof = null;
824 if (parent_dir != null) {
825@@ -729,6 +799,7 @@
826 bool found;
827
828 foreach (var loc in files) {
829+ assert (loc != null);
830 Async? dir = cache_lookup_parent (loc);
831
832 if (dir != null) {
833@@ -770,11 +841,10 @@
834 }
835
836 public static Async from_gfile (GLib.File file) {
837+ assert (file != null);
838 /* Note: cache_lookup creates directory_cache if necessary */
839 Async? dir = cache_lookup (file);
840- if (dir != null && !dir.is_local)
841- dir = null;
842-
843+ /* Both local and non-local files can be cached */
844 return dir ?? new Async (file);
845 }
846
847@@ -783,6 +853,7 @@
848 }
849
850 public static void remove_file_from_cache (GOF.File gof) {
851+ assert (gof != null);
852 Async? dir = cache_lookup (gof.directory);
853 if (dir != null)
854 dir.file_hash.remove (gof.location);
855@@ -797,9 +868,9 @@
856 return null;
857 }
858
859- if (file == null)
860- return null;
861-
862+ if (file == null) {
863+ critical ("Null file received in Async cache_lookup");
864+ }
865 dir_cache_lock.@lock ();
866 cached_dir = directory_cache.lookup (file);
867
868@@ -813,6 +884,8 @@
869 cached_dir = null;
870 directory_cache.remove (file);
871 }
872+ } else {
873+ debug ("Dir %s not in cache", file.get_uri ());
874 }
875 dir_cache_lock.unlock ();
876
877@@ -820,6 +893,10 @@
878 }
879
880 public static Async? cache_lookup_parent (GLib.File file) {
881+ if (file == null) {
882+ warning ("Null file submitted to cache lookup parent");
883+ return null;
884+ }
885 GLib.File? parent = file.get_parent ();
886 return parent != null ? cache_lookup (parent) : cache_lookup (file);
887 }
888@@ -837,6 +914,7 @@
889 /* We have to remove the dir's subfolders from cache too */
890 if (removed) {
891 foreach (var gfile in file_hash.get_keys ()) {
892+ assert (gfile != null);
893 var dir = cache_lookup (gfile);
894 if (dir != null)
895 dir.remove_dir_from_cache ();
896@@ -863,25 +941,18 @@
897 }
898
899 public bool is_empty () {
900- uint file_hash_count = 0;
901-
902- if (file_hash != null)
903- file_hash_count = file_hash.size ();
904-
905- if (state == State.LOADED && file_hash_count == 0)
906- return true;
907-
908- return false;
909+ return (state == State.LOADED && file_hash.size () == 0); /* only return true when loaded to avoid temporary appearance of empty message while loading */
910 }
911
912- public unowned List<unowned GOF.File>? get_sorted_dirs () {
913- if (state != State.LOADED)
914+ public unowned List<GOF.File>? get_sorted_dirs () {
915+ if (state != State.LOADED) { /* Can happen if pathbar tries to load unloadable directory */
916 return null;
917+ }
918
919 if (sorted_dirs != null)
920 return sorted_dirs;
921
922- foreach (var gof in file_hash.get_values()) {
923+ foreach (var gof in file_hash.get_values()) { /* returns owned values */
924 if (!gof.is_hidden && (gof.is_folder () || gof.is_smb_server ())) {
925 sorted_dirs.prepend (gof);
926 }
927@@ -971,4 +1042,14 @@
928
929 timeout_thumbsq = Timeout.add (40, queue_thumbs_timeout_cb);
930 }
931+
932+ private bool cancel_timeout (ref uint id) {
933+ if (id > 0) {
934+ Source.remove (id);
935+ id = 0;
936+ return true;
937+ } else {
938+ return false;
939+ }
940+ }
941 }
942
943=== modified file 'libcore/gof-file.c'
944--- libcore/gof-file.c 2015-12-26 19:42:49 +0000
945+++ libcore/gof-file.c 2016-01-27 19:54:54 +0000
946@@ -141,12 +141,14 @@
947 GOFDirectoryAsync *dir = NULL;
948
949 /* get the DirectoryAsync associated to the file */
950- dir = gof_directory_async_cache_lookup (file->directory);
951- if (dir != NULL) {
952- if (!file->is_hidden || gof_preferences_get_default ()->pref_show_hidden_files)
953- g_signal_emit_by_name (dir, "icon_changed", file);
954+ if (file->directory != NULL) {
955+ dir = gof_directory_async_cache_lookup (file->directory);
956+ if (dir != NULL) {
957+ if (!file->is_hidden || gof_preferences_get_default ()->pref_show_hidden_files)
958+ g_signal_emit_by_name (dir, "icon_changed", file);
959
960- g_object_unref (dir);
961+ g_object_unref (dir);
962+ }
963 }
964 g_signal_emit_by_name (file, "icon_changed");
965 }
966@@ -2099,7 +2101,10 @@
967 static void
968 gof_file_update_existing (GOFFile *file, GFile *new_location)
969 {
970- GOFDirectoryAsync *dir = gof_directory_async_cache_lookup (file->directory);
971+ GOFDirectoryAsync *dir = NULL;
972+ if (file->directory != NULL) {
973+ dir = gof_directory_async_cache_lookup (file->directory);
974+ }
975
976 gof_file_remove_from_caches (file);
977 file->is_gone = FALSE;
978@@ -2597,10 +2602,12 @@
979 gboolean
980 gof_file_thumb_can_frame (GOFFile *file)
981 {
982- GOFDirectoryAsync *dir;
983+ GOFDirectoryAsync *dir = NULL;
984
985 /* get the DirectoryAsync associated to the file */
986- dir = gof_directory_async_cache_lookup (file->directory);
987+ if (file->directory != NULL) {
988+ dir = gof_directory_async_cache_lookup (file->directory);
989+ }
990 if (dir != NULL) {
991 gboolean can_frame = !dir->uri_contain_keypath_icons;
992 g_object_unref (dir);
993
994=== modified file 'libwidgets/FileUtils.vala'
995--- libwidgets/FileUtils.vala 2015-11-22 17:56:28 +0000
996+++ libwidgets/FileUtils.vala 2016-01-27 19:54:54 +0000
997@@ -16,8 +16,6 @@
998 Authors : Jeremy Wootten <jeremy@elementaryos.org>
999 ***/
1000 namespace PF.FileUtils {
1001- const string reserved_chars = (GLib.Uri.RESERVED_CHARS_GENERIC_DELIMITERS + GLib.Uri.RESERVED_CHARS_SUBCOMPONENT_DELIMITERS + " ");
1002-
1003 /**
1004 * Gets a properly escaped GLib.File for the given path
1005 **/
1006@@ -73,11 +71,6 @@
1007 return file.get_parent () != null;
1008 }
1009
1010- public string? escape_uri (string uri, bool allow_utf8 = true) {
1011- reserved_chars.replace("#", "");
1012- return Uri.escape_string ((Uri.unescape_string (uri) ?? uri), reserved_chars, allow_utf8);
1013- }
1014-
1015 /** Produce a valid unescaped path **/
1016 public string sanitize_path (string? p, string? cp = null) {
1017 string path = "";
1018
1019=== modified file 'libwidgets/View/BreadcrumbsEntry.vala'
1020--- libwidgets/View/BreadcrumbsEntry.vala 2015-12-26 19:42:49 +0000
1021+++ libwidgets/View/BreadcrumbsEntry.vala 2016-01-27 19:54:54 +0000
1022@@ -383,13 +383,13 @@
1023 menu.deactivate.connect (() => {reset_elements_states ();});
1024
1025 build_base_menu (menu, loc);
1026+ GOF.Directory.Async? files_menu_dir = null;
1027 if (root != null) {
1028- var files_menu_dir = GOF.Directory.Async.from_gfile (root);
1029+ files_menu_dir = GOF.Directory.Async.from_gfile (root);
1030 files_menu_dir_handler_id = files_menu_dir.done_loading.connect (() => {
1031 append_subdirectories (menu, files_menu_dir);
1032 files_menu_dir.disconnect (files_menu_dir_handler_id);
1033 });
1034- files_menu_dir.init ();
1035 } else {
1036 warning ("Root directory null for %s", path);
1037 }
1038@@ -400,6 +400,10 @@
1039 right_click_menu_position_func,
1040 0,
1041 event.time);
1042+
1043+ if (files_menu_dir != null) {
1044+ files_menu_dir.init ();
1045+ }
1046 }
1047
1048 private void build_base_menu (Gtk.Menu menu, GLib.File loc) {
1049@@ -461,17 +465,18 @@
1050
1051 private void append_subdirectories (Gtk.Menu menu, GOF.Directory.Async dir) {
1052 /* Append list of directories at the same level */
1053- unowned List<GOF.File>? sorted_dirs = dir.get_sorted_dirs ();
1054- if (sorted_dirs.length () > 0) {
1055- menu.append (new Gtk.SeparatorMenuItem ());
1056- foreach (var gof in sorted_dirs) {
1057- var menuitem = new Gtk.MenuItem.with_label(gof.get_display_name ());
1058- menuitem.set_data ("location", gof.uri);
1059- menu.append (menuitem);
1060- menuitem.activate.connect (() => {
1061- text = menu.get_active ().get_data ("location");
1062- activate ();
1063- });
1064+ if (dir.can_load) {
1065+ unowned List<GOF.File>? sorted_dirs = dir.get_sorted_dirs ();
1066+ if (sorted_dirs.length () > 0) {
1067+ menu.append (new Gtk.SeparatorMenuItem ());
1068+ foreach (var gof in sorted_dirs) {
1069+ var menuitem = new Gtk.MenuItem.with_label(gof.get_display_name ());
1070+ menuitem.set_data ("location", gof.uri);
1071+ menu.append (menuitem);
1072+ menuitem.activate.connect ((mi) => {
1073+ activate_path (mi.get_data ("location"));
1074+ });
1075+ }
1076 }
1077 }
1078 menu.show_all ();
1079
1080=== modified file 'src/View/AbstractDirectoryView.vala'
1081--- src/View/AbstractDirectoryView.vala 2016-01-19 23:29:09 +0000
1082+++ src/View/AbstractDirectoryView.vala 2016-01-27 19:54:54 +0000
1083@@ -576,21 +576,31 @@
1084 /* Signal could be from subdirectory as well as slot directory */
1085 protected void connect_directory_handlers (GOF.Directory.Async dir) {
1086 assert (dir != null);
1087- dir.file_loaded.connect (on_directory_file_loaded);
1088 dir.file_added.connect (on_directory_file_added);
1089 dir.file_changed.connect (on_directory_file_changed);
1090 dir.file_deleted.connect (on_directory_file_deleted);
1091 dir.icon_changed.connect (on_directory_file_icon_changed);
1092+ dir.thumbs_loaded.connect (on_directory_thumbs_loaded);
1093+ connect_directory_loading_handlers (dir);
1094+ }
1095+
1096+ protected void connect_directory_loading_handlers (GOF.Directory.Async dir) {
1097+ dir.file_loaded.connect (on_directory_file_loaded);
1098 dir.done_loading.connect (on_directory_done_loading);
1099- dir.thumbs_loaded.connect (on_directory_thumbs_loaded);
1100+ }
1101+
1102+ protected void disconnect_directory_loading_handlers (GOF.Directory.Async dir) {
1103+ dir.file_loaded.disconnect (on_directory_file_loaded);
1104+ dir.done_loading.disconnect (on_directory_done_loading);
1105 }
1106
1107 protected void disconnect_directory_handlers (GOF.Directory.Async dir) {
1108 /* If the directory is still loading the file_loaded signal handler
1109 /* will not have been disconnected */
1110
1111- if (dir.is_loading ())
1112- dir.file_loaded.disconnect (on_directory_file_loaded);
1113+ if (dir.is_loading ()) {
1114+ disconnect_directory_loading_handlers (dir);
1115+ }
1116
1117 dir.file_added.disconnect (on_directory_file_added);
1118 dir.file_changed.disconnect (on_directory_file_changed);
1119@@ -606,17 +616,20 @@
1120 style_context.remove_class (MESSAGE_CLASS);
1121
1122 cancel ();
1123+ clear ();
1124+ disconnect_directory_handlers (old_dir);
1125+ connect_directory_handlers (new_dir);
1126+ }
1127+
1128+ public void clear () {
1129+ /* after calling this (prior to reloading), the directory must be re-initialised so
1130+ * we reconnect the file_loaded and done_loading signals */
1131 freeze_tree ();
1132- disconnect_directory_handlers (old_dir);
1133 block_model ();
1134 model.clear ();
1135 unblock_model ();
1136- /* As we connect the signal file_loaded signal handler, we initialise. */
1137- connect_directory_handlers (new_dir);
1138- }
1139-
1140- public void reload () {
1141- change_directory (slot.directory, slot.directory);
1142+ connect_directory_loading_handlers (slot.directory);
1143+ /* tree will be thawed after done loading */
1144 }
1145
1146 protected void connect_drag_drop_signals (Gtk.Widget widget) {
1147@@ -804,8 +817,7 @@
1148
1149 /* If in recent "folder" we need to refresh the view. */
1150 if (in_recent) {
1151- slot.directory.clear_directory_info ();
1152- slot.directory.need_reload ();
1153+ slot.reload ();
1154 }
1155 }
1156
1157@@ -898,8 +910,6 @@
1158 }
1159
1160 var file_to_rename = GOF.File.@get (new_file);
1161- view.slot.reload (true); /* non-local only */
1162-
1163 view.rename_file (file_to_rename); /* will wait for file to appear in model */
1164 }
1165
1166@@ -912,7 +922,6 @@
1167
1168 view.slot.directory.unblock_monitor ();
1169 view.can_trash_or_delete = true;
1170- view.slot.reload (true); /* non-local only */
1171 }
1172
1173 private void trash_or_delete_selected_files (bool delete_immediately = false) {
1174@@ -926,7 +935,6 @@
1175 unowned GLib.List<GOF.File> selection = get_selected_files_for_transfer ();
1176 if (selection != null) {
1177 can_trash_or_delete = false;
1178-
1179 trash_or_delete_files (selection, true, delete_immediately);
1180 }
1181 }
1182@@ -1197,7 +1205,6 @@
1183 pasted_files_list.prepend (k as File);
1184 });
1185
1186- view.slot.reload (true); /* non-local only */
1187 view.select_glib_files (pasted_files_list, pasted_files_list.first ().data);
1188 return false;
1189 });
1190@@ -1278,8 +1285,7 @@
1191
1192 private void on_directory_done_loading (GOF.Directory.Async dir) {
1193 /* Should only be called on directory creation or reload */
1194- dir.file_loaded.disconnect (on_directory_file_loaded);
1195- dir.done_loading.disconnect (on_directory_done_loading);
1196+ disconnect_directory_loading_handlers (dir);
1197 in_trash = slot.directory.is_trash;
1198 in_recent = slot.directory.is_recent;
1199 in_network_root = slot.directory.file.is_root_network_folder ();
1200@@ -1555,8 +1561,6 @@
1201 if (drop_occurred) {
1202 drop_occurred = false;
1203 if (current_actions != Gdk.DragAction.DEFAULT) {
1204- slot.reload (true); /* non-local only */
1205-
1206 switch (info) {
1207 case Marlin.TargetType.XDND_DIRECT_SAVE0:
1208 success = dnd_handler.handle_xdnddirectsave (context,
1209@@ -2715,12 +2719,10 @@
1210 (path != null && hover_path != null && path.compare (hover_path) != 0)) {
1211
1212 /* cannot get file info while network disconnected */
1213- if (slot.directory.is_local || slot.directory.check_network ()) {
1214- /* cannot get file info while network disconnected */
1215+ if (slot.directory.is_local || NetworkMonitor.get_default ().get_network_available ()) {
1216+ /* cannot get file info while network disconnected. */
1217 item_hovered (file);
1218 hover_path = path;
1219- } else {
1220- slot.reload (true); /* non-local only */
1221 }
1222 }
1223
1224@@ -2809,9 +2811,6 @@
1225 }
1226
1227 on_name_editing_canceled ();
1228-
1229- if (new_name != original_name)
1230- slot.reload (true); /* non-local only */
1231 }
1232
1233
1234@@ -2839,7 +2838,6 @@
1235 view.original_name);
1236
1237 view.select_gof_file (file); /* Select and scroll to show renamed file */
1238- view.slot.reload (true);
1239 }
1240
1241 if (pw != null) {
1242@@ -2854,20 +2852,22 @@
1243 /* If folder is empty, draw the empty message in the middle of the view
1244 * otherwise pass on event */
1245 var style_context = get_style_context ();
1246- if (slot.directory.is_empty () || slot.directory.permission_denied) {
1247+ if (slot.directory.is_empty ()) {
1248 Pango.Layout layout = create_pango_layout (null);
1249
1250 if (!style_context.has_class (MESSAGE_CLASS))
1251 style_context.add_class (MESSAGE_CLASS);
1252
1253- if (slot.directory.permission_denied)
1254+
1255+ if (slot.directory.permission_denied) {
1256 layout.set_markup (slot.denied_message, -1);
1257- else if (slot.directory.is_trash) /* must be empty */
1258+ } else if (slot.directory.is_trash) {
1259 layout.set_markup (slot.empty_trash_message, -1);
1260- else if (slot.directory.location.get_uri_scheme () == "recent")
1261+ } else if (slot.directory.is_recent) {
1262 layout.set_markup (slot.empty_recents_message, -1);
1263- else
1264+ } else {
1265 layout.set_markup (slot.empty_message, -1);
1266+ }
1267
1268 Pango.Rectangle? extents = null;
1269 layout.get_extents (null, out extents);
1270@@ -2880,8 +2880,9 @@
1271 get_style_context ().render_layout (cr, x, y, layout);
1272
1273 return true;
1274- } else if (style_context.has_class (MESSAGE_CLASS))
1275+ } else if (style_context.has_class (MESSAGE_CLASS)) {
1276 style_context.remove_class (MESSAGE_CLASS);
1277+ }
1278
1279 return false;
1280 }
1281
1282=== modified file 'src/View/Miller.vala'
1283--- src/View/Miller.vala 2015-12-29 12:48:51 +0000
1284+++ src/View/Miller.vala 2016-01-27 19:54:54 +0000
1285@@ -439,5 +439,9 @@
1286 public override void set_frozen_state (bool freeze) {
1287 current_slot.set_frozen_state (freeze);
1288 }
1289+
1290+ public override FileInfo? lookup_file_info (GLib.File loc) {
1291+ return current_slot.lookup_file_info (loc);
1292+ }
1293 }
1294 }
1295
1296=== modified file 'src/View/Slot.vala'
1297--- src/View/Slot.vala 2015-12-29 12:48:51 +0000
1298+++ src/View/Slot.vala 2016-01-27 19:54:54 +0000
1299@@ -25,7 +25,7 @@
1300 private FM.AbstractDirectoryView? dir_view = null;
1301
1302 protected bool updates_frozen = false;
1303-
1304+ protected bool original_reload_request = false;
1305 public bool has_autosized = false;
1306 public bool is_active {get; protected set;}
1307
1308@@ -133,10 +133,24 @@
1309 autosize_slot ();
1310
1311 set_view_updates_frozen (false);
1312+ updates_frozen = false;
1313 }
1314
1315 private void on_directory_need_reload (GOF.Directory.Async dir) {
1316- user_path_change_request (directory.location, false);
1317+ if (!updates_frozen) {
1318+ updates_frozen = true;
1319+ path_changed (false);
1320+ /* ViewContainer listens to this signal takes care of updating appearance
1321+ * If allow_mode_change is false View Container will not automagically
1322+ * switch to icon view for icon folders (needed for Miller View) */
1323+ dir_view.clear (); /* clear model but do not change directory */
1324+ /* Only need to initialise directory once - the slot that originally received the
1325+ * reload request does this */
1326+ if (original_reload_request) {
1327+ directory.reload ();
1328+ original_reload_request = false;
1329+ }
1330+ }
1331 }
1332
1333 private void set_up_directory (GLib.File loc) {
1334@@ -178,18 +192,19 @@
1335
1336 Pango.Layout layout = dir_view.create_pango_layout (null);
1337
1338- if (directory.is_empty ()) {
1339- if (directory.is_trash)
1340+ if (directory.is_empty ()) { /* No files in the file cache */
1341+ if (directory.permission_denied) {
1342+ layout.set_markup (denied_message, -1);
1343+ } else if (directory.is_trash) {
1344 layout.set_markup (empty_trash_message, -1);
1345- else if (directory.is_recent)
1346+ } else if (directory.is_recent) {
1347 layout.set_markup (empty_recents_message, -1);
1348- else
1349+ } else {
1350 layout.set_markup (empty_message, -1);
1351- } else if (directory.permission_denied)
1352- layout.set_markup (denied_message, -1);
1353- else
1354+ }
1355+ } else {
1356 layout.set_markup (GLib.Markup.escape_text (directory.longest_file_name), -1);
1357-
1358+ }
1359 Pango.Rectangle extents;
1360 layout.get_extents (null, out extents);
1361
1362@@ -227,8 +242,8 @@
1363 }
1364
1365 public override void reload (bool non_local_only = false) {
1366- if (!(non_local_only && directory.is_local)) {
1367- directory.clear_directory_info ();
1368+ if (!non_local_only || !directory.is_local) {
1369+ original_reload_request = true;
1370 directory.need_reload (); /* Signal will propagate to any other slot showing this directory */
1371 }
1372 }
1373@@ -357,5 +372,14 @@
1374 set_view_updates_frozen (freeze);
1375 frozen_changed (freeze);
1376 }
1377+
1378+ public override FileInfo? lookup_file_info (GLib.File loc) {
1379+ GOF.File? gof = directory.file_hash_lookup_location (loc);
1380+ if (gof != null) {
1381+ return gof.info;
1382+ } else {
1383+ return null;
1384+ }
1385+ }
1386 }
1387 }
1388
1389=== modified file 'src/View/ViewContainer.vala'
1390--- src/View/ViewContainer.vala 2015-12-29 12:48:51 +0000
1391+++ src/View/ViewContainer.vala 2016-01-27 19:54:54 +0000
1392@@ -167,8 +167,10 @@
1393 user_path_change_request (File.new_for_commandline_arg (loc));
1394 }
1395
1396- public void add_view (Marlin.ViewMode mode, GLib.File? loc = null) {
1397+ public void add_view (Marlin.ViewMode mode, GLib.File loc) {
1398 assert (view == null);
1399+ assert (loc != null);
1400+
1401 overlay_statusbar.cancel ();
1402 view_mode = mode;
1403 overlay_statusbar.showbar = view_mode != Marlin.ViewMode.LIST;
1404@@ -181,6 +183,7 @@
1405 connect_slot_signals (this.view);
1406 directory_is_loading (loc);
1407 view.directory.init ();
1408+ show_all ();
1409 /* NOTE: slot is created inactive to avoid bug during restoring multiple tabs
1410 * The slot becomes active when the tab becomes current */
1411 }
1412@@ -204,7 +207,6 @@
1413 disconnect_slot_signals (view);
1414 content = null; /* Make sure old slot and directory view are destroyed */
1415 view = null; /* Pre-requisite for add view */
1416- loading (false);
1417 }
1418 private void after_mode_change () {
1419 /* Slot is created inactive so we activate now since we must be the current tab
1420@@ -251,7 +253,6 @@
1421
1422 public void on_slot_path_changed (GOF.AbstractSlot slot, bool change_mode_to_icons) {
1423 assert (slot != null);
1424- overlay_statusbar.cancel ();
1425 /* automagicly enable icon view for icons keypath */
1426 if (change_mode_to_icons && view_mode != Marlin.ViewMode.ICON) {
1427 change_view_mode (Marlin.ViewMode.ICON);
1428@@ -260,11 +261,11 @@
1429 }
1430 }
1431
1432- public void directory_is_loading (GLib.File loc) {
1433- assert (slot != null);
1434+ private void directory_is_loading (GLib.File loc) {
1435+ loading (true);
1436+ overlay_statusbar.cancel ();
1437 overlay_statusbar.halign = Gtk.Align.END;
1438 refresh_slot_info (loc);
1439- loading (true);
1440 }
1441
1442 public void plugin_directory_loaded () {
1443@@ -283,8 +284,6 @@
1444
1445 public void refresh_slot_info (GLib.File loc) {
1446 update_tab_name (loc);
1447- browser.record_uri (loc.get_parse_name ()); /* will ignore null changes */
1448-
1449 window.loading_uri (loc.get_uri ());
1450 window.update_top_menu ();
1451 window.update_labels (loc.get_parse_name (), tab_name);
1452@@ -316,15 +315,7 @@
1453 else if (slot_path == "/")
1454 tab_name = _("File System");
1455 else {
1456- try {
1457- var info = loc.query_info (FileAttribute.STANDARD_DISPLAY_NAME, FileQueryInfoFlags.NONE);
1458- tab_name = info.get_attribute_string (FileAttribute.STANDARD_DISPLAY_NAME);
1459- }
1460- catch (GLib.Error e) {
1461- warning ("Could not get location display name. %s", e.message);
1462- tab_name = loc.get_basename ();
1463- can_show_folder = false;
1464- }
1465+ tab_name = Uri.unescape_string (Path.get_basename (loc.get_uri ()));
1466 }
1467
1468 if (tab_name == "-----")
1469@@ -339,24 +330,29 @@
1470 loading (false);
1471 can_show_folder = true;
1472
1473- if (!slot.directory.file.exists) {
1474- if (slot.can_create)
1475- content = new DirectoryNotFound (slot.directory, this);
1476- else
1477- content = new Marlin.View.Welcome (_("This Folder Does Not Exist"),
1478- _("You cannot create a folder here."));
1479- can_show_folder = false;
1480- } else if (slot.directory.permission_denied) {
1481- content = new Marlin.View.Welcome (_("This Folder Does Not Belong to You"),
1482- _("You don't have permission to view this folder."));
1483- can_show_folder = false;
1484- } else if (!slot.directory.can_load) {
1485- content = new Marlin.View.Welcome (_("Unable to Mount Folder"),
1486- _("The server for this folder could not be located."));
1487- can_show_folder = false;
1488+ /* First deal with all cases where directory could not be loaded */
1489+ if (!slot.directory.can_load) {
1490+ can_show_folder = false;
1491+ if (!slot.directory.file.exists) {
1492+ if (slot.can_create)
1493+ content = new DirectoryNotFound (slot.directory, this);
1494+ else
1495+ content = new Marlin.View.Welcome (_("This Folder Does Not Exist"),
1496+ _("You cannot create a folder here."));
1497+ } else if (slot.directory.permission_denied) {
1498+ content = new Marlin.View.Welcome (_("This Folder Does Not Belong to You"),
1499+ _("You don't have permission to view this folder."));
1500+ } else if (!slot.directory.file.is_connected) {
1501+ content = new Marlin.View.Welcome (_("Unable to Mount Folder"),
1502+ _("Could not connect to the server for this folder."));
1503+ } else {
1504+ content = new Marlin.View.Welcome (_("Unable show Folder"),
1505+ _("The server for this folder could not be located."));
1506+ }
1507+ /* Now deal with cases where file (s) within the loaded folder has to be selected */
1508 } else if (selected_locations != null) {
1509- view.select_glib_files (selected_locations, selected_locations.first ().data);
1510- selected_locations = null;
1511+ view.select_glib_files (selected_locations, selected_locations.first ().data);
1512+ selected_locations = null;
1513 } else if (slot.directory.selected_file != null) {
1514 if (slot.directory.selected_file.query_exists ()) {
1515 focus_location_if_in_current_directory (slot.directory.selected_file);
1516@@ -371,6 +367,8 @@
1517 if (can_show_folder) {
1518 assert (view != null);
1519 content = view.get_content_box ();
1520+ /* Only record valid folders (will also log Zeitgeist event) */
1521+ browser.record_uri (slot.location.get_parse_name ()); /* will ignore null changes */
1522 plugin_directory_loaded ();
1523 }
1524
1525@@ -395,7 +393,7 @@
1526
1527 public void set_active_state (bool is_active) {
1528 var aslot = get_current_slot ();
1529- if (aslot != null)
1530+ if (aslot != null && aslot.directory.can_load)
1531 aslot.set_active_state (is_active);
1532 }
1533
1534@@ -405,55 +403,45 @@
1535 aslot.set_frozen_state (is_frozen);
1536 }
1537
1538- public void focus_location (GLib.File? file,
1539- bool select_in_current_only = false,
1540+ public void focus_location (GLib.File loc,
1541+ bool no_path_change = false,
1542 bool unselect_others = false) {
1543+
1544 /* This function navigates to another folder if necessary if
1545 * select_in_current_only is not set to true.
1546- */
1547- if (unselect_others || file == null) {
1548- get_current_slot ().set_all_selected (false);
1549- selected_locations = null;
1550- }
1551+ */
1552+ assert (loc != null);
1553
1554- if (file == null || location.equal (file)) {
1555+ if (location.equal (loc)) {
1556 return;
1557 }
1558- GLib.File? loc = null;
1559- var filetype = file.query_file_type (GLib.FileQueryInfoFlags.NONE);
1560- if (filetype == FileType.UNKNOWN) {
1561- /* May be request for non-existing file - in which case
1562- * an opportunity will be given to create it when we try to
1563- * load it
1564- */
1565- loc = file;
1566- } else {
1567- File? parent = file.get_parent ();
1568- if (parent != null && location.equal (parent)) {
1569- if (select_in_current_only || filetype != FileType.DIRECTORY) {
1570- var list = new List<File> ();
1571- list.prepend (file);
1572- get_current_slot ().select_glib_files (list, file);
1573- } else
1574- loc = file;
1575- } else if (!select_in_current_only) {
1576- if (filetype == FileType.DIRECTORY)
1577- loc = file;
1578- else if (parent != null) {
1579- loc = parent;
1580- selected_locations.prepend (file);
1581+
1582+ FileInfo? info = get_current_slot ().lookup_file_info (loc);
1583+ FileType filetype = FileType.UNKNOWN;
1584+ if (info != null) { /* location is in the current folder */
1585+ filetype = info.get_file_type ();
1586+ if (filetype != FileType.DIRECTORY || no_path_change) {
1587+ if (unselect_others) {
1588+ get_current_slot ().set_all_selected (false);
1589+ selected_locations = null;
1590 }
1591+ var list = new List<File> ();
1592+ list.prepend (loc);
1593+ get_current_slot ().select_glib_files (list, loc);
1594+ return;
1595 }
1596+ } else if (no_path_change) { /* not in current, do not navigate to it*/
1597+ return;
1598 }
1599-
1600- if (loc != null)
1601+ /* Attempt to navigate to the location */
1602+ if (loc != null) {
1603 user_path_change_request (loc);
1604+ }
1605 }
1606
1607- public void focus_location_if_in_current_directory (GLib.File? file,
1608+ public void focus_location_if_in_current_directory (GLib.File? loc,
1609 bool unselect_others = false) {
1610-
1611- focus_location (file, true, unselect_others);
1612+ focus_location (loc, true, unselect_others);
1613 }
1614
1615 public string get_root_uri () {
1616
1617=== modified file 'src/View/Window.vala'
1618--- src/View/Window.vala 2015-12-26 19:42:49 +0000
1619+++ src/View/Window.vala 2016-01-27 19:54:54 +0000
1620@@ -524,7 +524,7 @@
1621 break;
1622 }
1623 current_tab.change_view_mode (mode);
1624- update_view_mode (mode);
1625+ /* ViewContainer takes care of changing appearance */
1626 }
1627
1628 private void action_go_to (GLib.SimpleAction action, GLib.Variant? param) {
1629@@ -747,7 +747,7 @@
1630 }
1631 }
1632
1633- public void update_view_mode (Marlin.ViewMode mode) {
1634+ private void update_view_mode (Marlin.ViewMode mode) { /* Called via update topmenu */
1635 GLib.SimpleAction action = get_action ("view_mode");
1636 action.set_state (mode_strings [(int)mode]);
1637 view_switcher.mode = mode;
1638@@ -844,8 +844,10 @@
1639
1640 GLib.File root_location = GLib.File.new_for_commandline_arg (unescaped_root_uri);
1641
1642- if (!valid_location (root_location))
1643- continue;
1644+ /* We do not check valid location here because it may cause the interface to hang
1645+ * before the window appears (e.g. if trying to connect to a server that has become unavailable)
1646+ * Leave it to GOF.Directory.Async to deal with invalid locations asynchronously.
1647+ */
1648
1649 add_tab (root_location, mode);
1650
1651@@ -890,32 +892,6 @@
1652 return tabs_added;
1653 }
1654
1655- private bool valid_location (GLib.File location) {
1656- GLib.FileInfo? info = null;
1657-
1658- string scheme = location.get_uri_scheme ();
1659- if (scheme == "smb" ||
1660- scheme == "ftp" ||
1661- scheme == "network")
1662- /* Do not restore remote and network locations */
1663- return true;
1664-
1665- try {
1666- info = location.query_info ("standard::*", GLib.FileQueryInfoFlags.NONE);
1667- }
1668- catch (GLib.Error e) {
1669- warning ("Invalid location on restoring tabs - %s", location.get_uri ());
1670- return false;
1671- }
1672-
1673- if (info.get_file_type () == GLib.FileType.DIRECTORY)
1674- return true;
1675- else {
1676- warning ("Attempt to restore a location that is not a directory");
1677- return false;
1678- }
1679- }
1680-
1681 private void expand_miller_view (string tip_uri, GLib.File root_location) {
1682 /* It might be more elegant for Miller.vala to handle this */
1683 var tab = tabs.current;
1684@@ -940,9 +916,6 @@
1685 uri += (GLib.Path.DIR_SEPARATOR_S + dir);
1686 gfile = GLib.File.new_for_uri (uri);
1687
1688- if (!valid_location (gfile))
1689- break;
1690-
1691 mwcols.add_location (gfile, mwcols.current_slot);
1692 }
1693 } else {
1694@@ -970,6 +943,7 @@
1695 }
1696
1697 public void mount_removed (Mount mount) {
1698+ debug ("Mount %s removed", mount.get_name ());
1699 GLib.File root = mount.get_root ();
1700
1701 foreach (var page in tabs.get_children ()) {

Subscribers

People subscribed via source and target branches

to all changes: