Merge lp:~jeremywootten/pantheon-files/fix-hang-on-large-copy into lp:~elementary-apps/pantheon-files/trunk

Proposed by Jeremy Wootten
Status: Merged
Approved by: Danielle Foré
Approved revision: 2374
Merged at revision: 2574
Proposed branch: lp:~jeremywootten/pantheon-files/fix-hang-on-large-copy
Merge into: lp:~elementary-apps/pantheon-files/trunk
Diff against target: 383 lines (+110/-63)
4 files modified
src/View/AbstractDirectoryView.vala (+62/-31)
src/View/AbstractTreeView.vala (+26/-18)
src/View/IconView.vala (+21/-13)
src/View/Slot.vala (+1/-1)
To merge this branch: bzr merge lp:~jeremywootten/pantheon-files/fix-hang-on-large-copy
Reviewer Review Type Date Requested Status
Adam Bieńkowski (community) testing Approve
Dieter Debast (community) ux Approve
Zisu Andrei Pending
Review via email: mp+314546@code.launchpad.net

This proposal supersedes a proposal from 2016-11-04.

Commit message

Fix hang on selecting many files after paste or view change.

Description of the change

This fixes the exponentially increasing time take to select the debuting files in the view after pasting. With a large number of files (e.g. 1000+) the interface appears to hang when using List View.

TO TEST:

A.
IN ICON VIEW
1) Create two test folders and open each in a tab
2) In one folder create a large number (~1000) of small files (~1K)
3) Select all the files (Ctrl-A)
4) Copy the files to the clipboard
5) Switch to empty folder and paste the files.

In trunk the interface appears to hang for a long time after the pasted files appear (A core is maxed out).

B. IN LIST VIEW
1) Create many files as described under A.
2) Select them all.
3) Switch to icon view.

In trunk the interface appears to hang for a long time while the selected files are re-selected (one at a time) in the new view (A core is maxed out).

KILL THE PANTHEON-FILES PROCESS BEFORE RUNNING THIS BRANCH.
In this branch the pasted files or reselected files are all selected soon after they appear. The interface does not hang.

The branch additionally fixes pasting into a List or Column View - all the pasted files are selected (trunk only selects the one file, unlike Icon View)

The same code is now used whether pasting is done via keyboard or drag and drop (simplification).

To post a comment you must log in.
Revision history for this message
Zisu Andrei (matzipan) wrote : Posted in a previous version of this proposal

I agree that this branch seems to alleviate the issue, but it does not make it disappear entirely.

review: Approve (functionality)
Revision history for this message
Zisu Andrei (matzipan) wrote : Posted in a previous version of this proposal

 Code also LGTM.

Not merging this yet. Waiting on merging okay from Cody.

review: Approve
Revision history for this message
Zisu Andrei (matzipan) wrote : Posted in a previous version of this proposal

Scratch that. I'm seeing a very nasty bug. I thought it was in trunk too but it isn't.

My folder structure is like this:

folder 1
2000 text files

If I am viewing this folder and I press ctrl+a, I then deselect "folder 1", I then click "folder 1" to enter it. This results in opening all 2000 files in Scratch.

review: Needs Fixing
Revision history for this message
Jeremy Wootten (jeremywootten) wrote : Posted in a previous version of this proposal

Thanks for spotting that bad regression. The latest version fixes that and also simplifies the code a little. Both pasted and dropped files are selected but it was being done in different ways for some reason. Now pasted files are selected in the same way as dropped files.

Revision history for this message
Zisu Andrei (matzipan) wrote : Posted in a previous version of this proposal

I can't seem to be noticing any significant improvement (maybe I was mistaken earlier). I made sure to kill all the processes, double checked the versions I have installed, etc.

I tried both over an SSD and an HDD.

review: Needs Information
Revision history for this message
Jeremy Wootten (jeremywootten) wrote : Posted in a previous version of this proposal

The problem does not occur if you use List or Column view (at least not with the operations mentioned in TESTING). Only if both views are Icon View. This is because only one of the pasted files is selected in List View, but they are all selected in Icon View (which is a bug in itself - the behaviour should be consistent).

