Cleanup: style, use braces for blenkernel
[blender.git] / source / blender / blenkernel / intern / blendfile.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 bke
19  *
20  * High level `.blend` file read/write,
21  * and functions for writing *partial* files (only selected data-blocks).
22  */
23
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include "MEM_guardedalloc.h"
28
29 #include "DNA_scene_types.h"
30 #include "DNA_screen_types.h"
31 #include "DNA_workspace_types.h"
32
33 #include "BLI_listbase.h"
34 #include "BLI_string.h"
35 #include "BLI_path_util.h"
36 #include "BLI_utildefines.h"
37
38 #include "IMB_colormanagement.h"
39
40 #include "BKE_appdir.h"
41 #include "BKE_blender.h"
42 #include "BKE_blender_version.h"
43 #include "BKE_blendfile.h"
44 #include "BKE_bpath.h"
45 #include "BKE_context.h"
46 #include "BKE_global.h"
47 #include "BKE_ipo.h"
48 #include "BKE_layer.h"
49 #include "BKE_library.h"
50 #include "BKE_main.h"
51 #include "BKE_report.h"
52 #include "BKE_scene.h"
53 #include "BKE_screen.h"
54 #include "BKE_workspace.h"
55
56 #include "BLO_readfile.h"
57 #include "BLO_writefile.h"
58
59 #include "RNA_access.h"
60
61 #include "RE_pipeline.h"
62
63 #ifdef WITH_PYTHON
64 #  include "BPY_extern.h"
65 #endif
66
67 /* -------------------------------------------------------------------- */
68 /** \name High Level `.blend` file read/write.
69  * \{ */
70
71 static bool clean_paths_visit_cb(void *UNUSED(userdata), char *path_dst, const char *path_src)
72 {
73   strcpy(path_dst, path_src);
74   BLI_path_native_slash(path_dst);
75   return !STREQ(path_dst, path_src);
76 }
77
78 /* make sure path names are correct for OS */
79 static void clean_paths(Main *main)
80 {
81   Scene *scene;
82
83   BKE_bpath_traverse_main(main, clean_paths_visit_cb, BKE_BPATH_TRAVERSE_SKIP_MULTIFILE, NULL);
84
85   for (scene = main->scenes.first; scene; scene = scene->id.next) {
86     BLI_path_native_slash(scene->r.pic);
87   }
88 }
89
90 static bool wm_scene_is_visible(wmWindowManager *wm, Scene *scene)
91 {
92   wmWindow *win;
93   for (win = wm->windows.first; win; win = win->next) {
94     if (win->scene == scene) {
95       return true;
96     }
97   }
98   return false;
99 }
100
101 /**
102  * Context matching, handle no-ui case
103  *
104  * \note this is called on Undo so any slow conversion functions here
105  * should be avoided or check (mode != LOAD_UNDO).
106  *
107  * \param bfd: Blend file data, freed by this function on exit.
108  * \param filepath: File path or identifier.
109  */
110 static void setup_app_data(bContext *C,
111                            BlendFileData *bfd,
112                            const char *filepath,
113                            const bool is_startup,
114                            ReportList *reports)
115 {
116   Main *bmain = G_MAIN;
117   Scene *curscene = NULL;
118   const bool recover = (G.fileflags & G_FILE_RECOVER) != 0;
119   enum {
120     LOAD_UI = 1,
121     LOAD_UI_OFF,
122     LOAD_UNDO,
123   } mode;
124
125   /* may happen with library files - UNDO file should never have NULL cursccene... */
126   if (ELEM(NULL, bfd->curscreen, bfd->curscene)) {
127     BKE_report(reports, RPT_WARNING, "Library file, loading empty scene");
128     mode = LOAD_UI_OFF;
129   }
130   else if (BLI_listbase_is_empty(&bfd->main->screens)) {
131     mode = LOAD_UNDO;
132   }
133   else if ((G.fileflags & G_FILE_NO_UI) && (is_startup == false)) {
134     mode = LOAD_UI_OFF;
135   }
136   else {
137     mode = LOAD_UI;
138   }
139
140   /* Free all render results, without this stale data gets displayed after loading files */
141   if (mode != LOAD_UNDO) {
142     RE_FreeAllRenderResults();
143   }
144
145   /* Only make filepaths compatible when loading for real (not undo) */
146   if (mode != LOAD_UNDO) {
147     clean_paths(bfd->main);
148   }
149
150   /* XXX here the complex windowmanager matching */
151
152   /* no load screens? */
153   if (mode != LOAD_UI) {
154     /* Logic for 'track_undo_scene' is to keep using the scene which the active screen has,
155      * as long as the scene associated with the undo operation is visible in one of the open windows.
156      *
157      * - 'curscreen->scene' - scene the user is currently looking at.
158      * - 'bfd->curscene' - scene undo-step was created in.
159      *
160      * This means users can have 2+ windows open and undo in both without screens switching.
161      * But if they close one of the screens,
162      * undo will ensure that the scene being operated on will be activated
163      * (otherwise we'd be undoing on an off-screen scene which isn't acceptable).
164      * see: T43424
165      */
166     wmWindow *win;
167     bScreen *curscreen = NULL;
168     ViewLayer *cur_view_layer;
169     bool track_undo_scene;
170
171     /* comes from readfile.c */
172     SWAP(ListBase, bmain->wm, bfd->main->wm);
173     SWAP(ListBase, bmain->workspaces, bfd->main->workspaces);
174     SWAP(ListBase, bmain->screens, bfd->main->screens);
175
176     /* we re-use current window and screen */
177     win = CTX_wm_window(C);
178     curscreen = CTX_wm_screen(C);
179     /* but use Scene pointer from new file */
180     curscene = bfd->curscene;
181     cur_view_layer = bfd->cur_view_layer;
182
183     track_undo_scene = (mode == LOAD_UNDO && curscreen && curscene && bfd->main->wm.first);
184
185     if (curscene == NULL) {
186       curscene = bfd->main->scenes.first;
187     }
188     /* empty file, we add a scene to make Blender work */
189     if (curscene == NULL) {
190       curscene = BKE_scene_add(bfd->main, "Empty");
191     }
192     if (cur_view_layer == NULL) {
193       /* fallback to scene layer */
194       cur_view_layer = BKE_view_layer_default_view(curscene);
195     }
196
197     if (track_undo_scene) {
198       /* keep the old (free'd) scene, let 'blo_lib_link_screen_restore'
199        * replace it with 'curscene' if its needed */
200     }
201     /* and we enforce curscene to be in current screen */
202     else if (win) { /* can run in bgmode */
203       win->scene = curscene;
204     }
205
206     /* BKE_blender_globals_clear will free G_MAIN, here we can still restore pointers */
207     blo_lib_link_restore(bmain, bfd->main, CTX_wm_manager(C), curscene, cur_view_layer);
208     if (win) {
209       curscene = win->scene;
210     }
211
212     if (track_undo_scene) {
213       wmWindowManager *wm = bfd->main->wm.first;
214       if (wm_scene_is_visible(wm, bfd->curscene) == false) {
215         curscene = bfd->curscene;
216         win->scene = curscene;
217         BKE_screen_view3d_scene_sync(curscreen, curscene);
218       }
219     }
220
221     /* We need to tag this here because events may be handled immediately after.
222      * only the current screen is important because we wont have to handle
223      * events from multiple screens at once.*/
224     if (curscreen) {
225       BKE_screen_gizmo_tag_refresh(curscreen);
226     }
227   }
228
229   /* free G_MAIN Main database */
230   //  CTX_wm_manager_set(C, NULL);
231   BKE_blender_globals_clear();
232
233   /* clear old property update cache, in case some old references are left dangling */
234   RNA_property_update_cache_free();
235
236   bmain = G_MAIN = bfd->main;
237
238   CTX_data_main_set(C, bmain);
239
240   if (bfd->user) {
241
242     /* only here free userdef themes... */
243     BKE_blender_userdef_data_set_and_free(bfd->user);
244     bfd->user = NULL;
245
246     /* Security issue: any blend file could include a USER block.
247      *
248      * Currently we load prefs from BLENDER_STARTUP_FILE and later on load BLENDER_USERPREF_FILE,
249      * to load the preferences defined in the users home dir.
250      *
251      * This means we will never accidentally (or maliciously)
252      * enable scripts auto-execution by loading a '.blend' file.
253      */
254     U.flag |= USER_SCRIPT_AUTOEXEC_DISABLE;
255   }
256
257   /* case G_FILE_NO_UI or no screens in file */
258   if (mode != LOAD_UI) {
259     /* leave entire context further unaltered? */
260     CTX_data_scene_set(C, curscene);
261   }
262   else {
263     CTX_wm_manager_set(C, bmain->wm.first);
264     CTX_wm_screen_set(C, bfd->curscreen);
265     CTX_data_scene_set(C, bfd->curscene);
266     CTX_wm_area_set(C, NULL);
267     CTX_wm_region_set(C, NULL);
268     CTX_wm_menu_set(C, NULL);
269     curscene = bfd->curscene;
270   }
271
272   /* Keep state from preferences. */
273   const int fileflags_keep = G_FILE_FLAG_ALL_RUNTIME;
274   G.fileflags = (G.fileflags & fileflags_keep) | (bfd->fileflags & ~fileflags_keep);
275
276   /* this can happen when active scene was lib-linked, and doesn't exist anymore */
277   if (CTX_data_scene(C) == NULL) {
278     wmWindow *win = CTX_wm_window(C);
279
280     /* in case we don't even have a local scene, add one */
281     if (!bmain->scenes.first) {
282       BKE_scene_add(bmain, "Empty");
283     }
284
285     CTX_data_scene_set(C, bmain->scenes.first);
286     win->scene = CTX_data_scene(C);
287     curscene = CTX_data_scene(C);
288   }
289
290   BLI_assert(curscene == CTX_data_scene(C));
291
292   /* special cases, override loaded flags: */
293   if (G.f != bfd->globalf) {
294     const int flags_keep = G_FLAG_ALL_RUNTIME;
295     bfd->globalf &= G_FLAG_ALL_READFILE;
296     bfd->globalf = (bfd->globalf & ~flags_keep) | (G.f & flags_keep);
297   }
298
299   G.f = bfd->globalf;
300
301 #ifdef WITH_PYTHON
302   /* let python know about new main */
303   BPY_context_update(C);
304 #endif
305
306   /* FIXME: this version patching should really be part of the file-reading code,
307    * but we still get too many unrelated data-corruption crashes otherwise... */
308   if (bmain->versionfile < 250) {
309     do_versions_ipos_to_animato(bmain);
310   }
311
312   bmain->recovered = 0;
313
314   /* startup.blend or recovered startup */
315   if (is_startup) {
316     bmain->name[0] = '\0';
317   }
318   else if (recover && G.relbase_valid) {
319     /* in case of autosave or quit.blend, use original filename instead
320      * use relbase_valid to make sure the file is saved, else we get <memory2> in the filename */
321     filepath = bfd->filename;
322     bmain->recovered = 1;
323
324     /* these are the same at times, should never copy to the same location */
325     if (bmain->name != filepath) {
326       BLI_strncpy(bmain->name, filepath, FILE_MAX);
327     }
328   }
329
330   /* baseflags, groups, make depsgraph, etc */
331   /* first handle case if other windows have different scenes visible */
332   if (mode == LOAD_UI) {
333     wmWindowManager *wm = bmain->wm.first;
334
335     if (wm) {
336       for (wmWindow *win = wm->windows.first; win; win = win->next) {
337         if (win->scene && win->scene != curscene) {
338           BKE_scene_set_background(bmain, win->scene);
339         }
340       }
341     }
342   }
343
344   /* Setting scene might require having a dependency graph, with copy on write
345    * we need to make sure we ensure scene has correct color management before
346    * constructing dependency graph.
347    */
348   if (mode != LOAD_UNDO) {
349     IMB_colormanagement_check_file_config(bmain);
350   }
351
352   BKE_scene_set_background(bmain, curscene);
353
354   if (mode != LOAD_UNDO) {
355     /* TODO(sergey): Can this be also move above? */
356     RE_FreeAllPersistentData();
357   }
358
359   MEM_freeN(bfd);
360 }
361
362 static int handle_subversion_warning(Main *main, ReportList *reports)
363 {
364   if (main->minversionfile > BLENDER_VERSION ||
365       (main->minversionfile == BLENDER_VERSION && main->minsubversionfile > BLENDER_SUBVERSION)) {
366     BKE_reportf(reports,
367                 RPT_ERROR,
368                 "File written by newer Blender binary (%d.%d), expect loss of data!",
369                 main->minversionfile,
370                 main->minsubversionfile);
371   }
372
373   return 1;
374 }
375
376 int BKE_blendfile_read(bContext *C,
377                        const char *filepath,
378                        const struct BlendFileReadParams *params,
379                        ReportList *reports)
380 {
381   BlendFileData *bfd;
382   int retval = BKE_BLENDFILE_READ_OK;
383
384   /* don't print user-pref loading */
385   if (strstr(filepath, BLENDER_STARTUP_FILE) == NULL) {
386     printf("Read blend: %s\n", filepath);
387   }
388
389   bfd = BLO_read_from_file(filepath, params->skip_flags, reports);
390   if (bfd) {
391     if (bfd->user) {
392       retval = BKE_BLENDFILE_READ_OK_USERPREFS;
393     }
394
395     if (0 == handle_subversion_warning(bfd->main, reports)) {
396       BKE_main_free(bfd->main);
397       MEM_freeN(bfd);
398       bfd = NULL;
399       retval = BKE_BLENDFILE_READ_FAIL;
400     }
401     else {
402       setup_app_data(C, bfd, filepath, params->is_startup, reports);
403     }
404   }
405   else {
406     BKE_reports_prependf(reports, "Loading '%s' failed: ", filepath);
407   }
408
409   return (bfd ? retval : BKE_BLENDFILE_READ_FAIL);
410 }
411
412 bool BKE_blendfile_read_from_memory(bContext *C,
413                                     const void *filebuf,
414                                     int filelength,
415                                     bool update_defaults,
416                                     const struct BlendFileReadParams *params,
417                                     ReportList *reports)
418 {
419   BlendFileData *bfd;
420
421   bfd = BLO_read_from_memory(filebuf, filelength, params->skip_flags, reports);
422   if (bfd) {
423     if (update_defaults) {
424       BLO_update_defaults_startup_blend(bfd->main, NULL);
425     }
426     setup_app_data(C, bfd, "<memory2>", params->is_startup, reports);
427   }
428   else {
429     BKE_reports_prepend(reports, "Loading failed: ");
430   }
431
432   return (bfd != NULL);
433 }
434
435 /* memfile is the undo buffer */
436 bool BKE_blendfile_read_from_memfile(bContext *C,
437                                      struct MemFile *memfile,
438                                      const struct BlendFileReadParams *params,
439                                      ReportList *reports)
440 {
441   Main *bmain = CTX_data_main(C);
442   BlendFileData *bfd;
443
444   bfd = BLO_read_from_memfile(
445       bmain, BKE_main_blendfile_path(bmain), memfile, params->skip_flags, reports);
446   if (bfd) {
447     /* remove the unused screens and wm */
448     while (bfd->main->wm.first) {
449       BKE_id_free(bfd->main, bfd->main->wm.first);
450     }
451     while (bfd->main->screens.first) {
452       BKE_id_free(bfd->main, bfd->main->screens.first);
453     }
454
455     setup_app_data(C, bfd, "<memory1>", params->is_startup, reports);
456   }
457   else {
458     BKE_reports_prepend(reports, "Loading failed: ");
459   }
460
461   return (bfd != NULL);
462 }
463
464 /**
465  * Utility to make a file 'empty' used for startup to optionally give an empty file.
466  * Handy for tests.
467  */
468 void BKE_blendfile_read_make_empty(bContext *C)
469 {
470   Main *bmain = CTX_data_main(C);
471   ListBase *lb;
472   ID *id;
473
474   FOREACH_MAIN_LISTBASE_BEGIN (bmain, lb) {
475     FOREACH_MAIN_LISTBASE_ID_BEGIN (lb, id) {
476       if (ELEM(GS(id->name), ID_SCE, ID_SCR, ID_WM, ID_WS)) {
477         break;
478       }
479       BKE_id_delete(bmain, id);
480     }
481     FOREACH_MAIN_LISTBASE_ID_END;
482   }
483   FOREACH_MAIN_LISTBASE_END;
484 }
485
486 /* only read the userdef from a .blend */
487 UserDef *BKE_blendfile_userdef_read(const char *filepath, ReportList *reports)
488 {
489   BlendFileData *bfd;
490   UserDef *userdef = NULL;
491
492   bfd = BLO_read_from_file(filepath, BLO_READ_SKIP_ALL & ~BLO_READ_SKIP_USERDEF, reports);
493   if (bfd) {
494     if (bfd->user) {
495       userdef = bfd->user;
496     }
497     BKE_main_free(bfd->main);
498     MEM_freeN(bfd);
499   }
500
501   return userdef;
502 }
503
504 UserDef *BKE_blendfile_userdef_read_from_memory(const void *filebuf,
505                                                 int filelength,
506                                                 ReportList *reports)
507 {
508   BlendFileData *bfd;
509   UserDef *userdef = NULL;
510
511   bfd = BLO_read_from_memory(
512       filebuf, filelength, BLO_READ_SKIP_ALL & ~BLO_READ_SKIP_USERDEF, reports);
513   if (bfd) {
514     if (bfd->user) {
515       userdef = bfd->user;
516     }
517     BKE_main_free(bfd->main);
518     MEM_freeN(bfd);
519   }
520   else {
521     BKE_reports_prepend(reports, "Loading failed: ");
522   }
523
524   return userdef;
525 }
526
527 /**
528  * Only write the userdef in a .blend
529  * \return success
530  */
531 bool BKE_blendfile_userdef_write(const char *filepath, ReportList *reports)
532 {
533   Main *mainb = MEM_callocN(sizeof(Main), "empty main");
534   bool ok = false;
535
536   if (BLO_write_file(mainb, filepath, G_FILE_USERPREFS, reports, NULL)) {
537     ok = true;
538   }
539
540   MEM_freeN(mainb);
541
542   return ok;
543 }
544
545 /**
546  * Only write the userdef in a .blend, merging with the existing blend file.
547  * \return success
548  *
549  * \note In the future we should re-evaluate user preferences,
550  * possibly splitting out system/hardware specific prefs.
551  */
552 bool BKE_blendfile_userdef_write_app_template(const char *filepath, ReportList *reports)
553 {
554   /* if it fails, overwrite is OK. */
555   UserDef *userdef_default = BKE_blendfile_userdef_read(filepath, NULL);
556   if (userdef_default == NULL) {
557     return BKE_blendfile_userdef_write(filepath, reports);
558   }
559
560   BKE_blender_userdef_app_template_data_swap(&U, userdef_default);
561   bool ok = BKE_blendfile_userdef_write(filepath, reports);
562   BKE_blender_userdef_app_template_data_swap(&U, userdef_default);
563   BKE_blender_userdef_data_free(userdef_default, false);
564   MEM_freeN(userdef_default);
565   return ok;
566 }
567
568 WorkspaceConfigFileData *BKE_blendfile_workspace_config_read(const char *filepath,
569                                                              const void *filebuf,
570                                                              int filelength,
571                                                              ReportList *reports)
572 {
573   BlendFileData *bfd;
574   WorkspaceConfigFileData *workspace_config = NULL;
575
576   if (filepath) {
577     bfd = BLO_read_from_file(filepath, BLO_READ_SKIP_USERDEF, reports);
578   }
579   else {
580     bfd = BLO_read_from_memory(filebuf, filelength, BLO_READ_SKIP_USERDEF, reports);
581   }
582
583   if (bfd) {
584     workspace_config = MEM_mallocN(sizeof(*workspace_config), __func__);
585     workspace_config->main = bfd->main;
586     workspace_config->workspaces = bfd->main->workspaces;
587
588     MEM_freeN(bfd);
589   }
590
591   return workspace_config;
592 }
593
594 bool BKE_blendfile_workspace_config_write(Main *bmain, const char *filepath, ReportList *reports)
595 {
596   int fileflags = G.fileflags & ~(G_FILE_NO_UI | G_FILE_HISTORY);
597   bool retval = false;
598
599   BKE_blendfile_write_partial_begin(bmain);
600
601   for (WorkSpace *workspace = bmain->workspaces.first; workspace; workspace = workspace->id.next) {
602     BKE_blendfile_write_partial_tag_ID(&workspace->id, true);
603   }
604
605   if (BKE_blendfile_write_partial(bmain, filepath, fileflags, reports)) {
606     retval = true;
607   }
608
609   BKE_blendfile_write_partial_end(bmain);
610
611   return retval;
612 }
613
614 void BKE_blendfile_workspace_config_data_free(WorkspaceConfigFileData *workspace_config)
615 {
616   BKE_main_free(workspace_config->main);
617   MEM_freeN(workspace_config);
618 }
619
620 /** \} */
621
622 /* -------------------------------------------------------------------- */
623 /** \name Partial `.blend` file save.
624  * \{ */
625
626 void BKE_blendfile_write_partial_begin(Main *bmain_src)
627 {
628   BKE_main_id_tag_all(bmain_src, LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT, false);
629 }
630
631 void BKE_blendfile_write_partial_tag_ID(ID *id, bool set)
632 {
633   if (set) {
634     id->tag |= LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT;
635   }
636   else {
637     id->tag &= ~(LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT);
638   }
639 }
640
641 static void blendfile_write_partial_cb(void *UNUSED(handle), Main *UNUSED(bmain), void *vid)
642 {
643   if (vid) {
644     ID *id = vid;
645     /* only tag for need-expand if not done, prevents eternal loops */
646     if ((id->tag & LIB_TAG_DOIT) == 0) {
647       id->tag |= LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT;
648     }
649
650     if (id->lib && (id->lib->id.tag & LIB_TAG_DOIT) == 0) {
651       id->lib->id.tag |= LIB_TAG_DOIT;
652     }
653   }
654 }
655
656 /**
657  * \return Success.
658  */
659 bool BKE_blendfile_write_partial(Main *bmain_src,
660                                  const char *filepath,
661                                  const int write_flags,
662                                  ReportList *reports)
663 {
664   Main *bmain_dst = MEM_callocN(sizeof(Main), "copybuffer");
665   ListBase *lbarray_dst[MAX_LIBARRAY], *lbarray_src[MAX_LIBARRAY];
666   int a, retval;
667
668   void *path_list_backup = NULL;
669   const int path_list_flag = (BKE_BPATH_TRAVERSE_SKIP_LIBRARY | BKE_BPATH_TRAVERSE_SKIP_MULTIFILE);
670
671   /* This is needed to be able to load that file as a real one later
672    * (otherwise main->name will not be set at read time). */
673   BLI_strncpy(bmain_dst->name, bmain_src->name, sizeof(bmain_dst->name));
674
675   BLO_main_expander(blendfile_write_partial_cb);
676   BLO_expand_main(NULL, bmain_src);
677
678   /* move over all tagged blocks */
679   set_listbasepointers(bmain_src, lbarray_src);
680   a = set_listbasepointers(bmain_dst, lbarray_dst);
681   while (a--) {
682     ID *id, *nextid;
683     ListBase *lb_dst = lbarray_dst[a], *lb_src = lbarray_src[a];
684
685     for (id = lb_src->first; id; id = nextid) {
686       nextid = id->next;
687       if (id->tag & LIB_TAG_DOIT) {
688         BLI_remlink(lb_src, id);
689         BLI_addtail(lb_dst, id);
690       }
691     }
692   }
693
694   /* Backup paths because remap relative will overwrite them.
695    *
696    * NOTE: we do this only on the list of datablocks that we are writing
697    * because the restored full list is not guaranteed to be in the same
698    * order as before, as expected by BKE_bpath_list_restore.
699    *
700    * This happens because id_sort_by_name does not take into account
701    * string case or the library name, so the order is not strictly
702    * defined for two linked datablocks with the same name! */
703   if (write_flags & G_FILE_RELATIVE_REMAP) {
704     path_list_backup = BKE_bpath_list_backup(bmain_dst, path_list_flag);
705   }
706
707   /* save the buffer */
708   retval = BLO_write_file(bmain_dst, filepath, write_flags, reports, NULL);
709
710   if (path_list_backup) {
711     BKE_bpath_list_restore(bmain_dst, path_list_flag, path_list_backup);
712     BKE_bpath_list_free(path_list_backup);
713   }
714
715   /* move back the main, now sorted again */
716   set_listbasepointers(bmain_src, lbarray_dst);
717   a = set_listbasepointers(bmain_dst, lbarray_src);
718   while (a--) {
719     ID *id;
720     ListBase *lb_dst = lbarray_dst[a], *lb_src = lbarray_src[a];
721
722     while ((id = BLI_pophead(lb_src))) {
723       BLI_addtail(lb_dst, id);
724       id_sort_by_name(lb_dst, id);
725     }
726   }
727
728   MEM_freeN(bmain_dst);
729
730   return retval;
731 }
732
733 void BKE_blendfile_write_partial_end(Main *bmain_src)
734 {
735   BKE_main_id_tag_all(bmain_src, LIB_TAG_NEED_EXPAND | LIB_TAG_DOIT, false);
736 }
737
738 /** \} */