File: ContractorProxy.vala

package info (click to toggle)
granite 6.2.0-6
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,748 kB
  • sloc: python: 10; makefile: 8
file content (328 lines) | stat: -rw-r--r-- 12,256 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
/*
 * Copyright 2011-2013 Lucas Baudin <xapantu@gmail.com>
 * Copyright 2011-2013 Akshay Shekher <voldyman666@gmail.com>
 * Copyright 2011-2013 Victor Martinez <victoreduardm@gmail.com>
 * SPDX-License-Identifier: LGPL-3.0-or-later
 */

namespace Granite.Services {
    /**
     * Interface for executing and accessing properties of Contractor actions
     */
    public interface Contract : Object {

        /**
         * Returns the display name of the contract, already internationalized
         *
         * @return The internationalized value of the 'Name' key in the .contract file.
         * As of 2014, Contractor uses gettext to handle internationalization.
         */
        public abstract string get_display_name ();

        /**
         * Returns the description of the contract, already internationalized
         *
         * @return The internationalized value of the 'Description' key in the .contract file.
         * As of 2014, Contractor uses gettext to handle internationalization.
         */
        public abstract string get_description ();

        /**
         * Returns an icon for this contract
         *
         * @return {@link GLib.Icon} based on the 'Icon' key in the .contract file.
         */
        public abstract Icon get_icon ();

        /**
         * Executes the action on the given file
         */
        public abstract void execute_with_file (File file) throws Error;

        /**
         * Executes the action on the given list of files
         */
        public abstract void execute_with_files (File[] files) throws Error;
    }

    /**
     * thrown by {@link Granite.Services.ContractorProxy}
     */
    public errordomain ContractorError {
        /**
         * Usually means that Contractor is not installed or not configured properly
         *
         * Contractor is not a compile-time dependency, so it is possible to
         * install an application that uses it without installing Contractor.
         *
         * Upon receiving this error the application should disable its Contractor-related
         * functionality, which typically means hiding the relevant UI elements.
         */
        SERVICE_NOT_AVAILABLE
    }

    internal struct ContractData {
        string id;
        string display_name;
        string description;
        string icon;
    }

    [DBus (name = "org.elementary.Contractor")]
    internal interface ContractorDBusAPI : Object {
        public signal void contracts_changed ();

        public abstract ContractData[] list_all_contracts () throws Error;
        public abstract ContractData[] get_contracts_by_mime (string mime_type) throws Error;
        public abstract ContractData[] get_contracts_by_mimelist (string[] mime_types) throws Error;
        public abstract void execute_with_uri (string id, string uri) throws Error;
        public abstract void execute_with_uri_list (string id, string[] uri) throws Error;
    }

    /**
     * Provides a convenient GObject wrapper around Contractor's D-bus API
     */
    public class ContractorProxy : Object {
        private class GenericContract : Object, Contract {
            public string id { get; private set; }

            private string display_name;
            private string description;
            private string icon_key;

            private Icon icon;

            public GenericContract (ContractData data) {
                icon_key = "";
                update_data (data);
            }

            public void update_data (ContractData data) {
                id = data.id ?? "";
                display_name = data.display_name ?? "";
                description = data.description ?? "";

                if (icon_key != data.icon) {
                    icon_key = data.icon ?? "";
                    icon = null;
                }
            }

            public string get_display_name () {
                return display_name;
            }

            public string get_description () {
                return description;
            }

            public Icon get_icon () {
                if (icon == null) {
                    if (Path.is_absolute (icon_key))
                        icon = new FileIcon (File.new_for_path (icon_key));
                    else
                        icon = new ThemedIcon.with_default_fallbacks (icon_key);
                }

                return icon;
            }

            public void execute_with_file (File file) throws Error {
                ContractorProxy.execute_with_uri (id, file.get_uri ());
            }

            public void execute_with_files (File[] files) throws Error {
                string[] uris = new string[0];

                foreach (var file in files)
                    uris += file.get_uri ();

                ContractorProxy.execute_with_uri_list (id, uris);
            }
        }

        /**
         * Emitted when the list of actions available to Contractor changes.
         * Application should generally request the updated list of actions upon receiving this signal.
         *
         * This is not obligatory for frequently updated lists (e.g. in context menus), 
         * but essential for applications that display action lists without re-requesting them.
         */
        public signal void contracts_changed ();

        private static ContractorDBusAPI contractor_dbus;
        private static Gee.HashMap<string, GenericContract> contracts;
        private static ContractorProxy instance;

        private ContractorProxy () throws Error {
            ensure ();
        }

        public static ContractorProxy get_instance () throws Error {
            if (instance == null)
                instance = new ContractorProxy ();
            return instance;
        }

