~elementary-apps/pantheon-files/trunk

« back to all changes in this revision

Viewing changes to libcore/gof-directory-async.vala

Various changes aimed at improving the connection and loading of network sites and fixes some possible bugs.

A new warning screen is shown if the connection or loading operation times out. Pressing reload under these conditions unmounts any connection and starts again.

GOF.File mount is set to the enclosing_mount when the location is not the root location.

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
 
28
28
    private uint load_timeout_id = 0;
29
29
    private uint mount_timeout_id = 0;
30
 
    private const int ENUMERATE_TIMEOUT_SEC = 15;
31
 
    private const int QUERY_INFO_TIMEOUT_SEC = 5;
32
 
    private const int MOUNT_TIMEOUT_SEC = 10;
 
30
    private const int CONNECT_SOCKET_TIMEOUT_SEC = 30;
 
31
    private const int ENUMERATE_TIMEOUT_SEC = 30;
 
32
    private const int QUERY_INFO_TIMEOUT_SEC = 20;
 
33
    private const int MOUNT_TIMEOUT_SEC = 60;
33
34
 
34
35
    public GLib.File location;
35
36
    public GLib.File? selected_file = null;
46
47
    public enum State {
47
48
        NOT_LOADED,
48
49
        LOADING,
49
 
        LOADED
 
50
        LOADED,
 
51
        TIMED_OUT
50
52
    }
51
53
    public State state {get; private set;}
52
54
 
102
104
        get { return cancellable.is_cancelled (); }
103
105
    }
104
106
 
 
107
    public string last_error_message {get; private set; default = "";}
 
108
 
