~elementary-apps/pantheon-files/trunk

1402.2.7 by Jeremy Wootten
Some refactoring and simplification
1
/***
2141.1.1 by jeremy at elementaryos
Fix copyright assignment where necessary
2
    Copyright (c)  1999, 2000 Eazel, Inc.
2432.1.1 by Leonardo Lemos
Bump copyright years
3
                   2015-2017 elementary LLC (http://launchpad.net/elementary)
1811.1.1 by Jeremy Wootten
Code clean: tabs, trailing space, headers, comments
4
5
    This program is free software: you can redistribute it and/or modify it
6
    under the terms of the GNU Lesser General Public License version 3, as published
2484.2.1 by Jeremy Wootten
Fix licensecheck warnings
7
    by the Free Software Foundation, Inc.,.
1811.1.1 by Jeremy Wootten
Code clean: tabs, trailing space, headers, comments
8
9
    This program is distributed in the hope that it will be useful, but
10
    WITHOUT ANY WARRANTY; without even the implied warranties of
11
    MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12
    PURPOSE. See the GNU General Public License for more details.
13
14
    You should have received a copy of the GNU General Public License along
15
    with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
    Authors : John Sullivan <sullivan@eazel.com>
1402.2.7 by Jeremy Wootten
Some refactoring and simplification
18
              ammonkey <am.monkeyd@gmail.com>
1811.1.1 by Jeremy Wootten
Code clean: tabs, trailing space, headers, comments
19
              Jeremy Wootten <jeremy@elementaryos.org>
1402.2.7 by Jeremy Wootten
Some refactoring and simplification
20
***/
21
1402.2.2 by Jeremy Wootten
Added vala files
22
namespace Marlin {
23
24
    public class BookmarkList : GLib.Object{
25
26
        enum JobType {
27
            LOAD = 1,
28
            SAVE = 2
29
        }
30
1527.2.2 by Tom Beckmann
use a dropdown instead of a popover, report folder access to zeitgeist
31
        public unowned GLib.List<Marlin.Bookmark> list { get; private set; }
32
1402.2.2 by Jeremy Wootten
Added vala files
33
        private GLib.FileMonitor monitor;
34
        private GLib.Queue<JobType> pending_ops;
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
35
        private static GLib.File bookmarks_file;
36
        private GOF.CallWhenReady call_when_ready;
1402.2.2 by Jeremy Wootten
Added vala files
37
1402.2.7 by Jeremy Wootten
Some refactoring and simplification
38
        private static BookmarkList instance = null;
1402.2.2 by Jeremy Wootten
Added vala files
39
40
        public signal void contents_changed ();
41
        public signal void deleted ();
42
43
        private BookmarkList () {
44
            list = new GLib.List<Marlin.Bookmark> ();
45
            pending_ops = new GLib.Queue<JobType> ();
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
46
1877.4.1 by Jeremy Wootten
Show user bookmarks when administrator
47
            /* Get the user config directory
48
             * When running under pkexec determine real user from PKEXEC_UID
49
             */
1877.4.6 by Jeremy Wootten
Use getpwuid () to find real user home
50
            string? user_home = Eel.get_real_user_home ();
1877.4.10 by Jeremy Wootten
Assign config_dir only once in BookmarkList
51
            string config_dir;
1877.4.1 by Jeremy Wootten
Show user bookmarks when administrator
52
1877.4.6 by Jeremy Wootten
Use getpwuid () to find real user home
53
            if (user_home != null) {
54
                config_dir = GLib.Path.build_filename (user_home, ".config");
1877.4.10 by Jeremy Wootten
Assign config_dir only once in BookmarkList
55
            } else {
56
                config_dir = GLib.Environment.get_user_config_dir ();
1877.4.1 by Jeremy Wootten
Show user bookmarks when administrator
57
            }
58
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
59
            /*Check bookmarks file exists and in right place */
1877.4.1 by Jeremy Wootten
Show user bookmarks when administrator
60
            string filename = GLib.Path.build_filename (config_dir,
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
61
                                                        "gtk-3.0",
62
                                                        "bookmarks",
63
                                                        null);
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
64
1402.2.11 by Jeremy Wootten
Re-formatting of Sidebar.vala, only monitor native bookmarks
65
            var file = GLib.File.new_for_path (filename);
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
66
            if (!file.query_exists (null)) {
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
67
                /* Bookmarks file does not exist in right place  ... create a new one */
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
68
                try {
69
                    file.get_parent ().make_directory_with_parents (null);
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
70
                }
71
                catch (GLib.Error error) {
72
                    /* Probably already exists */
73
                    warning ("Could not create bookmarks directory: %s", error.message);
74
                }
75
76
                try {
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
77
                    file.create (GLib.FileCreateFlags.NONE, null);
78
                }
79
                catch (GLib.Error error){
80
                    critical ("Could not create bookmarks file: %s", error.message);
81
                }
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
82
83
                /* load existing bookmarks from the old location if it exists */
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
84
                var old_filename = GLib.Path.build_filename (GLib.Environment.get_home_dir (),
85
                                                            ".gtk-bookmarks",
86
                                                            null);
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
87
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
88
                var old_file = GLib.File.new_for_path (old_filename);
89
                if (old_file.query_exists (null)) {
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
90
                    /* If there is a legacy bookmark file we copy it to the new location */
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
91
                    Marlin.BookmarkList.bookmarks_file = old_file;
92
                    load_bookmarks_file ();
93
                    Marlin.BookmarkList.bookmarks_file = file;
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
94
                } else {
95
                    /* Else populate the new file with default bookmarks */
96
                    Marlin.BookmarkList.bookmarks_file = file;
97
                    add_special_directories ();
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
98
                }
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
99
                save_bookmarks_file ();
100
            } else {
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
101
                Marlin.BookmarkList.bookmarks_file = file;
102
                load_bookmarks_file ();
103
            }
1402.2.2 by Jeremy Wootten
Added vala files
104
        }
105
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
106
       private void add_special_directory (GLib.UserDirectory user_dir, ref GLib.List<string> uris) {
107
            string? dir_s = Environment.get_user_special_dir (user_dir);
108
 
109
            if (dir_s != null)
110
                uris.prepend ("file://" + dir_s);
111
        }
112
113
       private void add_special_directories () {
114
           GLib.List<string> uris = null;
115
            add_special_directory (UserDirectory.DOCUMENTS, ref uris);
116
            add_special_directory (UserDirectory.DOWNLOAD, ref uris);
117
            add_special_directory (UserDirectory.MUSIC, ref uris);
118
            add_special_directory (UserDirectory.PUBLIC_SHARE, ref uris);
119
            add_special_directory (UserDirectory.PICTURES, ref uris);
120
            add_special_directory (UserDirectory.TEMPLATES, ref uris);
121
            add_special_directory (UserDirectory.VIDEOS, ref uris);
122
           
123
            uris.reverse ();
124
            insert_uris_at_end (uris);
125
        }
126
 
1402.2.2 by Jeremy Wootten
Added vala files
127
        public static BookmarkList get_instance () {
128
            if (instance == null)
129
                instance = new BookmarkList ();
130
131
            return instance;
132
        }
133
1643.2.34 by jeremy at elementaryos
Allow bookmarking of all mounted network locations by context menu
134
        public void insert_uri (string uri, uint index, string? label = null) {
135
            insert_item_internal (new Bookmark.from_uri (uri, label), index);
1402.2.2 by Jeremy Wootten
Added vala files
136
            save_bookmarks_file ();
137
        }
138
1643.2.34 by jeremy at elementaryos
Allow bookmarking of all mounted network locations by context menu
139
        public void insert_uri_at_end (string uri, string? label = null) {
140
            append_internal (new Bookmark.from_uri (uri, label));
1459.1.86 by jeremy at elementaryos
Implement bookmark action in context menu
141
            save_bookmarks_file ();
142
        }
143
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
144
        public void insert_uris (GLib.List<string> uris, uint index) {
2109.1.1 by jeremy at elementaryos
Fix placing of dropped bookmarks or files onto sidebar
145
            if (index > list.length ()) {
146
                critical ("Bookmarklist: Attempt to insert uri at out of range index");
147
                return;
148
            }
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
149
            uris.@foreach ((uri) => {
1402.2.2 by Jeremy Wootten
Added vala files
150
                insert_item_internal (new Bookmark.from_uri (uri, null), index);
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
151
                index++;
152
            });
1402.2.2 by Jeremy Wootten
Added vala files
153
            save_bookmarks_file ();
154
        }
155
1752.2.1 by jeremy at elementaryos
Add XDG folders by default
156
       public void insert_uris_at_end (GLib.List<string> uris) {
157
            uris.@foreach ((uri) => {
158
                append_internal (new Bookmark.from_uri (uri, null));
159
            });
160
            save_bookmarks_file ();
161
        }
162
 
1402.2.2 by Jeremy Wootten
Added vala files
163
        public bool contains (Marlin.Bookmark bm) {
164
            return (list.find_custom (bm, Marlin.Bookmark.compare_with) != null);
165
        }
166
167
        public void delete_item_at (uint index) {
168
            assert (index < list.length ());
169
            unowned GLib.List<Marlin.Bookmark> node = list.nth (index);
170
            list.remove_link (node);
171
            stop_monitoring_bookmark (node.data);
172
            save_bookmarks_file ();
173
        }
174
175
        public void delete_items_with_uri (string uri) {
176
            bool list_changed = false;
177
            unowned GLib.List<Marlin.Bookmark> node = list;
178
            unowned GLib.List<Marlin.Bookmark> next = node.next;
179
180
            for (node = list; node != null; node = next) {
181
                next = node.next;
182
                if (uri == node.data.get_uri ()) {
183
                    list.remove_link (node);
184
                    stop_monitoring_bookmark (node.data);
185
                    list_changed = true;
186
                }
187
            }
188
189
            if (list_changed)
190
                save_bookmarks_file ();
191
        }
192
193
        public uint length () {
194
            return list.length ();
195
        }
196
197
        public unowned Marlin.Bookmark? item_at (uint index) {
198
            assert (index < list.length ());
199
            return list.nth_data (index);
200
        }
201
202
        public void move_item (uint index, uint destination) {
2109.1.1 by jeremy at elementaryos
Fix placing of dropped bookmarks or files onto sidebar
203
            if (index > list.length ()) {
204
                critical ("Bookmarklist: Attempt to move bookmark from out of range index");
205
                return;
206
            }
207
208
            if (destination > list.length ()) {
209
                critical ("Bookmarklist: Attempt to move bookmark to out of range index");
210
                return;
211
            }
212
213
            if (index == destination) {
214
                return;
215
            }
1402.2.2 by Jeremy Wootten
Added vala files
216
217
            unowned GLib.List<Marlin.Bookmark> link = list.nth (index);
218
            list.remove_link (link);
219
            list.insert (link.data, (int)destination);
220
            save_bookmarks_file ();
221
        }
222
223
        private void append_internal (Marlin.Bookmark bookmark) {
224
            insert_item_internal (bookmark,-1);
225
        }
226
227
        private void insert_item_internal (Marlin.Bookmark bm, uint index) {
2112.1.1 by jeremy at elementaryos
Do not allow personal bookmarking of Home and FileSystem
228
            if (this.contains (bm)) {
229
                return;
230
            }
231
            /* Do not insert bookmark for home or filesystem root (already have builtins) */
232
            var path = bm.gof_file.location.get_path ();
1877.4.6 by Jeremy Wootten
Use getpwuid () to find real user home
233
234
            if ((path == Eel.get_real_user_home () || path == Path.DIR_SEPARATOR_S)) {
2112.1.1 by jeremy at elementaryos
Do not allow personal bookmarking of Home and FileSystem
235
                return;
236
            }
1402.2.2 by Jeremy Wootten
Added vala files
237
238
            list.insert (bm, (int)index);
239
            start_monitoring_bookmark (bm);
240
        }
241
242
        private void load_bookmarks_file () {
243
            schedule_job (JobType.LOAD);
244
        }
245
246
        private void save_bookmarks_file () {
247
            schedule_job (JobType.SAVE);
248
        }
249
250
        private void schedule_job (JobType job) {
251
            pending_ops.push_head (job);
252
            if (pending_ops.length == 1)
253
                process_next_op ();
254
        }
255
256
        private void load_bookmarks_file_async () {
257
            GLib.File file = get_bookmarks_file ();
258
            file.load_contents_async.begin (null, (obj, res) => {
259
                try {
260
                    uint8[] contents;
261
                    file.load_contents_async.end (res, out contents, null);
262
                    if (contents != null) {
263
                        bookmark_list_from_string ((string)contents);
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
264
                        this.call_when_ready = new GOF.CallWhenReady (get_gof_file_list (), files_ready);
2141.3.3 by jeremy at elementaryos
Get device info asynchronously; do not wait for bookmarked file info when loading list
265
                        contents_changed (); /* Call now to ensure sidebar is updated even if call_when_ready blocks */
1402.2.2 by Jeremy Wootten
Added vala files
266
                    }
267
                }
268
                catch (GLib.Error error) {
269
                    critical ("Error loadinging bookmark file %s", error.message);
270
                }
271
                op_processed_call_back ();
272
            });
273
        }
274
1452.1.1 by Jeremy Wootten
change return type of BookmarksList::get_gof_file_list() to unowned
275
        private unowned GLib.List<GOF.File> get_gof_file_list () {
276
            unowned GLib.List<GOF.File> files = null;
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
277
            list.@foreach ((bm) => {
278
                files.prepend (bm.gof_file);
279
            });
1452.1.1 by Jeremy Wootten
change return type of BookmarksList::get_gof_file_list() to unowned
280
            return files;
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
281
        }
282
283
        private void files_ready (GLib.List<GOF.File> files) {
2141.3.3 by jeremy at elementaryos
Get device info asynchronously; do not wait for bookmarked file info when loading list
284
            /* Sidebar does not use file.info when updating display so do not signal contents changed */
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
285
            call_when_ready = null;
286
        }
287
1402.2.2 by Jeremy Wootten
Added vala files
288
        private void bookmark_list_from_string (string contents) {
289
            list.@foreach (stop_monitoring_bookmark);
290
291
            uint count = 0;
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
292
            string [] lines = contents.split ("\n");
1402.2.2 by Jeremy Wootten
Added vala files
293
            foreach (string line in lines) {
294
                if (line[0] == '\0' || line[0] == ' ')
295
                    continue; /* ignore blank lines */
296
297
                string [] parts = line.split (" ", 2);
298
                if (parts.length == 2)
299
                    append_internal (new Marlin.Bookmark.from_uri (parts [0], parts [1]));
300
                else
301
                    append_internal (new Marlin.Bookmark.from_uri (parts [0]));
302
303
                count++;
304
            }
305
306
            list.@foreach (start_monitoring_bookmark);
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
307
1402.2.2 by Jeremy Wootten
Added vala files
308
            if (list.length () > count)
309
                /* renew bookmark that was deleted when bookmarks file was changed externally */
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
310
                save_bookmarks_file ();
1402.2.2 by Jeremy Wootten
Added vala files
311
        }
312
313
        private void save_bookmarks_file_async () {
314
            GLib.File file = get_bookmarks_file ();
315
            StringBuilder sb = new StringBuilder ();
316
317
            list.@foreach ((bookmark) => {
318
                sb.append (bookmark.get_uri ());
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
319
                sb.append (" " + bookmark.label);
1402.2.2 by Jeremy Wootten
Added vala files
320
                sb.append ("\n");
321
            });
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
322
1402.2.2 by Jeremy Wootten
Added vala files
323
            file.replace_contents_async.begin (sb.data,
324
                                               null,
325
                                               false,
326
                                               GLib.FileCreateFlags.NONE,
327
                                               null,
328
                                               (obj, res) => {
329
                try {
330
                    file.replace_contents_async.end (res, null);
331
                    contents_changed ();
332
                }
333
                catch (GLib.Error error) {
2033.6.1 by jeremy at elementaryos
Fix and refactor rename; remove need for custom gtk.vapi
334
                    warning ("Error replacing bookmarks file contents %s", error.message);
1402.2.2 by Jeremy Wootten
Added vala files
335
                }
336
                op_processed_call_back ();
337
            });
338
        }
339
340
        private static GLib.File get_bookmarks_file () {
1402.2.10 by Jeremy Wootten
Fix error in initializing FREE_SPACE and DISK_SIZE columns, some refactoring
341
            return Marlin.BookmarkList.bookmarks_file;
1402.2.2 by Jeremy Wootten
Added vala files
342
        }
343
344
345
        private void bookmarks_file_changed_call_back (GLib.File file,
346
                                                       GLib.File? other_file,
347
                                                       GLib.FileMonitorEvent event_type) {
348
349
            if (event_type == GLib.FileMonitorEvent.CHANGED
350
                || event_type == GLib.FileMonitorEvent.CREATED)
351
                load_bookmarks_file ();
352
        }
353
354
        private void bookmark_in_list_changed_callback (Marlin.Bookmark bookmark) {
355
            save_bookmarks_file ();
356
        }
357
358
        private void bookmark_in_list_to_be_deleted_callback (Marlin.Bookmark bookmark) {
359
            delete_items_with_uri (bookmark.get_uri ());
360
        }
361
362
        private  void start_monitoring_bookmarks_file () {
363
            GLib.File file = get_bookmarks_file ();
364
            try {
365
                monitor = file.monitor (GLib.FileMonitorFlags.SEND_MOVED, null);
366
                monitor.set_rate_limit (1000);
367
                monitor.changed.connect (bookmarks_file_changed_call_back);
368
            }
369
            catch (GLib.Error error) {
370
                warning ("Error starting to monitor bookmarks file: %s", error.message);
371
            }
372
        }
373
374
        private void stop_monitoring_bookmarks_file () {
375
            if (monitor == null)
376
                return;
377
378
            monitor.cancel ();
379
            monitor.changed.disconnect (bookmarks_file_changed_call_back);
380
            monitor = null;
381
        }
382
383
        private void start_monitoring_bookmark (Marlin.Bookmark bookmark) {
384
            bookmark.contents_changed.connect (bookmark_in_list_changed_callback);
385
            bookmark.deleted.connect (bookmark_in_list_to_be_deleted_callback);
386
387
        }
388
        private void stop_monitoring_bookmark (Marlin.Bookmark bookmark) {
389
            bookmark.contents_changed.disconnect (bookmark_in_list_changed_callback);
390
            bookmark.deleted.disconnect (bookmark_in_list_to_be_deleted_callback);
391
        }
392
393
        private void process_next_op () {
394
            stop_monitoring_bookmarks_file ();
395
            var pending = pending_ops.pop_tail ();
1402.2.11 by Jeremy Wootten
Re-formatting of Sidebar.vala, only monitor native bookmarks
396
            /* if job is SAVE then subsequent pending saves and loads are redundant
397
             * if job is LOAD then any pending changes requiring saving will be lost
398
             * so we can clear pending jobs */
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
399
            pending_ops.clear();
400
            /* block queue until job processed */
401
            pending_ops.push_head (pending);
402
1402.2.2 by Jeremy Wootten
Added vala files
403
            switch (pending) {
404
                case JobType.LOAD:
405
                    load_bookmarks_file_async ();
406
                    break;
407
                case JobType.SAVE:
408
                    save_bookmarks_file_async ();
409
                    break;
410
                default:
411
                    warning (@"Invalid booklist operation");
412
                    op_processed_call_back ();
413
                    break;
414
            }
415
        }
416
417
        private void op_processed_call_back () {
418
            pending_ops.pop_tail (); /* remove job just completed */
419
            if (!pending_ops.is_empty ())
420
                process_next_op ();
1402.2.5 by Jeremy Wootten
Clean up code; zoom not working
421
            else
1402.2.2 by Jeremy Wootten
Added vala files
422
                start_monitoring_bookmarks_file ();
423
        }
424
    }
1495.2.5 by Jeremy Wootten
Prevent simultaneous and duplicate mount/unmounts
425
}