        private static void ensure () throws Error {
            if (contractor_dbus == null) {
                try {
                    contractor_dbus = Bus.get_proxy_sync (BusType.SESSION,
                                                          "org.elementary.Contractor",
                                                          "/org/elementary/contractor");
                    contractor_dbus.contracts_changed.connect (on_contracts_changed);
                } catch (IOError e) {
                    throw new ContractorError.SERVICE_NOT_AVAILABLE (e.message);
                }
            }

            if (contracts == null)
                contracts = new Gee.HashMap<string, GenericContract> ();
        }

        private static void on_contracts_changed () {
            try {
                var all_contracts = get_all_contracts ();
                var to_remove = new Gee.LinkedList<GenericContract> ();

                // Remove contracts no longer present in the system.
                // get_all_contracts already provided references to the contracts
                // that have not been removed, so those are kept.
                foreach (var contract in contracts.values) {
                    if (!all_contracts.contains (contract))
                        to_remove.add (contract);
                }

                foreach (var contract in to_remove)
                    contracts.unset (contract.id);

                int diff = contracts.size - all_contracts.size;

                if (diff < 0)
                    critical ("Failed to add %d contracts.", diff);
                else if (diff > 0)
                    critical ("Failed to remove %d contracts.", diff);

                if (instance != null)
                    instance.contracts_changed ();
            } catch (Error err) {
                warning ("Could not process changes in contracts: %s", err.message);
            }
        }

        private static void execute_with_uri (string id, string uri) throws Error {
            ensure ();
            contractor_dbus.execute_with_uri (id, uri);
        }

        private static void execute_with_uri_list (string id, string[] uris) throws Error {
            ensure ();
            contractor_dbus.execute_with_uri_list (id, uris);
        }

        /**
         * Provides all the contracts.
         *
         * @return {@link Gee.List} containing all the contracts available in the system.
         */
        public static Gee.List<Contract> get_all_contracts () throws Error {
            ensure ();

            var data = contractor_dbus.list_all_contracts ();

            return get_contracts_from_data (data);
        }

        /**
         * Returns actions (contracts) applicable to the given mimetypes.
         *
         * @param mime_type Mimetype of file.
         * @return {@link Gee.List} of contracts that support the given mimetype.
         */
        public static Gee.List<Contract> get_contracts_by_mime (string mime_type) throws Error {
            ensure ();

            var data = contractor_dbus.get_contracts_by_mime (mime_type);

            return get_contracts_from_data (data);
        }

        /**
         * Returns actions (contracts) applicable to all given mimetypes.
         *
         * Only the contracts that support all of the mimetypes are returned.
         *
         * @param mime_types Array of mimetypes.
         * @return {@link Gee.List} of contracts that support the given mimetypes.
         */
        public static Gee.List<Contract> get_contracts_by_mimelist (string[] mime_types) throws Error {
            ensure ();

            var data = contractor_dbus.get_contracts_by_mimelist (mime_types);

            return get_contracts_from_data (data);
        }

        /**
         * Returns actions (contracts) applicable to the given file.
         *
         * Errors occurring in {@link GLib.File.query_info} method while looking up
         * the file (e.g. if the file is deleted) are forwarded to the caller.
         *
         * @param file An existing file.
         * @return {@link Gee.List} of contracts applicable to the given file.
         */
        public static Gee.List<Contract> get_contracts_for_file (File file) throws Error {
            File[] files = { file };
            return get_contracts_for_files (files);
        }

        /**
         * Returns actions (contracts) applicable to all given files.
         *
         * Only the contracts that support all of the files are returned.<<BR>>
         * Errors occurring in {@link GLib.File.query_info} method while looking up
         * the file (e.g. if the file is deleted) are forwarded to the caller.<<BR>>
         *
         * @param files Array of existing files.
         * @return {@link Gee.List} of contracts applicable to any of the given files.
         */
        public static Gee.List<Contract> get_contracts_for_files (File[] files) throws Error {
            var mime_types = new Gee.HashSet<string> (); //for automatic deduplication

            foreach (var file in files) {
                var content_type = file.query_info (FileAttribute.STANDARD_CONTENT_TYPE,
                                                    FileQueryInfoFlags.NONE).get_content_type ();
                mime_types.add (ContentType.get_mime_type (content_type));
            }

            return get_contracts_by_mimelist (mime_types.to_array ());
        }

        private static Gee.List<Contract> get_contracts_from_data (ContractData[] data) {
            var contract_list = new Gee.LinkedList<Contract> ();

            if (data != null) {
                foreach (var contract_data in data) {
                    string contract_id = contract_data.id;

                    // See if we have a contract already. Otherwise create a new one.
                    // We do this in order to be able to compare contracts by reference
                    // from client code.
                    var contract = contracts.get (contract_id);

                    if (contract == null) {
                        contract = new GenericContract (contract_data);
                        contracts.set (contract_id, contract);
                    } else {
                        contract.update_data (contract_data);
                    }

                    contract_list.add (contract);
                }
            }

            return contract_list;
        }
    }
}