Code cleanup: remove BLI_exist, now there is only BLI_exists. One function just
[blender.git] / source / blender / editors / space_file / fsmenu.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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
21  * All rights reserved.
22  *
23  * The Original Code is: all of this file.
24  *
25  * Contributor(s): Andrea Weikert (c) 2008 Blender Foundation.
26  *
27  * ***** END GPL LICENSE BLOCK *****
28  */
29
30 /** \file blender/editors/space_file/fsmenu.c
31  *  \ingroup spfile
32  */
33
34
35 #include <stdlib.h>
36 #include <string.h>
37 #include <stdio.h>
38 #include <math.h>
39
40 #include "MEM_guardedalloc.h"
41
42 #include "DNA_space_types.h" /* FILE_MAX */
43
44 #include "BLI_blenlib.h"
45 #include "BLI_linklist.h"
46 #include "BLI_dynstr.h"
47
48 #ifdef WIN32
49 #include <windows.h> /* need to include windows.h so _WIN32_IE is defined  */
50 #ifndef _WIN32_IE
51 #define _WIN32_IE 0x0400 /* minimal requirements for SHGetSpecialFolderPath on MINGW MSVC has this defined already */
52 #endif
53 #include <shlobj.h> /* for SHGetSpecialFolderPath, has to be done before BLI_winstuff because 'near' is disabled through BLI_windstuff */
54 #include "BLI_winstuff.h"
55 #endif
56
57 #ifdef __APPLE__
58 /* XXX BIG WARNING: carbon.h can not be included in blender code, it conflicts with struct ID */
59 #define ID ID_
60 #include <CoreServices/CoreServices.h>
61
62 #endif
63
64 #ifdef __linux__
65 #include <mntent.h>
66 #endif
67
68 #include "fsmenu.h"  /* include ourselves */
69
70
71 /* FSMENU HANDLING */
72
73         /* FSMenuEntry's without paths indicate seperators */
74 typedef struct _FSMenuEntry FSMenuEntry;
75 struct _FSMenuEntry {
76         FSMenuEntry *next;
77
78         char *path;
79         short save;
80 };
81
82 typedef struct FSMenu
83 {
84         FSMenuEntry *fsmenu_system;
85         FSMenuEntry *fsmenu_bookmarks;
86         FSMenuEntry *fsmenu_recent;
87
88 } FSMenu;
89
90 static FSMenu *g_fsmenu = NULL;
91
92 struct FSMenu* fsmenu_get(void)
93 {
94         if (!g_fsmenu) {
95                 g_fsmenu=MEM_callocN(sizeof(struct FSMenu), "fsmenu");
96         }
97         return g_fsmenu;
98 }
99
100 static FSMenuEntry *fsmenu_get_category(struct FSMenu* fsmenu, FSMenuCategory category)
101 {
102         FSMenuEntry *fsms = NULL;
103
104         switch(category) {
105                 case FS_CATEGORY_SYSTEM:
106                         fsms = fsmenu->fsmenu_system;
107                         break;
108                 case FS_CATEGORY_BOOKMARKS:
109                         fsms = fsmenu->fsmenu_bookmarks;
110                         break;
111                 case FS_CATEGORY_RECENT:
112                         fsms = fsmenu->fsmenu_recent;
113                         break;
114         }
115         return fsms;
116 }
117
118 static void fsmenu_set_category(struct FSMenu* fsmenu, FSMenuCategory category, FSMenuEntry *fsms)
119 {
120         switch(category) {
121                 case FS_CATEGORY_SYSTEM:
122                         fsmenu->fsmenu_system = fsms;
123                         break;
124                 case FS_CATEGORY_BOOKMARKS:
125                         fsmenu->fsmenu_bookmarks = fsms;
126                         break;
127                 case FS_CATEGORY_RECENT:
128                         fsmenu->fsmenu_recent = fsms;
129                         break;
130         }
131 }
132
133 int fsmenu_get_nentries(struct FSMenu* fsmenu, FSMenuCategory category)
134 {
135         FSMenuEntry *fsme;
136         int count= 0;
137
138         for (fsme= fsmenu_get_category(fsmenu, category); fsme; fsme= fsme->next) 
139                 count++;
140
141         return count;
142 }
143
144 char *fsmenu_get_entry(struct FSMenu* fsmenu, FSMenuCategory category, int idx)
145 {
146         FSMenuEntry *fsme;
147
148         for (fsme= fsmenu_get_category(fsmenu, category); fsme && idx; fsme= fsme->next)
149                 idx--;
150
151         return fsme?fsme->path:NULL;
152 }
153
154 short fsmenu_can_save (struct FSMenu* fsmenu, FSMenuCategory category, int idx)
155 {
156         FSMenuEntry *fsme;
157
158         for (fsme= fsmenu_get_category(fsmenu, category); fsme && idx; fsme= fsme->next)
159                 idx--;
160
161         return fsme?fsme->save:0;
162 }
163
164 void fsmenu_insert_entry(struct FSMenu* fsmenu, FSMenuCategory category, const char *path, int sorted, short save)
165 {
166         FSMenuEntry *prev;
167         FSMenuEntry *fsme;
168         FSMenuEntry *fsms;
169
170         fsms = fsmenu_get_category(fsmenu, category);
171         prev= fsme= fsms;
172
173         for (; fsme; prev= fsme, fsme= fsme->next) {
174                 if (fsme->path) {
175                         const int cmp_ret= BLI_path_cmp(path, fsme->path);
176                         if (cmp_ret == 0) {
177                                 return;
178                         }
179                         else if (sorted && cmp_ret < 0) {
180                                 break;
181                         }
182                 } else {
183                         // if we're bookmarking this, file should come 
184                         // before the last separator, only automatically added
185                         // current dir go after the last sep.
186                         if (save) {
187                                 break;
188                         }
189                 }
190         }
191         
192         fsme= MEM_mallocN(sizeof(*fsme), "fsme");
193         fsme->path= BLI_strdup(path);
194         fsme->save = save;
195
196         if (prev) {
197                 fsme->next= prev->next;
198                 prev->next= fsme;
199         } else {
200                 fsme->next= fsms;
201                 fsmenu_set_category(fsmenu, category, fsme);
202         }
203 }
204
205 void fsmenu_remove_entry(struct FSMenu* fsmenu, FSMenuCategory category, int idx)
206 {
207         FSMenuEntry *prev= NULL, *fsme= NULL;
208         FSMenuEntry *fsms = fsmenu_get_category(fsmenu, category);
209
210         for (fsme= fsms; fsme && idx; prev= fsme, fsme= fsme->next)             
211                 idx--;
212
213         if (fsme) {
214                 /* you should only be able to remove entries that were 
215                    not added by default, like windows drives.
216                    also separators (where path == NULL) shouldn't be removed */
217                 if (fsme->save && fsme->path) {
218
219                         /* remove fsme from list */
220                         if (prev) {
221                                 prev->next= fsme->next;
222                         } else {
223                                 fsms= fsme->next;
224                                 fsmenu_set_category(fsmenu, category, fsms);
225                         }
226                         /* free entry */
227                         MEM_freeN(fsme->path);
228                         MEM_freeN(fsme);
229                 }
230         }
231 }
232
233 void fsmenu_write_file(struct FSMenu* fsmenu, const char *filename)
234 {
235         FSMenuEntry *fsme= NULL;
236         int nskip= 0;
237
238         FILE *fp = fopen(filename, "w");
239         if (!fp) return;
240         
241         fprintf(fp, "[Bookmarks]\n");
242         for (fsme= fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsme; fsme= fsme->next) {
243                 if (fsme->path && fsme->save) {
244                         fprintf(fp, "%s\n", fsme->path);
245                 }
246         }
247         fprintf(fp, "[Recent]\n");
248         nskip = fsmenu_get_nentries(fsmenu, FS_CATEGORY_RECENT) - FSMENU_RECENT_MAX;
249         // skip first entries if list too long
250         for (fsme= fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT); fsme && (nskip>0); fsme= fsme->next, --nskip)
251                 ;
252         for (; fsme; fsme= fsme->next) {
253                 if (fsme->path && fsme->save) {
254                         fprintf(fp, "%s\n", fsme->path);
255                 }
256         }
257         fclose(fp);
258 }
259
260 void fsmenu_read_bookmarks(struct FSMenu* fsmenu, const char *filename)
261 {
262         char line[256];
263         FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
264         FILE *fp;
265
266         fp = fopen(filename, "r");
267         if (!fp) return;
268
269         while ( fgets ( line, 256, fp ) != NULL ) /* read a line */
270         {
271                 if (strncmp(line, "[Bookmarks]", 11)==0){
272                         category = FS_CATEGORY_BOOKMARKS;
273                 } else if (strncmp(line, "[Recent]", 8)==0){
274                         category = FS_CATEGORY_RECENT;
275                 } else {
276                         int len = strlen(line);
277                         if (len>0) {
278                                 if (line[len-1] == '\n') {
279                                         line[len-1] = '\0';
280                                 }
281                                 if (BLI_exists(line)) {
282                                         fsmenu_insert_entry(fsmenu, category, line, 0, 1);
283                                 }
284                         }
285                 }
286         }
287         fclose(fp);
288 }
289
290 void fsmenu_read_system(struct FSMenu* fsmenu)
291 {
292         char line[256];
293 #ifdef WIN32
294         /* Add the drive names to the listing */
295         {
296                 __int64 tmp;
297                 char tmps[4];
298                 int i;
299                         
300                 tmp= GetLogicalDrives();
301                 
302                 for (i=0; i < 26; i++) {
303                         if ((tmp>>i) & 1) {
304                                 tmps[0]='A'+i;
305                                 tmps[1]=':';
306                                 tmps[2]='\\';
307                                 tmps[3]=0;
308                                 
309                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, tmps, 1, 0);
310                         }
311                 }
312
313                 /* Adding Desktop and My Documents */
314                 SHGetSpecialFolderPath(0, line, CSIDL_PERSONAL, 0);
315                 fsmenu_insert_entry(fsmenu,FS_CATEGORY_BOOKMARKS, line, 1, 0);
316                 SHGetSpecialFolderPath(0, line, CSIDL_DESKTOPDIRECTORY, 0);
317                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
318         }
319 #else
320 #ifdef __APPLE__
321         {
322 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
323                 OSErr err=noErr;
324                 int i;
325                 const char *home;
326                 
327                 /* loop through all the OS X Volumes, and add them to the SYSTEM section */
328                 for (i=1; err!=nsvErr; i++)
329                 {
330                         FSRef dir;
331                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
332                         
333                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, NULL, kFSVolInfoNone, NULL, NULL, &dir);
334                         if (err != noErr)
335                                 continue;
336                         
337                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
338                         if (strcmp((char*)path, "/home") && strcmp((char*)path, "/net"))
339                         { /* /net and /home are meaningless on OSX, home folders are stored in /Users */
340                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
341                         }
342                 }
343
344                 /* As 10.4 doesn't provide proper API to retrieve the favorite places,
345                  assume they are the standard ones 
346                  TODO : replace hardcoded paths with proper BLI_get_folder calls */
347                 home = getenv("HOME");
348                 if(home) {
349                         BLI_snprintf(line, 256, "%s/", home);
350                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
351                         BLI_snprintf(line, 256, "%s/Desktop/", home);
352                         if (BLI_exists(line)) {
353                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
354                         }
355                         BLI_snprintf(line, 256, "%s/Documents/", home);
356                         if (BLI_exists(line)) {
357                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
358                         }
359                         BLI_snprintf(line, 256, "%s/Pictures/", home);
360                         if (BLI_exists(line)) {
361                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
362                         }
363                         BLI_snprintf(line, 256, "%s/Music/", home);
364                         if (BLI_exists(line)) {
365                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
366                         }
367                         BLI_snprintf(line, 256, "%s/Movies/", home);
368                         if (BLI_exists(line)) {
369                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
370                         }
371                 }
372 #else
373                 /* 10.5 provides ability to retrieve Finder favorite places */
374                 UInt32 seed;
375                 OSErr err = noErr;
376                 CFArrayRef pathesArray;
377                 LSSharedFileListRef list;
378                 LSSharedFileListItemRef itemRef;
379                 CFIndex i, pathesCount;
380                 CFURLRef cfURL = NULL;
381                 CFStringRef pathString = NULL;
382                 
383                 /* First get local mounted volumes */
384                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteVolumes, NULL);
385                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
386                 pathesCount = CFArrayGetCount(pathesArray);
387                 
388                 for (i=0; i<pathesCount; i++)
389                 {
390                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
391                         
392                         err = LSSharedFileListItemResolve(itemRef, 
393                                                                                           kLSSharedFileListNoUserInteraction
394                                                                                           | kLSSharedFileListDoNotMountVolumes, 
395                                                                                           &cfURL, NULL);
396                         if (err != noErr)
397                                 continue;
398                         
399                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
400                         
401                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
402                                 continue;
403                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
404                         
405                         CFRelease(pathString);
406                         CFRelease(cfURL);
407                 }
408                 
409                 CFRelease(pathesArray);
410                 CFRelease(list);
411                 
412                 /* Then get network volumes */
413                 err = noErr;
414                 for (i=1; err!=nsvErr; i++)
415                 {
416                         FSRef dir;
417                         FSVolumeRefNum volRefNum;
418                         struct GetVolParmsInfoBuffer volParmsBuffer;
419                         unsigned char path[FILE_MAXDIR+FILE_MAXFILE];
420                         
421                         err = FSGetVolumeInfo(kFSInvalidVolumeRefNum, i, &volRefNum, kFSVolInfoNone, NULL, NULL, &dir);
422                         if (err != noErr)
423                                 continue;
424                         
425                         err = FSGetVolumeParms(volRefNum, &volParmsBuffer, sizeof(volParmsBuffer));
426                         if ((err != noErr) || (volParmsBuffer.vMServerAdr == 0)) /* Exclude local devices */
427                                 continue;
428                         
429                         
430                         FSRefMakePath(&dir, path, FILE_MAXDIR+FILE_MAXFILE);
431                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, (char *)path, 1, 0);
432                 }
433                 
434                 /* Finally get user favorite places */
435                 list = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL);
436                 pathesArray = LSSharedFileListCopySnapshot(list, &seed);
437                 pathesCount = CFArrayGetCount(pathesArray);
438                 
439                 for (i=0; i<pathesCount; i++)
440                 {
441                         itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(pathesArray, i);
442                         
443                         err = LSSharedFileListItemResolve(itemRef, 
444                                                                                           kLSSharedFileListNoUserInteraction
445                                                                                           | kLSSharedFileListDoNotMountVolumes, 
446                                                                                           &cfURL, NULL);
447                         if (err != noErr)
448                                 continue;
449                         
450                         pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
451                         
452                         if (!CFStringGetCString(pathString,line,256,kCFStringEncodingASCII))
453                                 continue;
454                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
455                         
456                         CFRelease(pathString);
457                         CFRelease(cfURL);
458                 }
459                 
460                 CFRelease(pathesArray);
461                 CFRelease(list);
462 #endif /* OSX 10.5+ */
463         }
464 #else
465         /* unix */
466         {
467                 const char *home= getenv("HOME");
468
469                 if(home) {
470                         BLI_snprintf(line, FILE_MAXDIR, "%s/", home);
471                         fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
472                         BLI_snprintf(line, FILE_MAXDIR, "%s/Desktop/", home);
473                         if (BLI_exists(line)) {
474                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_BOOKMARKS, line, 1, 0);
475                         }
476                 }
477
478                 {
479                         int found= 0;
480 #ifdef __linux__
481                         /* loop over mount points */
482                         struct mntent *mnt;
483                         int len;
484                         FILE *fp;
485
486                         fp = setmntent (MOUNTED, "r");
487                         if (fp == NULL) {
488                                 fprintf(stderr, "could not get a list of mounted filesystemts\n");
489                         }
490                         else {
491                                 while ((mnt = getmntent (fp))) {
492                                         /* not sure if this is right, but seems to give the relevant mnts */
493                                         if(strncmp(mnt->mnt_fsname, "/dev", 4))
494                                                 continue;
495
496                                         len= strlen(mnt->mnt_dir);
497                                         if(len && mnt->mnt_dir[len-1] != '/') {
498                                                 BLI_snprintf(line, FILE_MAXDIR, "%s/", mnt->mnt_dir);
499                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, line, 1, 0);
500                                         }
501                                         else
502                                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, 1, 0);
503
504                                         found= 1;
505                                 }
506                                 if (endmntent (fp) == 0) {
507                                         fprintf(stderr, "could not close the list of mounted filesystemts\n");
508                                 }
509                         }
510 #endif
511
512                         /* fallback */
513                         if(!found)
514                                 fsmenu_insert_entry(fsmenu, FS_CATEGORY_SYSTEM, "/", 1, 0);
515                 }
516         }
517 #endif
518 #endif
519 }
520
521
522 static void fsmenu_free_category(struct FSMenu* fsmenu, FSMenuCategory category)
523 {
524         FSMenuEntry *fsme= fsmenu_get_category(fsmenu, category);
525
526         while (fsme) {
527                 FSMenuEntry *n= fsme->next;
528
529                 if (fsme->path) MEM_freeN(fsme->path);
530                 MEM_freeN(fsme);
531
532                 fsme= n;
533         }
534 }
535
536 void fsmenu_free(struct FSMenu* fsmenu)
537 {
538         fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
539         fsmenu_free_category(fsmenu, FS_CATEGORY_BOOKMARKS);
540         fsmenu_free_category(fsmenu, FS_CATEGORY_RECENT);
541         MEM_freeN(fsmenu);
542 }
543