105
109
    private Async (GLib.File _file) {
106
110
        /* Ensure uri is correctly escaped */
107
111
        location = GLib.File.new_for_uri (PF.FileUtils.escape_uri (_file.get_uri ()));
114
118
        scheme = location.get_uri_scheme ();
115
119
        is_trash = (scheme == "trash");
116
120
        is_recent = (scheme == "recent");
117
 
        is_no_info = ("cdda mtp".contains (scheme));
 
121
        is_no_info = ("cdda mtp ssh sftp afp dav davs".contains (scheme)); //Try lifting requirement for info on remote connections 
118
122
        is_local = is_trash || is_recent || (scheme == "file");
119
123
        is_network = !is_local && ("ftp sftp afp dav davs".contains (scheme));
120
124
        can_open_files = !("mtp".contains (scheme));
141
145
     **/
142
146
    public void init (GOFFileLoadedFunc? file_loaded_func = null) {
143
147
        if (state == State.LOADING) {
 
148
            debug ("Directory Init re-entered - already loading");
144
149
            return; /* Do not re-enter */
145
150
        }
146
151
        var previous_state = state;
163
168
     * the network is made
164
169
     */
165
170
    private async void prepare_directory (GOFFileLoadedFunc? file_loaded_func) {
 
171
        debug ("Preparing directory for loading");
 
172
        /* Force info to be refreshed - the GOF.File may have been created already by another part of the program
 
173
         * that did not ensure the correct info Aync purposes, and retrieved from cache (bug 1511307).
 
174
         */
 
175
        file.info = null;
166
176
        bool success = yield get_file_info ();
 
177
 
167
178
        if (success) {
168
179
            if (!is_no_info && !file.is_folder () && !file.is_root_network_folder ()) {
169
180
                debug ("Trying to load a non-folder - finding parent");
182
193
            warning ("Failed to get file info for file %s", file.uri);
183
194
        }
184
195
 
 
196
        if (success) {
 
197
            file.update ();
 
198
        }
 
199
        debug ("success %s; enclosing mount %s", success.to_string (), file.mount != null ? file.mount.get_name () : "null");
185
200
        yield make_ready (is_no_info || success, file_loaded_func); /* Only place that should call this function */
186
201
    }
187
202
 
188
203
    /*** Returns false if should be able to get info but were unable to ***/
189
204
    private async bool get_file_info () {
190
 
        /* Force info to be refreshed - the GOF.File may have been created already by another part of the program
191
 
         * that did not ensure the correct info Aync purposes, and retrieved from cache (bug 1511307).
192
 
         */
193
 
        file.info = null;
 
205
        debug ("get_file_info");
194
206
 
195
207
        if (is_network && !yield check_network ()) {
 
208
            warning ("No network found");
196
209
            file.is_connected = false;
197
210
            return false;
198
211
        }
199
212
 
200
 
        if (is_no_info) {
201
 
            /* Not a failure when not expected to get file info */
202
 
            return true;
203
 
        }
204
 
 
205
213
        if (is_local) {
 
214
            debug ("Loading info for local directory");
206
215
            return file.ensure_query_info ();
207
216
        }
208
217
 
209
218
        if (!yield try_query_info ()) { /* may already be mounted */
 
219
            debug ("try query info failed - trying to mount");
210
220
            if (yield mount_mountable ()) {
211
221
            /* Previously mounted Samba servers still appear mounted even if disconnected
212
222
             * e.g. by unplugging the network cable.  So the following function can block for
213
223
             * a long time; we therefore use a timeout */
214
224
                debug ("successful mount %s", file.uri);
215
 
                return yield try_query_info ();
 
225
                file.is_mounted = true;
 
226
                return (yield try_query_info ()) || is_no_info;
216
227
            } else {
 
228
                warning ("failed mount %s", file.uri);
217
229
                return false;
218
230
            }
219
231
        } else {
222
234
    }
223
235
 
224
236
    private async bool try_query_info () {
 
237
        debug ("try_query_info");
225
238
        cancellable = new Cancellable ();
226
239
        bool querying = true;
227
240
        assert (load_timeout_id == 0);
229
242
            if (querying) {
230
243
                warning ("Cancelled after timeout in query info async %s", file.uri);
231
244
                cancellable.cancel ();
 
245
                last_error_message = "Timed out while querying file info";
232
246
            }
233
247
            load_timeout_id = 0;
234
248
            return false;
244
258
        }
245
259
 
246
260
        if (success) {
247
 
            debug ("got file info");
 
261
            debug ("got file info - updating");
248
262
            file.update ();
 
263
            debug ("success %s; enclosing mount %s", success.to_string (), file.mount != null ? file.mount.get_name () : "null");
249
264
            return true;
250
265
        } else {
251
266
            warning ("Failed to get file info for %s", file.uri);
254
269
    }
255
270
 
256
271
    private async bool mount_mountable () {
 
272
        debug ("mount_mountable");
 
273
        bool res = false;
 
274
        var mount_op = new Gtk.MountOperation (null);
 
275
        cancellable = new Cancellable ();
 
276
 
257
277
        try {
258
 
            var mount_op = new Gtk.MountOperation (null);
259
 
            cancellable = new Cancellable ();
260
278
            bool mounting = true;
261
279
            bool asking_password = false;
262
280
            assert (mount_timeout_id == 0);
263
281
 
264
282
            mount_timeout_id = Timeout.add_seconds (MOUNT_TIMEOUT_SEC, () => {
265
283
                if (mounting && !asking_password) {
 
284
                    mount_timeout_id = 0;
266
285
                    warning ("Cancelled after timeout in mount mountable %s", file.uri);
 
286
                    last_error_message = ("Timed out when trying to mount %s").printf (file.uri);
 
287
                    state = State.TIMED_OUT;
267
288
                    cancellable.cancel ();
268
 
                    mount_timeout_id = 0;
 
289
 
269
290
                    return false;
270
291
                } else {
271
292
                    return true;
273
294
            });
274
295
 
275
296
            mount_op.ask_password.connect (() => {
 
297
                debug ("Asking for password");
276
298
                asking_password = true;
277
299
            });
278
300
 
279
301
            mount_op.reply.connect (() => {
 
302
                debug ("Password dialog finished");
280
303
                asking_password = false;
281
304
            });
282
305
 
283
 
            yield location.mount_enclosing_volume (0, mount_op, cancellable);
284
 
            var mount = location.find_enclosing_mount ();
285
 
 
286
 
            debug ("Found enclosing mount %s", mount != null ? mount.get_name () : "null");
287
 
            return mount != null;
 
306
            debug ("mounting ....");
 
307
            res =yield location.mount_enclosing_volume (GLib.MountMountFlags.NONE, mount_op, cancellable);
288
308
        } catch (Error e) {
 
309
            last_error_message = e.message;
289
310
            if (e is IOError.ALREADY_MOUNTED) {
290
311
                debug ("Already mounted %s", file.uri);
291
312
                file.is_connected = true;
 
313
                res = true;
292
314
            } else if (e is IOError.NOT_FOUND) {
293
315
                debug ("Enclosing mount not found %s (may be remote share)", file.uri);
294
 
                file.is_mounted = false;
295
 
                return true;
 
316
                /* Do not fail loading at this point - may still load */
 
317
                try {
 
318
                    yield location.mount_mountable (GLib.MountMountFlags.NONE, mount_op, cancellable);
 
319
                    res = true;
 
320
                } catch (GLib.Error e2) {
 
321
                    last_error_message = e2.message;
 
322
                    warning ("Unable to mount mountable");
 
323
                    res = false;
 
324
                }
 
325
 
296
326
            } else {
297
327
                file.is_connected = false;
298
328
                file.is_mounted = false;
 
329
                debug ("Setting mount null 1");
 
330
                file.mount = null;
299
331
                warning ("Mount_mountable failed: %s", e.message);
300
332
                if (e is IOError.PERMISSION_DENIED || e is IOError.FAILED_HANDLED) {
301
333
                    permission_denied = true;
302
334
                }
303
335
            }
304
 
            return false;
305
336
        } finally {
306
337
            cancel_timeout (ref mount_timeout_id);
307
338
        }
 
339
 
 
340
        debug ("success %s; enclosing mount %s", res.to_string (), file.mount != null ? file.mount.get_name () : "null");
 
341
        return res;
308
342
    }
309
343
 
310
344
    public async bool check_network () {
 
345
        debug ("check network");
311
346
        var net_mon = GLib.NetworkMonitor.get_default ();
312
347
        network_available = net_mon.get_network_available ();
313
348
 
314
349
        bool success = false;
315
350
 
316
351
        if (network_available) {
317
 
            SocketConnectable? connectable = null;
318
 
            try {
319
 
                connectable = NetworkAddress.parse_uri (file.uri, 21);
320
 
                if (((NetworkAddress)(connectable)).get_hostname () != "" && scheme != "smb") {
321
 
                    success = net_mon.can_reach (connectable, cancellable);
322
 
                    /* Try to connect for real.  This should time out after about 15 seconds if
323
 
                     * the host is not reachable */
324
 
                    var scl = new SocketClient ();
325
 
                    var sc = yield scl.connect_async (connectable, cancellable);
326
 
                    success = (sc != null && sc.is_connected ());
327
 
                    debug ("Attempt to connect to %s %s", file.uri, success ? "succeeded" : "failed");
 
352
            if (!file.is_mounted) {
 
353
                debug ("Network is available");
 
354
                if (scheme != "smb") {
 
355
                    try {
 
356
                        /* Try to connect for real.  */
 
357
                        var scl = new SocketClient ();
 
358
                        scl.set_timeout (CONNECT_SOCKET_TIMEOUT_SEC);
 
359
                        scl.set_tls (PF.FileUtils.get_is_tls_for_protocol (scheme));
 
360
                        debug ("Trying to connect to connectable");
 
361
                        var sc = yield scl.connect_to_uri_async (file.uri, PF.FileUtils.get_default_port_for_protocol (scheme), cancellable);
 
362
                        success = (sc != null && sc.is_connected ());
 
363
                        debug ("Socketclient is %s", sc == null ? "null" : (sc.is_connected () ? "connected" : "not connected"));
 
364
                    } catch (GLib.Error e) {
 
365
                        last_error_message = e.message;
 
366
                        warning ("Error: could not connect to connectable %s - %s", file.uri, e.message);
 
367
                        return false;
 
368
                    }
328
369
                } else {
329
370
                    success = true;
330
371
                }
331
 
            }
332
 
            catch (GLib.Error e) {
333
 
                warning ("Error connecting to connectable %s - %s", file.uri, e.message);
334
 
                success = false;
335
 
 
 
372
            } else {
 
373
                debug ("File is already mounted - not reconnecting");
 
374
                success = true;
336
375
            }
337
376
        } else {
338
377
            warning ("No network available");
339
378
        }
 
379
 
 
380
 
 
381
        debug ("Attempt to connect to %s %s", file.uri, success ? "succeeded" : "failed");
340
382
        return success;
341
383
    }
342
384
     
343
385
 
344
386
    private async void make_ready (bool ready, GOFFileLoadedFunc? file_loaded_func = null) {
 
387
        debug ("make ready");
345
388
        can_load = ready;
346
389
        if (!can_load) {
347
390
            warning ("%s cannot load.  Connected %s, Mounted %s, Exists %s", file.uri,
348
391
                                                                             file.is_connected.to_string (),
349
392
                                                                             file.is_mounted.to_string (),
350
393
                                                                             file.exists.to_string ());
351
 
            state = State.NOT_LOADED; /* ensure state is correct */
352
 
            done_loading ();
 
394
            after_loading (file_loaded_func);
353
395
            return;
354
396
        }
355
397
 
362
404
            dir_cache_lock.unlock ();
363
405
 
364
406
            is_ready = true;
 
407
            if (file.mount != null) {
 
408
                debug ("Directory has mount point");
 
409
                unowned GLib.List? trash_dirs = null;
 
410
                trash_dirs = Marlin.FileOperations.get_trash_dirs_for_mount (file.mount);
 
411
                has_trash_dirs = (trash_dirs != null);
 
412
            } else {
 
413
                has_trash_dirs = is_local;
 
414
            }
 
415
 
365
416
            yield list_directory_async (file_loaded_func);
366
417
 
367
418
            if (can_load) {
372
423
                        monitor.rate_limit = 100;
373
424
                        monitor.changed.connect (directory_changed);
374
425
                    } catch (IOError e) {
 
426
                        last_error_message = e.message;
375
427
                        if (!(e is IOError.NOT_MOUNTED)) {
376
428
                            /* Will fail for remote filesystems - not an error */
377
429
                            debug ("directory monitor failed: %s %s", e.message, file.uri);
380
432
                }
381
433
 
382
434
                set_confirm_trash ();
383
 
                file.mount = GOF.File.get_mount_at (location);
384
 
                if (file.mount != null) {
385
 
                    file.is_mounted = true;
386
 
                    unowned GLib.List? trash_dirs = null;
387
 
                    trash_dirs = Marlin.FileOperations.get_trash_dirs_for_mount (file.mount);
388
 
                    has_trash_dirs = (trash_dirs != null);
389
 
                } else {
390
 
                    has_trash_dirs = is_local;
391
 
                }
392
435
 
393
436
                if (is_trash) {
394
437
                    connect_volume_monitor_signals ();
451
494
 
452
495
 
453
496
    public void reload () {
 
497
        debug ("Reload - state is %s", state.to_string ());
 
498
        if (state == State.TIMED_OUT && file.is_mounted) {
 
499
            debug ("Unmounting because of timeout");
 
500
            cancellable.cancel ();
 
501
            cancellable = new Cancellable ();
 
502
            file.location.unmount_mountable (GLib.MountUnmountFlags.FORCE, cancellable);
 
503
            file.mount = null;
 
504
            file.is_mounted = false;
 
505
        }
 
506
 
454
507
        clear_directory_info ();
 
508
        state = State.NOT_LOADED;
455
509
        init ();
456
510
    }
457
511
 
465
519
        monitor = null;
466
520
        sorted_dirs = null;
467
521
        files_count = 0;
468
 
        state = State.NOT_LOADED;
469
522
        is_ready = false;
470
523
        can_load = false;
471
524
    }
472
525
 
473
526
    private void list_cached_files (GOFFileLoadedFunc? file_loaded_func = null) {
 
527
        debug ("list cached files");
474
528
        if (state != State.LOADED) {
475
529
            warning ("list cached files called in %s state - not expected to happen", state.to_string ());
476
530
            return;
490
544
    }
491
545
 
492
546
    private async void list_directory_async (GOFFileLoadedFunc? file_loaded_func) {
 
547
        debug ("list directory async");
493
548
        /* Should only be called after creation and if reloaded */
494
549
        if (!is_ready || file_hash.size () > 0) {
495
550
            critical ("(Re)load directory called when not cleared");
517
572
        files_count = 0;
518
573
        state = State.LOADING;
519
574
        bool show_hidden = is_trash || Preferences.get_default ().pref_show_hidden_files;
 
575
        bool server_responding = false;
520
576
 
521
577
        debug ("(Re)loading folder children"); /* Required for ctest */
522
578
 
524
580
            /* This may hang for a long time if the connection was closed but is still mounted so we
525
581
             * impose a time limit */
526
582
            load_timeout_id = Timeout.add_seconds (ENUMERATE_TIMEOUT_SEC, () => {
527
 
                cancellable.cancel ();
528
 
                load_timeout_id = 0;
529
 
                return false;
 
583
                if (server_responding) {
 
584
                    return true;
 
585
                } else {
 
586
                    debug ("Load timeout expired");
 
587
                    state = State.TIMED_OUT;
 
588
                    last_error_message = _("Server did not respond within time limit");
 
589
                    load_timeout_id = 0;
 
590
                    cancellable.cancel ();
 
591
 
 
592
                    return false;
 
593
                }
530
594
            });
531
595
 
532
596
            var e = yield this.location.enumerate_children_async (gio_attrs, 0, Priority.HIGH, cancellable);
533
 
            cancel_timeout (ref load_timeout_id);
 
597
            debug ("Obtained file enumerator for location %s", location.get_uri ());
534
598
 
535
599
            GOF.File? gof;
536
600
            GLib.File loc;
537
601
            while (!cancellable.is_cancelled ()) {
538
 
                var files = yield e.next_files_async (200, 0, cancellable);
539
 
                if (files == null) {
540
 
                    break;
541
 
                } else {
542
 
                    foreach (var file_info in files) {
543
 
                        loc = location.get_child (file_info.get_name ());
544
 
                        assert (loc != null);
545
 
                        gof = GOF.File.cache_lookup (loc);
546
 
 
547
 
                        if (gof == null) {
548
 
                            gof = new GOF.File (loc, location); /*does not add to GOF file cache */
 
602
                try {
 
603
                    server_responding = false;
 
604
                    var files = yield e.next_files_async (200, GLib.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
 
605
                    server_responding = true;
 
606
 
 
607
                    if (files == null) {
 
608
                        break;
 
609
                    } else {
 
610
                        foreach (var file_info in files) {
 
611
                            loc = location.get_child (file_info.get_name ());
 
612
                            assert (loc != null);
 
613
                            gof = GOF.File.cache_lookup (loc);
 
614
 
 
615
                            if (gof == null) {
 
616
                                gof = new GOF.File (loc, location); /*does not add to GOF file cache */
 
617
                            }
 
618
 
 
619
                            gof.info = file_info;
 
620
                            gof.update ();
 
621
 
 
622
                            file_hash.insert (gof.location, gof);
 
623
                            after_load_file (gof, show_hidden, file_loaded_func);
 
624
                            files_count++;
549
625
                        }
550
 
 
551
 
                        gof.info = file_info;
552
 
                        gof.update ();
553
 
 
554
 
                        file_hash.insert (gof.location, gof);
555
 
                        after_load_file (gof, show_hidden, file_loaded_func);
556
 
                        files_count++;
557
626
                    }
 
627
                } catch (Error e) {
 
628
                    last_error_message = e.message;
 
629
                    warning ("Error reported by next_files_async - %s", e.message);
558
630
                }
559
631
            }
560
 
            state = State.LOADED;
 
632
            /* Load as many files as we can get info for */
 
633
            if (!(cancellable.is_cancelled ())) {
 
634
                state = State.LOADED;
 
635
            }
561
636
        } catch (Error err) {
562
 
            warning ("Listing directory error: %s %s", err.message, file.uri);
 
637
            warning ("Listing directory error: %s, %s %s", last_error_message, err.message, file.uri);
563
638
            can_load = false;
564
639
            if (err is IOError.NOT_FOUND || err is IOError.NOT_DIRECTORY) {
565
640
                file.exists = false;
566
 
            } else if (err is IOError.PERMISSION_DENIED)
 
641
            } else if (err is IOError.PERMISSION_DENIED) {
567
642
                permission_denied = true;
568
 
            else if (err is IOError.NOT_MOUNTED)
 
643
            } else if (err is IOError.NOT_MOUNTED) {
 
644
                file.mount = null;
569
645
                file.is_mounted = false;
 
646
            }
 
647
        } finally {
 
648
            cancel_timeout (ref load_timeout_id);
 
649
            after_loading (file_loaded_func);
570
650
        }
571
 
 
572
 
        after_loading (file_loaded_func);
573
651
    }
574
652
 
575
653
    private void after_load_file (GOF.File gof, bool show_hidden, GOFFileLoadedFunc? file_loaded_func) {
587
665
    private void after_loading (GOFFileLoadedFunc? file_loaded_func) {
588
666
        /* If loading failed reset */
589
667
        debug ("after loading state is %s", state.to_string ());
590
 
        if (state == State.LOADING) {
591
 
            state = State.NOT_LOADED; /* else clear directory info will fail */
 
668
        if (state == State.LOADING || state == State.TIMED_OUT) {
 
669
            state = State.TIMED_OUT; /* else clear directory info will fail */
 
670
            can_load = false;
 
671
        }
 
672
 
 
673
        if (file_loaded_func == null) {
 
674
            done_loading ();
 
675
        }
 
676
 
 
677
        if (state != State.LOADED) {
592
678
            clear_directory_info ();
593
 
            can_load = false;
594
 
        }
595
 
        if (file_loaded_func == null) {
596
 
            done_loading ();
597
679
        }
598
680
    }
599
681
 
694
776
                f (gof);
695
777
            }
696
778
        } catch (Error err) {
 
779
            last_error_message = err.message;
697
780
            warning ("query info failed, %s %s", err.message, gof.uri);
698
781
            if (err is IOError.NOT_FOUND) {
699
782
                gof.exists = false;
968
1051
        if (cached_dir != null) {
969
1052
            if (cached_dir is Async && cached_dir.file != null) {
970
1053
                debug ("found cached dir %s", cached_dir.file.uri);
971
 
                if (cached_dir.file.info == null && cached_dir.can_load)
 
1054
                if (cached_dir.file.info == null && cached_dir.can_load) {
 
1055
                    debug ("updating cached file info");
972
1056
                    cached_dir.file.query_update ();  /* This is synchronous and causes blocking */
 
1057
                }
973
1058
            } else {
974
1059
                warning ("Invalid directory found in cache");
975
1060
                cached_dir = null;
1031
1116
        return this.state == State.LOADED;
1032
1117
    }
1033
1118
 
 
1119
    public bool has_timed_out () {
 
1120
        return this.state == State.TIMED_OUT;
 
1121
    }
 
1122
 
1034
1123
    public bool is_empty () {
1035
1124
        return (state == State.LOADED && file_hash.size () == 0); /* only return true when loaded to avoid temporary appearance of empty message while loading */
1036
1125
    }
1052
1141
        sorted_dirs.sort (GOF.File.compare_by_display_name);
1053
1142
        return sorted_dirs;
1054
1143
    }
 
1144
 
1055
1145
    private void cancel_timeouts () {
1056
1146
        cancel_timeout (ref idle_consume_changes_id);
1057
1147
        cancel_timeout (ref load_timeout_id);