Cleanup: rename 'name' to 'filepath' for DNA types
[blender.git] / source / blender / blenkernel / intern / bpath.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16
17 /** \file
18  * \ingroup bli
19  */
20
21 /* TODO,
22  * currently there are some cases we don't support.
23  * - passing output paths to the visitor?, like render out.
24  * - passing sequence strips with many images.
25  * - passing directory paths - visitors don't know which path is a dir or a file.
26  * */
27
28 #include <sys/stat.h>
29
30 #include <assert.h>
31 #include <string.h>
32
33 /* path/file handling stuff */
34 #ifndef WIN32
35 #  include <dirent.h>
36 #  include <unistd.h>
37 #else
38 #  include "BLI_winstuff.h"
39 #  include <io.h>
40 #endif
41
42 #include "MEM_guardedalloc.h"
43
44 #include "DNA_brush_types.h"
45 #include "DNA_cachefile_types.h"
46 #include "DNA_fluid_types.h"
47 #include "DNA_freestyle_types.h"
48 #include "DNA_image_types.h"
49 #include "DNA_material_types.h"
50 #include "DNA_mesh_types.h"
51 #include "DNA_modifier_types.h"
52 #include "DNA_movieclip_types.h"
53 #include "DNA_node_types.h"
54 #include "DNA_object_fluidsim_types.h"
55 #include "DNA_object_force_types.h"
56 #include "DNA_object_types.h"
57 #include "DNA_particle_types.h"
58 #include "DNA_pointcache_types.h"
59 #include "DNA_scene_types.h"
60 #include "DNA_sequence_types.h"
61 #include "DNA_sound_types.h"
62 #include "DNA_text_types.h"
63 #include "DNA_texture_types.h"
64 #include "DNA_vfont_types.h"
65 #include "DNA_volume_types.h"
66
67 #include "BLI_blenlib.h"
68 #include "BLI_utildefines.h"
69
70 #include "BKE_font.h"
71 #include "BKE_image.h"
72 #include "BKE_lib_id.h"
73 #include "BKE_library.h"
74 #include "BKE_main.h"
75 #include "BKE_node.h"
76 #include "BKE_report.h"
77 #include "BKE_sequencer.h"
78
79 #include "BKE_bpath.h" /* own include */
80
81 #include "CLG_log.h"
82
83 #ifndef _MSC_VER
84 #  include "BLI_strict_flags.h"
85 #endif
86
87 static CLG_LogRef LOG = {"bke.bpath"};
88
89 /* -------------------------------------------------------------------- */
90 /** \name Check Missing Files
91  * \{ */
92
93 static bool checkMissingFiles_visit_cb(void *userdata,
94                                        char *UNUSED(path_dst),
95                                        const char *path_src)
96 {
97   ReportList *reports = (ReportList *)userdata;
98
99   if (!BLI_exists(path_src)) {
100     BKE_reportf(reports, RPT_WARNING, "Path '%s' not found", path_src);
101   }
102
103   return false;
104 }
105
106 /* high level function */
107 void BKE_bpath_missing_files_check(Main *bmain, ReportList *reports)
108 {
109   BKE_bpath_traverse_main(bmain,
110                           checkMissingFiles_visit_cb,
111                           BKE_BPATH_TRAVERSE_ABS | BKE_BPATH_TRAVERSE_SKIP_PACKED,
112                           reports);
113 }
114
115 /** \} */
116
117 /* -------------------------------------------------------------------- */
118 /** \name Rebase Relative Paths
119  * \{ */
120
121 typedef struct BPathRebase_Data {
122   const char *basedir_src;
123   const char *basedir_dst;
124   ReportList *reports;
125
126   int count_tot;
127   int count_changed;
128   int count_failed;
129 } BPathRebase_Data;
130
131 static bool bpath_relative_rebase_visit_cb(void *userdata, char *path_dst, const char *path_src)
132 {
133   BPathRebase_Data *data = (BPathRebase_Data *)userdata;
134
135   data->count_tot++;
136
137   if (BLI_path_is_rel(path_src)) {
138     char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE];
139     BLI_strncpy(filepath, path_src, FILE_MAX);
140     if (BLI_path_abs(filepath, data->basedir_src)) {
141       BLI_path_normalize(NULL, filepath);
142
143       /* This may fail, if so it's fine to leave absolute since the path is still valid. */
144       BLI_path_rel(filepath, data->basedir_dst);
145
146       BLI_strncpy(path_dst, filepath, FILE_MAX);
147       data->count_changed++;
148       return true;
149     }
150     else {
151       /* Failed to make relative path absolute. */
152       BLI_assert(0);
153       BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src);
154       data->count_failed++;
155       return false;
156     }
157     return false;
158   }
159   else {
160     /* Absolute, leave this as-is. */
161     return false;
162   }
163 }
164
165 void BKE_bpath_relative_rebase(Main *bmain,
166                                const char *basedir_src,
167                                const char *basedir_dst,
168                                ReportList *reports)
169 {
170   BPathRebase_Data data = {NULL};
171   const int flag = BKE_BPATH_TRAVERSE_SKIP_LIBRARY;
172
173   BLI_assert(basedir_src[0] != '\0');
174   BLI_assert(basedir_dst[0] != '\0');
175
176   data.basedir_src = basedir_src;
177   data.basedir_dst = basedir_dst;
178   data.reports = reports;
179
180   BKE_bpath_traverse_main(bmain, bpath_relative_rebase_visit_cb, flag, (void *)&data);
181
182   BKE_reportf(reports,
183               data.count_failed ? RPT_WARNING : RPT_INFO,
184               "Total files %d | Changed %d | Failed %d",
185               data.count_tot,
186               data.count_changed,
187               data.count_failed);
188 }
189
190 /** \} */
191
192 /* -------------------------------------------------------------------- */
193 /** \name Make Paths Relative
194  * \{ */
195
196 typedef struct BPathRemap_Data {
197   const char *basedir;
198   ReportList *reports;
199
200   int count_tot;
201   int count_changed;
202   int count_failed;
203 } BPathRemap_Data;
204
205 static bool bpath_relative_convert_visit_cb(void *userdata, char *path_dst, const char *path_src)
206 {
207   BPathRemap_Data *data = (BPathRemap_Data *)userdata;
208
209   data->count_tot++;
210
211   if (BLI_path_is_rel(path_src)) {
212     return false; /* already relative */
213   }
214   else {
215     strcpy(path_dst, path_src);
216     BLI_path_rel(path_dst, data->basedir);
217     if (BLI_path_is_rel(path_dst)) {
218       data->count_changed++;
219     }
220     else {
221       BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made relative", path_src);
222       data->count_failed++;
223     }
224     return true;
225   }
226 }
227
228 void BKE_bpath_relative_convert(Main *bmain, const char *basedir, ReportList *reports)
229 {
230   BPathRemap_Data data = {NULL};
231   const int flag = BKE_BPATH_TRAVERSE_SKIP_LIBRARY;
232
233   if (basedir[0] == '\0') {
234     CLOG_ERROR(&LOG, "basedir='', this is a bug");
235     return;
236   }
237
238   data.basedir = basedir;
239   data.reports = reports;
240
241   BKE_bpath_traverse_main(bmain, bpath_relative_convert_visit_cb, flag, (void *)&data);
242
243   BKE_reportf(reports,
244               data.count_failed ? RPT_WARNING : RPT_INFO,
245               "Total files %d | Changed %d | Failed %d",
246               data.count_tot,
247               data.count_changed,
248               data.count_failed);
249 }
250
251 /** \} */
252
253 /* -------------------------------------------------------------------- */
254 /** \name Make Paths Absolute
255  * \{ */
256
257 static bool bpath_absolute_convert_visit_cb(void *userdata, char *path_dst, const char *path_src)
258 {
259   BPathRemap_Data *data = (BPathRemap_Data *)userdata;
260
261   data->count_tot++;
262
263   if (BLI_path_is_rel(path_src) == false) {
264     return false; /* already absolute */
265   }
266   else {
267     strcpy(path_dst, path_src);
268     BLI_path_abs(path_dst, data->basedir);
269     if (BLI_path_is_rel(path_dst) == false) {
270       data->count_changed++;
271     }
272     else {
273       BKE_reportf(data->reports, RPT_WARNING, "Path '%s' cannot be made absolute", path_src);
274       data->count_failed++;
275     }
276     return true;
277   }
278 }
279
280 /* similar to BKE_bpath_relative_convert - keep in sync! */
281 void BKE_bpath_absolute_convert(Main *bmain, const char *basedir, ReportList *reports)
282 {
283   BPathRemap_Data data = {NULL};
284   const int flag = BKE_BPATH_TRAVERSE_SKIP_LIBRARY;
285
286   if (basedir[0] == '\0') {
287     CLOG_ERROR(&LOG, "basedir='', this is a bug");
288     return;
289   }
290
291   data.basedir = basedir;
292   data.reports = reports;
293
294   BKE_bpath_traverse_main(bmain, bpath_absolute_convert_visit_cb, flag, (void *)&data);
295
296   BKE_reportf(reports,
297               data.count_failed ? RPT_WARNING : RPT_INFO,
298               "Total files %d | Changed %d | Failed %d",
299               data.count_tot,
300               data.count_changed,
301               data.count_failed);
302 }
303
304 /** \} */
305
306 /* -------------------------------------------------------------------- */
307 /** \name Find Missing Files
308  * \{ */
309
310 /**
311  * find this file recursively, use the biggest file so thumbnails don't get used by mistake
312  * \param filename_new: the path will be copied here, caller must initialize as empty string.
313  * \param dirname: subdir to search
314  * \param filename: set this filename
315  * \param filesize: filesize for the file
316  *
317  * \returns found: 1/0.
318  */
319 #define MAX_RECUR 16
320 static bool missing_files_find__recursive(char *filename_new,
321                                           const char *dirname,
322                                           const char *filename,
323                                           int64_t *r_filesize,
324                                           int *r_recur_depth)
325 {
326   /* file searching stuff */
327   DIR *dir;
328   struct dirent *de;
329   BLI_stat_t status;
330   char path[FILE_MAX];
331   int64_t size;
332   bool found = false;
333
334   dir = opendir(dirname);
335
336   if (dir == NULL) {
337     return found;
338   }
339
340   if (*r_filesize == -1) {
341     *r_filesize = 0; /* dir opened fine */
342   }
343
344   while ((de = readdir(dir)) != NULL) {
345
346     if (FILENAME_IS_CURRPAR(de->d_name)) {
347       continue;
348     }
349
350     BLI_join_dirfile(path, sizeof(path), dirname, de->d_name);
351
352     if (BLI_stat(path, &status) == -1) {
353       continue; /* cant stat, don't bother with this file, could print debug info here */
354     }
355
356     if (S_ISREG(status.st_mode)) {                              /* is file */
357       if (BLI_path_ncmp(filename, de->d_name, FILE_MAX) == 0) { /* name matches */
358         /* open the file to read its size */
359         size = status.st_size;
360         if ((size > 0) && (size > *r_filesize)) { /* find the biggest file */
361           *r_filesize = size;
362           BLI_strncpy(filename_new, path, FILE_MAX);
363           found = true;
364         }
365       }
366     }
367     else if (S_ISDIR(status.st_mode)) { /* is subdir */
368       if (*r_recur_depth <= MAX_RECUR) {
369         (*r_recur_depth)++;
370         found |= missing_files_find__recursive(
371             filename_new, path, filename, r_filesize, r_recur_depth);
372         (*r_recur_depth)--;
373       }
374     }
375   }
376   closedir(dir);
377   return found;
378 }
379
380 typedef struct BPathFind_Data {
381   const char *basedir;
382   const char *searchdir;
383   ReportList *reports;
384   bool find_all;
385 } BPathFind_Data;
386
387 static bool missing_files_find__visit_cb(void *userdata, char *path_dst, const char *path_src)
388 {
389   BPathFind_Data *data = (BPathFind_Data *)userdata;
390   char filename_new[FILE_MAX];
391
392   int64_t filesize = -1;
393   int recur_depth = 0;
394   bool found;
395
396   if (data->find_all == false) {
397     if (BLI_exists(path_src)) {
398       return false;
399     }
400   }
401
402   filename_new[0] = '\0';
403
404   found = missing_files_find__recursive(
405       filename_new, data->searchdir, BLI_path_basename(path_src), &filesize, &recur_depth);
406
407   if (filesize == -1) { /* could not open dir */
408     BKE_reportf(data->reports,
409                 RPT_WARNING,
410                 "Could not open directory '%s'",
411                 BLI_path_basename(data->searchdir));
412     return false;
413   }
414   else if (found == false) {
415     BKE_reportf(data->reports,
416                 RPT_WARNING,
417                 "Could not find '%s' in '%s'",
418                 BLI_path_basename(path_src),
419                 data->searchdir);
420     return false;
421   }
422   else {
423     bool was_relative = BLI_path_is_rel(path_dst);
424
425     BLI_strncpy(path_dst, filename_new, FILE_MAX);
426
427     /* keep path relative if the previous one was relative */
428     if (was_relative) {
429       BLI_path_rel(path_dst, data->basedir);
430     }
431
432     return true;
433   }
434 }
435
436 void BKE_bpath_missing_files_find(Main *bmain,
437                                   const char *searchpath,
438                                   ReportList *reports,
439                                   const bool find_all)
440 {
441   struct BPathFind_Data data = {NULL};
442   const int flag = BKE_BPATH_TRAVERSE_ABS | BKE_BPATH_TRAVERSE_RELOAD_EDITED;
443
444   data.basedir = BKE_main_blendfile_path(bmain);
445   data.reports = reports;
446   data.searchdir = searchpath;
447   data.find_all = find_all;
448
449   BKE_bpath_traverse_main(bmain, missing_files_find__visit_cb, flag, (void *)&data);
450 }
451
452 /** \} */
453
454 /* -------------------------------------------------------------------- */
455 /** \name Generic File Path Traversal API
456  * \{ */
457
458 /**
459  * Run a visitor on a string, replacing the contents of the string as needed.
460  */
461 static bool rewrite_path_fixed(char *path,
462                                BPathVisitor visit_cb,
463                                const char *absbase,
464                                void *userdata)
465 {
466   char path_src_buf[FILE_MAX];
467   const char *path_src;
468   char path_dst[FILE_MAX];
469
470   if (absbase) {
471     BLI_strncpy(path_src_buf, path, sizeof(path_src_buf));
472     BLI_path_abs(path_src_buf, absbase);
473     path_src = path_src_buf;
474   }
475   else {
476     path_src = path;
477   }
478
479   /* so functions can check old value */
480   BLI_strncpy(path_dst, path, FILE_MAX);
481
482   if (visit_cb(userdata, path_dst, path_src)) {
483     BLI_strncpy(path, path_dst, FILE_MAX);
484     return true;
485   }
486   else {
487     return false;
488   }
489 }
490
491 static bool rewrite_path_fixed_dirfile(char path_dir[FILE_MAXDIR],
492                                        char path_file[FILE_MAXFILE],
493                                        BPathVisitor visit_cb,
494                                        const char *absbase,
495                                        void *userdata)
496 {
497   char path_src[FILE_MAX];
498   char path_dst[FILE_MAX];
499
500   BLI_join_dirfile(path_src, sizeof(path_src), path_dir, path_file);
501
502   /* so functions can check old value */
503   BLI_strncpy(path_dst, path_src, FILE_MAX);
504
505   if (absbase) {
506     BLI_path_abs(path_src, absbase);
507   }
508
509   if (visit_cb(userdata, path_dst, (const char *)path_src)) {
510     BLI_split_dirfile(path_dst, path_dir, path_file, FILE_MAXDIR, FILE_MAXFILE);
511     return true;
512   }
513   else {
514     return false;
515   }
516 }
517
518 static bool rewrite_path_alloc(char **path,
519                                BPathVisitor visit_cb,
520                                const char *absbase,
521                                void *userdata)
522 {
523   char path_src_buf[FILE_MAX];
524   const char *path_src;
525   char path_dst[FILE_MAX];
526
527   if (absbase) {
528     BLI_strncpy(path_src_buf, *path, sizeof(path_src_buf));
529     BLI_path_abs(path_src_buf, absbase);
530     path_src = path_src_buf;
531   }
532   else {
533     path_src = *path;
534   }
535
536   if (visit_cb(userdata, path_dst, path_src)) {
537     MEM_freeN(*path);
538     (*path) = BLI_strdup(path_dst);
539     return true;
540   }
541   else {
542     return false;
543   }
544 }
545
546 /**
547  * Run visitor function 'visit' on all paths contained in 'id'.
548  */
549 void BKE_bpath_traverse_id(
550     Main *bmain, ID *id, BPathVisitor visit_cb, const int flag, void *bpath_user_data)
551 {
552   const char *absbase = (flag & BKE_BPATH_TRAVERSE_ABS) ? ID_BLEND_PATH(bmain, id) : NULL;
553
554   if ((flag & BKE_BPATH_TRAVERSE_SKIP_LIBRARY) && ID_IS_LINKED(id)) {
555     return;
556   }
557
558   switch (GS(id->name)) {
559     case ID_IM: {
560       Image *ima;
561       ima = (Image *)id;
562       if (BKE_image_has_packedfile(ima) == false || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) {
563         /* Skip empty file paths, these are typically from generated images and
564          * don't make sense to add directories to until the image has been saved
565          * once to give it a meaningful value. */
566         if (ELEM(ima->source, IMA_SRC_FILE, IMA_SRC_MOVIE, IMA_SRC_SEQUENCE, IMA_SRC_TILED) &&
567             ima->filepath[0]) {
568           if (rewrite_path_fixed(ima->filepath, visit_cb, absbase, bpath_user_data)) {
569             if (flag & BKE_BPATH_TRAVERSE_RELOAD_EDITED) {
570               if (!BKE_image_has_packedfile(ima) &&
571                   /* image may have been painted onto (and not saved, T44543) */
572                   !BKE_image_is_dirty(ima)) {
573                 BKE_image_signal(bmain, ima, NULL, IMA_SIGNAL_RELOAD);
574               }
575             }
576           }
577         }
578       }
579       break;
580     }
581     case ID_BR: {
582       Brush *brush = (Brush *)id;
583       if (brush->icon_filepath[0]) {
584         rewrite_path_fixed(brush->icon_filepath, visit_cb, absbase, bpath_user_data);
585       }
586       break;
587     }
588     case ID_OB: {
589       Object *ob = (Object *)id;
590       ModifierData *md;
591       ParticleSystem *psys;
592
593 #define BPATH_TRAVERSE_POINTCACHE(ptcaches) \
594   { \
595     PointCache *cache; \
596     for (cache = (ptcaches).first; cache; cache = cache->next) { \
597       if (cache->flag & PTCACHE_DISK_CACHE) { \
598         rewrite_path_fixed(cache->path, visit_cb, absbase, bpath_user_data); \
599       } \
600     } \
601   } \
602   (void)0
603
604       for (md = ob->modifiers.first; md; md = md->next) {
605         if (md->type == eModifierType_Fluidsim) {
606           FluidsimModifierData *fluidmd = (FluidsimModifierData *)md;
607           if (fluidmd->fss) {
608             rewrite_path_fixed(fluidmd->fss->surfdataPath, visit_cb, absbase, bpath_user_data);
609           }
610         }
611         else if (md->type == eModifierType_Fluid) {
612           FluidModifierData *mmd = (FluidModifierData *)md;
613           if (mmd->type & MOD_FLUID_TYPE_DOMAIN && mmd->domain) {
614             rewrite_path_fixed(mmd->domain->cache_directory, visit_cb, absbase, bpath_user_data);
615           }
616         }
617         else if (md->type == eModifierType_Cloth) {
618           ClothModifierData *clmd = (ClothModifierData *)md;
619           BPATH_TRAVERSE_POINTCACHE(clmd->ptcaches);
620         }
621         else if (md->type == eModifierType_Ocean) {
622           OceanModifierData *omd = (OceanModifierData *)md;
623           rewrite_path_fixed(omd->cachepath, visit_cb, absbase, bpath_user_data);
624         }
625         else if (md->type == eModifierType_MeshCache) {
626           MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
627           rewrite_path_fixed(mcmd->filepath, visit_cb, absbase, bpath_user_data);
628         }
629       }
630
631       if (ob->soft) {
632         BPATH_TRAVERSE_POINTCACHE(ob->soft->shared->ptcaches);
633       }
634
635       for (psys = ob->particlesystem.first; psys; psys = psys->next) {
636         BPATH_TRAVERSE_POINTCACHE(psys->ptcaches);
637       }
638
639 #undef BPATH_TRAVERSE_POINTCACHE
640
641       break;
642     }
643     case ID_SO: {
644       bSound *sound = (bSound *)id;
645       if (sound->packedfile == NULL || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) {
646         rewrite_path_fixed(sound->filepath, visit_cb, absbase, bpath_user_data);
647       }
648       break;
649     }
650     case ID_VO: {
651       Volume *volume = (Volume *)id;
652       if (volume->packedfile == NULL || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) {
653         rewrite_path_fixed(volume->filepath, visit_cb, absbase, bpath_user_data);
654       }
655       break;
656     }
657     case ID_TXT:
658       if (((Text *)id)->filepath) {
659         rewrite_path_alloc(&((Text *)id)->filepath, visit_cb, absbase, bpath_user_data);
660       }
661       break;
662     case ID_VF: {
663       VFont *vfont = (VFont *)id;
664       if (vfont->packedfile == NULL || (flag & BKE_BPATH_TRAVERSE_SKIP_PACKED) == 0) {
665         if (BKE_vfont_is_builtin(vfont) == false) {
666           rewrite_path_fixed(((VFont *)id)->filepath, visit_cb, absbase, bpath_user_data);
667         }
668       }
669       break;
670     }
671     case ID_MA: {
672       Material *ma = (Material *)id;
673       bNodeTree *ntree = ma->nodetree;
674
675       if (ntree) {
676         bNode *node;
677
678         for (node = ntree->nodes.first; node; node = node->next) {
679           if (node->type == SH_NODE_SCRIPT) {
680             NodeShaderScript *nss = (NodeShaderScript *)node->storage;
681             rewrite_path_fixed(nss->filepath, visit_cb, absbase, bpath_user_data);
682           }
683           else if (node->type == SH_NODE_TEX_IES) {
684             NodeShaderTexIES *ies = (NodeShaderTexIES *)node->storage;
685             rewrite_path_fixed(ies->filepath, visit_cb, absbase, bpath_user_data);
686           }
687         }
688       }
689       break;
690     }
691     case ID_NT: {
692       bNodeTree *ntree = (bNodeTree *)id;
693       bNode *node;
694
695       if (ntree->type == NTREE_SHADER) {
696         /* same as lines above */
697         for (node = ntree->nodes.first; node; node = node->next) {
698           if (node->type == SH_NODE_SCRIPT) {
699             NodeShaderScript *nss = (NodeShaderScript *)node->storage;
700             rewrite_path_fixed(nss->filepath, visit_cb, absbase, bpath_user_data);
701           }
702           else if (node->type == SH_NODE_TEX_IES) {
703             NodeShaderTexIES *ies = (NodeShaderTexIES *)node->storage;
704             rewrite_path_fixed(ies->filepath, visit_cb, absbase, bpath_user_data);
705           }
706         }
707       }
708       break;
709     }
710     case ID_SCE: {
711       Scene *scene = (Scene *)id;
712       if (scene->ed) {
713         Sequence *seq;
714
715         SEQ_BEGIN (scene->ed, seq) {
716           if (SEQ_HAS_PATH(seq)) {
717             StripElem *se = seq->strip->stripdata;
718
719             if (ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_SOUND_RAM) && se) {
720               rewrite_path_fixed_dirfile(
721                   seq->strip->dir, se->name, visit_cb, absbase, bpath_user_data);
722             }
723             else if ((seq->type == SEQ_TYPE_IMAGE) && se) {
724               /* might want an option not to loop over all strips */
725               unsigned int len = (unsigned int)MEM_allocN_len(se) / (unsigned int)sizeof(*se);
726               unsigned int i;
727
728               if (flag & BKE_BPATH_TRAVERSE_SKIP_MULTIFILE) {
729                 /* only operate on one path */
730                 len = MIN2(1u, len);
731               }
732
733               for (i = 0; i < len; i++, se++) {
734                 rewrite_path_fixed_dirfile(
735                     seq->strip->dir, se->name, visit_cb, absbase, bpath_user_data);
736               }
737             }
738             else {
739               /* simple case */
740               rewrite_path_fixed(seq->strip->dir, visit_cb, absbase, bpath_user_data);
741             }
742           }
743         }
744         SEQ_END;
745       }
746       break;
747     }
748     case ID_ME: {
749       Mesh *me = (Mesh *)id;
750       if (me->ldata.external) {
751         rewrite_path_fixed(me->ldata.external->filename, visit_cb, absbase, bpath_user_data);
752       }
753       break;
754     }
755     case ID_LI: {
756       Library *lib = (Library *)id;
757       /* keep packedfile paths always relative to the blend */
758       if (lib->packedfile == NULL) {
759         if (rewrite_path_fixed(lib->filepath, visit_cb, absbase, bpath_user_data)) {
760           BKE_library_filepath_set(bmain, lib, lib->filepath);
761         }
762       }
763       break;
764     }
765     case ID_MC: {
766       MovieClip *clip = (MovieClip *)id;
767       rewrite_path_fixed(clip->filepath, visit_cb, absbase, bpath_user_data);
768       break;
769     }
770     case ID_CF: {
771       CacheFile *cache_file = (CacheFile *)id;
772       rewrite_path_fixed(cache_file->filepath, visit_cb, absbase, bpath_user_data);
773       break;
774     }
775     default:
776       /* Nothing to do for other IDs that don't contain file paths. */
777       break;
778   }
779 }
780
781 void BKE_bpath_traverse_id_list(
782     Main *bmain, ListBase *lb, BPathVisitor visit_cb, const int flag, void *bpath_user_data)
783 {
784   ID *id;
785   for (id = lb->first; id; id = id->next) {
786     BKE_bpath_traverse_id(bmain, id, visit_cb, flag, bpath_user_data);
787   }
788 }
789
790 void BKE_bpath_traverse_main(Main *bmain,
791                              BPathVisitor visit_cb,
792                              const int flag,
793                              void *bpath_user_data)
794 {
795   ListBase *lbarray[MAX_LIBARRAY];
796   int a = set_listbasepointers(bmain, lbarray);
797   while (a--) {
798     BKE_bpath_traverse_id_list(bmain, lbarray[a], visit_cb, flag, bpath_user_data);
799   }
800 }
801
802 /**
803  * Rewrites a relative path to be relative to the main file - unless the path is
804  * absolute, in which case it is not altered.
805  */
806 bool BKE_bpath_relocate_visitor(void *pathbase_v, char *path_dst, const char *path_src)
807 {
808   /* be sure there is low chance of the path being too short */
809   char filepath[(FILE_MAXDIR * 2) + FILE_MAXFILE];
810   const char *base_new = ((char **)pathbase_v)[0];
811   const char *base_old = ((char **)pathbase_v)[1];
812
813   if (BLI_path_is_rel(base_old)) {
814     CLOG_ERROR(&LOG, "old base path '%s' is not absolute.", base_old);
815     return false;
816   }
817
818   /* Make referenced file absolute. This would be a side-effect of
819    * BLI_path_normalize, but we do it explicitly so we know if it changed. */
820   BLI_strncpy(filepath, path_src, FILE_MAX);
821   if (BLI_path_abs(filepath, base_old)) {
822     /* Path was relative and is now absolute. Remap.
823      * Important BLI_path_normalize runs before the path is made relative
824      * because it wont work for paths that start with "//../" */
825     BLI_path_normalize(base_new, filepath);
826     BLI_path_rel(filepath, base_new);
827     BLI_strncpy(path_dst, filepath, FILE_MAX);
828     return true;
829   }
830   else {
831     /* Path was not relative to begin with. */
832     return false;
833   }
834 }
835
836 /** \} */
837
838 /* -------------------------------------------------------------------- */
839 /** \name Backup/Restore/Free functions,
840  *
841  * \note These functions assume the data won't change order.
842  * \{ */
843
844 struct PathStore {
845   struct PathStore *next, *prev;
846 };
847
848 static bool bpath_list_append(void *userdata, char *UNUSED(path_dst), const char *path_src)
849 {
850   /* store the path and string in a single alloc */
851   ListBase *ls = userdata;
852   size_t path_size = strlen(path_src) + 1;
853   struct PathStore *path_store = MEM_mallocN(sizeof(struct PathStore) + path_size, __func__);
854   char *filepath = (char *)(path_store + 1);
855
856   memcpy(filepath, path_src, path_size);
857   BLI_addtail(ls, path_store);
858   return false;
859 }
860
861 static bool bpath_list_restore(void *userdata, char *path_dst, const char *path_src)
862 {
863   /* assume ls->first wont be NULL because the number of paths can't change!
864    * (if they do caller is wrong) */
865   ListBase *ls = userdata;
866   struct PathStore *path_store = ls->first;
867   const char *filepath = (char *)(path_store + 1);
868   bool ret;
869
870   if (STREQ(path_src, filepath)) {
871     ret = false;
872   }
873   else {
874     BLI_strncpy(path_dst, filepath, FILE_MAX);
875     ret = true;
876   }
877
878   BLI_freelinkN(ls, path_store);
879   return ret;
880 }
881
882 /* return ls_handle */
883 void *BKE_bpath_list_backup(Main *bmain, const int flag)
884 {
885   ListBase *ls = MEM_callocN(sizeof(ListBase), __func__);
886
887   BKE_bpath_traverse_main(bmain, bpath_list_append, flag, ls);
888
889   return ls;
890 }
891
892 void BKE_bpath_list_restore(Main *bmain, const int flag, void *ls_handle)
893 {
894   ListBase *ls = ls_handle;
895
896   BKE_bpath_traverse_main(bmain, bpath_list_restore, flag, ls);
897 }
898
899 void BKE_bpath_list_free(void *ls_handle)
900 {
901   ListBase *ls = ls_handle;
902   BLI_assert(BLI_listbase_is_empty(ls)); /* assumes we were used */
903   BLI_freelistN(ls);
904   MEM_freeN(ls);
905 }
906
907 /** \} */