gimpconfig-path.c 19 KB
Newer Older
1 2
/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
3
 *
4
 * gimpconfig-path.c
5 6
 * Copyright (C) 2001  Sven Neumann <sven@gimp.org>
 *
7
 * This library is free software: you can redistribute it and/or
8 9
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
10
 * version 3 of the License, or (at your option) any later version.
11
 *
12
 * This library is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public
18
 * License along with this library.  If not, see
19
 * <https://www.gnu.org/licenses/>.
20 21 22 23 24 25 26
 */

#include "config.h"

#include <stdio.h>
#include <string.h>

27
#include <gio/gio.h>
28

29
#include "libgimpbase/gimpbase.h"
30

31
#include "gimpconfig-error.h"
32 33
#include "gimpconfig-path.h"

34
#include "libgimp/libgimp-intl.h"
35 36


37 38 39 40 41 42 43 44 45
/**
 * SECTION: gimpconfig-path
 * @title: GimpConfig-path
 * @short_description: File path utilities for libgimpconfig.
 *
 * File path utilities for libgimpconfig.
 **/


46 47 48 49 50 51 52
/**
 * gimp_config_path_get_type:
 *
 * Reveals the object type
 *
 * Returns: the #GType for a GimpConfigPath string property
 *
53
 * Since: 2.4
54 55 56 57 58 59
 **/
GType
gimp_config_path_get_type (void)
{
  static GType path_type = 0;

60
  if (! path_type)
61
    {
62
      const GTypeInfo type_info = { 0, };
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

      path_type = g_type_register_static (G_TYPE_STRING, "GimpConfigPath",
                                          &type_info, 0);
    }

  return path_type;
}


/*
 * GIMP_TYPE_PARAM_CONFIG_PATH
 */

#define GIMP_PARAM_SPEC_CONFIG_PATH(pspec) (G_TYPE_CHECK_INSTANCE_CAST ((pspec), GIMP_TYPE_PARAM_CONFIG_PATH, GimpParamSpecConfigPath))

typedef struct _GimpParamSpecConfigPath GimpParamSpecConfigPath;

struct _GimpParamSpecConfigPath
{
  GParamSpecString    parent_instance;

  GimpConfigPathType  type;
};

static void  gimp_param_config_path_class_init (GParamSpecClass *class);

/**
 * gimp_param_config_path_get_type:
 *
 * Reveals the object type
 *
 * Returns: the #GType for a directory path object
 *
96
 * Since: 2.4
97 98 99 100 101 102
 **/
GType
gimp_param_config_path_get_type (void)
{
  static GType spec_type = 0;

103
  if (! spec_type)
104
    {
105
      const GTypeInfo type_info =
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
      {
        sizeof (GParamSpecClass),
        NULL, NULL,
        (GClassInitFunc) gimp_param_config_path_class_init,
        NULL, NULL,
        sizeof (GimpParamSpecConfigPath),
        0, NULL, NULL
      };

      spec_type = g_type_register_static (G_TYPE_PARAM_STRING,
                                          "GimpParamConfigPath",
                                          &type_info, 0);
    }

  return spec_type;
}

static void
gimp_param_config_path_class_init (GParamSpecClass *class)
{
  class->value_type = GIMP_TYPE_CONFIG_PATH;
}

/**
 * gimp_param_spec_config_path:
 * @name:          Canonical name of the param
 * @nick:          Nickname of the param
133
 * @blurb:         Brief description of param.
134
 * @type:          a #GimpConfigPathType value.
135 136 137 138 139 140 141
 * @default_value: Value to use if none is assigned.
 * @flags:         a combination of #GParamFlags
 *
 * Creates a param spec to hold a filename, dir name,
 * or list of file or dir names.
 * See g_param_spec_internal() for more information.
 *
142
 * Returns: (transfer full): a newly allocated #GParamSpec instance
143
 *
144
 * Since: 2.4
145 146 147 148 149 150
 **/
