Branch data Line data Source code
1 : : /********************************************************************\
2 : : * gnc-filepath-utils.c -- file path resolution utility *
3 : : * *
4 : : * This program is free software; you can redistribute it and/or *
5 : : * modify it under the terms of the GNU General Public License as *
6 : : * published by the Free Software Foundation; either version 2 of *
7 : : * the License, or (at your option) any later version. *
8 : : * *
9 : : * This program is distributed in the hope that it will be useful, *
10 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 : : * GNU General Public License for more details. *
13 : : * *
14 : : * You should have received a copy of the GNU General Public License*
15 : : * along with this program; if not, contact: *
16 : : * *
17 : : * Free Software Foundation Voice: +1-617-542-5942 *
18 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
20 : : \********************************************************************/
21 : :
22 : : /*
23 : : * @file gnc-filepath-utils.c
24 : : * @brief file path resolution utilities
25 : : * @author Copyright (c) 1998-2004 Linas Vepstas <linas@linas.org>
26 : : * @author Copyright (c) 2000 Dave Peticolas
27 : : */
28 : :
29 : : #include <glib.h>
30 : : #include <glib/gi18n.h>
31 : : #include <glib/gprintf.h>
32 : : #include <glib/gstdio.h>
33 : :
34 : : #include <config.h>
35 : :
36 : : #include <platform.h>
37 : : #if PLATFORM(WINDOWS)
38 : : #include <windows.h>
39 : : #include <Shlobj.h>
40 : : #endif
41 : :
42 : :
43 : : #include <stdlib.h>
44 : : #include <stdio.h>
45 : : #include <string.h>
46 : : #include <sys/types.h>
47 : : #include <sys/stat.h>
48 : : #ifdef HAVE_UNISTD_H
49 : : # include <unistd.h>
50 : : #endif
51 : : #include <errno.h>
52 : :
53 : : #include "gnc-path.h"
54 : : #include "gnc-filepath-utils.h"
55 : :
56 : : #if defined (_MSC_VER) || defined (G_OS_WIN32)
57 : : #include <glib/gwin32.h>
58 : : #ifndef PATH_MAX
59 : : #define PATH_MAX MAXPATHLEN
60 : : #endif
61 : : #endif
62 : : #ifdef MAC_INTEGRATION
63 : : #include <Foundation/Foundation.h>
64 : : #endif
65 : :
66 : : #include "gnc-locale-utils.hpp"
67 : : #include <boost/filesystem.hpp>
68 : : #include <boost/locale.hpp>
69 : : #include <regex>
70 : : #include <iostream>
71 : :
72 : : /* Below cvt and bfs_locale should be used with boost::filesystem::path (bfs)
73 : : * objects created alter in this source file. The rationale is as follows:
74 : : * - a bfs object has an internal, locale and platform dependent
75 : : * representation of a file system path
76 : : * - glib's internal representation is always utf8
77 : : * - when creating a bfs object, we should pass a cvt to convert from
78 : : * utf8 to the object's internal representation
79 : : * - if we later want to use the bfs object's internal representation
80 : : * in a glib context we should imbue the locale below so that
81 : : * bfs will use it to convert back to utf8
82 : : * - if the bfs object's internal representation will never be used
83 : : * in a glib context, imbuing is not needed (although probably more
84 : : * future proof)
85 : : * - also note creating a bfs based on another one will inherit the
86 : : * locale from the source path. So in that case there's not need
87 : : * to imbue it again.
88 : : */
89 : : #if PLATFORM(WINDOWS)
90 : : #include <codecvt>
91 : : using codecvt = std::codecvt_utf8<wchar_t, 0x10FFFF, std::little_endian>;
92 : : using string = std::wstring;
93 : : #else
94 : : /* See https://stackoverflow.com/questions/41744559/is-this-a-bug-of-gcc */
95 : : template<class I, class E, class S>
96 : : struct codecvt_r : std::codecvt<I, E, S>
97 : : {
98 : 354 : ~codecvt_r() {}
99 : : };
100 : : using codecvt = codecvt_r<wchar_t, char, std::mbstate_t>;
101 : : using string = std::string;
102 : : #endif
103 : : static codecvt cvt;
104 : : static std::locale bfs_locale(std::locale(), new codecvt);
105 : :
106 : : namespace bfs = boost::filesystem;
107 : : namespace bst = boost::system;
108 : : namespace bl = boost::locale;
109 : :
110 : : /** Check if the path exists and is a regular file.
111 : : *
112 : : * \param path -- freed if the path doesn't exist or isn't a regular file
113 : : *
114 : : * \return NULL or the path
115 : : */
116 : : static gchar *
117 : 456 : check_path_return_if_valid(gchar *path)
118 : : {
119 : 456 : if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
120 : : {
121 : 0 : return path;
122 : : }
123 : 456 : g_free (path);
124 : 456 : return NULL;
125 : : }
126 : :
127 : : /** @brief Create an absolute path when given a relative path;
128 : : * otherwise return the argument.
129 : : *
130 : : * @warning filefrag should be a simple path fragment. It shouldn't
131 : : * contain xml:// or http:// or <whatever>:// other protocol specifiers.
132 : : *
133 : : * If passed a string which g_path_is_absolute declares an absolute
134 : : * path, return the argument.
135 : : *
136 : : * Otherwise, assume that filefrag is a well-formed relative path and
137 : : * try to find a file with its path relative to
138 : : * \li the current working directory,
139 : : * \li the installed system-wide data directory (e.g., /usr/local/share/gnucash),
140 : : * \li the installed system configuration directory (e.g., /usr/local/etc/gnucash),
141 : : * \li or in the user's configuration directory (e.g., $HOME/.gnucash/data)
142 : : *
143 : : * The paths are searched for in that order. If a matching file is
144 : : * found, return the absolute path to it.
145 : :
146 : : * If one isn't found, return a absolute path relative to the
147 : : * user's configuration directory and note in the trace file that it
148 : : * needs to be created.
149 : : *
150 : : * @param filefrag The file path to resolve
151 : : *
152 : : * @return An absolute file path.
153 : : */
154 : : gchar *
155 : 184 : gnc_resolve_file_path (const gchar * filefrag)
156 : : {
157 : 184 : gchar *fullpath = NULL, *tmp_path = NULL;
158 : :
159 : : /* seriously invalid */
160 : 184 : if (!filefrag)
161 : : {
162 : 0 : g_critical("filefrag is NULL");
163 : 0 : return NULL;
164 : : }
165 : :
166 : : /* ---------------------------------------------------- */
167 : : /* OK, now we try to find or build an absolute file path */
168 : :
169 : : /* check for an absolute file path */
170 : 184 : if (g_path_is_absolute(filefrag))
171 : 32 : return g_strdup (filefrag);
172 : :
173 : : /* Look in the current working directory */
174 : 152 : tmp_path = g_get_current_dir();
175 : 152 : fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
176 : 152 : g_free(tmp_path);
177 : 152 : fullpath = check_path_return_if_valid(fullpath);
178 : 152 : if (fullpath != NULL)
179 : 0 : return fullpath;
180 : :
181 : : /* Look in the data dir (e.g. $PREFIX/share/gnucash) */
182 : 152 : tmp_path = gnc_path_get_pkgdatadir();
183 : 152 : fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
184 : 152 : g_free(tmp_path);
185 : 152 : fullpath = check_path_return_if_valid(fullpath);
186 : 152 : if (fullpath != NULL)
187 : 0 : return fullpath;
188 : :
189 : : /* Look in the config dir (e.g. $PREFIX/share/gnucash/accounts) */
190 : 152 : tmp_path = gnc_path_get_accountsdir();
191 : 152 : fullpath = g_build_filename(tmp_path, filefrag, (gchar *)NULL);
192 : 152 : g_free(tmp_path);
193 : 152 : fullpath = check_path_return_if_valid(fullpath);
194 : 152 : if (fullpath != NULL)
195 : 0 : return fullpath;
196 : :
197 : : /* Look in the users config dir (e.g. $HOME/.gnucash/data) */
198 : 152 : fullpath = g_strdup(gnc_build_data_path(filefrag));
199 : 152 : if (g_file_test(fullpath, G_FILE_TEST_IS_REGULAR))
200 : 0 : return fullpath;
201 : :
202 : : /* OK, it's not there. Note that it needs to be created and pass it
203 : : * back anyway */
204 : 152 : g_warning("create new file %s", fullpath);
205 : 152 : return fullpath;
206 : :
207 : : }
208 : :
209 : 434 : gchar *gnc_file_path_relative_part (const gchar *prefix, const gchar *path)
210 : : {
211 : 434 : std::string p{path};
212 : 434 : if (p.find(prefix) == 0)
213 : : {
214 : 434 : auto str = p.substr(strlen(prefix));
215 : 434 : return g_strdup(str.c_str());
216 : 434 : }
217 : 0 : return g_strdup(path);
218 : 434 : }
219 : :
220 : : /* Searches for a file fragment paths set via GNC_DOC_PATH environment
221 : : * variable. If this variable is not set, fall back to search in
222 : : * - a html directory in the local user's gnucash settings directory
223 : : * (typically $HOME/.gnucash/html)
224 : : * - the gnucash documentation directory
225 : : * (typically /usr/share/doc/gnucash)
226 : : * - the gnucash data directory
227 : : * (typically /usr/share/gnucash)
228 : : * It searches in this order.
229 : : *
230 : : * This is used by gnc_path_find_localized_file to search for
231 : : * localized versions of files if they exist.
232 : : */
233 : : static gchar *
234 : 0 : gnc_path_find_localized_html_file_internal (const gchar * file_name)
235 : : {
236 : 0 : gchar *full_path = NULL;
237 : : int i;
238 : 0 : const gchar *env_doc_path = g_getenv("GNC_DOC_PATH");
239 : 0 : const gchar *default_dirs[] =
240 : : {
241 : 0 : gnc_build_userdata_path ("html"),
242 : 0 : gnc_path_get_pkgdocdir (),
243 : 0 : gnc_path_get_pkgdatadir (),
244 : : NULL
245 : 0 : };
246 : : gchar **dirs;
247 : :
248 : 0 : if (!file_name || *file_name == '\0')
249 : 0 : return NULL;
250 : :
251 : : /* Allow search path override via GNC_DOC_PATH environment variable */
252 : 0 : if (env_doc_path)
253 : 0 : dirs = g_strsplit (env_doc_path, G_SEARCHPATH_SEPARATOR_S, -1);
254 : : else
255 : 0 : dirs = (gchar **)default_dirs;
256 : :
257 : 0 : for (i = 0; dirs[i]; i++)
258 : : {
259 : 0 : full_path = g_build_filename (dirs[i], file_name, (gchar *)NULL);
260 : 0 : g_debug ("Checking for existence of %s", full_path);
261 : 0 : full_path = check_path_return_if_valid (full_path);
262 : 0 : if (full_path != NULL)
263 : 0 : return full_path;
264 : : }
265 : :
266 : 0 : return NULL;
267 : : }
268 : :
269 : : /** @brief Find an absolute path to a localized version of a given
270 : : * relative path to a html or html related file.
271 : : * If no localized version exists, an absolute path to the file
272 : : * is searched for. If that file doesn't exist either, returns NULL.
273 : : *
274 : : * @warning file_name should be a simple path fragment. It shouldn't
275 : : * contain xml:// or http:// or <whatever>:// other protocol specifiers.
276 : : *
277 : : * If passed a string which g_path_is_absolute declares an absolute
278 : : * path, return the argument.
279 : : *
280 : : * Otherwise, assume that file_name is a well-formed relative path and
281 : : * try to find a file with its path relative to
282 : : * \li a localized subdirectory in the html directory
283 : : * of the user's configuration directory
284 : : * (e.g. $HOME/.gnucash/html/de_DE, $HOME/.gnucash/html/en,...)
285 : : * \li a localized subdirectory in the gnucash documentation directory
286 : : * (e.g. /usr/share/doc/gnucash/C,...)
287 : : * \li the html directory of the user's configuration directory
288 : : * (e.g. $HOME/.gnucash/html)
289 : : * \li the gnucash documentation directory
290 : : * (e.g. /usr/share/doc/gnucash/)
291 : : * \li last resort option: the gnucash data directory
292 : : * (e.g. /usr/share/gnucash/)
293 : : *
294 : : * The paths are searched for in that order. If a matching file is
295 : : * found, return the absolute path to it.
296 : :
297 : : * If one isn't found, return NULL.
298 : : *
299 : : * @param file_name The file path to resolve
300 : : *
301 : : * @return An absolute file path or NULL if no file is found.
302 : : */
303 : : gchar *
304 : 0 : gnc_path_find_localized_html_file (const gchar *file_name)
305 : : {
306 : 0 : gchar *loc_file_name = NULL;
307 : 0 : gchar *full_path = NULL;
308 : : const gchar * const *lang;
309 : :
310 : 0 : if (!file_name || *file_name == '\0')
311 : 0 : return NULL;
312 : :
313 : : /* An absolute path is returned unmodified. */
314 : 0 : if (g_path_is_absolute (file_name))
315 : 0 : return g_strdup (file_name);
316 : :
317 : : /* First try to find the file in any of the localized directories
318 : : * the user has set up on his system
319 : : */
320 : 0 : for (lang = g_get_language_names (); *lang; lang++)
321 : : {
322 : 0 : loc_file_name = g_build_filename (*lang, file_name, (gchar *)NULL);
323 : 0 : full_path = gnc_path_find_localized_html_file_internal (loc_file_name);
324 : 0 : g_free (loc_file_name);
325 : 0 : if (full_path != NULL)
326 : 0 : return full_path;
327 : : }
328 : :
329 : : /* If not found in a localized directory, try to find the file
330 : : * in any of the base directories
331 : : */
332 : 0 : return gnc_path_find_localized_html_file_internal (file_name);
333 : :
334 : : }
335 : :
336 : : /* ====================================================================== */
337 : : static auto gnc_userdata_home = bfs::path();
338 : : static auto gnc_userconfig_home = bfs::path();
339 : : static auto build_dir = bfs::path();
340 : : /*Provide static-stored strings for gnc_userdata_home and
341 : : * gnc_userconfig_home to ensure that the cstrings don't go out of
342 : : * scope when gnc_userdata_dir and gnc_userconfig_dir return them.
343 : : */
344 : : static std::string gnc_userdata_home_str;
345 : : static std::string gnc_userconfig_home_str;
346 : :
347 : 189 : static bool dir_is_descendant (const bfs::path& path, const bfs::path& base)
348 : : {
349 : 189 : auto test_path = path;
350 : 189 : if (bfs::exists (path))
351 : 171 : test_path = bfs::canonical (path);
352 : 189 : auto test_base = base;
353 : 189 : if (bfs::exists (base))
354 : 182 : test_base = bfs::canonical (base);
355 : :
356 : 189 : auto is_descendant = (test_path.string() == test_base.string());
357 : 536 : while (!test_path.empty() && !is_descendant)
358 : : {
359 : 347 : test_path = test_path.parent_path();
360 : 347 : is_descendant = (test_path.string() == test_base.string());
361 : : }
362 : 189 : return is_descendant;
363 : 189 : }
364 : :
365 : : /** @brief Check that the supplied directory path exists, is a directory, and
366 : : * that the user has adequate permissions to use it.
367 : : *
368 : : * @param dirname The path to check
369 : : */
370 : : static bool
371 : 189 : gnc_validate_directory (const bfs::path &dirname)
372 : : {
373 : 189 : if (dirname.empty())
374 : 0 : return false;
375 : :
376 : 189 : auto create_dirs = true;
377 : 189 : if (build_dir.empty() || !dir_is_descendant (dirname, build_dir))
378 : : {
379 : : /* Gnucash won't create a home directory
380 : : * if it doesn't exist yet. So if the directory to create
381 : : * is a descendant of the homedir, we can't create it either.
382 : : * This check is conditioned on do_homedir_check because
383 : : * we need to overrule it during build (when guile interferes)
384 : : * and testing.
385 : : */
386 : 24 : bfs::path home_dir(g_get_home_dir(), cvt);
387 : 24 : home_dir.imbue(bfs_locale);
388 : 24 : auto homedir_exists = bfs::exists(home_dir);
389 : 24 : auto is_descendant = dir_is_descendant (dirname, home_dir);
390 : 24 : if (!homedir_exists && is_descendant)
391 : 2 : create_dirs = false;
392 : 24 : }
393 : :
394 : : /* Create directories if they don't exist yet and we can
395 : : *
396 : : * Note this will do nothing if the directory and its
397 : : * parents already exist, but will fail if the path
398 : : * points to a file or a softlink. So it serves as a test
399 : : * for that as well.
400 : : */
401 : 189 : if (create_dirs)
402 : 187 : bfs::create_directories(dirname);
403 : : else
404 : 2 : throw (bfs::filesystem_error (
405 : 6 : std::string (dirname.string() +
406 : 4 : " is a descendant of a non-existing home directory. As " +
407 : : PACKAGE_NAME +
408 : : " will never create a home directory this path can't be used"),
409 : 4 : dirname, bst::error_code(bst::errc::permission_denied, bst::generic_category())));
410 : :
411 : 187 : auto d = bfs::directory_entry (dirname);
412 : 187 : auto perms = d.status().permissions();
413 : :
414 : : /* On Windows only write permission will be checked.
415 : : * So strictly speaking we'd need two error messages here depending
416 : : * on the platform. For simplicity this detail is glossed over though. */
417 : : #if PLATFORM(WINDOWS)
418 : : auto check_perms = bfs::owner_read | bfs::owner_write;
419 : : #else
420 : 187 : auto check_perms = bfs::owner_all;
421 : : #endif
422 : 187 : if ((perms & check_perms) != check_perms)
423 : 0 : throw (bfs::filesystem_error(
424 : 0 : std::string("Insufficient permissions, at least write and access permissions required: ")
425 : 0 : + dirname.string(), dirname,
426 : 0 : bst::error_code(bst::errc::permission_denied, bst::generic_category())));
427 : :
428 : 187 : return true;
429 : 187 : }
430 : :
431 : : /* Will attempt to copy all files and directories from src to dest
432 : : * Returns true if successful or false if not */
433 : : static bool
434 : 3 : copy_recursive(const bfs::path& src, const bfs::path& dest)
435 : : {
436 : 3 : if (!bfs::exists(src))
437 : 3 : return false;
438 : :
439 : : // Don't copy on self
440 : 0 : if (src.compare(dest) == 0)
441 : 0 : return false;
442 : :
443 : 0 : auto old_str = src.string();
444 : 0 : auto old_len = old_str.size();
445 : : try
446 : : {
447 : : /* Note: the for(auto elem : iterator) construct fails
448 : : * on travis (g++ 4.8.x, boost 1.54) so I'm using
449 : : * a traditional for loop here */
450 : 0 : for(auto direntry = bfs::recursive_directory_iterator(src);
451 : 0 : direntry != bfs::recursive_directory_iterator(); ++direntry)
452 : : {
453 : : #ifdef G_OS_WIN32
454 : : string cur_str = direntry->path().wstring();
455 : : #else
456 : 0 : string cur_str = direntry->path().string();
457 : : #endif
458 : 0 : auto cur_len = cur_str.size();
459 : 0 : string rel_str(cur_str, old_len, cur_len - old_len);
460 : 0 : bfs::path relpath(rel_str, cvt);
461 : 0 : auto newpath = bfs::absolute (relpath.relative_path(), dest);
462 : 0 : newpath.imbue(bfs_locale);
463 : 0 : bfs::copy(direntry->path(), newpath);
464 : 0 : }
465 : : }
466 : 0 : catch(const bfs::filesystem_error& ex)
467 : : {
468 : 0 : g_warning("An error occurred while trying to migrate the user configation from\n%s to\n%s"
469 : : "(Error: %s)",
470 : : src.string().c_str(), gnc_userdata_home_str.c_str(),
471 : : ex.what());
472 : 0 : return false;
473 : 0 : }
474 : :
475 : 0 : return true;
476 : 0 : }
477 : :
478 : : #ifdef G_OS_WIN32
479 : : /* g_get_user_data_dir defaults to CSIDL_LOCAL_APPDATA, but
480 : : * the roaming profile makes more sense.
481 : : * So this function is a copy of glib's internal get_special_folder
482 : : * and minimally adjusted to fetch CSIDL_APPDATA
483 : : */
484 : : static bfs::path
485 : : get_user_data_dir ()
486 : : {
487 : : wchar_t path[MAX_PATH+1];
488 : : HRESULT hr;
489 : : LPITEMIDLIST pidl = NULL;
490 : : BOOL b;
491 : :
492 : : hr = SHGetSpecialFolderLocation (NULL, CSIDL_APPDATA, &pidl);
493 : : if (hr == S_OK)
494 : : {
495 : : b = SHGetPathFromIDListW (pidl, path);
496 : : CoTaskMemFree (pidl);
497 : : }
498 : : bfs::path retval(path, cvt);
499 : : retval.imbue(bfs_locale);
500 : : return retval;
501 : : }
502 : : #elif defined MAC_INTEGRATION
503 : : static bfs::path
504 : : get_user_data_dir()
505 : : {
506 : : NSFileManager*fm = [NSFileManager defaultManager];
507 : : NSArray* appSupportDir = [fm URLsForDirectory:NSApplicationSupportDirectory
508 : : inDomains:NSUserDomainMask];
509 : : NSString *dirPath = nullptr;
510 : : if ([appSupportDir count] > 0)
511 : : {
512 : : NSURL* dirUrl = [appSupportDir objectAtIndex:0];
513 : : dirPath = [dirUrl path];
514 : : }
515 : : return [dirPath UTF8String];
516 : : }
517 : : #else
518 : : static bfs::path
519 : 3 : get_user_data_dir()
520 : : {
521 : 3 : return g_get_user_data_dir();
522 : : }
523 : : #endif
524 : :
525 : : /* Returns an absolute path to the user's data directory.
526 : : * Note the default path depends on the platform.
527 : : * - Windows: CSIDL_APPDATA
528 : : * - OS X: $HOME/Application Support
529 : : * - Linux: $XDG_DATA_HOME (or the default $HOME/.local/share)
530 : : */
531 : : static bfs::path
532 : 3 : get_userdata_home(void)
533 : : {
534 : 3 : auto try_tmp_dir = true;
535 : 3 : auto userdata_home = get_user_data_dir();
536 : :
537 : : /* g_get_user_data_dir doesn't check whether the path exists nor attempts to
538 : : * create it. So while it may return an actual path we may not be able to use it.
539 : : * Let's check that now */
540 : 3 : if (!userdata_home.empty())
541 : : {
542 : : try
543 : : {
544 : 3 : gnc_validate_directory(userdata_home); // May throw
545 : 2 : try_tmp_dir = false;
546 : : }
547 : 1 : catch (const bfs::filesystem_error& ex)
548 : : {
549 : 1 : auto path_string = userdata_home.string();
550 : 1 : g_warning("%s is not a suitable base directory for the user data. "
551 : : "Trying temporary directory instead.\n(Error: %s)",
552 : : path_string.c_str(), ex.what());
553 : 1 : }
554 : : }
555 : :
556 : : /* The path we got is not usable, so fall back to a path in TMP_DIR.
557 : : Hopefully we can always write there. */
558 : 3 : if (try_tmp_dir)
559 : : {
560 : 1 : bfs::path newpath(g_get_tmp_dir (), cvt);
561 : 1 : userdata_home = newpath / g_get_user_name ();
562 : 1 : userdata_home.imbue(bfs_locale);
563 : 1 : }
564 : 3 : g_assert(!userdata_home.empty());
565 : :
566 : 3 : return userdata_home;
567 : 0 : }
568 : :
569 : : /* Returns an absolute path to the user's config directory.
570 : : * Note the default path depends on the platform.
571 : : * - Windows: CSIDL_APPDATA
572 : : * - OS X: $HOME/Application Support
573 : : * - Linux: $XDG_CONFIG_HOME (or the default $HOME/.config)
574 : : */
575 : : static bfs::path
576 : 41 : get_userconfig_home(void)
577 : : {
578 : : /* On Windows and Macs the data directory is used, for Linux
579 : : $HOME/.config is used */
580 : : #if defined (G_OS_WIN32) || defined (MAC_INTEGRATION)
581 : : return get_user_data_dir();
582 : : #else
583 : 41 : return g_get_user_config_dir();
584 : : #endif
585 : : }
586 : :
587 : 3 : static std::string migrate_gnc_datahome()
588 : : {
589 : : // Specify location of dictionaries
590 : 3 : bfs::path old_dir(g_get_home_dir(), cvt);
591 : 3 : old_dir /= ".gnucash";
592 : :
593 : 3 : std::stringstream migration_msg;
594 : 3 : migration_msg.imbue(gnc_get_boost_locale());
595 : :
596 : : /* Step 1: copy directory $HOME/.gnucash to $GNC_DATA_HOME */
597 : 3 : auto full_copy = copy_recursive (old_dir, gnc_userdata_home);
598 : :
599 : : /* Step 2: move user editable config files from GNC_DATA_HOME to GNC_CONFIG_HOME
600 : : These files are:
601 : : - log.conf
602 : : - the most recent of "config-2.0.user", "config-1.8.user", "config-1.6.user",
603 : : "config.user"
604 : : Note: we'll also rename config.user to config-user.scm to make it more clear
605 : : this file is meant for custom scm code to load at run time */
606 : 3 : auto failed = std::vector<std::string>{};
607 : 3 : auto succeeded = std::vector<std::string>{};
608 : :
609 : : /* Move log.conf
610 : : * Note on OS X/Quarz and Windows GNC_DATA_HOME and GNC_CONFIG_HOME are the same, so this will do nothing */
611 : 3 : auto oldlogpath = gnc_userdata_home / "log.conf";
612 : 3 : auto newlogpath = gnc_userconfig_home / "log.conf";
613 : : try
614 : : {
615 : 3 : if (bfs::exists (oldlogpath) && gnc_validate_directory (gnc_userconfig_home) &&
616 : 0 : (oldlogpath != newlogpath))
617 : : {
618 : 0 : bfs::rename (oldlogpath, newlogpath);
619 : 0 : succeeded.emplace_back ("log.conf");
620 : : }
621 : : }
622 : 0 : catch (const bfs::filesystem_error& ex)
623 : : {
624 : 0 : failed.emplace_back ("log.conf");
625 : 0 : }
626 : :
627 : : /* Move/rename the most relevant config*.user file. The relevance comes from
628 : : * the order in which these files were searched for at gnucash startup.
629 : : * Make note of other config*.user files found to inform the user they are now ignored */
630 : : auto user_config_files = std::vector<std::string>
631 : : {
632 : : "config-2.0.user", "config-1.8.user",
633 : : "config-1.6.user", "config.user"
634 : 21 : };
635 : 3 : auto conf_exist_vec = std::vector<std::string> {};
636 : 3 : auto renamed_config = std::string();
637 : 15 : for (auto conf_file : user_config_files)
638 : : {
639 : 12 : auto oldconfpath = gnc_userdata_home / conf_file;
640 : : try
641 : : {
642 : 12 : if (bfs::exists (oldconfpath) && gnc_validate_directory (gnc_userconfig_home))
643 : : {
644 : : // Only migrate the most relevant of the config*.user files
645 : 0 : if (renamed_config.empty())
646 : : {
647 : : /* Translators: this string refers to a file name that gets renamed */
648 : 0 : renamed_config = conf_file + " (" + _("Renamed to:") + " config-user.scm)";
649 : 0 : auto newconfpath = gnc_userconfig_home / "config-user.scm";
650 : 0 : bfs::rename (oldconfpath, newconfpath);
651 : 0 : }
652 : : else
653 : : {
654 : : /* We want to report the obsolete file to the user */
655 : 0 : conf_exist_vec.emplace_back (conf_file);
656 : 0 : if (full_copy)
657 : : /* As we copied from .gnucash, just delete the obsolete file as well. It's still
658 : : * present in .gnucash if the user wants to recover it */
659 : 0 : bfs::remove (oldconfpath);
660 : : }
661 : : }
662 : : }
663 : 0 : catch (const bfs::filesystem_error& ex)
664 : : {
665 : 0 : failed.emplace_back (conf_file);
666 : 0 : }
667 : 12 : }
668 : 3 : if (!renamed_config.empty())
669 : 0 : succeeded.emplace_back (renamed_config);
670 : :
671 : : /* Step 3: inform the user of additional changes */
672 : 3 : if (full_copy || !succeeded.empty() || !conf_exist_vec.empty() || !failed.empty())
673 : 0 : migration_msg << _("Notice") << std::endl << std::endl;
674 : :
675 : 3 : if (full_copy)
676 : : {
677 : : migration_msg
678 : 0 : << _("Your gnucash metadata has been migrated.") << std::endl << std::endl
679 : : /* Translators: this refers to a directory name. */
680 : 0 : << _("Old location:") << " " << old_dir.string() << std::endl
681 : : /* Translators: this refers to a directory name. */
682 : 0 : << _("New location:") << " " << gnc_userdata_home.string() << std::endl << std::endl
683 : : // Translators {1} will be replaced with the package name (typically Gnucash) at runtime
684 : 0 : << bl::format (std::string{_("If you no longer intend to run {1} 2.6.x or older on this system you can safely remove the old directory.")})
685 : 0 : % PACKAGE_NAME;
686 : : }
687 : :
688 : 3 : if (full_copy &&
689 : 0 : (!succeeded.empty() || !conf_exist_vec.empty() || !failed.empty()))
690 : 0 : migration_msg << std::endl << std::endl
691 : 0 : << _("In addition:");
692 : :
693 : 3 : if (!succeeded.empty())
694 : : {
695 : 0 : migration_msg << std::endl << std::endl;
696 : 0 : if (full_copy)
697 : 0 : migration_msg << bl::format (std::string{ngettext("The following file has been copied to {1} instead:",
698 : : "The following files have been copied to {1} instead:",
699 : 0 : succeeded.size())}) % gnc_userconfig_home.string().c_str();
700 : : else
701 : 0 : migration_msg << bl::format (std::string{_("The following file in {1} has been renamed:")})
702 : 0 : % gnc_userconfig_home.string().c_str();
703 : :
704 : 0 : migration_msg << std::endl;
705 : 0 : for (const auto& success_file : succeeded)
706 : 0 : migration_msg << "- " << success_file << std::endl;
707 : : }
708 : 3 : if (!conf_exist_vec.empty())
709 : : {
710 : : migration_msg << "\n\n"
711 : 0 : << ngettext("The following file has become obsolete and will be ignored:",
712 : : "The following files have become obsolete and will be ignored:",
713 : 0 : conf_exist_vec.size())
714 : 0 : << std::endl;
715 : 0 : for (const auto& obs_file : conf_exist_vec)
716 : 0 : migration_msg << "- " << obs_file << std::endl;
717 : : }
718 : 3 : if (!failed.empty())
719 : : {
720 : 0 : migration_msg << std::endl << std::endl
721 : 0 : << bl::format (std::string{ngettext("The following file could not be moved to {1}:",
722 : : "The following files could not be moved to {1}:",
723 : 0 : failed.size())}) % gnc_userconfig_home.string().c_str()
724 : 0 : << std::endl;
725 : 0 : for (const auto& failed_file : failed)
726 : 0 : migration_msg << "- " << failed_file << std::endl;
727 : : }
728 : :
729 : 6 : return migration_msg.str ();
730 : 3 : }
731 : :
732 : :
733 : :
734 : : #if defined G_OS_WIN32 ||defined MAC_INTEGRATION
735 : : constexpr auto path_package = PACKAGE_NAME;
736 : : #else
737 : : constexpr auto path_package = PROJECT_NAME;
738 : : #endif
739 : :
740 : : // Initialize the user's config directory for gnucash
741 : : // creating it if it doesn't exist yet.
742 : : static void
743 : 37 : gnc_file_path_init_config_home (void)
744 : : {
745 : 37 : auto have_valid_userconfig_home = false;
746 : :
747 : : /* If this code is run while building/testing, use a fake GNC_CONFIG_HOME
748 : : * in the base of the build directory. This is to deal with all kinds of
749 : : * issues when the build environment is not a complete environment (like
750 : : * it could be missing a valid home directory). */
751 : 37 : auto env_build_dir = g_getenv ("GNC_BUILDDIR");
752 : 37 : bfs::path new_dir(env_build_dir ? env_build_dir : "", cvt);
753 : 37 : new_dir.imbue(bfs_locale);
754 : 37 : build_dir = std::move(new_dir);
755 : 37 : auto running_uninstalled = (g_getenv ("GNC_UNINSTALLED") != NULL);
756 : 37 : if (running_uninstalled && !build_dir.empty())
757 : : {
758 : 33 : gnc_userconfig_home = build_dir / "gnc_config_home";
759 : : try
760 : : {
761 : 33 : gnc_validate_directory (gnc_userconfig_home); // May throw
762 : 33 : have_valid_userconfig_home = true;
763 : : }
764 : 0 : catch (const bfs::filesystem_error& ex)
765 : : {
766 : 0 : auto path_string = gnc_userconfig_home.string();
767 : 0 : g_warning("%s (due to run during at build time) is not a suitable directory for user configuration files. "
768 : : "Trying another directory instead.\n(Error: %s)",
769 : : path_string.c_str(), ex.what());
770 : 0 : }
771 : : }
772 : :
773 : 37 : if (!have_valid_userconfig_home)
774 : : {
775 : : /* If environment variable GNC_CONFIG_HOME is set, try whether
776 : : * it points at a valid directory. */
777 : 4 : auto gnc_userconfig_home_env = g_getenv ("GNC_CONFIG_HOME");
778 : 4 : if (gnc_userconfig_home_env)
779 : : {
780 : 0 : bfs::path newdir(gnc_userconfig_home_env, cvt);
781 : 0 : newdir.imbue(bfs_locale);
782 : 0 : gnc_userconfig_home = std::move(newdir);
783 : : try
784 : : {
785 : 0 : gnc_validate_directory (gnc_userconfig_home); // May throw
786 : 0 : have_valid_userconfig_home = true;
787 : : }
788 : 0 : catch (const bfs::filesystem_error& ex)
789 : : {
790 : 0 : auto path_string = gnc_userconfig_home.string();
791 : 0 : g_warning("%s (from environment variable 'GNC_CONFIG_HOME') is not a suitable directory for user configuration files. "
792 : : "Trying the default instead.\n(Error: %s)",
793 : : path_string.c_str(), ex.what());
794 : 0 : }
795 : 0 : }
796 : : }
797 : :
798 : 37 : if (!have_valid_userconfig_home)
799 : : {
800 : : /* Determine platform dependent default userconfig_home_path
801 : : * and check whether it's valid */
802 : 4 : auto userconfig_home = get_userconfig_home();
803 : 4 : gnc_userconfig_home = userconfig_home / path_package;
804 : : try
805 : : {
806 : 4 : gnc_validate_directory (gnc_userconfig_home);
807 : : }
808 : 0 : catch (const bfs::filesystem_error& ex)
809 : : {
810 : 0 : g_warning ("User configuration directory doesn't exist, yet could not be created. Proceed with caution.\n"
811 : : "(Error: %s)", ex.what());
812 : 0 : }
813 : 4 : }
814 : 37 : gnc_userconfig_home_str = gnc_userconfig_home.string();
815 : 37 : }
816 : :
817 : : // Initialize the user's config directory for gnucash
818 : : // creating it if it didn't exist yet.
819 : : // The function will return true if the directory already
820 : : // existed or false if it had to be created
821 : : static bool
822 : 37 : gnc_file_path_init_data_home (void)
823 : : {
824 : : // Initialize the user's data directory for gnucash
825 : 37 : auto gnc_userdata_home_exists = false;
826 : 37 : auto have_valid_userdata_home = false;
827 : :
828 : : /* If this code is run while building/testing, use a fake GNC_DATA_HOME
829 : : * in the base of the build directory. This is to deal with all kinds of
830 : : * issues when the build environment is not a complete environment (like
831 : : * it could be missing a valid home directory). */
832 : 37 : auto env_build_dir = g_getenv ("GNC_BUILDDIR");
833 : 37 : bfs::path new_dir(env_build_dir ? env_build_dir : "", cvt);
834 : 37 : new_dir.imbue(bfs_locale);
835 : 37 : build_dir = std::move(new_dir);
836 : 37 : auto running_uninstalled = (g_getenv ("GNC_UNINSTALLED") != NULL);
837 : 37 : if (running_uninstalled && !build_dir.empty())
838 : : {
839 : 33 : gnc_userdata_home = build_dir / "gnc_data_home";
840 : : try
841 : : {
842 : 33 : gnc_validate_directory (gnc_userdata_home); // May throw
843 : 33 : have_valid_userdata_home = true;
844 : 33 : gnc_userdata_home_exists = true; // To prevent possible migration further down
845 : : }
846 : 0 : catch (const bfs::filesystem_error& ex)
847 : : {
848 : 0 : auto path_string = gnc_userdata_home.string();
849 : 0 : g_warning("%s (due to run during at build time) is not a suitable directory for user data. "
850 : : "Trying another directory instead.\n(Error: %s)",
851 : : path_string.c_str(), ex.what());
852 : 0 : }
853 : : }
854 : :
855 : 37 : if (!have_valid_userdata_home)
856 : : {
857 : : /* If environment variable GNC_DATA_HOME is set, try whether
858 : : * it points at a valid directory. */
859 : 4 : auto gnc_userdata_home_env = g_getenv ("GNC_DATA_HOME");
860 : 4 : if (gnc_userdata_home_env)
861 : : {
862 : 2 : bfs::path newdir(gnc_userdata_home_env, cvt);
863 : 2 : newdir.imbue(bfs_locale);
864 : 2 : gnc_userdata_home = std::move(newdir);
865 : : try
866 : : {
867 : 2 : gnc_userdata_home_exists = bfs::exists (gnc_userdata_home);
868 : 2 : gnc_validate_directory (gnc_userdata_home); // May throw
869 : 1 : have_valid_userdata_home = true;
870 : : }
871 : 1 : catch (const bfs::filesystem_error& ex)
872 : : {
873 : 1 : auto path_string = gnc_userdata_home.string();
874 : 1 : g_warning("%s (from environment variable 'GNC_DATA_HOME') is not a suitable directory for user data. "
875 : : "Trying the default instead.\n(Error: %s)",
876 : : path_string.c_str(), ex.what());
877 : 1 : }
878 : 2 : }
879 : : }
880 : :
881 : 37 : if (!have_valid_userdata_home)
882 : : {
883 : : /* Determine platform dependent default userdata_home_path
884 : : * and check whether it's valid */
885 : 3 : auto userdata_home = get_userdata_home();
886 : 3 : gnc_userdata_home = userdata_home / path_package;
887 : : try
888 : : {
889 : 3 : gnc_userdata_home_exists = bfs::exists (gnc_userdata_home);
890 : 3 : gnc_validate_directory (gnc_userdata_home);
891 : : }
892 : 0 : catch (const bfs::filesystem_error& ex)
893 : : {
894 : 0 : g_warning ("User data directory doesn't exist, yet could not be created. Proceed with caution.\n"
895 : : "(Error: %s)", ex.what());
896 : 0 : }
897 : 3 : }
898 : 37 : gnc_userdata_home_str = gnc_userdata_home.string();
899 : 37 : return gnc_userdata_home_exists;
900 : 37 : }
901 : :
902 : : // Initialize the user's config and data directory for gnucash
903 : : // This function will also create these directories if they didn't
904 : : // exist yet.
905 : : // In addition it will trigger a migration if the user's data home
906 : : // didn't exist but the now obsolete GNC_DOT_DIR ($HOME/.gnucash)
907 : : // does.
908 : : // Finally it well ensure a number of default required directories
909 : : // will be created if they don't exist yet.
910 : : char *
911 : 37 : gnc_filepath_init (void)
912 : : {
913 : 37 : gnc_userconfig_home = get_userconfig_home() / path_package;
914 : 37 : gnc_userconfig_home_str = gnc_userconfig_home.string();
915 : :
916 : 37 : gnc_file_path_init_config_home ();
917 : 37 : auto gnc_userdata_home_exists = gnc_file_path_init_data_home ();
918 : :
919 : : /* Run migration code before creating the default directories
920 : : If migrating, these default directories are copied instead of created. */
921 : 37 : auto migration_notice = std::string ();
922 : 37 : if (!gnc_userdata_home_exists)
923 : 3 : migration_notice = migrate_gnc_datahome();
924 : :
925 : : /* Try to create the standard subdirectories for gnucash' user data */
926 : : try
927 : : {
928 : 37 : gnc_validate_directory (gnc_userdata_home / "books");
929 : 37 : gnc_validate_directory (gnc_userdata_home / "checks");
930 : 37 : gnc_validate_directory (gnc_userdata_home / "translog");
931 : : }
932 : 0 : catch (const bfs::filesystem_error& ex)
933 : : {
934 : 0 : g_warning ("Default user data subdirectories don't exist, yet could not be created. Proceed with caution.\n"
935 : : "(Error: %s)", ex.what());
936 : 0 : }
937 : :
938 : 74 : return migration_notice.empty() ? NULL : g_strdup (migration_notice.c_str());
939 : 37 : }
940 : :
941 : : /** @fn const gchar * gnc_userdata_dir ()
942 : : * @brief Ensure that the user's configuration directory exists and is minimally populated.
943 : : *
944 : : * Note that the default path depends on the platform.
945 : : * - Windows: CSIDL_APPDATA/Gnucash
946 : : * - OS X: $HOME/Application Support/Gnucash
947 : : * - Linux: $XDG_DATA_HOME/gnucash (or the default $HOME/.local/share/gnucash)
948 : : *
949 : : * If any of these paths don't exist and can't be created the function will
950 : : * fall back to
951 : : * - $HOME/.gnucash
952 : : * - <TMP_DIR>/gnucash
953 : : * (in that order)
954 : : *
955 : : * @return An absolute path to the configuration directory. This string is
956 : : * owned by the gnc_filepath_utils code and should not be freed by the user.
957 : : */
958 : :
959 : :
960 : : /* Note Don't create missing directories automatically
961 : : * here and in the next function except if the
962 : : * target directory is the temporary directory. This
963 : : * should be done properly at a higher level (in the gui
964 : : * code most likely) very early in application startup.
965 : : * This call is just a fallback to prevent the code from
966 : : * crashing because no directories were configured. This
967 : : * weird construct is set up because compiling our guile
968 : : * scripts also triggers this code and that's not the
969 : : * right moment to start creating the necessary directories.
970 : : * FIXME A better approach would be to have the gnc_userdata_home
971 : : * verification/creation be part of the application code instead
972 : : * of libgnucash. If libgnucash needs access to this directory
973 : : * libgnucash will need some kind of initialization routine
974 : : * that the application can call to set (among others) the proper
975 : : * gnc_uderdata_home for libgnucash. The only other aspect to
976 : : * consider here is how to handle this in the bindings (if they
977 : : * need it).
978 : : */
979 : : const gchar *
980 : 0 : gnc_userdata_dir (void)
981 : : {
982 : 0 : if (gnc_userdata_home.empty())
983 : 0 : gnc_filepath_init();
984 : 0 : return g_strdup(gnc_userdata_home_str.c_str());
985 : : }
986 : :
987 : : /** @fn const gchar * gnc_userconfig_dir ()
988 : : * @brief Return the user's config directory for gnucash
989 : : *
990 : : * @note the default path depends on the platform.
991 : : * - Windows: CSIDL_APPDATA/Gnucash
992 : : * - OS X: $HOME/Application Support/Gnucash
993 : : * - Linux: $XDG_CONFIG_HOME/gnucash (or the default $HOME/.config/gnucash)
994 : : *
995 : : * @note gnucash will not create this directory if it doesn't exist
996 : : *
997 : : * @return An absolute path to the configuration directory. This string is
998 : : * owned by the gnc_filepath_utils code and should not be freed by the user.
999 : : */
1000 : : const gchar *
1001 : 0 : gnc_userconfig_dir (void)
1002 : : {
1003 : 0 : if (gnc_userdata_home.empty())
1004 : 0 : gnc_filepath_init();
1005 : :
1006 : 0 : return gnc_userconfig_home_str.c_str();
1007 : : }
1008 : :
1009 : : static const bfs::path&
1010 : 205 : gnc_userdata_dir_as_path (void)
1011 : : {
1012 : 205 : if (gnc_userdata_home.empty())
1013 : : /* Don't create missing directories automatically except
1014 : : * if the target directory is the temporary directory. This
1015 : : * should be done properly at a higher level (in the gui
1016 : : * code most likely) very early in application startup.
1017 : : * This call is just a fallback to prevent the code from
1018 : : * crashing because no directories were configured. */
1019 : 34 : gnc_filepath_init();
1020 : :
1021 : 205 : return gnc_userdata_home;
1022 : : }
1023 : :
1024 : : static const bfs::path&
1025 : 0 : gnc_userconfig_dir_as_path (void)
1026 : : {
1027 : 0 : if (gnc_userdata_home.empty())
1028 : : /* Don't create missing directories automatically except
1029 : : * if the target directory is the temporary directory. This
1030 : : * should be done properly at a higher level (in the gui
1031 : : * code most likely) very early in application startup.
1032 : : * This call is just a fallback to prevent the code from
1033 : : * crashing because no directories were configured. */
1034 : 0 : gnc_filepath_init();
1035 : :
1036 : 0 : return gnc_userconfig_home;
1037 : : }
1038 : :
1039 : 0 : gchar *gnc_file_path_absolute (const gchar *prefix, const gchar *relative)
1040 : : {
1041 : 0 : bfs::path path_relative (relative);
1042 : 0 : path_relative.imbue (bfs_locale);
1043 : 0 : bfs::path path_absolute;
1044 : 0 : bfs::path path_head;
1045 : :
1046 : 0 : if (prefix == nullptr)
1047 : : {
1048 : 0 : const gchar *doc_dir = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS);
1049 : 0 : if (doc_dir == nullptr)
1050 : 0 : path_head = bfs::path (gnc_userdata_dir ()); // running as root maybe
1051 : : else
1052 : 0 : path_head = bfs::path (doc_dir);
1053 : :
1054 : 0 : path_head.imbue (bfs_locale);
1055 : 0 : path_absolute = absolute (path_relative, path_head);
1056 : : }
1057 : : else
1058 : : {
1059 : 0 : bfs::path path_head (prefix);
1060 : 0 : path_head.imbue (bfs_locale);
1061 : 0 : path_absolute = absolute (path_relative, path_head);
1062 : 0 : }
1063 : 0 : path_absolute.imbue (bfs_locale);
1064 : :
1065 : 0 : return g_strdup (path_absolute.string().c_str());
1066 : 0 : }
1067 : :
1068 : : /** @fn gchar * gnc_build_userdata_path (const gchar *filename)
1069 : : * @brief Make a path to filename in the user's gnucash data directory.
1070 : : *
1071 : : * @param filename The name of the file
1072 : : *
1073 : : * @return An absolute path. The returned string should be freed by the user
1074 : : * using g_free().
1075 : : */
1076 : :
1077 : : gchar *
1078 : 38 : gnc_build_userdata_path (const gchar *filename)
1079 : : {
1080 : 38 : return g_strdup((gnc_userdata_dir_as_path() / filename).string().c_str());
1081 : : }
1082 : :
1083 : : /** @fn gchar * gnc_build_userconfig_path (const gchar *filename)
1084 : : * @brief Make a path to filename in the user's configuration directory.
1085 : : *
1086 : : * @param filename The name of the file
1087 : : *
1088 : : * @return An absolute path. The returned string should be freed by the user
1089 : : * using g_free().
1090 : : */
1091 : :
1092 : : gchar *
1093 : 0 : gnc_build_userconfig_path (const gchar *filename)
1094 : : {
1095 : 0 : return g_strdup((gnc_userconfig_dir_as_path() / filename).string().c_str());
1096 : : }
1097 : :
1098 : : /* Test whether c is a valid character for a win32 file name.
1099 : : * If so return false, otherwise return true.
1100 : : */
1101 : : static bool
1102 : 4149 : is_invalid_char (char c)
1103 : : {
1104 : 4149 : return (c == '/') || ( c == ':');
1105 : : }
1106 : :
1107 : : static bfs::path
1108 : 167 : gnc_build_userdata_subdir_path (const gchar *subdir, const gchar *filename)
1109 : : {
1110 : 167 : auto fn = std::string(filename);
1111 : :
1112 : 167 : std::replace_if (fn.begin(), fn.end(), is_invalid_char, '_');
1113 : 334 : auto result = (gnc_userdata_dir_as_path() / subdir) / fn;
1114 : 334 : return result;
1115 : 167 : }
1116 : :
1117 : : /** @fn gchar * gnc_build_book_path (const gchar *filename)
1118 : : * @brief Make a path to filename in the book subdirectory of the user's configuration directory.
1119 : : *
1120 : : * @param filename The name of the file
1121 : : *
1122 : : * @return An absolute path. The returned string should be freed by the user
1123 : : * using g_free().
1124 : : */
1125 : :
1126 : : gchar *
1127 : 5 : gnc_build_book_path (const gchar *filename)
1128 : : {
1129 : 5 : auto path = gnc_build_userdata_subdir_path("books", filename).string();
1130 : 10 : return g_strdup(path.c_str());
1131 : 5 : }
1132 : :
1133 : : /** @fn gchar * gnc_build_translog_path (const gchar *filename)
1134 : : * @brief Make a path to filename in the translog subdirectory of the user's configuration directory.
1135 : : *
1136 : : * @param filename The name of the file
1137 : : *
1138 : : * @return An absolute path. The returned string should be freed by the user
1139 : : * using g_free().
1140 : : */
1141 : :
1142 : : gchar *
1143 : 5 : gnc_build_translog_path (const gchar *filename)
1144 : : {
1145 : 5 : auto path = gnc_build_userdata_subdir_path("translog", filename).string();
1146 : 10 : return g_strdup(path.c_str());
1147 : 5 : }
1148 : :
1149 : : /** @fn gchar * gnc_build_data_path (const gchar *filename)
1150 : : * @brief Make a path to filename in the data subdirectory of the user's configuration directory.
1151 : : *
1152 : : * @param filename The name of the file
1153 : : *
1154 : : * @return An absolute path. The returned string should be freed by the user
1155 : : * using g_free().
1156 : : */
1157 : :
1158 : : gchar *
1159 : 157 : gnc_build_data_path (const gchar *filename)
1160 : : {
1161 : 157 : auto path = gnc_build_userdata_subdir_path("data", filename).string();
1162 : 314 : return g_strdup(path.c_str());
1163 : 157 : }
1164 : :
1165 : : /** @fn gchar * gnc_build_scm_path (const gchar *filename)
1166 : : * @brief Make a path to filename in the scm directory.
1167 : : *
1168 : : * @param filename The name of the file
1169 : : *
1170 : : * @return An absolute path. The returned string should be freed by the user
1171 : : * using g_free().
1172 : : */
1173 : :
1174 : : gchar *
1175 : 12 : gnc_build_scm_path (const gchar *filename)
1176 : : {
1177 : 12 : gchar *scmdir = gnc_path_get_scmdir ();
1178 : 12 : gchar *result = g_build_filename (scmdir, filename, (gchar *)NULL);
1179 : 12 : g_free (scmdir);
1180 : 12 : return result;
1181 : : }
1182 : :
1183 : : /** @fn gchar * gnc_build_report_path (const gchar *filename)
1184 : : * @brief Make a path to filename in the report directory.
1185 : : *
1186 : : * @param filename The name of the file
1187 : : *
1188 : : * @return An absolute path. The returned string should be freed by the user
1189 : : * using g_free().
1190 : : */
1191 : :
1192 : : gchar *
1193 : 0 : gnc_build_report_path (const gchar *filename)
1194 : : {
1195 : 0 : gchar *rptdir = gnc_path_get_reportdir ();
1196 : 0 : gchar *result = g_build_filename (rptdir, filename, (gchar *)NULL);
1197 : 0 : g_free (rptdir);
1198 : 0 : return result;
1199 : : }
1200 : :
1201 : : /** @fn gchar * gnc_build_reports_path (const gchar *dirname)
1202 : : * @brief Make a path to dirname in the reports directory.
1203 : : *
1204 : : * @param dirname The name of the subdirectory
1205 : : *
1206 : : * @return An absolute path. The returned string should be freed by the user
1207 : : * using g_free().
1208 : : */
1209 : :
1210 : : gchar *
1211 : 0 : gnc_build_reports_path (const gchar *dirname)
1212 : : {
1213 : 0 : gchar *rptsdir = gnc_path_get_reportsdir ();
1214 : 0 : gchar *result = g_build_filename (rptsdir, dirname, (gchar *)NULL);
1215 : 0 : g_free (rptsdir);
1216 : 0 : return result;
1217 : : }
1218 : :
1219 : : /** @fn gchar * gnc_build_stdreports_path (const gchar *filename)
1220 : : * @brief Make a path to filename in the standard reports directory.
1221 : : *
1222 : : * @param filename The name of the file
1223 : : *
1224 : : * @return An absolute path. The returned string should be freed by the user
1225 : : * using g_free().
1226 : : */
1227 : :
1228 : : gchar *
1229 : 0 : gnc_build_stdreports_path (const gchar *filename)
1230 : : {
1231 : 0 : gchar *stdrptdir = gnc_path_get_stdreportsdir ();
1232 : 0 : gchar *result = g_build_filename (stdrptdir, filename, (gchar *)NULL);
1233 : 0 : g_free (stdrptdir);
1234 : 0 : return result;
1235 : : }
1236 : :
1237 : : static gchar *
1238 : 0 : gnc_filepath_locate_file (const gchar *default_path, const gchar *name)
1239 : : {
1240 : : gchar *fullname;
1241 : :
1242 : 0 : g_return_val_if_fail (name != NULL, NULL);
1243 : :
1244 : 0 : if (g_path_is_absolute (name))
1245 : 0 : fullname = g_strdup (name);
1246 : 0 : else if (default_path)
1247 : 0 : fullname = g_build_filename (default_path, name, nullptr);
1248 : : else
1249 : 0 : fullname = gnc_resolve_file_path (name);
1250 : :
1251 : 0 : if (!g_file_test (fullname, G_FILE_TEST_IS_REGULAR))
1252 : : {
1253 : 0 : g_warning ("Could not locate file %s", name);
1254 : 0 : g_free (fullname);
1255 : 0 : return NULL;
1256 : : }
1257 : :
1258 : 0 : return fullname;
1259 : : }
1260 : :
1261 : : gchar *
1262 : 0 : gnc_filepath_locate_data_file (const gchar *name)
1263 : : {
1264 : 0 : gchar *pkgdatadir = gnc_path_get_pkgdatadir ();
1265 : 0 : gchar *result = gnc_filepath_locate_file (pkgdatadir, name);
1266 : 0 : g_free (pkgdatadir);
1267 : 0 : return result;
1268 : : }
1269 : :
1270 : : gchar *
1271 : 0 : gnc_filepath_locate_pixmap (const gchar *name)
1272 : : {
1273 : : gchar *default_path;
1274 : : gchar *fullname;
1275 : 0 : gchar* pkgdatadir = gnc_path_get_pkgdatadir ();
1276 : :
1277 : 0 : default_path = g_build_filename (pkgdatadir, "pixmaps", nullptr);
1278 : 0 : g_free(pkgdatadir);
1279 : 0 : fullname = gnc_filepath_locate_file (default_path, name);
1280 : 0 : g_free(default_path);
1281 : :
1282 : 0 : return fullname;
1283 : : }
1284 : :
1285 : : gchar *
1286 : 0 : gnc_filepath_locate_ui_file (const gchar *name)
1287 : : {
1288 : : gchar *default_path;
1289 : : gchar *fullname;
1290 : 0 : gchar* pkgdatadir = gnc_path_get_pkgdatadir ();
1291 : :
1292 : 0 : default_path = g_build_filename (pkgdatadir, "ui", nullptr);
1293 : 0 : g_free(pkgdatadir);
1294 : 0 : fullname = gnc_filepath_locate_file (default_path, name);
1295 : 0 : g_free(default_path);
1296 : :
1297 : 0 : return fullname;
1298 : : }
1299 : :
1300 : : gchar *
1301 : 0 : gnc_filepath_locate_doc_file (const gchar *name)
1302 : : {
1303 : 0 : gchar *docdir = gnc_path_get_pkgdocdir ();
1304 : 0 : gchar *result = gnc_filepath_locate_file (docdir, name);
1305 : 0 : g_free (docdir);
1306 : 0 : return result;
1307 : : }
1308 : :
1309 : : std::vector<EnvPaths>
1310 : 0 : gnc_list_all_paths ()
1311 : : {
1312 : 0 : if (gnc_userdata_home.empty())
1313 : 0 : gnc_filepath_init ();
1314 : :
1315 : : return {
1316 : 0 : { "GNC_USERDATA_DIR", gnc_userdata_home_str.c_str(), true},
1317 : 0 : { "GNC_USERCONFIG_DIR", gnc_userconfig_home_str.c_str(), true },
1318 : 0 : { "GNC_BIN", g_getenv ("GNC_BIN"), false },
1319 : 0 : { "GNC_LIB", g_getenv ("GNC_LIB"), false },
1320 : 0 : { "GNC_CONF", g_getenv ("GNC_CONF"), false },
1321 : 0 : { "GNC_DATA", g_getenv ("GNC_DATA"), false },
1322 : 0 : };
1323 : : }
1324 : :
1325 : : static const std::regex
1326 : : backup_regex (".*[.](?:xac|gnucash)[.][0-9]{14}[.](?:xac|gnucash)$");
1327 : :
1328 : 0 : gboolean gnc_filename_is_backup (const char *filename)
1329 : : {
1330 : 0 : return std::regex_match (filename, backup_regex);
1331 : : }
1332 : :
1333 : : static const std::regex
1334 : : datafile_regex (".*[.](?:xac|gnucash)$");
1335 : :
1336 : 0 : gboolean gnc_filename_is_datafile (const char *filename)
1337 : : {
1338 : 0 : return !gnc_filename_is_backup (filename) &&
1339 : 0 : std::regex_match (filename, datafile_regex);
1340 : : }
1341 : :
1342 : : std::ofstream
1343 : 0 : gnc_open_filestream(const char* path)
1344 : : {
1345 : 0 : bfs::path bfs_path(path, cvt);
1346 : 0 : bfs_path.imbue(bfs_locale);
1347 : 0 : return std::ofstream(bfs_path.c_str());
1348 : 0 : }
1349 : : /* =============================== END OF FILE ========================== */
|