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;
}
}
}
|