GParamSpec *
gimp_param_spec_config_path (const gchar        *name,
                             const gchar        *nick,
                             const gchar        *blurb,
                             GimpConfigPathType  type,
151
                             const gchar        *default_value,
152 153 154 155 156 157 158
                             GParamFlags         flags)
{
  GParamSpecString *pspec;

  pspec = g_param_spec_internal (GIMP_TYPE_PARAM_CONFIG_PATH,
                                 name, nick, blurb, flags);

159
  pspec->default_value = g_strdup (default_value);
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174

  GIMP_PARAM_SPEC_CONFIG_PATH (pspec)->type = type;

  return G_PARAM_SPEC (pspec);
}

/**
 * gimp_param_spec_config_path_type:
 * @pspec:         A #GParamSpec for a path param
 *
 * Tells whether the path param encodes a filename,
 * dir name, or list of file or dir names.
 *
 * Returns: a #GimpConfigPathType value
 *
175
 * Since: 2.4
176 177 178 179 180 181 182 183
 **/
GimpConfigPathType
gimp_param_spec_config_path_type (GParamSpec *pspec)
{
  g_return_val_if_fail (GIMP_IS_PARAM_SPEC_CONFIG_PATH (pspec), 0);

  return GIMP_PARAM_SPEC_CONFIG_PATH (pspec)->type;
}
184

185

186 187 188
/*
 * GimpConfig path utilities
 */
189

190
static gchar        * gimp_config_path_expand_only   (const gchar  *path,
191
                                                      GError      **error) G_GNUC_MALLOC;
192
static inline gchar * gimp_config_path_extract_token (const gchar **str);
193
static gchar        * gimp_config_path_unexpand_only (const gchar  *path) G_GNUC_MALLOC;
194

Sven Neumann's avatar
Sven Neumann committed
195

196 197
/**
 * gimp_config_build_data_path:
Sven Neumann's avatar
Sven Neumann committed
198 199 200 201 202 203
 * @name: directory name (in UTF-8 encoding)
 *
 * Creates a search path as it is used in the gimprc file.  The path
 * returned by gimp_config_build_data_path() includes a directory
 * below the user's gimp directory and one in the system-wide data
 * directory.
204
 *
Sven Neumann's avatar
Sven Neumann committed
205 206 207 208 209
 * Note that you cannot use this path directly with gimp_path_parse().
 * As it is in the gimprc notation, you first need to expand and
 * recode it using gimp_config_path_expand().
 *
 * Returns: a newly allocated string
210
 *
211
 * Since: 2.4
212
 **/
213 214 215 216 217 218 219 220
gchar *
gimp_config_build_data_path (const gchar *name)
{
  return g_strconcat ("${gimp_dir}", G_DIR_SEPARATOR_S, name,
                      G_SEARCHPATH_SEPARATOR_S,
                      "${gimp_data_dir}", G_DIR_SEPARATOR_S, name,
                      NULL);
}
221

222 223
/**
 * gimp_config_build_plug_in_path:
Sven Neumann's avatar
Sven Neumann committed
224 225 226 227 228 229
 * @name: directory name (in UTF-8 encoding)
 *
 * Creates a search path as it is used in the gimprc file.  The path
 * returned by gimp_config_build_plug_in_path() includes a directory
 * below the user's gimp directory and one in the system-wide plug-in
 * directory.
230
 *
Sven Neumann's avatar
Sven Neumann committed
231 232 233 234 235
 * Note that you cannot use this path directly with gimp_path_parse().
 * As it is in the gimprc notation, you first need to expand and
 * recode it using gimp_config_path_expand().
 *
 * Returns: a newly allocated string
236
 *
237
 * Since: 2.4
238
 **/
239 240 241 242 243 244 245
gchar *
gimp_config_build_plug_in_path (const gchar *name)
{
  return g_strconcat ("${gimp_dir}", G_DIR_SEPARATOR_S, name,
                      G_SEARCHPATH_SEPARATOR_S,
                      "${gimp_plug_in_dir}", G_DIR_SEPARATOR_S, name,
                      NULL);
246
}
247

