/*
* Copyright 2013 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3, as published
* by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranties of
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see .
*
* Authors:
* Ted Gould
*/
#include
#include "helpers.h"
/* Take an app ID and validate it and then break it up
and spit it out. These are newly allocated strings */
gboolean
app_id_to_triplet (const gchar * app_id, gchar ** package, gchar ** application, gchar ** version)
{
/* 'Parse' the App ID */
gchar ** app_id_segments = g_strsplit(app_id, "_", 4);
if (g_strv_length(app_id_segments) != 3) {
g_debug("Unable to parse Application ID: %s", app_id);
g_strfreev(app_id_segments);
return FALSE;
}
if (package != NULL) {
*package = app_id_segments[0];
} else {
g_free(app_id_segments[0]);
}
if (application != NULL) {
*application = app_id_segments[1];
} else {
g_free(app_id_segments[1]);
}
if (version != NULL) {
*version = app_id_segments[2];
} else {
g_free(app_id_segments[2]);
}
g_free(app_id_segments);
return TRUE;
}
/* Take a manifest, parse it, find the application and
and then return the path to the desktop file */
gchar *
manifest_to_desktop (const gchar * app_dir, const gchar * app_id)
{
gchar * package = NULL;
gchar * application = NULL;
gchar * version = NULL;
JsonParser * parser = NULL;
GError * error = NULL;
gchar * desktoppath = NULL;
if (!app_id_to_triplet(app_id, &package, &application, &version)) {
g_warning("Unable to parse triplet: %s", app_id);
return NULL;
}
gchar * manifestfile = g_strdup_printf("%s.manifest", package);
gchar * manifestpath = g_build_filename(app_dir, ".click", "info", manifestfile, NULL);
g_free(manifestfile);
if (!g_file_test(manifestpath, G_FILE_TEST_EXISTS)) {
g_warning("Unable to find manifest file: %s", manifestpath);
goto manifest_out;
}
parser = json_parser_new();
json_parser_load_from_file(parser, manifestpath, &error);
if (error != NULL) {
g_warning("Unable to load manifest file '%s': %s", manifestpath, error->message);
g_error_free(error);
goto manifest_out;
}
JsonNode * root = json_parser_get_root(parser);
if (json_node_get_node_type(root) != JSON_NODE_OBJECT) {
g_warning("Manifest '%s' doesn't start with an object", manifestpath);
goto manifest_out;
}
JsonObject * rootobj = json_node_get_object(root);
if (!json_object_has_member(rootobj, "version")) {
g_warning("Manifest '%s' doesn't have a version", manifestpath);
goto manifest_out;
}
if (g_strcmp0(json_object_get_string_member(rootobj, "version"), version) != 0) {
g_warning("Manifest '%s' version '%s' doesn't match AppID version '%s'", manifestpath, json_object_get_string_member(rootobj, "version"), version);
goto manifest_out;
}
if (!json_object_has_member(rootobj, "hooks")) {
g_warning("Manifest '%s' doesn't have an hooks section", manifestpath);
goto manifest_out;
}
JsonObject * appsobj = json_object_get_object_member(rootobj, "hooks");
if (appsobj == NULL) {
g_warning("Manifest '%s' has an hooks section that is not a JSON object", manifestpath);
goto manifest_out;
}
if (!json_object_has_member(appsobj, application)) {
g_warning("Manifest '%s' doesn't have the application '%s' defined", manifestpath, application);
goto manifest_out;
}
JsonObject * appobj = json_object_get_object_member(appsobj, application);
if (appobj == NULL) {
g_warning("Manifest '%s' has a definition for application '%s' that is not an object", manifestpath, application);
goto manifest_out;
}
gchar * filename = NULL;
if (json_object_has_member(appobj, "desktop")) {
filename = g_strdup(json_object_get_string_member(appobj, "desktop"));
} else {
filename = g_strdup_printf("%s.desktop", application);
}
desktoppath = g_build_filename(app_dir, filename, NULL);
g_free(filename);
if (!g_file_test(desktoppath, G_FILE_TEST_EXISTS)) {
g_warning("Application desktop file '%s' doesn't exist", desktoppath);
g_free(desktoppath);
desktoppath = NULL;
}
manifest_out:
g_clear_object(&parser);
g_free(manifestpath);
g_free(package);
g_free(application);
g_free(version);
return desktoppath;
}
/* Take a desktop file, make sure that it makes sense and
then return the exec line */
gchar *
desktop_to_exec (GKeyFile * desktop_file, const gchar * from)
{
GError * error = NULL;
if (!g_key_file_has_group(desktop_file, "Desktop Entry")) {
g_warning("Desktop file '%s' does not have a 'Desktop Entry' group", from);
return NULL;
}
gchar * type = g_key_file_get_string(desktop_file, "Desktop Entry", "Type", &error);
if (error != NULL) {
g_warning("Desktop file '%s' unable to get type: %s", from, error->message);
g_error_free(error);
g_free(type);
return NULL;
}
if (g_strcmp0(type, "Application") != 0) {
g_warning("Desktop file '%s' has a type of '%s' instead of 'Application'", from, type);
g_free(type);
return NULL;
}
g_free(type);
if (g_key_file_has_key(desktop_file, "Desktop Entry", "NoDisplay", NULL)) {
gboolean nodisplay = g_key_file_get_boolean(desktop_file, "Desktop Entry", "NoDisplay", NULL);
if (nodisplay) {
g_warning("Desktop file '%s' is set to not display, not copying", from);
return NULL;
}
}
if (g_key_file_has_key(desktop_file, "Desktop Entry", "Hidden", NULL)) {
gboolean hidden = g_key_file_get_boolean(desktop_file, "Desktop Entry", "Hidden", NULL);
if (hidden) {
g_warning("Desktop file '%s' is set to be hidden, not copying", from);
return NULL;
}
}
if (g_key_file_has_key(desktop_file, "Desktop Entry", "Terminal", NULL)) {
gboolean terminal = g_key_file_get_boolean(desktop_file, "Desktop Entry", "Terminal", NULL);
if (terminal) {
g_warning("Desktop file '%s' is set to run in a terminal, not copying", from);
return NULL;
}
}
if (!g_key_file_has_key(desktop_file, "Desktop Entry", "Exec", NULL)) {
g_warning("Desktop file '%s' has no 'Exec' key", from);
return NULL;
}
gchar * exec = g_key_file_get_string(desktop_file, "Desktop Entry", "Exec", NULL);
return exec;
}
/* Sets an upstart variable, currently using initctl */
void
set_upstart_variable (const gchar * variable, const gchar * value)
{
GError * error = NULL;
gchar * command[4] = {
"initctl",
"set-env",
NULL,
NULL
};
gchar * variablestr = g_strdup_printf("%s=%s", variable, value);
command[2] = variablestr;
g_spawn_sync(NULL, /* working directory */
command,
NULL, /* environment */
G_SPAWN_SEARCH_PATH,
NULL, NULL, /* child setup */
NULL, /* stdout */
NULL, /* stderr */
NULL, /* exit status */
&error);
if (error != NULL) {
g_warning("Unable to set variable '%s' to '%s': %s", variable, value, error->message);
g_error_free(error);
}
g_free(variablestr);
return;
}
/* Convert a URI into a file */
static gchar *
uri2file (const gchar * uri)
{
GError * error = NULL;
gchar * retval = g_filename_from_uri(uri, NULL, &error);
if (error != NULL) {
g_warning("Unable to resolve '%s' to a filename: %s", uri, error->message);
g_error_free(error);
}
if (retval == NULL) {
retval = g_strdup("");
}
g_debug("Converting URI '%s' to file '%s'", uri, retval);
return retval;
}
/* free a string in an array */
static void
free_string (gpointer value)
{
gchar ** str = (gchar **)value;
g_free(*str);
return;
}
/* Builds the file list from the URI list */
static gchar *
build_file_list (const gchar * uri_list)
{
gchar ** uri_split = g_strsplit(uri_list, " ", 0);
GArray * outarray = g_array_new(TRUE, FALSE, sizeof(gchar *));
g_array_set_clear_func(outarray, free_string);
int i;
for (i = 0; uri_split[i] != NULL; i++) {
gchar * path = uri2file(uri_split[i]);
g_array_append_val(outarray, path);
}
gchar * filelist = g_strjoinv(" ", (gchar **)outarray->data);
g_array_free(outarray, TRUE);
g_strfreev(uri_split);
return filelist;
}
/* Make sure we have the single URI variable */
static inline void
ensure_singleuri (gchar ** single_uri, const gchar * uri_list)
{
if (uri_list == NULL) {
return;
}
if (*single_uri != NULL) {
return;
}
*single_uri = g_strdup(uri_list);
g_utf8_strchr(*single_uri, -1, ' ')[0] = '\0';
return;
}
/* Make sure we have a single file variable */
static inline void
ensure_singlefile (gchar ** single_file, gchar ** single_uri, const gchar * uri_list)
{
if (uri_list == NULL) {
return;
}
if (*single_file != NULL) {
return;
}
ensure_singleuri(single_uri, uri_list);
if (single_uri != NULL) {
*single_file = uri2file(*single_uri);
}
return;
}
/* Parse a desktop exec line and return the next string */
gchar *
desktop_exec_parse (const gchar * execline, const gchar * uri_list)
{
gchar ** execsplit = g_strsplit(execline, "%", 0);
/* If we didn't have any codes, just exit here */
if (execsplit[1] == NULL) {
g_strfreev(execsplit);
return g_strdup(execline);
}
if (uri_list != NULL && uri_list[0] == '\0') {
uri_list = NULL;
}
int i;
gchar * single_uri = NULL;
gchar * single_file = NULL;
gchar * file_list = NULL;
gboolean previous_percent = FALSE;
GArray * outarray = g_array_new(TRUE, FALSE, sizeof(const gchar *));
g_array_append_val(outarray, execsplit[0]);
/* The variables allowed in an exec line from the Freedesktop.org Desktop
File specification: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables */
for (i = 1; execsplit[i] != NULL; i++) {
const gchar * skipchar = &(execsplit[i][1]);
/* Handle the case of %%F printing "%F" */
if (previous_percent) {
g_array_append_val(outarray, execsplit[i]);
previous_percent = FALSE;
continue;
}
switch (execsplit[i][0]) {
case '\0': {
const gchar * percent = "%";
g_array_append_val(outarray, percent); /* %% is the literal */
previous_percent = TRUE;
break;
}
case 'd':
case 'D':
case 'n':
case 'N':
case 'v':
case 'm':
/* Deprecated */
g_array_append_val(outarray, skipchar);
break;
case 'f':
ensure_singlefile(&single_file, &single_uri, uri_list);
if (single_file != NULL) {
g_array_append_val(outarray, single_file);
}
g_array_append_val(outarray, skipchar);
break;
case 'F':
if (uri_list != NULL) {
if (file_list == NULL) {
file_list = build_file_list(uri_list);
}
g_array_append_val(outarray, file_list);
}
g_array_append_val(outarray, skipchar);
break;
case 'i':
case 'c':
case 'k':
/* Perhaps? Not sure anyone uses these */
g_array_append_val(outarray, skipchar);
break;
case 'U':
if (uri_list != NULL) {
g_array_append_val(outarray, uri_list);
}
g_array_append_val(outarray, skipchar);
break;
case 'u':
ensure_singleuri(&single_uri, uri_list);
if (single_uri != NULL) {
g_array_append_val(outarray, single_uri);
}
g_array_append_val(outarray, skipchar);
break;
default:
g_warning("Desktop Exec line code '%%%c' unknown, skipping.", execsplit[i][0]);
g_array_append_val(outarray, skipchar);
break;
}
}
gchar * output = g_strjoinv(" ", (gchar **)outarray->data);
g_array_free(outarray, TRUE);
g_free(single_uri);
g_free(single_file);
g_free(file_list);
g_strfreev(execsplit);
return output;
}