462 lines
13 KiB
C
462 lines
13 KiB
C
/* ide-language-defaults.c
|
|
*
|
|
* Copyright 2015-2019 Christian Hergert <christian@hergert.me>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "ide-language-defaults"
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <glib/gi18n.h>
|
|
#include <libide-threading.h>
|
|
|
|
#include "ide-language-defaults.h"
|
|
|
|
#define SCHEMA_ID "org.gnome.builder.editor.language"
|
|
#define PATH_BASE "/org/gnome/builder/editor/language/"
|
|
|
|
static gboolean initialized;
|
|
static gboolean initializing;
|
|
static GList *tasks;
|
|
|
|
G_LOCK_DEFINE (lock);
|
|
|
|
static gboolean
|
|
strv_equal (gchar **a,
|
|
gchar **b)
|
|
{
|
|
if (a == b)
|
|
return TRUE;
|
|
else if (!a && b)
|
|
return FALSE;
|
|
else if (a && !b)
|
|
return FALSE;
|
|
|
|
for (;;)
|
|
{
|
|
if (*a == NULL && *b == NULL)
|
|
return TRUE;
|
|
|
|
if (*a == NULL || *b == NULL)
|
|
return FALSE;
|
|
|
|
if (g_strcmp0 (*a, *b) == 0)
|
|
continue;
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ide_language_defaults_migrate (GKeyFile *key_file,
|
|
gint current_version,
|
|
gint new_version,
|
|
GError **error)
|
|
{
|
|
gchar **groups;
|
|
gsize n_groups = 0;
|
|
|
|
g_assert (key_file);
|
|
g_assert (current_version >= 0);
|
|
g_assert (current_version >= 0);
|
|
g_assert (new_version > current_version);
|
|
|
|
groups = g_key_file_get_groups (key_file, &n_groups);
|
|
|
|
for (gsize i = 0; i < n_groups; i++)
|
|
{
|
|
const gchar *group = groups [i];
|
|
g_autoptr(GSettings) settings = NULL;
|
|
g_autofree gchar *lang_path = NULL;
|
|
gchar **keys;
|
|
gsize n_keys = 0;
|
|
|
|
g_assert (group != NULL);
|
|
|
|
if (g_str_equal (group, "global"))
|
|
continue;
|
|
|
|
lang_path = g_strdup_printf (PATH_BASE"%s/", group);
|
|
g_assert (lang_path);
|
|
|
|
settings = g_settings_new_with_path (SCHEMA_ID, lang_path);
|
|
g_assert (G_IS_SETTINGS (settings));
|
|
|
|
keys = g_key_file_get_keys (key_file, group, &n_keys, NULL);
|
|
g_assert (keys);
|
|
|
|
for (gsize j = 0; j < n_keys; j++)
|
|
{
|
|
const gchar *key = keys [j];
|
|
g_autoptr(GVariant) default_value = NULL;
|
|
|
|
g_assert (key);
|
|
|
|
default_value = g_settings_get_default_value (settings, key);
|
|
g_assert (default_value);
|
|
|
|
/*
|
|
* For all of the variant types we support, check to see if the value
|
|
* is matching the default schema value. If so, update the key to the
|
|
* new override value.
|
|
*
|
|
* This will not overwrite any change settings for files that the
|
|
* user has previously loaded, but will for others. Overriding things
|
|
* we have overriden gets pretty nasty, since we change things out
|
|
* from under the user.
|
|
*
|
|
* That may change in the future, but not today.
|
|
*/
|
|
|
|
if (g_variant_is_of_type (default_value, G_VARIANT_TYPE_STRING))
|
|
{
|
|
g_autofree gchar *current_str = NULL;
|
|
g_autofree gchar *override_str = NULL;
|
|
const gchar *default_str;
|
|
|
|
default_str = g_variant_get_string (default_value, NULL);
|
|
current_str = g_settings_get_string (settings, key);
|
|
override_str = g_key_file_get_string (key_file, group, key, NULL);
|
|
|
|
if (g_strcmp0 (default_str, current_str) == 0)
|
|
g_settings_set_string (settings, key, override_str);
|
|
}
|
|
else if (g_variant_is_of_type (default_value, G_VARIANT_TYPE_BOOLEAN))
|
|
{
|
|
gboolean current_bool;
|
|
gboolean override_bool;
|
|
gboolean default_bool;
|
|
|
|
default_bool = g_variant_get_boolean (default_value);
|
|
current_bool = g_settings_get_boolean (settings, key);
|
|
override_bool = g_key_file_get_boolean (key_file, group, key, NULL);
|
|
|
|
if (default_bool == current_bool)
|
|
g_settings_set_boolean (settings, key, override_bool);
|
|
}
|
|
else if (g_variant_is_of_type (default_value, G_VARIANT_TYPE_INT32))
|
|
{
|
|
gint32 current_int32;
|
|
gint32 override_int32;
|
|
gint32 default_int32;
|
|
|
|
default_int32 = g_variant_get_int32 (default_value);
|
|
current_int32 = g_settings_get_int (settings, key);
|
|
override_int32 = g_key_file_get_integer (key_file, group, key, NULL);
|
|
|
|
if (default_int32 == current_int32)
|
|
g_settings_set_int (settings, key, override_int32);
|
|
}
|
|
else if (g_variant_is_of_type (default_value, G_VARIANT_TYPE_STRING_ARRAY))
|
|
{
|
|
g_auto(GStrv) current_strv = NULL;
|
|
g_auto(GStrv) override_strv = NULL;
|
|
g_autofree const gchar **default_strv = NULL;
|
|
|
|
default_strv = g_variant_get_strv (default_value, NULL);
|
|
current_strv = g_settings_get_strv (settings, key);
|
|
override_strv = g_key_file_get_string_list (key_file, group, key, NULL, NULL);
|
|
|
|
if (strv_equal ((gchar **)default_strv, current_strv))
|
|
g_settings_set_strv (settings, key, (const gchar * const *)override_strv);
|
|
}
|
|
else
|
|
{
|
|
g_error ("Teach me about variant type: %s",
|
|
g_variant_get_type_string (default_value));
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
ide_language_defaults_get_current_version (const gchar *path,
|
|
GError **error)
|
|
{
|
|
GError *local_error = NULL;
|
|
g_autofree gchar *contents = NULL;
|
|
gsize length = 0;
|
|
gint64 version;
|
|
|
|
if (!g_file_get_contents (path, &contents, &length, &local_error))
|
|
{
|
|
if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
|
|
{
|
|
g_clear_error (&local_error);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
g_propagate_error (error, local_error);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (!g_str_is_ascii (contents))
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
_("%s contained invalid ASCII"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if ((length == 0) || (contents [0] == '\0'))
|
|
return 0;
|
|
|
|
version = g_ascii_strtoll (contents, NULL, 10);
|
|
|
|
if ((version < 0) || (version >= G_MAXINT))
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
_("Failed to parse integer from “%s”"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
return version;
|
|
}
|
|
|
|
static GBytes *
|
|
ide_language_defaults_get_defaults (GError **error)
|
|
{
|
|
return g_resources_lookup_data ("/org/gnome/builder/file-settings/defaults.ini",
|
|
G_RESOURCE_LOOKUP_FLAGS_NONE, error);
|
|
}
|
|
|
|
static void
|
|
ide_language_defaults_init_worker (IdeTask *task,
|
|
gpointer source_object,
|
|
gpointer task_data,
|
|
GCancellable *cancellable)
|
|
{
|
|
g_autofree gchar *version_path = NULL;
|
|
g_autofree gchar *version_contents = NULL;
|
|
g_autofree gchar *version_dir = NULL;
|
|
g_autoptr(GBytes) defaults = NULL;
|
|
g_autoptr(GKeyFile) key_file = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
gint global_version;
|
|
gint current_version;
|
|
gboolean ret;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_TASK (task));
|
|
g_assert (source_object == NULL);
|
|
g_assert (task_data == NULL);
|
|
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
|
|
version_path = g_build_filename (g_get_user_config_dir (),
|
|
ide_get_program_name (),
|
|
"syntax",
|
|
".defaults",
|
|
NULL);
|
|
current_version = ide_language_defaults_get_current_version (version_path, &error);
|
|
|
|
if (current_version < 0)
|
|
{
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
IDE_GOTO (failure);
|
|
}
|
|
|
|
IDE_TRACE_MSG ("Current language defaults at version %d", current_version);
|
|
|
|
defaults = ide_language_defaults_get_defaults (&error);
|
|
g_assert (defaults != NULL);
|
|
|
|
key_file = g_key_file_new ();
|
|
ret = g_key_file_load_from_data (key_file,
|
|
g_bytes_get_data (defaults, NULL),
|
|
g_bytes_get_size (defaults),
|
|
G_KEY_FILE_NONE,
|
|
&error);
|
|
|
|
if (!ret)
|
|
{
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
IDE_GOTO (failure);
|
|
}
|
|
|
|
if (!g_key_file_has_group (key_file, "global") ||
|
|
!g_key_file_has_key (key_file, "global", "version", NULL))
|
|
{
|
|
ide_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
_("language defaults missing version in [global] group."));
|
|
IDE_GOTO (failure);
|
|
}
|
|
|
|
global_version = g_key_file_get_integer (key_file, "global", "version", &error);
|
|
|
|
if (global_version == 0 && error != NULL)
|
|
{
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
IDE_GOTO (failure);
|
|
}
|
|
|
|
g_clear_error (&error);
|
|
|
|
if (global_version > current_version)
|
|
{
|
|
if (!ide_language_defaults_migrate (key_file, current_version, global_version, &error))
|
|
{
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
IDE_GOTO (failure);
|
|
}
|
|
|
|
version_contents = g_strdup_printf ("%d", global_version);
|
|
|
|
version_dir = g_path_get_dirname (version_path);
|
|
|
|
if (!g_file_test (version_dir, G_FILE_TEST_IS_DIR))
|
|
{
|
|
if (g_mkdir_with_parents (version_dir, 0750) == -1)
|
|
{
|
|
ide_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
g_io_error_from_errno (errno),
|
|
"%s", g_strerror (errno));
|
|
IDE_GOTO (failure);
|
|
}
|
|
}
|
|
|
|
IDE_TRACE_MSG ("Writing new language defaults version to \"%s\"", version_path);
|
|
|
|
if (!g_file_set_contents (version_path, version_contents, -1, &error))
|
|
{
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
IDE_GOTO (failure);
|
|
}
|
|
}
|
|
|
|
ide_task_return_boolean (task, TRUE);
|
|
|
|
{
|
|
GList *list;
|
|
GList *iter;
|
|
|
|
G_LOCK (lock);
|
|
|
|
initializing = FALSE;
|
|
initialized = TRUE;
|
|
|
|
list = tasks;
|
|
tasks = NULL;
|
|
|
|
G_UNLOCK (lock);
|
|
|
|
for (iter = list; iter; iter = iter->next)
|
|
{
|
|
ide_task_return_boolean (iter->data, TRUE);
|
|
g_object_unref (iter->data);
|
|
}
|
|
|
|
g_list_free (list);
|
|
}
|
|
|
|
IDE_EXIT;
|
|
|
|
failure:
|
|
{
|
|
GList *list;
|
|
GList *iter;
|
|
|
|
G_LOCK (lock);
|
|
|
|
initializing = FALSE;
|
|
initialized = TRUE;
|
|
|
|
list = tasks;
|
|
tasks = NULL;
|
|
|
|
G_UNLOCK (lock);
|
|
|
|
for (iter = list; iter; iter = iter->next)
|
|
{
|
|
ide_task_return_new_error (iter->data,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_FAILED,
|
|
_("Failed to initialize defaults."));
|
|
g_object_unref (iter->data);
|
|
}
|
|
|
|
g_list_free (list);
|
|
}
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
void
|
|
ide_language_defaults_init_async (GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(IdeTask) task = NULL;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
|
|
task = ide_task_new (NULL, cancellable, callback, user_data);
|
|
|
|
G_LOCK (lock);
|
|
|
|
if (initialized)
|
|
{
|
|
ide_task_return_boolean (task, TRUE);
|
|
}
|
|
else if (initializing)
|
|
{
|
|
tasks = g_list_prepend (tasks, g_object_ref (task));
|
|
}
|
|
else
|
|
{
|
|
initializing = TRUE;
|
|
ide_task_run_in_thread (task, ide_language_defaults_init_worker);
|
|
}
|
|
|
|
G_UNLOCK (lock);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
gboolean
|
|
ide_language_defaults_init_finish (GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
IdeTask *task = (IdeTask *)result;
|
|
gboolean ret;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_val_if_fail (IDE_IS_TASK (task), FALSE);
|
|
|
|
ret = ide_task_propagate_boolean (task, error);
|
|
|
|
IDE_RETURN (ret);
|
|
}
|