~elementary-pantheon/contractor/master

« back to all changes in this revision

Viewing changes to src/contractor.vala

  • Committer: am.monkeyd@gmail.com
  • Date: 2011-07-11 07:43:23 UTC
  • Revision ID: git-v1:05b68a0db47c65cd1ff9ef89a972395ca53ffeaa
let's roll

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*  
 
2
 * Copyright (C) 2011 Elementary Developers
 
3
 * 
 
4
 * This program is free software: you can redistribute it and/or modify
 
5
 * it under the terms of the GNU General Public License as published by
 
6
 * the Free Software Foundation, either version 3 of the License, or
 
7
 * (at your option) any later version.
 
8
 * 
 
9
 * This program is distributed in the hope that it will be useful,
 
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
 * GNU General Public License for more details.
 
13
 * 
 
14
 * You should have received a copy of the GNU General Public License
 
15
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
16
 *
 
17
 * Author: ammonkey <am.monkeyd@gmail.com>
 
18
 */ 
 
19
 
 
20
/* 
 
21
 * ContractFileService heavily inspired from Synapse DesktopFileService.
 
22
 * Kudos to the Synapse's developpers ! 
 
23
 */
 
24
 
 
25
using GLib;
 
26
 
 
27
namespace Contractor
 
28
{
 
29
    [DBus (name = "org.elementary.contractor")]
 
30
    public class Contractor : Object {
 
31
        construct {
 
32
            /*application_id = "org.elementary.contractor";
 
33
              flags = GLib.ApplicationFlags.IS_SERVICE;*/
 
34
            startup ();
 
35
        }
 
36
 
 
37
        private ContractFileService cfs;
 
38
 
 
39
        //protected override void startup () 
 
40
        private void startup () 
 
41
        {
 
42
            cfs = new ContractFileService ();
 
43
        }
 
44
 
 
45
        private GLib.HashTable<string,string> get_filtered_entry (ContractFileInfo entry, File file)
 
46
        {
 
47
            GLib.HashTable<string,string> filtered_entry;
 
48
            
 
49
            filtered_entry = new GLib.HashTable<string,string> (str_hash, str_equal);
 
50
            filtered_entry.insert ("Name", entry.name);
 
51
            filtered_entry.insert ("Description", entry.description);
 
52
            filtered_entry.insert ("Exec", get_exec_from_entry (entry, file));
 
53
            filtered_entry.insert ("IconName", entry.icon_name);
 
54
 
 
55
            return filtered_entry;
 
56
        }
 
57
 
 
58
        public GLib.HashTable<string,string>[] GetServicesByLocation (string strlocation, string? file_mime = null) 
 
59
        {
 
60
            File file = File.new_for_commandline_arg (strlocation);
 
61
            GLib.HashTable<string,string>[] filtered = null;
 
62
            
 
63
            if (!cfs.initialized)
 
64
                return filtered;
 
65
            
 
66
            bool uri_constraint = !file.is_native ();
 
67
 
 
68
            /*if (file.query_exists ()) {
 
69
              message ("file exist");
 
70
              }*/
 
71
            string mimetype;
 
72
            if (file_mime == null || file_mime.length <= 0)
 
73
                mimetype = query_content_type (file);
 
74
            else
 
75
                mimetype = file_mime;
 
76
 
 
77
            //message ("test path %s %s %s", file.get_path (), file.get_uri (), mimetype);
 
78
            if (mimetype != null) 
 
79
            {
 
80
                var list_for_all = cfs.get_contract_files_for_type ("all");
 
81
                if (list_for_all.size > 0)
 
82
                {
 
83
                    foreach (var entry in list_for_all)
 
84
                    {
 
85
                        if (uri_constraint && !entry.take_uri_args) 
 
86
                            break; 
 
87
                        var filtered_entry = get_filtered_entry (entry, file);
 
88
                        debug ("desc: %s exec: %s", filtered_entry.lookup ("Description"), filtered_entry.lookup ("Exec"));
 
89
                        filtered += filtered_entry;
 
90
                    }
 
91
                }
 
92
                var parent_mime = get_parent_mime (mimetype);
 
93
                if (parent_mime != null) 
 
94
                {
 
95
                    var list_for_parent = cfs.get_contract_files_for_type (parent_mime);
 
96
                    if (list_for_parent.size > 0)
 
97
                    {
 
98
                        foreach (var entry in list_for_parent)
 
99
                        {
 
100
                            if (uri_constraint && !entry.take_uri_args) 
 
101
                                break; 
 
102
                            var filtered_entry = get_filtered_entry (entry, file);
 
103
                            debug ("desc: %s exec: %s", filtered_entry.lookup ("Description"), filtered_entry.lookup ("Exec"));
 
104
                            filtered += filtered_entry;
 
105
                        }
 
106
                    }
 
107
                }
 
108
                var list_for_mimetype = cfs.get_contract_files_for_type (mimetype);
 
109
                if (list_for_mimetype.size > 0)
 
110
                {
 
111
                    foreach (var entry in list_for_mimetype)
 
112
                    {
 
113
                        if (uri_constraint && !entry.take_uri_args) 
 
114
                            break;
 
115
                        var filtered_entry = get_filtered_entry (entry, file);
 
116
                        debug ("desc: %s exec: %s", filtered_entry.lookup ("Description"), filtered_entry.lookup ("Exec"));
 
117
                        filtered += filtered_entry;
 
118
                    }
 
119
                }
 
120
            }
 
121
 
 
122
            return filtered;
 
123
        }
 
124
 
 
125
        private string get_exec_from_entry (ContractFileInfo cfi, File file)
 
126
        {
 
127
            if (cfi.take_uri_args)
 
128
                return (cfi.exec.printf (file.get_uri ()));
 
129
            else
 
130
                return (cfi.exec.printf (file.get_path ()));
 
131
        }
 
132
 
 
133
        private string get_parent_mime (string mimetype)
 
134
        {
 
135
            string parentmime = null;
 
136
            var arr = mimetype.split ("/", 2);
 
137
            if (arr.length == 2)
 
138
                parentmime = arr[0];
 
139
 
 
140
            return parentmime;
 
141
        }
 
142
 
 
143
        private string query_content_type (File file)
 
144
        {
 
145
            string mimetype = null;
 
146
 
 
147
            try {
 
148
                var file_info = file.query_info ("standard::content-type", FileQueryInfoFlags.NONE);
 
149
                mimetype = file_info.get_content_type ();
 
150
            } catch (Error e) {
 
151
                warning ("file query_info error %s: %s\n", file.get_uri (), e.message);
 
152
            }
 
153
 
 
154
            return mimetype;
 
155
        }
 
156
    }
 
157
 
 
158
    public class ContractFileInfo: Object
 
159
    {
 
160
        public string name { get; construct set; }
 
161
        public string exec { get; set; }
 
162
        public string description { get; set; }
 
163
        public string[] mime_types = null;
 
164
        public string icon_name { get; construct set; default = ""; }
 
165
        public bool take_multi_args { get; set; }
 
166
        public bool take_uri_args { get; set; }
 
167
 
 
168
        public string filename { get; construct set; }
 
169
        public bool is_valid { get; private set; default = true; }
 
170
 
 
171
        private static const string GROUP = "Contractor Entry";
 
172
 
 
173
        public ContractFileInfo.for_keyfile (string path, KeyFile keyfile)
 
174
        {
 
175
            Object (filename: path);
 
176
 
 
177
            init_from_keyfile (keyfile);
 
178
        }
 
179
 
 
180
        private void init_from_keyfile (KeyFile keyfile)
 
181
        {
 
182
            try
 
183
            {
 
184
                name = keyfile.get_locale_string (GROUP, "Name");
 
185
                exec = keyfile.get_string (GROUP, "Exec");
 
186
                description = keyfile.get_locale_string (GROUP, "Description");
 
187
                mime_types = keyfile.get_string_list (GROUP, "MimeType");
 
188
 
 
189
                if (keyfile.has_key (GROUP, "Icon"))
 
190
                {
 
191
                    icon_name = keyfile.get_locale_string (GROUP, "Icon");
 
192
                    if (!Path.is_absolute (icon_name) &&
 
193
                        (icon_name.has_suffix (".png") ||
 
194
                         icon_name.has_suffix (".svg") ||
 
195
                         icon_name.has_suffix (".xpm")))
 
196
                    {
 
197
                        icon_name = icon_name.substring (0, icon_name.length - 4);
 
198
                    }
 
199
                }
 
200
            }
 
201
            catch (Error err)
 
202
            {
 
203
                warning ("cannot init keyfile: %s", err.message);
 
204
                is_valid = false;
 
205
            }
 
206
        }
 
207
 
 
208
    }
 
209
 
 
210
    public class ContractFileService : Object
 
211
    {
 
212
        //private static unowned ContractFileService? cfservice;
 
213
        private File directory;
 
214
        private FileMonitor monitor = null;
 
215
 
 
216
        private Gee.List<ContractFileInfo> all_contract_files;
 
217
 
 
218
        private Gee.Map<unowned string, Gee.List<ContractFileInfo> > mimetype_map;
 
219
        private Gee.Map<string, Gee.List<ContractFileInfo> > exec_map;
 
220
        private Gee.Map<string, ContractFileInfo> contract_id_map;
 
221
 
 
222
        public bool initialized { get; private set; default = false; }
 
223
 
 
224
        public signal void initialization_done ();
 
225
 
 
226
        public ContractFileService ()
 
227
        {
 
228
            all_contract_files = new Gee.ArrayList<ContractFileInfo> ();
 
229
            initialize ();
 
230
        }
 
231
 
 
232
        private async void initialize ()
 
233
        {
 
234
            yield load_all_contract_files ();
 
235
            initialized = true;
 
236
            initialization_done ();
 
237
        }
 
238
 
 
239
        private async void load_all_contract_files (bool should_monitor=true)
 
240
        {
 
241
            Gee.Set<File> contract_file_dirs = new Gee.HashSet<File> ();
 
242
 
 
243
            directory = File.new_for_path ("/usr/share/contractor/");
 
244
            yield process_directory (directory, contract_file_dirs);
 
245
 
 
246
            create_maps ();
 
247
 
 
248
            if (should_monitor) {
 
249
                try {
 
250
                    monitor = directory.monitor_directory (0);
 
251
                } catch (IOError e) {
 
252
                    error ("directory monitor failed: %s", e.message);
 
253
                }
 
254
                monitor.changed.connect (contract_file_directory_changed);
 
255
            }
 
256
        }
 
257
 
 
258
        private async void process_directory (File directory,
 
259
                                              Gee.Set<File> monitored_dirs)
 
260
        {
 
261
            try {
 
262
                /*bool exists = yield Utils.query_exists_async (directory);
 
263
                  if (!exists) return;*/
 
264
                var enumerator = yield directory.enumerate_children_async (
 
265
                                                                           FILE_ATTRIBUTE_STANDARD_NAME + "," + FILE_ATTRIBUTE_STANDARD_TYPE,
 
266
                                                                           0, 0);
 
267
                var files = yield enumerator.next_files_async (1024, 0);
 
268
                foreach (var f in files)
 
269
                {
 
270
                    unowned string name = f.get_name ();
 
271
                    if (f.get_file_type () == FileType.REGULAR && name.has_suffix (".contract"))
 
272
                    {
 
273
                        yield load_contract_file (directory.get_child (name));
 
274
                        message ("found: %s", name);
 
275
                    }
 
276
                }
 
277
            } catch (Error err) {
 
278
                warning ("%s", err.message);
 
279
            }
 
280
        }
 
281
 
 
282
        private async void load_contract_file (File file)
 
283
        {
 
284
            try {
 
285
                size_t len;
 
286
                string contents;
 
287
                bool success = yield file.load_contents_async (null, 
 
288
                                                               out contents, out len);
 
289
                
 
290
                if (success)
 
291
                {
 
292
                    var keyfile = new KeyFile ();
 
293
                    keyfile.load_from_data (contents, len, 0);
 
294
                    var cfi = new ContractFileInfo.for_keyfile (file.get_path (), keyfile);
 
295
                    if (cfi.is_valid)
 
296
                    {
 
297
                        all_contract_files.add (cfi);
 
298
                    }
 
299
                }
 
300
            } catch (Error err) {
 
301
                warning ("%s", err.message);
 
302
            }
 
303
        }
 
304
 
 
305
        private void create_maps ()
 
306
        {
 
307
            // create mimetype maps
 
308
            mimetype_map =
 
309
                new Gee.HashMap<unowned string, Gee.List<ContractFileInfo> > ();
 
310
            // and exec map
 
311
            exec_map =
 
312
                new Gee.HashMap<string, Gee.List<ContractFileInfo> > ();
 
313
            // and desktop id map
 
314
            contract_id_map =
 
315
                new Gee.HashMap<string, ContractFileInfo> ();
 
316
 
 
317
            Regex exec_re;
 
318
            try {
 
319
                exec_re = new Regex ("%[fFuU]");
 
320
            } catch (Error err) {
 
321
                critical ("%s", err.message);
 
322
                return;
 
323
            }
 
324
 
 
325
            foreach (var cfi in all_contract_files)
 
326
            {
 
327
                //message ("create_map %s", cfi.name);
 
328
                string exec = "";
 
329
 
 
330
                string[] parameter = null;
 
331
                MatchInfo info = null;
 
332
 
 
333
                try {
 
334
                    if (exec_re.match (cfi.exec, 0, out info)) {
 
335
                        parameter = info.fetch_all();
 
336
                        if (parameter.length != 1) {
 
337
                            warning ("argument definition eroned in %s", cfi.name);
 
338
                        } else {
 
339
                            var argt = parameter[0];
 
340
                            if (argt == "%u" || argt == "%f")
 
341
                                cfi.take_multi_args = false;
 
342
                            else
 
343
                                cfi.take_multi_args = true;
 
344
                            if (argt == "%u" || argt == "%U")
 
345
                                cfi.take_uri_args = true;
 
346
                            else
 
347
                                cfi.take_uri_args = false;
 
348
                            //cfi.args = parameter[0];
 
349
                        }
 
350
                    }
 
351
                    exec = exec_re.replace_literal (cfi.exec, -1, 0, "%s");
 
352
                    //message ("exec: %s", exec);
 
353
                } catch (RegexError err) {
 
354
                    error ("%s", err.message);
 
355
                }
 
356
                exec = exec.strip ();
 
357
                cfi.exec = exec;
 
358
                // update exec map
 
359
                Gee.List<ContractFileInfo>? exec_list = exec_map[exec];
 
360
                if (exec_list == null)
 
361
                {
 
362
                    exec_list = new Gee.ArrayList<ContractFileInfo> ();
 
363
                    exec_map[exec] = exec_list;
 
364
                }
 
365
                exec_list.add (cfi);
 
366
 
 
367
                // update contract id map
 
368
                contract_id_map[Path.get_basename (cfi.filename)] = cfi;
 
369
 
 
370
                // update mimetype map
 
371
                if (cfi.mime_types == null) continue;
 
372
 
 
373
                foreach (unowned string mime_type in cfi.mime_types)
 
374
                {
 
375
                    Gee.List<ContractFileInfo>? list = mimetype_map[mime_type];
 
376
                    if (list == null)
 
377
                    {
 
378
                        list = new Gee.ArrayList<ContractFileInfo> ();
 
379
                        mimetype_map[mime_type] = list;
 
380
                    }
 
381
                    list.add (cfi);
 
382
                }
 
383
            }
 
384
        }
 
385
 
 
386
        private uint timer_id = 0;
 
387
 
 
388
        private void contract_file_directory_changed (File file, File? other_file, FileMonitorEvent event)
 
389
        {
 
390
            //message ("file_directory_changed");
 
391
            if (timer_id != 0)
 
392
            {
 
393
                Source.remove (timer_id);
 
394
            }
 
395
 
 
396
            timer_id = Timeout.add (1000, () =>
 
397
            {
 
398
                timer_id = 0;
 
399
                reload_contract_files ();
 
400
                return false;
 
401
            });
 
402
        }
 
403
 
 
404
        private async void reload_contract_files ()
 
405
        {
 
406
            debug ("Reloading contract files...");
 
407
            all_contract_files.clear ();
 
408
            yield load_all_contract_files (false);
 
409
        }
 
410
 
 
411
        private void add_cfi_for_mime (string mime, Gee.Set<ContractFileInfo> ret)
 
412
        {
 
413
            var cfis = mimetype_map[mime];
 
414
            if (cfis != null) ret.add_all (cfis);
 
415
        }
 
416
 
 
417
        public Gee.List<ContractFileInfo> get_contract_files_for_type (string mime_type)
 
418
        {
 
419
            var cfi_set = new Gee.HashSet<ContractFileInfo> ();
 
420
            add_cfi_for_mime (mime_type, cfi_set);
 
421
            var ret = new Gee.ArrayList<ContractFileInfo> ();
 
422
            ret.add_all (cfi_set);
 
423
            return ret;
 
424
        }
 
425
    }
 
426
 
 
427
 
 
428
    void on_bus_aquired (DBusConnection conn) {
 
429
        try {
 
430
            conn.register_object ("/org/elementary/contractor", new Contractor ());
 
431
        } catch (IOError e) {
 
432
            error ("Could not register service\n");
 
433
        }
 
434
    }
 
435
 
 
436
    public static int main (string[] args) {
 
437
        //var app = new Contractor ();
 
438
        //app.run (args);
 
439
        Bus.own_name (BusType.SESSION, "org.elementary.contractor", BusNameOwnerFlags.NONE,
 
440
                      on_bus_aquired,
 
441
                      () => {},
 
442
                      () => error ("Could not aquire name\n"));
 
443
 
 
444
        new MainLoop ().run ();
 
445
 
 
446
        return 0;
 
447
    }
 
448
 
 
449
}