Skip to content
Snippets Groups Projects
Select Git revision
  • v3.3.6
  • CyclesPhi-dev default
  • blenderphi-v4.5-v1
  • main protected
  • blender-v4.5-release
  • cycles-v4.5-aurora
  • anari-v4.5
  • anari3
  • anari2
  • anari
  • blender-v4.4-release
  • anary-cycles-device
  • xml-exporter-main
  • blender-v4.3-release
  • temp-sculpt-dyntopo
  • blender-v3.3-release
  • brush-assets-project
  • pr-extensions-tidy-space
  • blender-v4.0-release
  • universal-scene-description
  • blender-v4.1-release
  • v4.4.0
  • v4.2.8
  • v3.6.21
  • v4.2.7
  • v3.6.20
  • v4.2.6
  • v4.3.2
  • v4.2.5
  • v3.6.19
  • v4.3.1
  • v4.3.0
  • v3.6.18
  • v4.2.4
  • v3.6.17
  • v4.2.3
  • v3.6.16
  • v4.2.2
  • v4.2.1
  • v3.6.15
  • v4.2.0
41 results

bpath.c

  • bpath.c 18.88 KiB
    /* SPDX-License-Identifier: GPL-2.0-or-later */
    
    /** \file
     * \ingroup bke
     */
    
    /* TODO:
     * currently there are some cases we don't support.
     * - passing output paths to the visitor?, like render out.
     * - passing sequence strips with many images.
     * - passing directory paths - visitors don't know which path is a dir or a file.
     */
    
    #include <sys/stat.h>
    
    #include <string.h>
    
    /* path/file handling stuff */
    #ifndef WIN32
    #  include <dirent.h>
    #  include <unistd.h>
    #else
    #  include "BLI_winstuff.h"
    #  include <io.h>
    #endif
    
    #include "MEM_guardedalloc.h"
    
    #include "DNA_brush_types.h"
    #include "DNA_cachefile_types.h"
    #include "DNA_fluid_types.h"
    #include "DNA_freestyle_types.h"
    #include "DNA_image_types.h"
    #include "DNA_material_types.h"
    #include "DNA_mesh_types.h"
    #include "DNA_modifier_types.h"
    #include "DNA_movieclip_types.h"
    #include "DNA_node_types.h"
    #include "DNA_object_fluidsim_types.h"
    #include "DNA_object_force_types.h"
    #include "DNA_object_types.h"
    #include "DNA_particle_types.h"
    #include "DNA_pointcache_types.h"
    #include "DNA_scene_types.h"
    #include "DNA_sequence_types.h"
    #include "DNA_sound_types.h"
    #include "DNA_text_types.h"
    #include "DNA_texture_types.h"
    #include "DNA_vfont_types.h"
    #include "DNA_volume_types.h"
    
    #include "BLI_blenlib.h"
    #include "BLI_utildefines.h"
    
    #include "BKE_idtype.h"
    #include "BKE_image.h"
    #include "BKE_lib_id.h"
    #include "BKE_library.h"
    #include "BKE_main.h"
    #include "BKE_node.h"
    #include "BKE_report.h"
    #include "BKE_vfont.h"
    
    #include "BKE_bpath.h" /* own include */
    
    #include "CLG_log.h"
    
    #include "SEQ_iterator.h"
    
    #ifndef _MSC_VER
    #  include "BLI_strict_flags.h"
    #endif
    
    static CLG_LogRef LOG = {"bke.bpath"};
    
    /* -------------------------------------------------------------------- */
    /** \name Generic File Path Traversal API
     * \{ */
    
    void BKE_bpath_foreach_path_id(BPathForeachPathData *bpath_data, ID *id)
    {
      const eBPathForeachFlag flag = bpath_data->flag;
      const char *absbase = (flag & BKE_BPATH_FOREACH_PATH_ABSOLUTE) ?
                                ID_BLEND_PATH(bpath_data->bmain, id) :
                                NULL;
      bpath_data->absolute_base_path = absbase;
    
      if ((flag & BKE_BPATH_FOREACH_PATH_SKIP_LINKED) && ID_IS_LINKED(id)) {
        return;
      }
    
      if (id->library_weak_reference != NULL &&
          (flag & BKE_BPATH_TRAVERSE_SKIP_WEAK_REFERENCES) == 0) {
        BKE_bpath_foreach_path_fixed_process(bpath_data, id->library_weak_reference->library_filepath);
      }
    
      bNodeTree *embedded_node_tree = ntreeFromID(id);
      if (embedded_node_tree != NULL) {
        BKE_bpath_foreach_path_id(bpath_data, &embedded_node_tree->id);
      }
    
      const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id);
    
      BLI_assert(id_type != NULL);
      if (id_type == NULL || id_type->foreach_path == NULL) {
        return;
      }
    
      id_type->foreach_path(id, bpath_data);
    }
    
    void BKE_bpath_foreach_path_main(BPathForeachPathData *bpath_data)
    {
      ID *id;
      FOREACH_MAIN_ID_BEGIN (bpath_data->bmain, id) {
        BKE_bpath_foreach_path_id(bpath_data, id);
      }
      FOREACH_MAIN_ID_END;
    }
    
    bool BKE_bpath_foreach_path_fixed_process(BPathForeachPathData *bpath_data, char *path)
    {
      const char *absolute_base_path = bpath_data->absolute_base_path;
    
      char path_src_buf[FILE_MAX];
      const char *path_src;
      char path_dst[FILE_MAX];
    
      if (absolute_base_path) {
        BLI_strncpy(path_src_buf, path, sizeof(path_src_buf));
        BLI_path_abs(path_src_buf, absolute_base_path);
        path_src = path_src_buf;
      }
      else {
        path_src = path;
      }
    
      /* so functions can check old value */
      BLI_strncpy(path_dst, path, FILE_MAX);
    
      if (bpath_data->callback_function(bpath_data, path_dst, path_src)) {
        BLI_strncpy(path, path_dst, FILE_MAX);
        return true;
      }
    
      return false;
    }
    
    bool BKE_bpath_foreach_path_dirfile_fixed_process(BPathForeachPathData *bpath_data,
                                                      char *path_dir,
                                                      char *path_file)
    {
      const char *absolute_base_path = bpath_data->absolute_base_path;
    
      char path_src[FILE_MAX];
      char path_dst[FILE_MAX];
    
      BLI_join_dirfile(path_src, sizeof(path_src), path_dir, path_file);
    
      /* So that functions can access the old value. */
      BLI_strncpy(path_dst, path_src, FILE_MAX);
    
      if (absolute_base_path) {
        BLI_path_abs(path_src, absolute_base_path);
      }
    
      if (bpath_data->callback_function(bpath_data, path_dst, (const char *)path_src)) {
        BLI_split_dirfile(path_dst, path_dir, path_file, FILE_MAXDIR, FILE_MAXFILE);
        return true;
      }
    
      return false;
    }
    
    bool BKE_bpath_foreach_path_allocated_process(BPathForeachPathData *bpath_data, char **path)
    {
      const char *absolute_base_path = bpath_data->absolute_base_path;
    
      char path_src_buf[FILE_MAX];
      const char *path_src;
      char path_dst[FILE_MAX];
    
      if (absolute_base_path) {
        BLI_strncpy(path_src_buf, *path, sizeof(path_src_buf));
        BLI_path_abs(path_src_buf, absolute_base_path);
        path_src = path_src_buf;
      }
      else {
        path_src = *path;
      }
    
      if (bpath_data->callback_function(bpath_data, path_dst, path_src)) {
        MEM_freeN(*path);
        (*path) = BLI_strdup(path_dst);
        return true;
      }
    
      return false;
    }
    
    /** \} */
    
    /* -------------------------------------------------------------------- */
    /** \name Check Missing Files
     * \{ */
    
    static bool check_missing_files_foreach_path_cb(BPathForeachPathData *bpath_data,
                                                    char *UNUSED(path_dst),
                                                    const char *path_src)
    {
      ReportList *reports = (ReportList *)bpath_data->user_data;
    
      if (!BLI_exists(path_src)) {
        BKE_reportf(reports, RPT_WARNING, "Path '%s' not found", path_src);
      }
    
      return false;
    }
    
    void BKE_bpath_missing_files_check(Main *bmain, ReportList *reports)
    {
      BKE_bpath_foreach_path_main(&(BPathForeachPathData){
          .bmain = bmain,
          .callback_function = check_missing_files_foreach_path_cb,
          .flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_SKIP_PACKED |
                  BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN | BKE_BPATH_TRAVERSE_SKIP_WEAK_REFERENCES,
          .user_data = reports});
    }
    
    /** \} */
    
    /* -------------------------------------------------------------------- */
    /** \name Find Missing Files
     * \{ */
    
    #define MAX_DIR_RECURSE 16
    #define FILESIZE_INVALID_DIRECTORY -1
    
    /** Find the given filename recursively in the given search directory and its sub-directories.
     *
     * \note Use the biggest matching file found, so that thumbnails don't get used by mistake.
     *
     * \param search_directory: Directory to search in.
     * \param filename_src: Search for this filename.
     * \param r_filename_new: The path of the new found file will be copied here, caller must
     *                        initialize as empty string.
     * \param r_filesize: Size of the file, `FILESIZE_INVALID_DIRECTORY` if search directory could not
     *                    be opened.
     * \param r_recurse_depth: Current recursion depth.
     *
     * \return true if found, false otherwise.
     */
    static bool missing_files_find__recursive(const char *search_directory,
                                              const char *filename_src,
                                              char r_filename_new[FILE_MAX],
                                              int64_t *r_filesize,
                                              int *r_recurse_depth)
    {
      /* TODO: Move this function to BLI_path_utils? The 'biggest size' behavior is quite specific
       * though... */
      DIR *dir;
      BLI_stat_t status;
      char path[FILE_MAX];
      int64_t size;
      bool found = false;
    
      dir = opendir(search_directory);
    
      if (dir == NULL) {
        return found;
      }
    
      if (*r_filesize == FILESIZE_INVALID_DIRECTORY) {
        *r_filesize = 0; /* The directory opened fine. */
      }
    
      for (struct dirent *de = readdir(dir); de != NULL; de = readdir(dir)) {
        if (FILENAME_IS_CURRPAR(de->d_name)) {
          continue;
        }
    
        BLI_join_dirfile(path, sizeof(path), search_directory, de->d_name);
    
        if (BLI_stat(path, &status) == -1) {
          CLOG_WARN(&LOG, "Cannot get file status (`stat()`) of '%s'", path);
          continue;
        }
    
        if (S_ISREG(status.st_mode)) {                                  /* It is a file. */
          if (BLI_path_ncmp(filename_src, de->d_name, FILE_MAX) == 0) { /* Names match. */
            size = status.st_size;
            if ((size > 0) && (size > *r_filesize)) { /* Find the biggest matching file. */
              *r_filesize = size;
              BLI_strncpy(r_filename_new, path, FILE_MAX);
              found = true;
            }
          }
        }
        else if (S_ISDIR(status.st_mode)) { /* It is a sub-directory. */
          if (*r_recurse_depth <= MAX_DIR_RECURSE) {
            (*r_recurse_depth)++;
            found |= missing_files_find__recursive(
                path, filename_src, r_filename_new, r_filesize, r_recurse_depth);
            (*r_recurse_depth)--;
          }
        }
      }
    
      closedir(dir);
      return found;
    }
    
    typedef struct BPathFind_Data {
      const char *basedir;
      const char *searchdir;
      ReportList *reports;
      bool find_all; /* Also search for files which current path is still valid. */
    } BPathFind_Data;
    
    static bool missing_files_find_foreach_path_cb(BPathForeachPathData *bpath_data,
                                                   char *path_dst,
                                                   const char *path_src)
    {
      BPathFind_Data *data = (BPathFind_Data *)bpath_data->user_data;
      char filename_new[FILE_MAX];
    
      int64_t filesize = FILESIZE_INVALID_DIRECTORY;
      int recurse_depth = 0;
      bool is_found;
    
      if (!data->find_all && BLI_exists(path_src)) {
        return false;
      }
    
      filename_new[0] = '\0';
    
      is_found = missing_files_find__recursive(
          data->searchdir, BLI_path_basename(path_src), filename_new, &filesize, &recurse_depth);
    
      if (filesize == FILESIZE_INVALID_DIRECTORY) {
        BKE_reportf(data->reports,
                    RPT_WARNING,
                    "Could not open the directory '%s'",
                    BLI_path_basename(data->searchdir));
        return false;
      }
      if (is_found == false) {
        BKE_reportf(data->reports,
                    RPT_WARNING,
                    "Could not find '%s' in '%s'",
                    BLI_path_basename(path_src),
                    data->searchdir);
        return false;
      }
    
      bool was_relative = BLI_path_is_rel(path_dst);
    
      BLI_strncpy(path_dst, filename_new, FILE_MAX);
    
      /* Keep the path relative if the previous one was relative. */
      if (was_relative) {
        BLI_path_rel(path_dst, data->basedir);
      }
    
      return true;
    }
    
    void BKE_bpath_missing_files_find(Main *bmain,
                                      const char *searchpath,
                                      ReportList *reports,
                                      const bool find_all)
    {
      struct BPathFind_Data data = {NULL};
      const int flag = BKE_BPATH_FOREACH_PATH_ABSOLUTE | BKE_BPATH_FOREACH_PATH_RELOAD_EDITED |
                       BKE_BPATH_FOREACH_PATH_RESOLVE_TOKEN;
    
      data.basedir = BKE_main_blendfile_path(bmain);
      data.reports = reports;
      data.searchdir = searchpath;
      data.find_all = find_all;
    
      BKE_bpath_foreach_path_main(
          &(BPathForeachPathData){.bmain = bmain,
                                  .callback_function = missing_files_find_foreach_path_cb,
                                  .flag = flag,
                                  .user_data = &data});
    }
    
    #undef MAX_DIR_RECURSE
    #undef FILESIZE_INVALID_DIRECTORY
    
    /** \} */
    
    /* -------------------------------------------------------------------- */
    /** \name Rebase Relative Paths
     * \{ */
    
    typedef struct BPathRebase_Data {
      const char *basedir_src;
      const char *basedir_dst;
      ReportList *reports;
    
      int count_tot;
      int count_changed;
      int count_failed;
    } BPathRebase_Data;
    
    static bool relative_rebase_foreach_path_cb(BPathForeachPathData *bpath_data,
                                                char *path_dst,
                                                const char *path_src)
    {
      BPathRebase_Data *data = (BPathRebase_Data *)bpath_data->user_data;
    
      data->count_tot++;
    
      if (!BLI_path_is_rel(path_src)) {
        /* Absolute, leave this as-is. */
        return false;
      }
    
      char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE];
      BLI_strncpy(filepath, path_src, FILE_MAX);
      if (!BLI_path_abs(filepath, data->basedir_src)) {
        BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src);
        data->count_failed++;
        return false;
      }
    
      BLI_path_normalize(NULL, filepath);
    
      /* This may fail, if so it's fine to leave absolute since the path is still valid. */
      BLI_path_rel(filepath, data->basedir_dst);
    
      BLI_strncpy(path_dst, filepath, FILE_MAX);
      data->count_changed++;
      return true;
    }
    
    void BKE_bpath_relative_rebase(Main *bmain,
                                   const char *basedir_src,
                                   const char *basedir_dst,
                                   ReportList *reports)
    {
      BPathRebase_Data data = {NULL};
      const int flag = (BKE_BPATH_FOREACH_PATH_SKIP_LINKED | BKE_BPATH_FOREACH_PATH_SKIP_MULTIFILE);
    
      BLI_assert(basedir_src[0] != '\0');
      BLI_assert(basedir_dst[0] != '\0');
    
      data.basedir_src = basedir_src;
      data.basedir_dst = basedir_dst;
      data.reports = reports;
    
      BKE_bpath_foreach_path_main(
          &(BPathForeachPathData){.bmain = bmain,
                                  .callback_function = relative_rebase_foreach_path_cb,
                                  .flag = flag,
                                  .user_data = &data});
    
      BKE_reportf(reports,
                  data.count_failed ? RPT_WARNING : RPT_INFO,
                  "Total files %d | Changed %d | Failed %d",
                  data.count_tot,
                  data.count_changed,
                  data.count_failed);
    }
    
    /** \} */
    
    /* -------------------------------------------------------------------- */
    /** \name Make Paths Relative Or Absolute
     * \{ */
    
    typedef struct BPathRemap_Data {
      const char *basedir;
      ReportList *reports;
    
      int count_tot;
      int count_changed;
      int count_failed;
    } BPathRemap_Data;
    
    static bool relative_convert_foreach_path_cb(BPathForeachPathData *bpath_data,
                                                 char *path_dst,
                                                 const char *path_src)
    {
      BPathRemap_Data *data = (BPathRemap_Data *)bpath_data->user_data;
    
      data->count_tot++;
    
      if (BLI_path_is_rel(path_src)) {
        return false; /* Already relative. */
      }
    
      BLI_strncpy(path_dst, path_src, FILE_MAX);
      BLI_path_rel(path_dst, data->basedir);
      if (BLI_path_is_rel(path_dst)) {
        data->count_changed++;
      }
      else {
        BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made relative", path_src);
        data->count_failed++;
      }
      return true;
    }
    
    static bool absolute_convert_foreach_path_cb(BPathForeachPathData *bpath_data,
                                                 char *path_dst,
                                                 const char *path_src)
    {
      BPathRemap_Data *data = (BPathRemap_Data *)bpath_data->user_data;
    
      data->count_tot++;
    
      if (!BLI_path_is_rel(path_src)) {
        return false; /* Already absolute. */
      }
    
      BLI_strncpy(path_dst, path_src, FILENAME_MAX);
      BLI_path_abs(path_dst, data->basedir);
      if (BLI_path_is_rel(path_dst) == false) {
        data->count_changed++;
      }
      else {
        BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src);
        data->count_failed++;
      }
      return true;
    }
    
    static void bpath_absolute_relative_convert(Main *bmain,
                                                const char *basedir,
                                                ReportList *reports,
                                                BPathForeachPathFunctionCallback callback_function)
    {
      BPathRemap_Data data = {NULL};
      const int flag = BKE_BPATH_FOREACH_PATH_SKIP_LINKED;
    
      BLI_assert(basedir[0] != '\0');
      if (basedir[0] == '\0') {
        CLOG_ERROR(&LOG, "basedir='', this is a bug");
        return;
      }
    
      data.basedir = basedir;
      data.reports = reports;
    
      BKE_bpath_foreach_path_main(&(BPathForeachPathData){
          .bmain = bmain, .callback_function = callback_function, .flag = flag, .user_data = &data});
    
      BKE_reportf(reports,
                  data.count_failed ? RPT_WARNING : RPT_INFO,
                  "Total files %d | Changed %d | Failed %d",
                  data.count_tot,
                  data.count_changed,
                  data.count_failed);
    }
    
    void BKE_bpath_relative_convert(Main *bmain, const char *basedir, ReportList *reports)
    {
      bpath_absolute_relative_convert(bmain, basedir, reports, relative_convert_foreach_path_cb);
    }
    
    void BKE_bpath_absolute_convert(Main *bmain, const char *basedir, ReportList *reports)
    {
      bpath_absolute_relative_convert(bmain, basedir, reports, absolute_convert_foreach_path_cb);
    }
    
    /** \} */
    
    /* -------------------------------------------------------------------- */
    /** \name Backup/Restore/Free paths list functions.
     *
     * \{ */
    
    struct PathStore {
      struct PathStore *next, *prev;
    };
    
    static bool bpath_list_append(BPathForeachPathData *bpath_data,
                                  char *UNUSED(path_dst),
                                  const char *path_src)
    {
      ListBase *path_list = bpath_data->user_data;
      size_t path_size = strlen(path_src) + 1;
    
      /* NOTE: the PathStore and its string are allocated together in a single alloc. */
      struct PathStore *path_store = MEM_mallocN(sizeof(struct PathStore) + path_size, __func__);
      char *filepath = (char *)(path_store + 1);
    
      BLI_strncpy(filepath, path_src, path_size);
      BLI_addtail(path_list, path_store);
      return false;
    }
    
    static bool bpath_list_restore(BPathForeachPathData *bpath_data,
                                   char *path_dst,
                                   const char *path_src)
    {
      ListBase *path_list = bpath_data->user_data;
    
      /* `ls->first` should never be NULL, because the number of paths should not change.
       * If this happens, there is a bug in caller code. */
      BLI_assert(!BLI_listbase_is_empty(path_list));
    
      struct PathStore *path_store = path_list->first;
      const char *filepath = (char *)(path_store + 1);
      bool is_path_changed = false;
    
      if (!STREQ(path_src, filepath)) {
        BLI_strncpy(path_dst, filepath, FILE_MAX);
        is_path_changed = true;
      }
    
      BLI_freelinkN(path_list, path_store);
      return is_path_changed;
    }
    
    void *BKE_bpath_list_backup(Main *bmain, const eBPathForeachFlag flag)
    {
      ListBase *path_list = MEM_callocN(sizeof(ListBase), __func__);
    
      BKE_bpath_foreach_path_main(&(BPathForeachPathData){.bmain = bmain,
                                                          .callback_function = bpath_list_append,
                                                          .flag = flag,
                                                          .user_data = path_list});
    
      return path_list;
    }
    
    void BKE_bpath_list_restore(Main *bmain, const eBPathForeachFlag flag, void *path_list_handle)
    {
      ListBase *path_list = path_list_handle;
    
      BKE_bpath_foreach_path_main(&(BPathForeachPathData){.bmain = bmain,
                                                          .callback_function = bpath_list_restore,
                                                          .flag = flag,
                                                          .user_data = path_list});
    }
    
    void BKE_bpath_list_free(void *path_list_handle)
    {
      ListBase *path_list = path_list_handle;
      /* The whole list should have been consumed by #BKE_bpath_list_restore, see also comment in
       * #bpath_list_restore. */
      BLI_assert(BLI_listbase_is_empty(path_list));
    
      BLI_freelistN(path_list);
      MEM_freeN(path_list);
    }
    
    /** \} */