248 249
/**
 * gimp_config_build_writable_path:
Sven Neumann's avatar
Sven Neumann committed
250 251 252 253 254 255 256 257 258
 * @name: directory name (in UTF-8 encoding)
 *
 * Creates a search path as it is used in the gimprc file.  The path
 * returned by gimp_config_build_writable_path() is just the writable
 * parts of the search path constructed by gimp_config_build_data_path().
 *
 * Note that you cannot use this path directly with gimp_path_parse().
 * As it is in the gimprc notation, you first need to expand and
 * recode it using gimp_config_path_expand().
259
 *
Sven Neumann's avatar
Sven Neumann committed
260
 * Returns: a newly allocated string
261
 *
262
 * Since: 2.4
263 264 265 266 267 268 269
 **/
gchar *
gimp_config_build_writable_path (const gchar *name)
{
  return g_strconcat ("${gimp_dir}", G_DIR_SEPARATOR_S, name, NULL);
}

270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
/**
 * gimp_config_build_system_path:
 * @name: directory name (in UTF-8 encoding)
 *
 * Creates a search path as it is used in the gimprc file.  The path
 * returned by gimp_config_build_system_path() is just the read-only
 * parts of the search path constructed by gimp_config_build_plug_in_path().
 *
 * Note that you cannot use this path directly with gimp_path_parse().
 * As it is in the gimprc notation, you first need to expand and
 * recode it using gimp_config_path_expand().
 *
 * Returns: a newly allocated string
 *
 * Since: 2.10.6
 **/
gchar *
gimp_config_build_system_path (const gchar *name)
{
  return g_strconcat ("${gimp_plug_in_dir}", G_DIR_SEPARATOR_S, name, NULL);
}
291

292 293
/**
 * gimp_config_path_expand:
294
 * @path:   a NUL-terminated string in UTF-8 encoding
295
 * @recode: whether to convert to the filesystem's encoding
296
 * @error:  return location for errors
297
 *
298 299 300 301 302 303 304 305 306
 * Paths as stored in gimprc and other config files have to be treated
 * special.  The string may contain special identifiers such as for
 * example ${gimp_dir} that have to be substituted before use. Also
 * the user's filesystem may be in a different encoding than UTF-8
 * (which is what is used for the gimprc). This function does the
 * variable substitution for you and can also attempt to convert to
 * the filesystem encoding.
 *
 * To reverse the expansion, use gimp_config_path_unexpand().
307
 *
308
 * Returns: a newly allocated NUL-terminated string
309
 *
310
 * Since: 2.4
311
 **/
312
gchar *
313 314 315
gimp_config_path_expand (const gchar  *path,
                         gboolean      recode,
                         GError      **error)
