commit before doing some hefty shapekey change, will break compilation
[blender-staging.git] / source / blender / blenlib / intern / BLI_bfile.c
1 /*
2  * $Id$
3  *
4  * ***** BEGIN GPL LICENSE BLOCK *****
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  *
20  * The Original Code is Copyright (C) 2009 by Stichting Blender Foundation.
21  * All rights reserved.
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  * BFILE* based abstraction for file access.
25  */
26
27 #include <string.h>
28 #include <stdlib.h>
29 #ifndef WIN32
30  #include <unistd.h>
31  #include <sys/param.h>
32 // #include <libgen.h>
33 #else
34  #include <io.h>
35  #include "BLI_winstuff.h"
36 #endif
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <fcntl.h>
40
41 #include "MEM_guardedalloc.h"
42 #include "BKE_utildefines.h"
43 #include "BKE_blender.h"
44 #include "BLI_util.h"
45 #include "BLI_fileops.h"
46 #include "BLI_storage.h"
47 #include "BLI_bfile.h"
48
49 /* Internal bfile classification flags */
50 #define BCF_OPEN     (0)
51 #define BCF_FOPEN    (1<<0)
52 #define BCF_READ     (1<<1)
53 #define BCF_WRITE    (1<<2)
54 #define BCF_AT_END   (1<<3)
55 #define BCF_DISCARD  (1<<4)
56
57 /* Declaration of internal functions */
58 void chomp(char* line);
59 void expand_envvars(char* src, char* dst);
60 void fill_paths(BFILE *bfile, const char *path);
61 char* find_in_pathlist(char* filename, char* pathlist);
62 void init_vars_from_file(const char* path);
63 void setup_temp();
64
65 /*** Exported functions ***/
66
67 BFILE *BLI_bfile_fopen(const char *path, const char *mode, int bflags,
68                        BEnvVarFam envvars)
69 {
70         BFILE *bfile;
71
72         bfile = MEM_mallocN(sizeof(BFILE), "bfile-fopen");
73         bfile->classf = BCF_FOPEN;
74         bfile->uflags = bflags;
75
76         /* From fopen() doc, we can guess some logic:
77         r  BCF_READ
78         r+ BCF_READ | BCF_WRITE
79         w  BCF_DISCARD | BCF_WRITE
80         w+ BCF_DISCARD | BCF_WRITE | BCF_READ
81         a  BCF_AT_END | BCF_WRITE
82         a+ BCF_AT_END | BCF_WRITE | BCF_READ
83         */
84         if(strchr(mode, 'r'))
85                 bfile->classf |= BCF_READ;
86         if(strchr(mode, 'w'))
87                 bfile->classf |= (BCF_DISCARD | BCF_WRITE);
88         if(strchr(mode, 'a'))
89                 bfile->classf |= (BCF_AT_END | BCF_WRITE);
90         if(strchr(mode, '+'))
91                 bfile->classf |= (BCF_READ | BCF_WRITE);
92
93         fill_paths(bfile, path);
94
95         bfile->stream = fopen(bfile->tpath, mode);
96         // detect failed fopen
97         bfile->fd = fileno(bfile->stream);
98         return bfile;
99 }
100
101
102 BFILE *BLI_bfile_open(const char *pathname, int flags, int bflags,
103                       BEnvVarFam envvars)
104 {
105         BFILE *bfile;
106
107         bfile = MEM_mallocN(sizeof(BFILE), "bfile-open");
108         bfile->classf = BCF_OPEN;
109         bfile->uflags = bflags;
110
111         /* Easy mapping for open() */
112         if(flags & O_RDONLY)
113                 bfile->classf |= BCF_READ;
114         if(flags & O_WRONLY)
115                 bfile->classf |= BCF_WRITE;
116         if(flags & O_RDWR)
117                 bfile->classf |= (BCF_READ | BCF_WRITE);
118         if(flags & O_APPEND)
119                 bfile->classf |= BCF_AT_END;
120         if(flags & O_TRUNC)
121                 bfile->classf |= BCF_DISCARD;
122
123         fill_paths(bfile, pathname);
124
125         bfile->fd = open(bfile->tpath, flags);
126         // detect failed open
127 //      bfile->stream = fdopen(bfile->fd, XXX); /* MSWindows _fdopen? */
128         return bfile;
129 }
130
131
132 FILE *BLI_bfile_file_from_bfile(BFILE *bfile) {
133         return bfile->stream;
134 }
135
136
137 int BLI_bfile_fd_from_bfile(BFILE *bfile) {
138         return bfile->fd;
139 }
140
141
142 ssize_t BLI_bfile_write(BFILE *f, const void *buf, size_t count) {
143         ssize_t ret;
144
145         ret = write((f->fd), buf, count);
146         if (ret == -1) {
147                 f->error = 1;
148         }
149
150         return ret;
151 }
152
153
154 ssize_t BLI_bfile_read(BFILE *f, void *buf, size_t count) {
155         ssize_t ret;
156
157         ret = read((f->fd), buf, count);
158         if (ret == -1) {
159                 f->error = 1;
160         }
161
162         return ret;
163 }
164
165
166 size_t BLI_bfile_fwrite(const void *ptr, size_t size, size_t nmemb,
167                         BFILE *f)
168 {
169         size_t ret;
170
171         ret = fwrite(ptr, size, nmemb, f->stream);
172         if (ret < 0) {
173                 f->error = 1;
174         }
175
176         return ret;
177 }
178
179
180 size_t BLI_bfile_fread(void *ptr, size_t size, size_t nmemb, BFILE *f) {
181         size_t ret;
182
183         ret = fread(ptr, size, nmemb, f->stream);
184         if ((ret < 0) && ferror(f->stream)) {
185                 f->error = 1;
186         }
187
188         return ret;
189 }
190
191
192 void BLI_bfile_close(BFILE *bfile) {
193         if((bfile->classf | BCF_WRITE) &&
194            !(bfile->uflags | BFILE_RAW)) {
195                 /* Make sure data is on disk */
196                 /* Move to final name if no errors */
197         }
198
199         /* Normal close */
200
201         /* Cleanup */
202         if(bfile->fpath) {
203                 MEM_freeN(bfile->fpath);
204         }
205         if(bfile->tpath) {
206                 MEM_freeN(bfile->tpath);
207         }
208 }
209
210
211 void BLI_bfile_clear_error(BFILE *bfile) {
212         bfile->error = 0;
213 }
214
215
216 void BLI_bfile_set_error(BFILE *bfile, int error) {
217         /* No cheating, use clear_error() for 0 */
218         if (error) {
219                 bfile->error = error;
220         }
221 }
222
223
224 #if defined(WIN32)
225  #define LAST_SESSION_FILE "%HOME%\\Blender\\last-session FIXME FIXME FIXME"
226  #define ENVIRONMENT_FILE "FIXME"
227  #define SHARED_DIRECTORY "FIXME TOO"
228 #elif defined(OSX)
229  #define LAST_SESSION_FILE "${HOME}/Library/Application Support/Blender/last-session"
230  #define ENVIRONMENT_FILE "${HOME}/Library/Application Support/Blender/${BLENDER_VERSION}/environment"
231  #define SHARED_DIRECTORY "/Library/Application Support/Blender"
232 #else
233  #define LAST_SESSION_FILE "${HOME}/.blender/last-session"
234  #define ENVIRONMENT_FILE "${HOME}/.blender/${BLENDER_VERSION}/environment"
235  #define SHARED_DIRECTORY "/usr/share/blender"
236 #endif
237 void BLI_bfile_init_vars() {
238         char file[MAXPATHLEN];
239         char temp[MAXPATHLEN];
240         extern char bprogname[];
241         FILE* fp;
242
243         /* This one is unconditional */
244         sprintf(temp, "%d", BLENDER_VERSION);
245         BLI_setenv("BLENDER_VERSION", temp);
246
247         /* Is this unpack&run? */
248         sprintf(temp, "%s/%d/environment", dirname(bprogname), BLENDER_VERSION);
249         if(BLI_exist(temp)) {
250                 BLI_setenv_if_new("BLENDER_SHARE", dirname(bprogname));
251         } else {
252                 BLI_setenv_if_new("BLENDER_SHARE", SHARED_DIRECTORY);
253         }
254
255         expand_envvars(LAST_SESSION_FILE, file);
256         fp = fopen(file, "r");
257         /* 1st line, read previous version */
258         if (fp && (fscanf(fp, "%3c\n", temp) == 1)) {
259                 temp[3] = '\0';
260                 BLI_setenv("BLENDER_VERSION_PREV", temp);
261                 /* 2nd line, read previous session path if needed */
262                 if(!getenv("BLENDER_TEMP")) {
263                         if ((fgets(temp, MAXPATHLEN, fp) != NULL)) {
264                                 /* Clean any \n */
265                                 chomp(temp);
266                                 /* Check the dir is still there or generate new one */
267                                 if(!BLI_exist(temp)) {
268                                         setup_temp();
269                                 }
270                         } else {
271                                 /* We have to generate it for sure */
272                                 setup_temp();
273                         }
274                 }
275         } else {
276                 /* Probably new user, or only <=249 before */
277                 BLI_setenv("BLENDER_VERSION_PREV", "0");
278                 setup_temp();
279         }
280
281         if(fp) {
282                 fclose(fp);
283         }
284
285         /* Load vars from user and system files */
286         expand_envvars(ENVIRONMENT_FILE, file);
287         init_vars_from_file(file);
288         sprintf(temp, "/%d/environment", BLENDER_VERSION);
289         BLI_make_file_string("/", file, getenv("BLENDER_SHARE"), temp);
290         init_vars_from_file(file);
291 }
292
293
294 /*** Internal functions ***/
295
296 /**
297  Eliminate trailing EOL by writing a \0 over it.
298  Name taken from Perl.
299  */
300 void chomp(char* line) {
301         int len = strlen(line);
302 #ifndef WIN32
303         if (line[len - 1] == '\n') {
304                 line[len - 1] = '\0';
305         }
306 #else
307         if ((line[len - 2] == '\r' ) && ((line[len - 1] == '\n'))) {
308                 line[len - 2] = '\0';
309         }
310 #endif /* WIN32 */
311 }
312
313
314 /**
315  Parse a file with lines like FOO=bar (comment lines have # as first
316  character) assigning to envvar FOO the value bar if FOO does not
317  exist yet.
318  Any white space before FOO, around the = or trailing will be used,
319  so beware.
320  */
321 #define MAX_LINE 4096
322 #define ENV_VAR 256
323 #define VAR_LEN 8192
324 void init_vars_from_file(const char* path) {
325         char line[MAX_LINE];
326         char name[ENV_VAR];
327         FILE *fp;
328         char* separator;
329         char expanded[VAR_LEN];
330
331         fp = fopen(path, "r");
332         if (!fp) return;
333
334         while (fgets(line, MAX_LINE, fp) != NULL) {
335                 /* Ignore comment lines */
336                 if (line[0] == '#')
337                         continue;
338
339                 /* Split into envvar name and contents */
340                 separator = strchr(line, '=');
341                 if(separator && ((separator - line) < ENV_VAR)) {
342                         /* First remove EOL */
343                         chomp(line);
344                         strncpy(name, line, separator - line);
345                         name[separator - line] = '\0';
346                         expand_envvars(separator + 1, expanded);
347                         BLI_setenv_if_new(name, expanded);
348                 }
349         }
350         fclose(fp);
351 }
352
353
354 /**
355  Look for ${} (or %%) env vars in src and expand if the var
356  exists (even if empty value). If not exist, the name is left as is.
357  The process is done all over src, and nested ${${}} is not supported.
358  src must be \0 terminated, and dst must be big enough.
359 */
360 #ifndef WIN32
361  #define ENVVAR_PREFFIX "${"
362  #define ENVVAR_P_SIZE 2
363  #define ENVVAR_SUFFIX "}"
364  #define ENVVAR_S_SIZE 1
365 #else
366  #define ENVVAR_PREFFIX "%"
367  #define ENVVAR_P_SIZE 1
368  #define ENVVAR_SUFFIX "%"
369  #define ENVVAR_S_SIZE 1
370 #endif /* WIN32 */
371 void expand_envvars(char* src, char* dst) {
372         char* hit1;
373         char* hit2;
374         char name[ENV_VAR];
375         char* value;
376         int prevlen;
377         int done = 0;
378         char* source = src;
379
380         dst[0] = '\0';
381         while (!done) {
382                 hit1 = strstr(source, ENVVAR_PREFFIX);
383                 if (hit1) {
384                         hit2 = strstr(hit1 + ENVVAR_P_SIZE, ENVVAR_SUFFIX);
385                         if (hit2) {
386                                 /* "Copy" the leading part, if any */
387                                 if (hit1 != source) {
388                                         prevlen = strlen(dst);
389                                         strncat(dst, source, hit1 - source);
390                                         dst[prevlen + (hit1 - source)] = '\0';
391                                 }
392                                 /* Figure the name of the env var we just found  */
393                                 strncpy(name, hit1 + ENVVAR_P_SIZE,
394                                         hit2 - (hit1 + ENVVAR_P_SIZE));
395                                 name[hit2 - (hit1 + ENVVAR_P_SIZE)] = '\0';
396                                 /* See if we can get something with that name */
397                                 value = getenv(name);
398                                 if (value) {
399                                         /* Push the var value */
400                                         strcat(dst, value);
401                                 } else {
402                                         /* Leave the var name, so it is clear that it failed */
403                                         strcat(dst, ENVVAR_PREFFIX);
404                                         strcat(dst, name);
405                                         strcat(dst, ENVVAR_SUFFIX);
406                                 }
407                                 /* Continue after closing mark, like a new string */
408                                 source = hit2 + ENVVAR_S_SIZE;
409                         } else {
410                                 /* Non terminated var so "copy as is" and finish */
411                                 strcat(dst, source);
412                                 done = 1;
413                         }
414                 } else {
415                         /* "Copy" whatever is left */
416                         strcat(dst, source);
417                         done = 1;
418                 }
419         }
420 }
421
422
423 /**
424  Return a full path if the filename exists when combined
425  with any item from pathlist. Or NULL otherwise.
426  */
427 #ifdef WIN32
428  #define SEPARATOR ';'
429 #else
430  #define SEPARATOR ':'
431 #endif
432 char* find_in_pathlist(char* filename, char* pathlist) {
433         char first[FILE_MAX + 10];
434         char* rest = NULL;
435
436         /* Separate first path from rest, use typical separator for current OS */
437         rest = strchr(pathlist, SEPARATOR);
438         if (rest) {
439                 strncpy(first, pathlist, rest - pathlist);
440                 first[rest - pathlist] = '\0';
441                 /* Skip the separator so it becomes a valid new pathlist */
442                 rest++;
443         } else {
444                 strcpy(first, pathlist);
445         }
446
447         /* Check if combination exists */
448         BLI_add_slash(first);
449         strcat(first, filename);
450         if(BLI_exist(first)) {
451                 return strdup(first);
452         }
453
454         /* First path failed, try with rest of paths if possible */
455         if(rest) {
456                 return find_in_pathlist(filename, rest);
457         } else {
458                 return NULL;
459         }
460 }
461
462
463 /**
464  Setup fpath and tpath based in the needs of the bfile.
465  */
466 void fill_paths(BFILE *bfile, const char *path) {
467         char* source_path = NULL;
468         char* temp_path = NULL;
469         int bflags = bfile->uflags;
470
471         if(bflags & BFILE_NORMAL || bflags & BFILE_RAW) {
472 //              bfile->fpath is path with // replaced
473         }
474         if(bflags & BFILE_TEMP) {
475                 temp_path = MEM_mallocN(MAXPATHLEN, "bfile-fpath-1");
476                 snprintf(temp_path, MAXPATHLEN, "%s/%s", getenv("BLENDER_TEMP"), path);
477                 bfile->fpath = temp_path;
478         }
479         if(bflags & BFILE_CONFIG) {
480 //              bfile->fpath is userdir+version+path
481 //              source_path is first hit in (if using fallback to older versions)
482 //                  userdir+curversion+path (... userdir+limitversion+path) sysdir+path
483 //              (limitversion is based in path, using some kind of regex or "tables")
484         }
485
486         if(bfile->classf & BCF_WRITE && !(bflags & BFILE_RAW)) {
487                 /* Generate temp path */
488                 temp_path = MEM_mallocN(MAXPATHLEN, "bfile-fpath-2");
489                 snprintf(temp_path, MAXPATHLEN, "%s.XXXXXX", path);
490                 bfile->tpath = mkdtemp(temp_path);
491                 if(!(bfile->classf & BCF_DISCARD)) {
492                         /* Copy data to tpath */
493                         if(source_path) {
494                                 // copy it from older version or sys version
495                         }
496                 }
497         } else {
498                 bfile->tpath = bfile->fpath;
499         }
500 }
501
502
503 /**
504  Create a temp directory in safe and multiuser way.
505  */
506 void setup_temp() {
507         char template[MAXPATHLEN];
508         char* tempdir;
509
510         if(getenv("TMPDIR")) {
511                 sprintf(template, "%s/blender-XXXXXX", getenv("TMPDIR"));
512         } else {
513                 sprintf(template, "/tmp/blender-XXXXXX");
514 // MacOSX NSTemporaryDirectory and WIN32 ???
515         }
516         tempdir = mkdtemp(template);
517         BLI_setenv("BLENDER_TEMP", tempdir);
518 }
519