1 /* ide-buildconfig-config-provider.c 2 * 3 * Copyright 2016 Matthew Leeds <mleeds@redhat.com> 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 * SPDX-License-Identifier: GPL-3.0-or-later 19 */ 20 21 #define G_LOG_DOMAIN "ide-buildconfig-config-provider" 22 23 #include "config.h" 24 25 #include <dazzle.h> 26 #include <gio/gio.h> 27 #include <glib/gi18n.h> 28 #include <string.h> 29 30 #include <libide-core.h> 31 #include <libide-foundry.h> 32 #include <libide-threading.h> 33 34 #include "ide-buildconfig-config.h" 35 #include "ide-buildconfig-config-provider.h" 36 37 #define DOT_BUILDCONFIG ".buildconfig" 38 39 struct _IdeBuildconfigConfigProvider 40 { 41 IdeObject parent_instance; 42 43 /* 44 * A GPtrArray of IdeBuildconfigConfiguration that have been registered. 45 * We append/remove to/from this array in our default signal handler for 46 * the ::added and ::removed signals. 47 */ 48 GPtrArray *configs; 49 50 /* 51 * The GKeyFile that was parsed from disk. We keep this around so that 52 * we can persist the changes back without destroying comments. 53 */ 54 GKeyFile *key_file; 55 56 /* 57 * If we removed items from the keyfile, we need to know that so that 58 * we persist it back to disk. We only persist back to disk if this bit 59 * is set or if any of our registered configs are "dirty". 60 * 61 * We try hard to avoid writing .buildconfig files unless we know the 62 * user did something to change a config. Otherwise we would liter 63 * everyone's projects with .buildconfig files. 64 */ 65 guint key_file_dirty : 1; 66 }; 67 68 static gchar * 69 gen_next_id (const gchar *id) 70 { 71 g_auto(GStrv) parts = g_strsplit (id, "-", 0); 72 guint len = g_strv_length (parts); 73 const gchar *end; 74 guint64 n64; 75 76 if (len == 0) 77 goto add_suffix; 78 79 end = parts[len - 1]; 80 81 n64 = g_ascii_strtoull (end, (gchar **)&end, 10); 82 if (n64 == 0 || n64 == G_MAXUINT64 || *end != 0) 83 goto add_suffix; 84 85 g_free (g_steal_pointer (&parts[len -1])); 86 parts[len -1] = g_strdup_printf ("%"G_GUINT64_FORMAT, n64+1); 87 return g_strjoinv ("-", parts); 88 89 add_suffix: 90 return g_strdup_printf ("%s-2", id); 91 } 92 93 static gchar * 94 get_next_id (IdeConfigManager *manager, 95 const gchar *id) 96 { 97 g_autoptr(GPtrArray) tries = NULL; 98 99 g_assert (IDE_IS_CONFIG_MANAGER (manager)); 100 101 tries = g_ptr_array_new_with_free_func (g_free); 102 103 while (ide_config_manager_get_config (manager, id)) 104 { 105 g_autofree gchar *next = gen_next_id (id); 106 id = next; 107 g_ptr_array_add (tries, g_steal_pointer (&next)); 108 } 109 110 return g_strdup (id); 111 } 112 113 static void 114 load_string (IdeConfig *config, 115 GKeyFile *key_file, 116 const gchar *group, 117 const gchar *key, 118 const gchar *property) 119 { 120 g_assert (IDE_IS_CONFIG (config)); 121 g_assert (key_file != NULL); 122 g_assert (group != NULL); 123 g_assert (key != NULL); 124 125 if (g_key_file_has_key (key_file, group, key, NULL)) 126 { 127 g_auto(GValue) value = G_VALUE_INIT; 128 129 g_value_init (&value, G_TYPE_STRING); 130 g_value_take_string (&value, g_key_file_get_string (key_file, group, key, NULL)); 131 g_object_set_property (G_OBJECT (config), property, &value); 132 } 133 } 134 135 static void 136 load_strv (IdeConfig *config, 137 GKeyFile *key_file, 138 const gchar *group, 139 const gchar *key, 140 const gchar *property) 141 { 142 g_assert (IDE_IS_CONFIG (config)); 143 g_assert (key_file != NULL); 144 g_assert (group != NULL); 145 g_assert (key != NULL); 146 147 if (g_key_file_has_key (key_file, group, key, NULL)) 148 { 149 g_auto(GStrv) strv = NULL; 150 g_auto(GValue) value = G_VALUE_INIT; 151 152 strv = g_key_file_get_string_list (key_file, group, key, NULL, NULL); 153 g_value_init (&value, G_TYPE_STRV); 154 g_value_take_boxed (&value, g_steal_pointer (&strv)); 155 g_object_set_property (G_OBJECT (config), property, &value); 156 } 157 } 158 159 static void 160 load_environ (IdeConfig *config, 161 GKeyFile *key_file, 162 const gchar *group) 163 { 164 IdeEnvironment *environment; 165 g_auto(GStrv) keys = NULL; 166 gsize len = 0; 167 168 g_assert (IDE_IS_CONFIG (config)); 169 g_assert (key_file != NULL); 170 g_assert (group != NULL); 171 172 environment = ide_config_get_environment (config); 173 keys = g_key_file_get_keys (key_file, group, &len, NULL); 174 175 for (gsize i = 0; i < len; i++) 176 { 177 g_autofree gchar *value = NULL; 178 179 value = g_key_file_get_string (key_file, group, keys[i], NULL); 180 if (value != NULL) 181 ide_environment_setenv (environment, keys [i], value); 182 } 183 } 184 185 static IdeConfig * 186 ide_buildconfig_config_provider_create (IdeBuildconfigConfigProvider *self, 187 const gchar *config_id) 188 { 189 g_autoptr(IdeConfig) config = NULL; 190 g_autofree gchar *env_group = NULL; 191 192 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 193 g_assert (self->key_file != NULL); 194 g_assert (config_id != NULL); 195 196 config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG, 197 "id", config_id, 198 "parent", self, 199 NULL); 200 201 load_string (config, self->key_file, config_id, "config-opts", "config-opts"); 202 load_string (config, self->key_file, config_id, "name", "display-name"); 203 load_string (config, self->key_file, config_id, "run-opts", "run-opts"); 204 load_string (config, self->key_file, config_id, "runtime", "runtime-id"); 205 load_string (config, self->key_file, config_id, "toolchain", "toolchain-id"); 206 load_string (config, self->key_file, config_id, "prefix", "prefix"); 207 load_string (config, self->key_file, config_id, "app-id", "app-id"); 208 load_strv (config, self->key_file, config_id, "prebuild", "prebuild"); 209 load_strv (config, self->key_file, config_id, "postbuild", "postbuild"); 210 211 if (g_key_file_has_key (self->key_file, config_id, "builddir", NULL)) 212 { 213 if (g_key_file_get_boolean (self->key_file, config_id, "builddir", NULL)) 214 ide_config_set_locality (config, IDE_BUILD_LOCALITY_OUT_OF_TREE); 215 else 216 ide_config_set_locality (config, IDE_BUILD_LOCALITY_IN_TREE); 217 } 218 219 env_group = g_strdup_printf ("%s.environment", config_id); 220 if (g_key_file_has_group (self->key_file, env_group)) 221 load_environ (config, self->key_file, env_group); 222 223 return g_steal_pointer (&config); 224 } 225 226 static void 227 ide_buildconfig_config_provider_load_async (IdeConfigProvider *provider, 228 GCancellable *cancellable, 229 GAsyncReadyCallback callback, 230 gpointer user_data) 231 { 232 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider; 233 g_autoptr(IdeConfig) fallback = NULL; 234 g_autoptr(IdeTask) task = NULL; 235 g_autoptr(GError) error = NULL; 236 g_autofree gchar *path = NULL; 237 g_auto(GStrv) groups = NULL; 238 IdeContext *context; 239 gsize len; 240 241 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 242 g_assert (self->key_file == NULL); 243 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); 244 245 task = ide_task_new (self, cancellable, callback, user_data); 246 ide_task_set_source_tag (task, ide_buildconfig_config_provider_load_async); 247 ide_task_set_priority (task, G_PRIORITY_LOW); 248 249 self->key_file = g_key_file_new (); 250 251 /* 252 * We could do this in a thread, but it's not really worth it. We want these 253 * configs loaded ASAP, and nothing can really progress until it's loaded 254 * anyway. 255 */ 256 257 context = ide_object_get_context (IDE_OBJECT (self)); 258 path = ide_context_build_filename (context, DOT_BUILDCONFIG, NULL); 259 if (!g_file_test (path, G_FILE_TEST_IS_REGULAR)) 260 goto add_default; 261 262 if (!g_key_file_load_from_file (self->key_file, path, G_KEY_FILE_KEEP_COMMENTS, &error)) 263 { 264 g_warning ("Failed to load .buildconfig: %s", error->message); 265 goto add_default; 266 } 267 268 groups = g_key_file_get_groups (self->key_file, &len); 269 270 for (gsize i = 0; i < len; i++) 271 { 272 g_autoptr(IdeConfig) config = NULL; 273 const gchar *group = groups[i]; 274 275 if (strchr (group, '.') != NULL) 276 continue; 277 278 config = ide_buildconfig_config_provider_create (self, group); 279 ide_config_set_dirty (config, FALSE); 280 ide_config_provider_emit_added (provider, config); 281 } 282 283 if (self->configs->len > 0) 284 goto complete; 285 286 add_default: 287 /* "Default" is not translated because .buildconfig can be checked in */ 288 fallback = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG, 289 "display-name", "Default", 290 "id", "default", 291 "parent", self, 292 "runtime-id", "host", 293 "toolchain-id", "default", 294 NULL); 295 ide_config_set_dirty (fallback, FALSE); 296 ide_config_provider_emit_added (provider, fallback); 297 298 complete: 299 ide_task_return_boolean (task, TRUE); 300 } 301 302 static gboolean 303 ide_buildconfig_config_provider_load_finish (IdeConfigProvider *provider, 304 GAsyncResult *result, 305 GError **error) 306 { 307 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (provider)); 308 g_assert (IDE_IS_TASK (result)); 309 g_assert (ide_task_is_valid (IDE_TASK (result), provider)); 310 311 return ide_task_propagate_boolean (IDE_TASK (result), error); 312 } 313 314 static void 315 ide_buildconfig_config_provider_save_cb (GObject *object, 316 GAsyncResult *result, 317 gpointer user_data) 318 { 319 GFile *file = (GFile *)object; 320 g_autoptr(IdeTask) task = user_data; 321 g_autoptr(GError) error = NULL; 322 323 g_assert (G_IS_FILE (file)); 324 g_assert (G_IS_ASYNC_RESULT (result)); 325 g_assert (IDE_IS_TASK (task)); 326 327 if (!g_file_replace_contents_finish (file, result, NULL, &error)) 328 ide_task_return_error (task, g_steal_pointer (&error)); 329 else 330 ide_task_return_boolean (task, TRUE); 331 } 332 333 static void 334 ide_buildconfig_config_provider_save_async (IdeConfigProvider *provider, 335 GCancellable *cancellable, 336 GAsyncReadyCallback callback, 337 gpointer user_data) 338 { 339 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider; 340 g_autoptr(GHashTable) group_names = NULL; 341 g_autoptr(IdeTask) task = NULL; 342 g_autoptr(GFile) file = NULL; 343 g_autoptr(GBytes) bytes = NULL; 344 g_autoptr(GError) error = NULL; 345 g_auto(GStrv) groups = NULL; 346 g_autofree gchar *path = NULL; 347 g_autofree gchar *data = NULL; 348 IdeConfigManager *manager; 349 IdeContext *context; 350 gboolean dirty = FALSE; 351 gsize length = 0; 352 353 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 354 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); 355 g_assert (self->key_file != NULL); 356 357 task = ide_task_new (self, cancellable, callback, user_data); 358 ide_task_set_source_tag (task, ide_buildconfig_config_provider_save_async); 359 ide_task_set_priority (task, G_PRIORITY_LOW); 360 361 dirty = self->key_file_dirty; 362 363 /* If no configs are dirty, short circuit to avoid writing any files to disk. */ 364 for (guint i = 0; !dirty && i < self->configs->len; i++) 365 { 366 IdeConfig *config = g_ptr_array_index (self->configs, i); 367 dirty |= ide_config_get_dirty (config); 368 } 369 370 if (!dirty) 371 { 372 ide_task_return_boolean (task, TRUE); 373 return; 374 } 375 376 context = ide_object_get_context (IDE_OBJECT (self)); 377 manager = ide_config_manager_from_context (context); 378 path = ide_context_build_filename (context, DOT_BUILDCONFIG, NULL); 379 file = g_file_new_for_path (path); 380 381 /* 382 * We keep the GKeyFile around from when we parsed .buildconfig, so that we 383 * can try to preserve comments and such when writing back. 384 * 385 * This means that we need to fill in all our known configuration sections, 386 * and then remove any that were removed since we were parsed it last. 387 */ 388 389 group_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); 390 391 for (guint i = 0; i < self->configs->len; i++) 392 { 393 IdeConfig *config = g_ptr_array_index (self->configs, i); 394 g_autofree gchar *env_group = NULL; 395 const gchar *config_id; 396 IdeEnvironment *env; 397 guint n_items; 398 399 if (!ide_config_get_dirty (config)) 400 continue; 401 402 config_id = ide_config_get_id (config); 403 env_group = g_strdup_printf ("%s.environment", config_id); 404 405 /* 406 * Track our known group names, so we can remove missing names after 407 * we've updated the GKeyFile. 408 */ 409 g_hash_table_insert (group_names, g_strdup (config_id), NULL); 410 g_hash_table_insert (group_names, g_strdup (env_group), NULL); 411 412 #define PERSIST_STRING_KEY(key, getter) \ 413 g_key_file_set_string (self->key_file, config_id, key, \ 414 ide_config_##getter (config) ?: "") 415 #define PERSIST_STRV_KEY(key, getter) G_STMT_START { \ 416 const gchar * const *val = ide_buildconfig_config_##getter (IDE_BUILDCONFIG_CONFIG (config)); \ 417 gsize vlen = val ? g_strv_length ((gchar **)val) : 0; \ 418 g_key_file_set_string_list (self->key_file, config_id, key, val, vlen); \ 419 } G_STMT_END 420 421 PERSIST_STRING_KEY ("name", get_display_name); 422 PERSIST_STRING_KEY ("runtime", get_runtime_id); 423 PERSIST_STRING_KEY ("toolchain", get_toolchain_id); 424 PERSIST_STRING_KEY ("config-opts", get_config_opts); 425 PERSIST_STRING_KEY ("run-opts", get_run_opts); 426 PERSIST_STRING_KEY ("prefix", get_prefix); 427 PERSIST_STRING_KEY ("app-id", get_app_id); 428 PERSIST_STRV_KEY ("postbuild", get_postbuild); 429 PERSIST_STRV_KEY ("prebuild", get_prebuild); 430 431 #undef PERSIST_STRING_KEY 432 #undef PERSIST_STRV_KEY 433 434 if (ide_config_get_locality (config) == IDE_BUILD_LOCALITY_IN_TREE) 435 g_key_file_set_boolean (self->key_file, config_id, "builddir", FALSE); 436 else if (ide_config_get_locality (config) == IDE_BUILD_LOCALITY_OUT_OF_TREE) 437 g_key_file_set_boolean (self->key_file, config_id, "builddir", TRUE); 438 else 439 g_key_file_remove_key (self->key_file, config_id, "builddir", NULL); 440 441 if (config == ide_config_manager_get_current (manager)) 442 g_key_file_set_boolean (self->key_file, config_id, "default", TRUE); 443 else 444 g_key_file_remove_key (self->key_file, config_id, "default", NULL); 445 446 env = ide_config_get_environment (config); 447 448 /* 449 * Remove all environment keys that are no longer specified in the 450 * environment. This allows us to just do a single pass of additions 451 * from the environment below. 452 */ 453 if (g_key_file_has_group (self->key_file, env_group)) 454 { 455 g_auto(GStrv) keys = NULL; 456 457 if (NULL != (keys = g_key_file_get_keys (self->key_file, env_group, NULL, NULL))) 458 { 459 for (guint j = 0; keys [j]; j++) 460 { 461 if (!ide_environment_getenv (env, keys [j])) 462 g_key_file_remove_key (self->key_file, env_group, keys [j], NULL); 463 } 464 } 465 } 466 467 n_items = g_list_model_get_n_items (G_LIST_MODEL (env)); 468 469 for (guint j = 0; j < n_items; j++) 470 { 471 g_autoptr(IdeEnvironmentVariable) var = NULL; 472 const gchar *key; 473 const gchar *value; 474 475 var = g_list_model_get_item (G_LIST_MODEL (env), j); 476 key = ide_environment_variable_get_key (var); 477 value = ide_environment_variable_get_value (var); 478 479 if (!dzl_str_empty0 (key)) 480 g_key_file_set_string (self->key_file, env_group, key, value ?: ""); 481 } 482 483 ide_config_set_dirty (config, FALSE); 484 } 485 486 /* Now truncate any old groups in the keyfile. */ 487 if (NULL != (groups = g_key_file_get_groups (self->key_file, NULL))) 488 { 489 for (guint i = 0; groups [i]; i++) 490 { 491 if (!g_hash_table_contains (group_names, groups [i])) 492 g_key_file_remove_group (self->key_file, groups [i], NULL); 493 } 494 } 495 496 if (!(data = g_key_file_to_data (self->key_file, &length, &error))) 497 { 498 ide_task_return_error (task, g_steal_pointer (&error)); 499 return; 500 } 501 502 self->key_file_dirty = FALSE; 503 504 if (length == 0) 505 { 506 /* Remove the file if it exists, since it would be empty */ 507 g_file_delete (file, cancellable, NULL); 508 ide_task_return_boolean (task, TRUE); 509 return; 510 } 511 512 bytes = g_bytes_new_take (g_steal_pointer (&data), length); 513 514 g_file_replace_contents_bytes_async (file, 515 bytes, 516 NULL, 517 FALSE, 518 G_FILE_CREATE_NONE, 519 cancellable, 520 ide_buildconfig_config_provider_save_cb, 521 g_steal_pointer (&task)); 522 } 523 524 static gboolean 525 ide_buildconfig_config_provider_save_finish (IdeConfigProvider *provider, 526 GAsyncResult *result, 527 GError **error) 528 { 529 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (provider)); 530 g_assert (IDE_IS_TASK (result)); 531 g_assert (ide_task_is_valid (IDE_TASK (result), provider)); 532 533 return ide_task_propagate_boolean (IDE_TASK (result), error); 534 } 535 536 static void 537 ide_buildconfig_config_provider_delete (IdeConfigProvider *provider, 538 IdeConfig *config) 539 { 540 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider; 541 g_autoptr(IdeConfig) hold = NULL; 542 g_autofree gchar *env = NULL; 543 const gchar *config_id; 544 gboolean had_group; 545 546 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 547 g_assert (IDE_IS_BUILDCONFIG_CONFIG (config)); 548 g_assert (self->key_file != NULL); 549 g_assert (self->configs->len > 0); 550 551 hold = g_object_ref (config); 552 553 if (!g_ptr_array_remove (self->configs, hold)) 554 { 555 g_critical ("No such configuration %s", 556 ide_config_get_id (hold)); 557 return; 558 } 559 560 config_id = ide_config_get_id (config); 561 had_group = g_key_file_has_group (self->key_file, config_id); 562 env = g_strdup_printf ("%s.environment", config_id); 563 g_key_file_remove_group (self->key_file, config_id, NULL); 564 g_key_file_remove_group (self->key_file, env, NULL); 565 566 self->key_file_dirty = had_group; 567 568 /* 569 * If we removed our last buildconfig, synthesize a new one to replace it so 570 * that we never have no configurations available. We add it before we remove 571 * @config so that we never have zero configurations available. 572 * 573 * At some point in the future we might want a read only NULL configuration 574 * for fallback, and group configs by type or something. But until we have 575 * designs for that, this will do. 576 */ 577 if (self->configs->len == 0) 578 { 579 g_autoptr(IdeConfig) new_config = NULL; 580 581 /* "Default" is not translated because .buildconfig can be checked in */ 582 new_config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG, 583 "display-name", "Default", 584 "id", "default", 585 "parent", self, 586 "runtime-id", "host", 587 "toolchain-id", "default", 588 NULL); 589 590 /* 591 * Only persist this back if there was data in the keyfile 592 * before we were requested to delete the build-config. 593 */ 594 ide_config_set_dirty (new_config, had_group); 595 ide_config_provider_emit_added (provider, new_config); 596 } 597 598 ide_config_provider_emit_removed (provider, hold); 599 } 600 601 static void 602 ide_buildconfig_config_provider_duplicate (IdeConfigProvider *provider, 603 IdeConfig *config) 604 { 605 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider; 606 g_autoptr(IdeConfig) new_config = NULL; 607 g_autofree GParamSpec **pspecs = NULL; 608 g_autofree gchar *new_config_id = NULL; 609 g_autofree gchar *new_name = NULL; 610 IdeConfigManager *manager; 611 IdeEnvironment *env; 612 const gchar *config_id; 613 const gchar *name; 614 IdeContext *context; 615 guint n_pspecs = 0; 616 617 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 618 g_assert (IDE_IS_CONFIG (config)); 619 g_assert (IDE_IS_BUILDCONFIG_CONFIG (config)); 620 621 context = ide_object_get_context (IDE_OBJECT (self)); 622 g_assert (IDE_IS_CONTEXT (context)); 623 624 manager = ide_config_manager_from_context (context); 625 g_assert (IDE_IS_CONFIG_MANAGER (manager)); 626 627 config_id = ide_config_get_id (config); 628 g_return_if_fail (config_id != NULL); 629 630 new_config_id = get_next_id (manager, config_id); 631 g_return_if_fail (new_config_id != NULL); 632 633 name = ide_config_get_display_name (config); 634 /* translators: %s is replaced with the name of the configuration */ 635 new_name = g_strdup_printf (_("%s (Copy)"), name); 636 637 env = ide_config_get_environment (config); 638 639 new_config = g_object_new (IDE_TYPE_BUILDCONFIG_CONFIG, 640 "id", new_config_id, 641 "display-name", new_name, 642 "parent", self, 643 NULL); 644 645 ide_environment_copy_into (env, ide_config_get_environment (new_config), TRUE); 646 647 pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (new_config), &n_pspecs); 648 649 for (guint i = 0; i < n_pspecs; i++) 650 { 651 GParamSpec *pspec = pspecs[i]; 652 653 if (g_str_equal (pspec->name, "id") || 654 g_str_equal (pspec->name, "display-name") || 655 g_type_is_a (pspec->value_type, G_TYPE_BOXED) || 656 g_type_is_a (pspec->value_type, G_TYPE_OBJECT)) 657 continue; 658 659 660 if ((pspec->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE && 661 (pspec->flags & G_PARAM_CONSTRUCT_ONLY) == 0) 662 { 663 GValue value = G_VALUE_INIT; 664 665 g_value_init (&value, pspec->value_type); 666 g_object_get_property (G_OBJECT (config), pspec->name, &value); 667 g_object_set_property (G_OBJECT (new_config), pspec->name, &value); 668 } 669 } 670 671 ide_config_set_dirty (new_config, TRUE); 672 ide_config_provider_emit_added (provider, new_config); 673 } 674 675 static void 676 ide_buildconfig_config_provider_unload (IdeConfigProvider *provider) 677 { 678 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider; 679 g_autoptr(GPtrArray) configs = NULL; 680 681 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 682 g_assert (self->configs != NULL); 683 684 configs = g_steal_pointer (&self->configs); 685 self->configs = g_ptr_array_new_with_free_func (g_object_unref); 686 687 for (guint i = 0; i < configs->len; i++) 688 { 689 IdeConfig *config = g_ptr_array_index (configs, i); 690 ide_config_provider_emit_removed (provider, config); 691 } 692 } 693 694 static void 695 ide_buildconfig_config_provider_added (IdeConfigProvider *provider, 696 IdeConfig *config) 697 { 698 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider; 699 700 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 701 g_assert (IDE_IS_CONFIG (config)); 702 g_assert (self->configs != NULL); 703 704 g_ptr_array_add (self->configs, g_object_ref (config)); 705 } 706 707 static void 708 ide_buildconfig_config_provider_removed (IdeConfigProvider *provider, 709 IdeConfig *config) 710 { 711 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)provider; 712 713 g_assert (IDE_IS_BUILDCONFIG_CONFIG_PROVIDER (self)); 714 g_assert (IDE_IS_CONFIG (config)); 715 g_assert (self->configs != NULL); 716 717 /* It's possible we already removed it by now */ 718 g_ptr_array_remove (self->configs, config); 719 720 ide_object_destroy (IDE_OBJECT (config)); 721 } 722 723 static void 724 configuration_provider_iface_init (IdeConfigProviderInterface *iface) 725 { 726 iface->added = ide_buildconfig_config_provider_added; 727 iface->removed = ide_buildconfig_config_provider_removed; 728 iface->load_async = ide_buildconfig_config_provider_load_async; 729 iface->load_finish = ide_buildconfig_config_provider_load_finish; 730 iface->save_async = ide_buildconfig_config_provider_save_async; 731 iface->save_finish = ide_buildconfig_config_provider_save_finish; 732 iface->delete = ide_buildconfig_config_provider_delete; 733 iface->duplicate = ide_buildconfig_config_provider_duplicate; 734 iface->unload = ide_buildconfig_config_provider_unload; 735 } 736 737 G_DEFINE_FINAL_TYPE_WITH_CODE (IdeBuildconfigConfigProvider, 738 ide_buildconfig_config_provider, 739 IDE_TYPE_OBJECT, 740 G_IMPLEMENT_INTERFACE (IDE_TYPE_CONFIG_PROVIDER, 741 configuration_provider_iface_init)) 742 743 static void 744 ide_buildconfig_config_provider_finalize (GObject *object) 745 { 746 IdeBuildconfigConfigProvider *self = (IdeBuildconfigConfigProvider *)object; 747 748 g_clear_pointer (&self->configs, g_ptr_array_unref); 749 g_clear_pointer (&self->key_file, g_key_file_free); 750 751 G_OBJECT_CLASS (ide_buildconfig_config_provider_parent_class)->finalize (object); 752 } 753 754 static void 755 ide_buildconfig_config_provider_class_init (IdeBuildconfigConfigProviderClass *klass) 756 { 757 GObjectClass *object_class = G_OBJECT_CLASS (klass); 758 759 object_class->finalize = ide_buildconfig_config_provider_finalize; 760 } 761 762 static void 763 ide_buildconfig_config_provider_init (IdeBuildconfigConfigProvider *self) 764 { 765 self->configs = g_ptr_array_new_with_free_func (g_object_unref); 766 } 767