You should be able to reproduce the bug using only one tab using the second set of instructions now in the description.

Revision history for this message
Jeremy Wootten (jeremywootten) wrote : Posted in a previous version of this proposal

Testing instructions corrected and extended.

Revision history for this message
Dieter Debast (ddieter) wrote :

I can confirm that it works much smoother on this branch.

review: Approve (ux)
Revision history for this message
Adam Bieńkowski (donadigo) wrote :

This works properly here however I cannot verify if the changes in the code make sense because of Files code complexity, anyway I'm giving this an approve.

review: Approve (testing)
Revision history for this message
RabbitBot (rabbitbot-a) wrote :

Attempt to merge into lp:pantheon-files failed due to conflicts:

text conflict in src/View/AbstractDirectoryView.vala

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

Conflict with trunk r2572 resolved.

Revision history for this message
RabbitBot (rabbitbot-a) wrote :

Attempt to merge into lp:pantheon-files failed due to conflicts:

text conflict in src/View/AbstractDirectoryView.vala

2374. By Jeremy Wootten

Merge trunk to r2573 and resolve conflict in AbstractDirectoryView

Revision history for this message
Jeremy Wootten (jeremywootten) wrote :

Conflict with trunk r2573 resolved.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/View/AbstractDirectoryView.vala'
--- src/View/AbstractDirectoryView.vala 2017-06-05 20:25:37 +0000
+++ src/View/AbstractDirectoryView.vala 2017-06-06 09:01:03 +0000
@@ -239,12 +239,20 @@
239 action_set_enabled (common_actions, "paste_into", false);239 action_set_enabled (common_actions, "paste_into", false);
240 action_set_enabled (window.win_actions, "select_all", false);240 action_set_enabled (window.win_actions, "select_all", false);
241241
242 /* Fix problems when navigating away from directory with large number
243 * of selected files (e.g. OverlayBar critical errors)
244 */
245 disconnect_tree_signals ();
246
242 size_allocate.disconnect (on_size_allocate);247 size_allocate.disconnect (on_size_allocate);
243 clipboard.changed.disconnect (on_clipboard_changed);248 clipboard.changed.disconnect (on_clipboard_changed);
244 view.enter_notify_event.disconnect (on_enter_notify_event);249 view.enter_notify_event.disconnect (on_enter_notify_event);
245 view.key_press_event.disconnect (on_view_key_press_event);250 view.key_press_event.disconnect (on_view_key_press_event);
246 } else if (!value && _is_frozen) {251 } else if (!value && _is_frozen) {
247 update_menu_actions ();252 /* Ensure selected files and menu actions are up to date */
253 connect_tree_signals ();
254 on_view_selection_changed ();
255
248 size_allocate.connect (on_size_allocate);256 size_allocate.connect (on_size_allocate);
249 clipboard.changed.connect (on_clipboard_changed);257 clipboard.changed.connect (on_clipboard_changed);
250 view.enter_notify_event.connect (on_enter_notify_event);258 view.enter_notify_event.connect (on_enter_notify_event);
@@ -427,37 +435,45 @@
427 });435 });
428 }436 }
429 }437 }
430 public void select_glib_files (GLib.List<GLib.File> location_list, GLib.File? focus_location) {438
431 unselect_all ();439 public void select_glib_files_when_thawed (GLib.List<GLib.File> location_list, GLib.File? focus_location) {
432 GLib.List<GOF.File>? file_list = null;440 GLib.List<GOF.File>? file_list = null;
433441
434 location_list.@foreach ((loc) => {442 location_list.@foreach ((loc) => {
435 file_list.prepend (GOF.File.@get (loc));443 file_list.prepend (GOF.File.@get (loc));
436 });444 });
437445
446 GLib.File? focus = focus_location != null ? focus_location.dup () : null;
447
438 /* Because the Icon View disconnects the model while loading, we need to wait until448 /* Because the Icon View disconnects the model while loading, we need to wait until
439 * the tree is thawed and the model reconnected before selecting the files */449 * the tree is thawed and the model reconnected before selecting the files */
440 Idle.add_full (GLib.Priority.LOW, () => {450 Idle.add_full (GLib.Priority.LOW, () => {
441 if (!tree_frozen) {451 if (!tree_frozen) {
442 file_list.@foreach ((file) => {452 select_file_paths (file_list, focus);
443 Gtk.TreeIter iter;
444 if (model.get_first_iter_for_file (file, out iter)) {
445 Gtk.TreePath path = model.get_path (iter);
446 if (path != null) {
447 select_path (path);
448 if (focus_location != null && focus_location.equal (file.location)) {
449 /* set cursor and scroll to focus location*/
450 set_cursor (path, false, false, true);
451 }
452 }
453 }
454 });
455 return false;453 return false;
456 } else454 } else {
457 return true;455 return true;
456 }
458 });457 });
459 }458 }
460459
460 private void select_file_paths (GLib.List<GOF.File> files, GLib.File? focus) {
461
462 Gtk.TreeIter iter;
463 disconnect_tree_signals (); /* Avoid unnecessary signal processing */
464 unselect_all ();
465
466 foreach (GOF.File f in files) {
467 if (model.get_first_iter_for_file (f, out iter)) {
468 var path = model.get_path (iter);
469 select_path (path, focus != null && focus.equal (f.location)); /* Cursor follows if matches focus location*/
470 }
471 }
472
473 connect_tree_signals ();
474 on_view_selection_changed (); /* Update selected files and menu actions */
475 }
476
461 public unowned GLib.List<GLib.AppInfo> get_open_with_apps () {477 public unowned GLib.List<GLib.AppInfo> get_open_with_apps () {
462 return open_with_apps;478 return open_with_apps;
463 }479 }
@@ -587,7 +603,7 @@
587 return; /* file not in model */603 return; /* file not in model */
588604
589 var path = model.get_path (iter);605 var path = model.get_path (iter);
590 select_path (path);606 select_path (path); /* Cursor does not follow */
591 }607 }
592608
593 /** Directory signal handlers. */609 /** Directory signal handlers. */
@@ -1203,6 +1219,7 @@
1203 clipboard.copy_files (get_selected_files_for_transfer (get_files_for_action ()));1219 clipboard.copy_files (get_selected_files_for_transfer (get_files_for_action ()));
1204 }1220 }
12051221
1222
1206 public static void after_pasting_files (GLib.HashTable? uris, void* pointer) {1223 public static void after_pasting_files (GLib.HashTable? uris, void* pointer) {
1207 if (pointer == null)1224 if (pointer == null)
1208 return;1225 return;
@@ -1225,7 +1242,7 @@
1225 pasted_files_list.prepend (k as File);1242 pasted_files_list.prepend (k as File);
1226 });1243 });
12271244
1228 view.select_glib_files (pasted_files_list, pasted_files_list.first ().data);1245 view.select_glib_files_when_thawed (pasted_files_list, pasted_files_list.first ().data);
1229 return false;1246 return false;
1230 });1247 });
1231 }1248 }
@@ -2562,15 +2579,25 @@
2562 activate_selected_items (Marlin.OpenFlag.DEFAULT);2579 activate_selected_items (Marlin.OpenFlag.DEFAULT);
2563 }2580 }
25642581
2582 uint update_selected_timeout_id = 0;
2565 protected virtual void on_view_selection_changed () {2583 protected virtual void on_view_selection_changed () {
2584 /* updating selecting file list is expensive for large selections so throttle */
2585 if (update_selected_timeout_id == 0) {
2586 after_selected_files_changed (); /* Make sure first update happens immediately */
2587 update_selected_timeout_id = Timeout.add_full (GLib.Priority.LOW, 100, () => {
2588 update_selected_timeout_id = 0;
2589 after_selected_files_changed ();
2590 return false;
2591 });
2592 }
2593 }
2594
2595 private void after_selected_files_changed () {
2566 update_selected_files ();2596 update_selected_files ();
2567 update_menu_actions ();2597 update_menu_actions ();
2568 if (is_frozen)
2569 return;
2570
2571 selection_changed (get_selected_files ());2598 selection_changed (get_selected_files ());
2572 }2599 }
25732600
2574/** Keyboard event handling **/2601/** Keyboard event handling **/
25752602
2576 /** Returns true if the code parameter matches the keycode of the keyval parameter for2603 /** Returns true if the code parameter matches the keycode of the keyval parameter for
@@ -2793,7 +2820,7 @@
2793 linear_select_path (path);2820 linear_select_path (path);
2794 } else if (no_mods) {2821 } else if (no_mods) {
2795 unselect_path (old_path);2822 unselect_path (old_path);
2796 select_path (path);2823 select_path (path, true); /* Cursor follows */
2797 }2824 }
2798 }2825 }
2799 return true;2826 return true;
@@ -3153,12 +3180,13 @@
3153 }3180 }
31543181
3155 if (!path_selected && click_zone != ClickZone.HELPER) {3182 if (!path_selected && click_zone != ClickZone.HELPER) {
3156 if (no_mods)3183 if (no_mods) {
3157 unselect_all ();3184 unselect_all ();
31583185 }
3159 /* If modifier pressed then default handler determines selection */3186 /* If modifier pressed then default handler determines selection */
3160 if (no_mods && !on_blank)3187 if (no_mods && !on_blank) {
3161 select_path (path);3188 select_path (path, true); /* Cursor follows */
3189 }
3162 }3190 }
31633191
3164 bool result = true;3192 bool result = true;
@@ -3216,7 +3244,7 @@
3216 unselect_path (path);3244 unselect_path (path);
3217 } else {3245 } else {
3218 should_deselect = false;3246 should_deselect = false;
3219 select_path (path);3247 select_path (path); /* Cursor follows */
3220 }3248 }
3221 }3249 }
32223250
@@ -3245,7 +3273,7 @@
32453273
3246 case Gdk.BUTTON_SECONDARY:3274 case Gdk.BUTTON_SECONDARY:
3247 if (click_zone == ClickZone.NAME ||3275 if (click_zone == ClickZone.NAME ||
3248 (click_zone == ClickZone.BLANK_PATH)) {3276 click_zone == ClickZone.BLANK_PATH) {
32493277
3250 select_path (path);3278 select_path (path);
3251 } else if (click_zone == ClickZone.INVALID) {3279 } else if (click_zone == ClickZone.INVALID) {
@@ -3458,6 +3486,7 @@
3458 cancel_drag_timer ();3486 cancel_drag_timer ();
3459 cancel_timeout (ref drag_scroll_timer_id);3487 cancel_timeout (ref drag_scroll_timer_id);
3460 cancel_timeout (ref add_remove_file_timeout_id);3488 cancel_timeout (ref add_remove_file_timeout_id);
3489 cancel_timeout (ref update_selected_timeout_id);
3461 /* List View will take care of unloading subdirectories */3490 /* List View will take care of unloading subdirectories */
3462 }3491 }
34633492
@@ -3498,7 +3527,7 @@
3498 public abstract Gtk.TreePath? get_path_at_cursor ();3527 public abstract Gtk.TreePath? get_path_at_cursor ();
3499 public abstract void select_all ();3528 public abstract void select_all ();
3500 public abstract void unselect_all ();3529 public abstract void unselect_all ();
3501 public abstract void select_path (Gtk.TreePath? path);3530 public abstract void select_path (Gtk.TreePath? path, bool cursor_follows = false);
3502 public abstract void unselect_path (Gtk.TreePath? path);3531 public abstract void unselect_path (Gtk.TreePath? path);
3503 public abstract bool path_is_selected (Gtk.TreePath? path);3532 public abstract bool path_is_selected (Gtk.TreePath? path);
3504 public abstract bool get_visible_range (out Gtk.TreePath? start_path, out Gtk.TreePath? end_path);3533 public abstract bool get_visible_range (out Gtk.TreePath? start_path, out Gtk.TreePath? end_path);
@@ -3524,6 +3553,8 @@
3524 protected abstract void thaw_tree ();3553 protected abstract void thaw_tree ();
3525 protected new abstract void freeze_child_notify ();3554 protected new abstract void freeze_child_notify ();
3526 protected new abstract void thaw_child_notify ();3555 protected new abstract void thaw_child_notify ();
3556 protected abstract void connect_tree_signals ();
3557 protected abstract void disconnect_tree_signals ();
3527 protected abstract bool is_on_icon (int x, int y, int orig_x, int orig_y, ref bool on_helper);3558 protected abstract bool is_on_icon (int x, int y, int orig_x, int orig_y, ref bool on_helper);
35283559
3529/** Unimplemented methods3560/** Unimplemented methods
35303561
=== modified file 'src/View/AbstractTreeView.vala'
--- src/View/AbstractTreeView.vala 2017-03-01 17:43:40 +0000
+++ src/View/AbstractTreeView.vala 2017-06-06 09:01:03 +0000
@@ -64,6 +64,10 @@
6464
65 protected void set_up_view () {65 protected void set_up_view () {
66 connect_tree_signals ();66 connect_tree_signals ();
67 tree.realize.connect ((w) => {
68 tree.grab_focus ();
69 tree.columns_autosize ();
70 });
67 }71 }
6872
69 protected override void set_up_name_renderer () {73 protected override void set_up_name_renderer () {
@@ -76,13 +80,11 @@
76 name_renderer.yalign = 0.5f;80 name_renderer.yalign = 0.5f;
77 }81 }
7882
79 protected void connect_tree_signals () {83 protected override void connect_tree_signals () {
80 tree.get_selection ().changed.connect (on_view_selection_changed);84 tree.get_selection ().changed.connect (on_view_selection_changed);
8185 }
82 tree.realize.connect ((w) => {86 protected override void disconnect_tree_signals () {
83 tree.grab_focus ();87 tree.get_selection ().changed.disconnect (on_view_selection_changed);
84 tree.columns_autosize ();
85 });
86 }88 }
8789
88 protected override Gtk.Widget? create_view () {90 protected override Gtk.Widget? create_view () {
@@ -131,21 +133,27 @@
131 tree.get_selection ().unselect_all ();133 tree.get_selection ().unselect_all ();
132 }134 }
133135
134 public override void select_path (Gtk.TreePath? path) {136 /* Avoid using this function with "cursor_follows = true" to select large numbers of files one by one
137 * It would take an exponentially long time. Use "select_files" function in parent class.
138 */
139 public override void select_path (Gtk.TreePath? path, bool cursor_follows = false) {
135 if (path != null) {140 if (path != null) {
136 var selection = tree.get_selection ();141 var selection = tree.get_selection ();
137 /* Unlike for IconView, set_cursor unselects previously selected paths (Gtk bug?),
138 * so we have to remember them and reselect afterwards */
139 GLib.List<Gtk.TreePath> selected_paths = null;
140 selection.selected_foreach ((m, p, i) => {
141 selected_paths.prepend (p);
142 });
143 /* Ensure cursor follows last selection */
144 tree.set_cursor (path, null, false); /* This selects path but unselects rest! */
145 selection.select_path (path);142 selection.select_path (path);
146 selected_paths.@foreach ((p) => {143 if (cursor_follows) {
147 selection.select_path (p);144 /* Unlike for IconView, set_cursor unselects previously selected paths (Gtk bug?),
148 });145 * so we have to remember them and reselect afterwards */
146 GLib.List<Gtk.TreePath> selected_paths = null;
147 selection.selected_foreach ((m, p, i) => {
148 selected_paths.prepend (p);
149 });
150 /* Ensure cursor follows last selection */
151 tree.set_cursor (path, null, false); /* This selects path but unselects rest! */
152
153 selected_paths.@foreach ((p) => {
154 selection.select_path (p);
155 });
156 }
149 }157 }
150 }158 }
151 public override void unselect_path (Gtk.TreePath? path) {159 public override void unselect_path (Gtk.TreePath? path) {
152160
=== modified file 'src/View/IconView.vala'
--- src/View/IconView.vala 2017-03-01 17:43:40 +0000
+++ src/View/IconView.vala 2017-06-06 09:01:03 +0000
@@ -49,6 +49,9 @@
49 (tree as Gtk.CellLayout).add_attribute (icon_renderer, "file", FM.ListModel.ColumnID.FILE_COLUMN);49 (tree as Gtk.CellLayout).add_attribute (icon_renderer, "file", FM.ListModel.ColumnID.FILE_COLUMN);
5050
51 connect_tree_signals ();51 connect_tree_signals ();
52 tree.realize.connect ((w) => {
53 tree.grab_focus ();
54 });
52 }55 }
5356
54 protected override void set_up_name_renderer () {57 protected override void set_up_name_renderer () {
@@ -63,12 +66,11 @@
63 }66 }
6467
6568
66 private void connect_tree_signals () {69 protected override void connect_tree_signals () {
67 tree.selection_changed.connect (on_view_selection_changed);70 tree.selection_changed.connect (on_view_selection_changed);
6871 }
69 tree.realize.connect ((w) => {72 protected override void disconnect_tree_signals () {
70 tree.grab_focus ();73 tree.selection_changed.disconnect (on_view_selection_changed);
71 });
72 }74 }
7375
74 protected override Gtk.Widget? create_view () {76 protected override Gtk.Widget? create_view () {
@@ -134,14 +136,19 @@
134 }136 }
135137
136 public override void unselect_all () {138 public override void unselect_all () {
137 tree.unselect_all ();139 tree.unselect_all ();
138 }140 }
139141
140 public override void select_path (Gtk.TreePath? path) {142 /* Avoid using this function with "cursor_follows = true" to select large numbers of files one by one
143 * It would take an exponentially long time. Use "select_files" function in parent class.
144 */
145 public override void select_path (Gtk.TreePath? path, bool cursor_follows = false) {
141 if (path != null) {146 if (path != null) {
142 /* Ensure cursor follows last selection */147 tree.select_path (path); /* This selects path but does not unselect the rest (unlike TreeView) */
143 tree.set_cursor (path, null, false); /* This selects path but does not unselect the rest (unlike TreeView) */148
144 tree.select_path (path);149 if (cursor_follows) {
150 tree.set_cursor (path, null, false);
151 }
145 }152 }
146 }153 }
147154
@@ -362,22 +369,23 @@
362 end_path = direction_change ? last_selected : first_selected;369 end_path = direction_change ? last_selected : first_selected;
363 }370 }
364371
372 /* Cursor follows when selecting path */
365 if (before_first) {373 if (before_first) {
366 do {374 do {
367 p2 = p.copy ();375 p2 = p.copy ();
368 select_path (p);376 select_path (p, true);
369 p.next ();377 p.next ();
370 } while (p.compare (p2) != 0 && p.compare (end_path) <= 0);378 } while (p.compare (p2) != 0 && p.compare (end_path) <= 0);
371 } else if (after_last) {379 } else if (after_last) {
372 do {380 do {
373 select_path (p);381 select_path (p, true);
374 p2 = p.copy ();382 p2 = p.copy ();
375 p.prev ();383 p.prev ();
376 } while (p.compare (p2) != 0 && p.compare (end_path) >= 0);384 } while (p.compare (p2) != 0 && p.compare (end_path) >= 0);
377 } else {/* between first and last */385 } else {/* between first and last */
378 do {386 do {
379 p2 = p.copy ();387 p2 = p.copy ();
380 select_path (p);388 select_path (p, true);
381 p.prev ();389 p.prev ();
382 } while (p.compare (p2) != 0 && p.compare (first_selected) >= 0);390 } while (p.compare (p2) != 0 && p.compare (first_selected) >= 0);
383391
384392
=== modified file 'src/View/Slot.vala'
--- src/View/Slot.vala 2017-02-04 10:57:49 +0000
+++ src/View/Slot.vala 2017-06-06 09:01:03 +0000
@@ -350,7 +350,7 @@
350350
351 public override void select_glib_files (GLib.List<GLib.File> files, GLib.File? focus_location) {351 public override void select_glib_files (GLib.List<GLib.File> files, GLib.File? focus_location) {
352 if (dir_view != null)352 if (dir_view != null)
353 dir_view.select_glib_files (files, focus_location);353 dir_view.select_glib_files_when_thawed (files, focus_location);
354 }354 }
355355
356 public void select_gof_file (GOF.File gof) {356 public void select_gof_file (GOF.File gof) {

Subscribers

People subscribed via source and target branches

to all changes: