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