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