2
* Copyright (C) 2011 Elementary Developers
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.
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.
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/>.
17
* Author: ammonkey <am.monkeyd@gmail.com>
21
* ContractFileService heavily inspired from Synapse DesktopFileService.
22
* Kudos to the Synapse's developpers !
29
[DBus (name = "org.elementary.contractor")]
30
public class Contractor : Object {
32
/*application_id = "org.elementary.contractor";
33
flags = GLib.ApplicationFlags.IS_SERVICE;*/
37
private ContractFileService cfs;
39
//protected override void startup ()
40
private void startup ()
42
cfs = new ContractFileService ();
45
private GLib.HashTable<string,string> get_filtered_entry (ContractFileInfo entry, File file)
47
GLib.HashTable<string,string> filtered_entry;
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);
55
return filtered_entry;
58
public GLib.HashTable<string,string>[] GetServicesByLocation (string strlocation, string? file_mime = null)
60
File file = File.new_for_commandline_arg (strlocation);
61
GLib.HashTable<string,string>[] filtered = null;
66
bool uri_constraint = !file.is_native ();
68
/*if (file.query_exists ()) {
69
message ("file exist");
72
if (file_mime == null || file_mime.length <= 0)
73
mimetype = query_content_type (file);
77
//message ("test path %s %s %s", file.get_path (), file.get_uri (), mimetype);
80
var list_for_all = cfs.get_contract_files_for_type ("all");
81
if (list_for_all.size > 0)
83
foreach (var entry in list_for_all)
85
if (uri_constraint && !entry.take_uri_args)
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;
92
var parent_mime = get_parent_mime (mimetype);
93
if (parent_mime != null)
95
var list_for_parent = cfs.get_contract_files_for_type (parent_mime);
96
if (list_for_parent.size > 0)
98
foreach (var entry in list_for_parent)
100
if (uri_constraint && !entry.take_uri_args)
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;
108
var list_for_mimetype = cfs.get_contract_files_for_type (mimetype);
109
if (list_for_mimetype.size > 0)
111
foreach (var entry in list_for_mimetype)
113
if (uri_constraint && !entry.take_uri_args)
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;
125
private string get_exec_from_entry (ContractFileInfo cfi, File file)
127
if (cfi.take_uri_args)
128
return (cfi.exec.printf (file.get_uri ()));
130
return (cfi.exec.printf (file.get_path ()));
133
private string get_parent_mime (string mimetype)
135
string parentmime = null;
136
var arr = mimetype.split ("/", 2);
143
private string query_content_type (File file)
145
string mimetype = null;
148
var file_info = file.query_info ("standard::content-type", FileQueryInfoFlags.NONE);
149
mimetype = file_info.get_content_type ();
151
warning ("file query_info error %s: %s\n", file.get_uri (), e.message);
158
public class ContractFileInfo: Object
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; }
168
public string filename { get; construct set; }
169
public bool is_valid { get; private set; default = true; }
171
private static const string GROUP = "Contractor Entry";
173
public ContractFileInfo.for_keyfile (string path, KeyFile keyfile)
175
Object (filename: path);
177
init_from_keyfile (keyfile);
180
private void init_from_keyfile (KeyFile keyfile)
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");
189
if (keyfile.has_key (GROUP, "Icon"))
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")))
197
icon_name = icon_name.substring (0, icon_name.length - 4);
203
warning ("cannot init keyfile: %s", err.message);
210
public class ContractFileService : Object
212
//private static unowned ContractFileService? cfservice;
213
private File directory;
214
private FileMonitor monitor = null;
216
private Gee.List<ContractFileInfo> all_contract_files;
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;
222
public bool initialized { get; private set; default = false; }
224
public signal void initialization_done ();
226
public ContractFileService ()
228
all_contract_files = new Gee.ArrayList<ContractFileInfo> ();
232
private async void initialize ()
234
yield load_all_contract_files ();
236
initialization_done ();
239
private async void load_all_contract_files (bool should_monitor=true)
241
Gee.Set<File> contract_file_dirs = new Gee.HashSet<File> ();
243
directory = File.new_for_path ("/usr/share/contractor/");
244
yield process_directory (directory, contract_file_dirs);
248
if (should_monitor) {
250
monitor = directory.monitor_directory (0);
251
} catch (IOError e) {
252
error ("directory monitor failed: %s", e.message);
254
monitor.changed.connect (contract_file_directory_changed);
258
private async void process_directory (File directory,
259
Gee.Set<File> monitored_dirs)
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,
267
var files = yield enumerator.next_files_async (1024, 0);
268
foreach (var f in files)
270
unowned string name = f.get_name ();
271
if (f.get_file_type () == FileType.REGULAR && name.has_suffix (".contract"))
273
yield load_contract_file (directory.get_child (name));
274
message ("found: %s", name);
277
} catch (Error err) {
278
warning ("%s", err.message);
282
private async void load_contract_file (File file)
287
bool success = yield file.load_contents_async (null,
288
out contents, out len);
292
var keyfile = new KeyFile ();
293
keyfile.load_from_data (contents, len, 0);
294
var cfi = new ContractFileInfo.for_keyfile (file.get_path (), keyfile);
297
all_contract_files.add (cfi);
300
} catch (Error err) {
301
warning ("%s", err.message);
305
private void create_maps ()
307
// create mimetype maps
309
new Gee.HashMap<unowned string, Gee.List<ContractFileInfo> > ();
312
new Gee.HashMap<string, Gee.List<ContractFileInfo> > ();
313
// and desktop id map
315
new Gee.HashMap<string, ContractFileInfo> ();
319
exec_re = new Regex ("%[fFuU]");
320
} catch (Error err) {
321
critical ("%s", err.message);
325
foreach (var cfi in all_contract_files)
327
//message ("create_map %s", cfi.name);
330
string[] parameter = null;
331
MatchInfo info = null;
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);
339
var argt = parameter[0];
340
if (argt == "%u" || argt == "%f")
341
cfi.take_multi_args = false;
343
cfi.take_multi_args = true;
344
if (argt == "%u" || argt == "%U")
345
cfi.take_uri_args = true;
347
cfi.take_uri_args = false;
348
//cfi.args = parameter[0];
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);
356
exec = exec.strip ();
359
Gee.List<ContractFileInfo>? exec_list = exec_map[exec];
360
if (exec_list == null)
362
exec_list = new Gee.ArrayList<ContractFileInfo> ();
363
exec_map[exec] = exec_list;
367
// update contract id map
368
contract_id_map[Path.get_basename (cfi.filename)] = cfi;
370
// update mimetype map
371
if (cfi.mime_types == null) continue;
373
foreach (unowned string mime_type in cfi.mime_types)
375
Gee.List<ContractFileInfo>? list = mimetype_map[mime_type];
378
list = new Gee.ArrayList<ContractFileInfo> ();
379
mimetype_map[mime_type] = list;
386
private uint timer_id = 0;
388
private void contract_file_directory_changed (File file, File? other_file, FileMonitorEvent event)
390
//message ("file_directory_changed");
393
Source.remove (timer_id);
396
timer_id = Timeout.add (1000, () =>
399
reload_contract_files ();
404
private async void reload_contract_files ()
406
debug ("Reloading contract files...");
407
all_contract_files.clear ();
408
yield load_all_contract_files (false);
411
private void add_cfi_for_mime (string mime, Gee.Set<ContractFileInfo> ret)
413
var cfis = mimetype_map[mime];
414
if (cfis != null) ret.add_all (cfis);
417
public Gee.List<ContractFileInfo> get_contract_files_for_type (string mime_type)
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);
428
void on_bus_aquired (DBusConnection conn) {
430
conn.register_object ("/org/elementary/contractor", new Contractor ());
431
} catch (IOError e) {
432
error ("Could not register service\n");
436
public static int main (string[] args) {
437
//var app = new Contractor ();
439
Bus.own_name (BusType.SESSION, "org.elementary.contractor", BusNameOwnerFlags.NONE,
442
() => error ("Could not aquire name\n"));
444
new MainLoop ().run ();