Merge lp:~jeremywootten/pantheon-files/fix-hang-on-large-copy into lp:~elementary-apps/pantheon-files/trunk
- fix-hang-on-large-copy
- Merge into trunk
Status: | Superseded |
---|---|
Proposed branch: | lp:~jeremywootten/pantheon-files/fix-hang-on-large-copy |
Merge into: | lp:~elementary-apps/pantheon-files/trunk |
Diff against target: |
435 lines (+116/-87) 4 files modified
src/View/AbstractDirectoryView.vala (+68/-55) 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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zisu Andrei (community) | Needs Information | ||
Review via email: mp+310070@code.launchpad.net |
This proposal has been superseded by a proposal from 2017-01-11.
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).
Zisu Andrei (matzipan) wrote : | # |
Code also LGTM.
Not merging this yet. Waiting on merging okay from Cody.
Zisu Andrei (matzipan) wrote : | # |
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.
- 2369. By Jeremy Wootten
-
Merge trunk to r2377
- 2370. By Jeremy Wootten
-
Update menu actions immediately on first selection change; select pasted files and dropped files the same way; remove unnecessary flag
Jeremy Wootten (jeremywootten) wrote : | # |
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.
Zisu Andrei (matzipan) wrote : | # |
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.
Jeremy Wootten (jeremywootten) wrote : | # |
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.
Jeremy Wootten (jeremywootten) wrote : | # |
Testing instructions corrected and extended.
- 2371. By Jeremy Wootten
-
Merge trunk to r2441
- 2372. By Jeremy Wootten
-
Merge trunk to r2470
- 2373. By Jeremy Wootten
-
Merge trunk to r2572 and resolve conflict
- 2374. By Jeremy Wootten
-
Merge trunk to r2573 and resolve conflict in AbstractDirecto
ryView
Unmerged revisions
Preview Diff
1 | === modified file 'src/View/AbstractDirectoryView.vala' |
2 | --- src/View/AbstractDirectoryView.vala 2016-12-31 19:42:09 +0000 |
3 | +++ src/View/AbstractDirectoryView.vala 2017-01-11 17:17:31 +0000 |
4 | @@ -215,9 +215,7 @@ |
5 | private bool can_trash_or_delete = true; |
6 | |
7 | /* Rapid keyboard paste support */ |
8 | - protected bool pasting_files = false; |
9 | protected bool select_added_files = false; |
10 | - private HashTable? pasted_files = null; |
11 | |
12 | public bool renaming {get; protected set; default = false;} |
13 | |
14 | @@ -230,12 +228,20 @@ |
15 | action_set_enabled (common_actions, "paste_into", false); |
16 | action_set_enabled (window.win_actions, "select_all", false); |
17 | |
18 | + /* Fix problems when navigating away from directory with large number |
19 | + * of selected files (e.g. OverlayBar critical errors) |
20 | + */ |
21 | + disconnect_tree_signals (); |
22 | + |
23 | size_allocate.disconnect (on_size_allocate); |
24 | clipboard.changed.disconnect (on_clipboard_changed); |
25 | view.enter_notify_event.disconnect (on_enter_notify_event); |
26 | view.key_press_event.disconnect (on_view_key_press_event); |
27 | } else if (!value && _is_frozen) { |
28 | - update_menu_actions (); |
29 | + /* Ensure selected files and menu actions are up to date */ |
30 | + connect_tree_signals (); |
31 | + on_view_selection_changed (); |
32 | + |
33 | size_allocate.connect (on_size_allocate); |
34 | clipboard.changed.connect (on_clipboard_changed); |
35 | view.enter_notify_event.connect (on_enter_notify_event); |
36 | @@ -417,37 +423,45 @@ |
37 | }); |
38 | } |
39 | } |
40 | - public void select_glib_files (GLib.List<GLib.File> location_list, GLib.File? focus_location) { |
41 | - unselect_all (); |
42 | + |
43 | + public void select_glib_files_when_thawed (GLib.List<GLib.File> location_list, GLib.File? focus_location) { |
44 | GLib.List<GOF.File>? file_list = null; |
45 | |
46 | location_list.@foreach ((loc) => { |
47 | file_list.prepend (GOF.File.@get (loc)); |
48 | }); |
49 | |
50 | + GLib.File? focus = focus_location != null ? focus_location.dup () : null; |
51 | + |
52 | /* Because the Icon View disconnects the model while loading, we need to wait until |
53 | * the tree is thawed and the model reconnected before selecting the files */ |
54 | Idle.add_full (GLib.Priority.LOW, () => { |
55 | if (!tree_frozen) { |
56 | - file_list.@foreach ((file) => { |
57 | - Gtk.TreeIter iter; |
58 | - if (model.get_first_iter_for_file (file, out iter)) { |
59 | - Gtk.TreePath path = model.get_path (iter); |
60 | - if (path != null) { |
61 | - select_path (path); |
62 | - if (focus_location != null && focus_location.equal (file.location)) { |
63 | - /* set cursor and scroll to focus location*/ |
64 | - set_cursor (path, false, false, true); |
65 | - } |
66 | - } |
67 | - } |
68 | - }); |
69 | + select_file_paths (file_list, focus); |
70 | return false; |
71 | - } else |
72 | + } else { |
73 | return true; |
74 | + } |
75 | }); |
76 | } |
77 | |
78 | + private void select_file_paths (GLib.List<GOF.File> files, GLib.File? focus) { |
79 | + |
80 | + Gtk.TreeIter iter; |
81 | + disconnect_tree_signals (); /* Avoid unnecessary signal processing */ |
82 | + unselect_all (); |
83 | + |
84 | + foreach (GOF.File f in files) { |
85 | + if (model.get_first_iter_for_file (f, out iter)) { |
86 | + var path = model.get_path (iter); |
87 | + select_path (path, focus != null && focus.equal (f.location)); /* Cursor follows if matches focus location*/ |
88 | + } |
89 | + } |
90 | + |
91 | + connect_tree_signals (); |
92 | + on_view_selection_changed (); /* Update selected files and menu actions */ |
93 | + } |
94 | + |
95 | public unowned GLib.List<GLib.AppInfo> get_open_with_apps () { |
96 | return open_with_apps; |
97 | } |
98 | @@ -577,7 +591,7 @@ |
99 | return; /* file not in model */ |
100 | |
101 | var path = model.get_path (iter); |
102 | - select_path (path); |
103 | + select_path (path); /* Cursor does not follow */ |
104 | } |
105 | |
106 | /** Directory signal handlers. */ |
107 | @@ -1190,6 +1204,7 @@ |
108 | clipboard.copy_files (get_selected_files_for_transfer (get_files_for_action ())); |
109 | } |
110 | |
111 | + |
112 | public static void after_pasting_files (GLib.HashTable? uris, void* pointer) { |
113 | if (pointer == null) |
114 | return; |
115 | @@ -1200,29 +1215,12 @@ |
116 | return; |
117 | } |
118 | |
119 | - view.pasting_files = false; |
120 | - if (uris == null || uris.size () == 0) |
121 | + if (uris == null || uris.size () == 0) { |
122 | return; |
123 | - |
124 | - view.pasted_files = uris; |
125 | - |
126 | - Idle.add (() => { |
127 | - /* Select the most recently pasted files */ |
128 | - GLib.List<GLib.File> pasted_files_list = null; |
129 | - view.pasted_files.foreach ((k, v) => { |
130 | - if (k is GLib.File) |
131 | - pasted_files_list.prepend (k as File); |
132 | - }); |
133 | - |
134 | - view.select_glib_files (pasted_files_list, pasted_files_list.first ().data); |
135 | - return false; |
136 | - }); |
137 | + } |
138 | } |
139 | |
140 | private void on_common_action_paste_into (GLib.SimpleAction action, GLib.Variant? param) { |
141 | - if (pasting_files) |
142 | - return; |
143 | - |
144 | var file = get_files_for_action ().nth_data (0); |
145 | |
146 | if (file != null && clipboard.can_paste) { |
147 | @@ -1236,10 +1234,10 @@ |
148 | |
149 | if (target.has_uri_scheme ("trash")) { |
150 | /* Pasting files into trash is equivalent to trash or delete action */ |
151 | - pasting_files = false; |
152 | + select_added_files = false; |
153 | call_back = (GLib.Callback)after_trash_or_delete; |
154 | } else { |
155 | - pasting_files = true; |
156 | + select_added_files = true; |
157 | /* callback takes care of selecting pasted files */ |
158 | call_back = (GLib.Callback)after_pasting_files; |
159 | } |
160 | @@ -2570,15 +2568,25 @@ |
161 | activate_selected_items (Marlin.OpenFlag.DEFAULT); |
162 | } |
163 | |
164 | + uint update_selected_timeout_id = 0; |
165 | protected virtual void on_view_selection_changed () { |
166 | + /* updating selecting file list is expensive for large selections so throttle */ |
167 | + if (update_selected_timeout_id == 0) { |
168 | + after_selected_files_changed (); /* Make sure first update happens immediately */ |
169 | + update_selected_timeout_id = Timeout.add_full (GLib.Priority.LOW, 100, () => { |
170 | + update_selected_timeout_id = 0; |
171 | + after_selected_files_changed (); |
172 | + return false; |
173 | + }); |
174 | + } |
175 | + } |
176 | + |
177 | + private void after_selected_files_changed () { |
178 | update_selected_files (); |
179 | update_menu_actions (); |
180 | - if (is_frozen) |
181 | - return; |
182 | - |
183 | selection_changed (get_selected_files ()); |
184 | } |
185 | - |
186 | + |
187 | /** Keyboard event handling **/ |
188 | |
189 | /** Returns true if the code parameter matches the keycode of the keyval parameter for |
190 | @@ -2801,7 +2809,7 @@ |
191 | linear_select_path (path); |
192 | } else if (no_mods) { |
193 | unselect_path (old_path); |
194 | - select_path (path); |
195 | + select_path (path, true); /* Cursor follows */ |
196 | } |
197 | } |
198 | return true; |
199 | @@ -3161,12 +3169,13 @@ |
200 | } |
201 | |
202 | if (!path_selected && click_zone != ClickZone.HELPER) { |
203 | - if (no_mods) |
204 | + if (no_mods) { |
205 | unselect_all (); |
206 | - |
207 | + } |
208 | /* If modifier pressed then default handler determines selection */ |
209 | - if (no_mods && !on_blank) |
210 | - select_path (path); |
211 | + if (no_mods && !on_blank) { |
212 | + select_path (path, true); /* Cursor follows */ |
213 | + } |
214 | } |
215 | |
216 | bool result = true; |
217 | @@ -3222,7 +3231,7 @@ |
218 | if (path_selected) { |
219 | unselect_path (path); |
220 | } else { |
221 | - select_path (path); |
222 | + select_path (path, true); /* Cursor follows */ |
223 | } |
224 | } |
225 | |
226 | @@ -3255,9 +3264,10 @@ |
227 | |
228 | case Gdk.BUTTON_SECONDARY: |
229 | if (click_zone == ClickZone.NAME || |
230 | - (!single_click_rename && click_zone == ClickZone.BLANK_PATH)) |
231 | + (!single_click_rename && click_zone == ClickZone.BLANK_PATH)) { |
232 | |
233 | - select_path (path); |
234 | + select_path (path); /* Curso does not follow */ |
235 | + } |
236 | |
237 | unblock_drag_and_drop (); |
238 | result = handle_secondary_button_click (event); |
239 | @@ -3462,6 +3472,7 @@ |
240 | cancel_drag_timer (); |
241 | cancel_timeout (ref drag_scroll_timer_id); |
242 | cancel_timeout (ref add_remove_file_timeout_id); |
243 | + cancel_timeout (ref update_selected_timeout_id); |
244 | /* List View will take care of unloading subdirectories */ |
245 | } |
246 | |
247 | @@ -3518,7 +3529,7 @@ |
248 | public abstract Gtk.TreePath? get_path_at_cursor (); |
249 | public abstract void select_all (); |
250 | public abstract void unselect_all (); |
251 | - public abstract void select_path (Gtk.TreePath? path); |
252 | + public abstract void select_path (Gtk.TreePath? path, bool cursor_follows = false); |
253 | public abstract void unselect_path (Gtk.TreePath? path); |
254 | public abstract bool path_is_selected (Gtk.TreePath? path); |
255 | public abstract bool get_visible_range (out Gtk.TreePath? start_path, out Gtk.TreePath? end_path); |
256 | @@ -3544,7 +3555,9 @@ |
257 | protected abstract void thaw_tree (); |
258 | protected new abstract void freeze_child_notify (); |
259 | protected new abstract void thaw_child_notify (); |
260 | - |
261 | + protected abstract void connect_tree_signals (); |
262 | + protected abstract void disconnect_tree_signals (); |
263 | + |
264 | /** Unimplemented methods |
265 | * fm_directory_view_parent_set () - purpose unclear |
266 | */ |
267 | |
268 | === modified file 'src/View/AbstractTreeView.vala' |
269 | --- src/View/AbstractTreeView.vala 2016-12-31 19:42:09 +0000 |
270 | +++ src/View/AbstractTreeView.vala 2017-01-11 17:17:31 +0000 |
271 | @@ -64,6 +64,10 @@ |
272 | |
273 | protected void set_up_view () { |
274 | connect_tree_signals (); |
275 | + tree.realize.connect ((w) => { |
276 | + tree.grab_focus (); |
277 | + tree.columns_autosize (); |
278 | + }); |
279 | } |
280 | |
281 | protected override void set_up_name_renderer () { |
282 | @@ -76,13 +80,11 @@ |
283 | name_renderer.yalign = 0.5f; |
284 | } |
285 | |
286 | - protected void connect_tree_signals () { |
287 | + protected override void connect_tree_signals () { |
288 | tree.get_selection ().changed.connect (on_view_selection_changed); |
289 | - |
290 | - tree.realize.connect ((w) => { |
291 | - tree.grab_focus (); |
292 | - tree.columns_autosize (); |
293 | - }); |
294 | + } |
295 | + protected override void disconnect_tree_signals () { |
296 | + tree.get_selection ().changed.disconnect (on_view_selection_changed); |
297 | } |
298 | |
299 | protected override Gtk.Widget? create_view () { |
300 | @@ -131,21 +133,27 @@ |
301 | tree.get_selection ().unselect_all (); |
302 | } |
303 | |
304 | - public override void select_path (Gtk.TreePath? path) { |
305 | + /* Avoid using this function with "cursor_follows = true" to select large numbers of files one by one |
306 | + * It would take an exponentially long time. Use "select_files" function in parent class. |
307 | + */ |
308 | + public override void select_path (Gtk.TreePath? path, bool cursor_follows = false) { |
309 | if (path != null) { |
310 | var selection = tree.get_selection (); |
311 | - /* Unlike for IconView, set_cursor unselects previously selected paths (Gtk bug?), |
312 | - * so we have to remember them and reselect afterwards */ |
313 | - GLib.List<Gtk.TreePath> selected_paths = null; |
314 | - selection.selected_foreach ((m, p, i) => { |
315 | - selected_paths.prepend (p); |
316 | - }); |
317 | - /* Ensure cursor follows last selection */ |
318 | - tree.set_cursor (path, null, false); /* This selects path but unselects rest! */ |
319 | selection.select_path (path); |
320 | - selected_paths.@foreach ((p) => { |
321 | - selection.select_path (p); |
322 | - }); |
323 | + if (cursor_follows) { |
324 | + /* Unlike for IconView, set_cursor unselects previously selected paths (Gtk bug?), |
325 | + * so we have to remember them and reselect afterwards */ |
326 | + GLib.List<Gtk.TreePath> selected_paths = null; |
327 | + selection.selected_foreach ((m, p, i) => { |
328 | + selected_paths.prepend (p); |
329 | + }); |
330 | + /* Ensure cursor follows last selection */ |
331 | + tree.set_cursor (path, null, false); /* This selects path but unselects rest! */ |
332 | + |
333 | + selected_paths.@foreach ((p) => { |
334 | + selection.select_path (p); |
335 | + }); |
336 | + } |
337 | } |
338 | } |
339 | public override void unselect_path (Gtk.TreePath? path) { |
340 | |
341 | === modified file 'src/View/IconView.vala' |
342 | --- src/View/IconView.vala 2016-12-31 19:42:09 +0000 |
343 | +++ src/View/IconView.vala 2017-01-11 17:17:31 +0000 |
344 | @@ -47,6 +47,9 @@ |
345 | (tree as Gtk.CellLayout).add_attribute (icon_renderer, "file", FM.ListModel.ColumnID.FILE_COLUMN); |
346 | |
347 | connect_tree_signals (); |
348 | + tree.realize.connect ((w) => { |
349 | + tree.grab_focus (); |
350 | + }); |
351 | } |
352 | |
353 | protected override void set_up_name_renderer () { |
354 | @@ -61,12 +64,11 @@ |
355 | } |
356 | |
357 | |
358 | - private void connect_tree_signals () { |
359 | + protected override void connect_tree_signals () { |
360 | tree.selection_changed.connect (on_view_selection_changed); |
361 | - |
362 | - tree.realize.connect ((w) => { |
363 | - tree.grab_focus (); |
364 | - }); |
365 | + } |
366 | + protected override void disconnect_tree_signals () { |
367 | + tree.selection_changed.disconnect (on_view_selection_changed); |
368 | } |
369 | |
370 | protected override Gtk.Widget? create_view () { |
371 | @@ -132,14 +134,19 @@ |
372 | } |
373 | |
374 | public override void unselect_all () { |
375 | - tree.unselect_all (); |
376 | + tree.unselect_all (); |
377 | } |
378 | |
379 | - public override void select_path (Gtk.TreePath? path) { |
380 | + /* Avoid using this function with "cursor_follows = true" to select large numbers of files one by one |
381 | + * It would take an exponentially long time. Use "select_files" function in parent class. |
382 | + */ |
383 | + public override void select_path (Gtk.TreePath? path, bool cursor_follows = false) { |
384 | if (path != null) { |
385 | - /* Ensure cursor follows last selection */ |
386 | - tree.set_cursor (path, null, false); /* This selects path but does not unselect the rest (unlike TreeView) */ |
387 | - tree.select_path (path); |
388 | + tree.select_path (path); /* This selects path but does not unselect the rest (unlike TreeView) */ |
389 | + |
390 | + if (cursor_follows) { |
391 | + tree.set_cursor (path, null, false); |
392 | + } |
393 | } |
394 | } |
395 | |
396 | @@ -360,22 +367,23 @@ |
397 | end_path = direction_change ? last_selected : first_selected; |
398 | } |
399 | |
400 | + /* Cursor follows when selecting path */ |
401 | if (before_first) { |
402 | do { |
403 | p2 = p.copy (); |
404 | - select_path (p); |
405 | + select_path (p, true); |
406 | p.next (); |
407 | } while (p.compare (p2) != 0 && p.compare (end_path) <= 0); |
408 | } else if (after_last) { |
409 | do { |
410 | - select_path (p); |
411 | + select_path (p, true); |
412 | p2 = p.copy (); |
413 | p.prev (); |
414 | } while (p.compare (p2) != 0 && p.compare (end_path) >= 0); |
415 | } else {/* between first and last */ |
416 | do { |
417 | p2 = p.copy (); |
418 | - select_path (p); |
419 | + select_path (p, true); |
420 | p.prev (); |
421 | } while (p.compare (p2) != 0 && p.compare (first_selected) >= 0); |
422 | |
423 | |
424 | === modified file 'src/View/Slot.vala' |
425 | --- src/View/Slot.vala 2016-09-21 18:27:17 +0000 |
426 | +++ src/View/Slot.vala 2017-01-11 17:17:31 +0000 |
427 | @@ -351,7 +351,7 @@ |
428 | |
429 | public override void select_glib_files (GLib.List<GLib.File> files, GLib.File? focus_location) { |
430 | if (dir_view != null) |
431 | - dir_view.select_glib_files (files, focus_location); |
432 | + dir_view.select_glib_files_when_thawed (files, focus_location); |
433 | } |
434 | |
435 | public void select_gof_file (GOF.File gof) { |
I agree that this branch seems to alleviate the issue, but it does not make it disappear entirely.