316 317 318 319 320 321 322 323 324
{
  g_return_val_if_fail (path != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (recode)
    {
      gchar *retval;
      gchar *expanded = gimp_config_path_expand_only (path, error);

325 326 327
      if (! expanded)
        return NULL;

328 329 330 331 332 333
      retval = g_filename_from_utf8 (expanded, -1, NULL, NULL, error);

      g_free (expanded);

      return retval;
    }
334 335

  return gimp_config_path_expand_only (path, error);
336 337
}

338 339
/**
 * gimp_config_path_expand_to_files:
340
 * @path:  a NUL-terminated string in UTF-8 encoding
341 342 343 344 345 346 347 348 349
 * @error: return location for errors
 *
 * Paths as stored in the gimprc have to be treated special. The
 * string may contain special identifiers such as for example
 * ${gimp_dir} that have to be substituted before use. Also the user's
 * filesystem may be in a different encoding than UTF-8 (which is what
 * is used for the gimprc).
 *
 * This function runs @path through gimp_config_path_expand() and
350 351
 * gimp_path_parse(), then turns the filenames returned by
 * gimp_path_parse() into GFile using g_file_new_for_path().
352
 *
353
 * Returns: (element-type GFile) (transfer full):
354
                 a #GList of newly allocated #GFile objects.
355
 *
356
 * Since: 2.10
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
 **/
GList *
gimp_config_path_expand_to_files (const gchar  *path,
                                  GError      **error)
{
  GList *files;
  GList *list;
  gchar *expanded;

  g_return_val_if_fail (path != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  expanded = gimp_config_path_expand (path, TRUE, error);

  if (! expanded)
    return NULL;

  files = gimp_path_parse (expanded, 256, FALSE, NULL);

376 377
  g_free (expanded);

378 379 380 381 382 383 384 385 386 387 388
  for (list = files; list; list = g_list_next (list))
    {
      gchar *dir = list->data;

      list->data = g_file_new_for_path (dir);
      g_free (dir);
    }

  return files;
}

389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
/**
 * gimp_config_path_unexpand:
 * @path:   a NUL-terminated string
 * @recode: whether @path is in filesystem encoding or UTF-8
 * @error:  return location for errors
 *
 * The inverse operation of gimp_config_path_expand()
 *
 * This function takes a @path and tries to substitute the first
 * elements by well-known special identifiers such as for example
 * ${gimp_dir}. The unexpanded path can then be stored in gimprc and
 * other config files.
 *
 * If @recode is %TRUE then @path is in local filesystem encoding,
 * if @recode is %FALSE then @path is assumed to be UTF-8.
 *
405
 * Returns: a newly allocated NUL-terminated UTF-8 string
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
 *
 * Since: 2.10
 **/
gchar *
gimp_config_path_unexpand (const gchar  *path,
                           gboolean      recode,
                           GError      **error)
{
  g_return_val_if_fail (path != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  if (recode)
    {
      gchar *retval;
      gchar *utf8 = g_filename_to_utf8 (path, -1, NULL, NULL, error);

      if (! utf8)
        return NULL;

      retval = gimp_config_path_unexpand_only (utf8);

      g_free (utf8);

      return retval;
    }

  return gimp_config_path_unexpand_only (path);
}
434

435 436 437 438 439 440 441 442 443 444
/**
 * gimp_file_new_for_config_path:
 * @path:   a NUL-terminated string in UTF-8 encoding
 * @error:  return location for errors
 *
 * Expands @path using gimp_config_path_expand() and returns a #GFile
 * for the expanded path.
 *
 * To reverse the expansion, use gimp_file_get_config_path().
 *
445 446
 * Returns: (nullable) (transfer full): a newly allocated #GFile,
 *          or %NULL if the expansion failed.
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471
 *
 * Since: 2.10
 **/
GFile *
gimp_file_new_for_config_path (const gchar  *path,
                               GError      **error)
{
  GFile *file = NULL;
  gchar *expanded;

  g_return_val_if_fail (path != NULL, NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  expanded = gimp_config_path_expand (path, TRUE, error);

  if (expanded)
    {
      file = g_file_new_for_path (expanded);
      g_free (expanded);
    }

  return file;
}

/**
472
 * gimp_file_get_config_path:
473 474 475 476 477 478 479 480
 * @file:   a #GFile
 * @error:  return location for errors
 *
 * Unexpands @file's path using gimp_config_path_unexpand() and
 * returns the unexpanded path.
 *
 * The inverse operation of gimp_file_new_for_config_path().
 *
481
 * Returns: a newly allocated NUL-terminated UTF-8 string, or %NULL if
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
 *               unexpanding failed.
 *
 * Since: 2.10
 **/
gchar *
gimp_file_get_config_path (GFile   *file,
                           GError **error)
{
  gchar *unexpanded = NULL;
  gchar *path;

  g_return_val_if_fail (G_IS_FILE (file), NULL);
  g_return_val_if_fail (error == NULL || *error == NULL, NULL);

  path = g_file_get_path (file);

  if (path)
    {
      unexpanded = gimp_config_path_unexpand (path, TRUE, error);
      g_free (path);
    }
  else
    {
      g_set_error_literal (error, 0, 0,
506
                           _("File has no path representation"));
507 508 509 510 511 512 513 514
    }

  return unexpanded;
}


/*  private functions  */

515 516
#define SUBSTS_ALLOC 4

517 518 519
static gchar *
gimp_config_path_expand_only (const gchar  *path,
                              GError      **error)
520
{
521
  const gchar *home;
Sven Neumann's avatar
Sven Neumann committed
522
  const gchar *p;
523
  const gchar *s;
Sven Neumann's avatar
Sven Neumann committed
524
  gchar       *n;
525
  gchar       *token;
526
  gchar       *filename = NULL;
527
  gchar       *expanded = NULL;
528 529 530
  gchar      **substs   = NULL;
  guint        n_substs = 0;
  gint         length   = 0;
531
  gint         i;
532

533 534 535 536
  home = g_get_home_dir ();
  if (home)
    home = gimp_filename_to_utf8 (home);

537
  p = path;
538

539
  while (*p)
540
    {
541
      if (*p == '~' && home)
Sven Neumann's avatar
Sven Neumann committed
542
        {
543 544
          length += strlen (home);
          p += 1;
Sven Neumann's avatar
Sven Neumann committed
545
        }
546
      else if ((token = gimp_config_path_extract_token (&p)) != NULL)
547
        {
548 549 550
          for (i = 0; i < n_substs; i++)
            if (strcmp (substs[2*i], token) == 0)
              break;
551

552 553 554 555 556 557
          if (i < n_substs)
            {
              s = substs[2*i+1];
            }
          else
            {
558
              s = NULL;
559

560
              if (strcmp (token, "gimp_dir") == 0)
561
                s = gimp_directory ();
562
              else if (strcmp (token, "gimp_data_dir") == 0)
563
                s = gimp_data_directory ();
564
              else if (strcmp (token, "gimp_plug_in_dir") == 0 ||
Sven Neumann's avatar
Sven Neumann committed
565
                       strcmp (token, "gimp_plugin_dir") == 0)
566
                s = gimp_plug_in_directory ();
567
              else if (strcmp (token, "gimp_sysconf_dir") == 0)
568
                s = gimp_sysconf_directory ();
569 570
              else if (strcmp (token, "gimp_installation_dir") == 0)
                s = gimp_installation_directory ();
571 572 573 574
              else if (strcmp (token, "gimp_cache_dir") == 0)
                s = gimp_cache_directory ();
              else if (strcmp (token, "gimp_temp_dir") == 0)
                s = gimp_temp_directory ();
575

576 577
              if (!s)
                s = g_getenv (token);
578

579
#ifdef G_OS_WIN32
580 581 582 583 584 585 586 587 588
              /* The default user gimprc on Windows references
               * ${TEMP}, but not all Windows installations have that
               * environment variable, even if it should be kinda
               * standard. So special-case it.
               */
              if (!s && strcmp (token, "TEMP") == 0)
                s = g_get_tmp_dir ();
#endif  /* G_OS_WIN32 */
            }
589

590 591
          if (!s)
            {
592
              g_set_error (error, GIMP_CONFIG_ERROR, GIMP_CONFIG_ERROR_PARSE,
Jehan's avatar
Jehan committed
593
                           _("Cannot expand ${%s}"), token);
594 595
              g_free (token);
              goto cleanup;
596
            }
597

598 599
          if (n_substs % SUBSTS_ALLOC == 0)
            substs = g_renew (gchar *, substs, 2 * (n_substs + SUBSTS_ALLOC));
600

601
          substs[2*n_substs]     = token;
602
          substs[2*n_substs + 1] = (gchar *) gimp_filename_to_utf8 (s);
603

604 605 606
          length += strlen (substs[2*n_substs + 1]);

          n_substs++;
607 608
        }
      else
Sven Neumann's avatar
Sven Neumann committed
609
        {
610
          length += g_utf8_skip[(const guchar) *p];
611
          p = g_utf8_next_char (p);
Sven Neumann's avatar
Sven Neumann committed
612
        }
613 614
    }

615
  if (n_substs == 0)
616
    return g_strdup (path);
617

618
  expanded = g_new (gchar, length + 1);
619

620
  p = path;
621
  n = expanded;
622

Sven Neumann's avatar
Sven Neumann committed
623
  while (*p)
624
    {
625
      if (*p == '~' && home)
Sven Neumann's avatar
Sven Neumann committed
626 627 628 629 630 631
        {
          *n = '\0';
          strcat (n, home);
          n += strlen (home);
          p += 1;
        }
632
      else if ((token = gimp_config_path_extract_token (&p)) != NULL)
633
        {
634
          for (i = 0; i < n_substs; i++)
635
            {
636
              if (strcmp (substs[2*i], token) == 0)
637
                {
638
                  s = substs[2*i+1];
639

Sven Neumann's avatar
Sven Neumann committed
640
                  *n = '\0';
641 642
                  strcat (n, s);
                  n += strlen (s);
643 644 645 646 647 648

                  break;
                }
            }

          g_free (token);
Sven Neumann's avatar
Sven Neumann committed
649
        }
650
      else
Sven Neumann's avatar
Sven Neumann committed
651 652 653
        {
          *n++ = *p++;
        }
654 655
    }

Sven Neumann's avatar
Sven Neumann committed
656
  *n = '\0';
657 658

 cleanup:
659 660
  for (i = 0; i < n_substs; i++)
    g_free (substs[2*i]);
661

662
  g_free (substs);
663
  g_free (filename);
664

665
  return expanded;
666
}
667 668

static inline gchar *
669
gimp_config_path_extract_token (const gchar **str)
670 671 672 673 674 675 676 677 678
{
  const gchar *p;
  gchar       *token;

  if (strncmp (*str, "${", 2))
    return NULL;

  p = *str + 2;

679
  while (*p && (*p != '}'))
680 681
    p = g_utf8_next_char (p);

682
  if (! *p)
683 684 685 686 687 688 689 690
    return NULL;

  token = g_strndup (*str + 2, g_utf8_pointer_to_offset (*str + 2, p));

  *str = p + 1; /* after the closing bracket */

  return token;
}
691 692 693 694 695 696 697 698 699 700 701 702 703 704 705

static gchar *
gimp_config_path_unexpand_only (const gchar *path)
{
  const struct
  {
    const gchar *id;
    const gchar *prefix;
  }
  identifiers[] =
  {
    { "${gimp_plug_in_dir}",      gimp_plug_in_directory () },
    { "${gimp_data_dir}",         gimp_data_directory () },
    { "${gimp_sysconf_dir}",      gimp_sysconf_directory () },
    { "${gimp_installation_dir}", gimp_installation_directory () },
706 707
    { "${gimp_cache_dir}",        gimp_cache_directory () },
    { "${gimp_temp_dir}",         gimp_temp_directory () },
708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743
    { "${gimp_dir}",              gimp_directory () }
  };

  GList *files;
  GList *list;
  gchar *unexpanded;

  files = gimp_path_parse (path, 256, FALSE, NULL);

  for (list = files; list; list = g_list_next (list))
    {
      gchar *dir = list->data;
      gint   i;

      for (i = 0; i < G_N_ELEMENTS (identifiers); i++)
        {
          if (g_str_has_prefix (dir, identifiers[i].prefix))
            {
              gchar *tmp = g_strconcat (identifiers[i].id,
                                        dir + strlen (identifiers[i].prefix),
                                        NULL);

              g_free (dir);
              list->data = tmp;

              break;
            }
        }
    }

  unexpanded = gimp_path_to_str (files);

  gimp_path_free (files);

  return unexpanded;
}