rework index builder, add pro ffmpeg format
[goodguy/history.git] / cinelerra-5.1 / guicast / filesystem.C
1 /*
2  * CINELERRA
3  * Copyright (C) 2010 Adam Williams <broadcast at earthling dot net>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  */
20
21 #include "bcsignals.h"
22 #include "cstrdup.h"
23 #include <dirent.h>
24 #include <errno.h>
25 #include <pwd.h>
26 #include <stddef.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32 #include <time.h>
33 #include <unistd.h>
34 #include <utime.h>
35
36
37 #include "filesystem.h"
38
39 FileItem::FileItem()
40 {
41         path = 0;
42         name = 0;
43         reset();
44 }
45
46 FileItem::FileItem(char *path,
47         char *name,
48         int is_dir,
49         int64_t size,
50         int month,
51         int day,
52         int year,
53         int64_t calendar_time)
54 {
55         this->path = new char[strlen(path)];
56         this->name = new char[strlen(name)];
57         if(this->path) strcpy(this->path, path);
58         if(this->name) strcpy(this->name, name);
59         this->is_dir = is_dir;
60         this->size = size;
61         this->month = month;
62         this->day = day;
63         this->year = year;
64         this->calendar_time = calendar_time;
65 }
66
67 FileItem::~FileItem()
68 {
69         reset();
70 }
71
72 int FileItem::reset()
73 {
74         if(this->path) delete [] this->path;
75         if(this->name) delete [] this->name;
76         path = 0;
77         name = 0;
78         is_dir = 0;
79         size = 0;
80         month = 0;
81         day = 0;
82         year = 0;
83         calendar_time = 0;
84         return 0;
85 }
86
87 int FileItem::set_path(char *path)
88 {
89         if(this->path) delete [] this->path;
90         this->path = new char[strlen(path) + 1];
91         strcpy(this->path, path);
92         return 0;
93 }
94
95 int FileItem::set_name(char *name)
96 {
97         if(this->name) delete [] this->name;
98         this->name = new char[strlen(name) + 1];
99         strcpy(this->name, name);
100         return 0;
101 }
102
103 const char* FileItem::get_path()
104 {
105         return path;
106 }
107
108 const char* FileItem::get_name()
109 {
110         return name;
111 }
112
113 int FileItem::get_is_dir()
114 {
115         return is_dir;
116 }
117
118
119
120 FileSystem::FileSystem()
121 {
122         reset_parameters();
123         (void)getcwd(current_dir, BCTEXTLEN);
124 }
125
126 FileSystem::~FileSystem()
127 {
128         delete_directory();
129 }
130
131 int FileSystem::reset_parameters()
132 {
133         show_all_files = 0;
134         want_directory = 0;
135         strcpy(filter, "");
136         strcpy(current_dir, "");
137         sort_order = SORT_ASCENDING;
138         sort_field = SORT_PATH;
139         return 0;
140 }
141
142 int FileSystem::delete_directory()
143 {
144         for(int i = 0; i < dir_list.total; i++)
145         {
146                 delete dir_list.values[i];
147         }
148         dir_list.remove_all();
149         return 0;
150 }
151
152 int FileSystem::set_sort_order(int value)
153 {
154         this->sort_order = value;
155         return 0;
156 }
157
158 int FileSystem::set_sort_field(int field)
159 {
160         this->sort_field = field;
161         return 0;
162 }
163
164 // filename.with.dots.extension
165 //   becomes
166 // extension.dots.with.filename
167
168 int FileSystem::dot_reverse_filename(char *out, const char *in)
169 {
170         int i, i2, j=0, lastdot;
171         lastdot = strlen(in);
172         for ( i=strlen(in); --i >= 0; ) {
173                 if (in[i] == '.') {
174                         i2 = i+1;
175                                 while (i2 < lastdot)
176                                         out[j++] = in[i2++];
177                         out[j++] = in[i];
178                         lastdot = i;
179                 }
180         }
181         if (in[++i] != '.') {
182                 while (i < lastdot) out[j++] = in[i++];
183         }
184         out[j++] = '\0';
185         return 0;
186 }
187
188 int FileSystem::path_ascending(const void *ptr1, const void *ptr2)
189 {
190         FileItem *item1 = *(FileItem**)ptr1;
191         FileItem *item2 = *(FileItem**)ptr2;
192 //printf("path_ascending %p %p\n", ptr1, ptr2);
193         return strcasecmp(item1->name, item2->name);
194 }
195
196 int FileSystem::path_descending(const void *ptr1, const void *ptr2)
197 {
198         FileItem *item1 = *(FileItem**)ptr1;
199         FileItem *item2 = *(FileItem**)ptr2;
200         return strcasecmp(item2->name, item1->name);
201 }
202
203
204 int FileSystem::size_ascending(const void *ptr1, const void *ptr2)
205 {
206         FileItem *item1 = *(FileItem**)ptr1;
207         FileItem *item2 = *(FileItem**)ptr2;
208         return item1->size >= item2->size;
209 }
210
211 int FileSystem::size_descending(const void *ptr1, const void *ptr2)
212 {
213         FileItem *item1 = *(FileItem**)ptr1;
214         FileItem *item2 = *(FileItem**)ptr2;
215         return item1->size <= item2->size;
216 }
217
218
219 int FileSystem::date_ascending(const void *ptr1, const void *ptr2)
220 {
221         FileItem *item1 = *(FileItem**)ptr1;
222         FileItem *item2 = *(FileItem**)ptr2;
223         return item1->calendar_time >= item2->calendar_time;
224 }
225
226 int FileSystem::date_descending(const void *ptr1, const void *ptr2)
227 {
228         FileItem *item1 = *(FileItem**)ptr1;
229         FileItem *item2 = *(FileItem**)ptr2;
230         return item1->calendar_time <= item2->calendar_time;
231 }
232
233 int FileSystem::ext_ascending(const void *ptr1, const void *ptr2)
234 {
235         char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
236         FileItem *item1 = *(FileItem**)ptr1;
237         FileItem *item2 = *(FileItem**)ptr2;
238         dot_reverse_filename(dotreversedname1,item1->name);
239         dot_reverse_filename(dotreversedname2,item2->name);
240         return strcasecmp(dotreversedname1, dotreversedname2);
241 }
242
243 int FileSystem::ext_descending(const void *ptr1, const void *ptr2)
244 {
245         char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
246         FileItem *item1 = *(FileItem**)ptr1;
247         FileItem *item2 = *(FileItem**)ptr2;
248         dot_reverse_filename(dotreversedname1,item1->name);
249         dot_reverse_filename(dotreversedname2,item2->name);
250         return strcasecmp(dotreversedname2, dotreversedname1);
251 }
252
253 int FileSystem::sort_table(ArrayList<FileItem*> *dir_list)
254 {
255 #define SORT_MACRO(compare) \
256         qsort(dir_list->values, dir_list->size(), sizeof(FileItem*), compare);
257
258         if(!dir_list || !dir_list->size()) return 0;
259
260 //printf("FileSystem::sort_table %p\n", dir_list->values);
261         switch(sort_field)
262         {
263                 case SORT_PATH:
264                         if(sort_order == SORT_ASCENDING)
265                                 SORT_MACRO(path_ascending)
266                         else
267                                 SORT_MACRO(path_descending)
268                         break;
269                 case SORT_SIZE:
270                         if(sort_order == SORT_ASCENDING)
271                                 SORT_MACRO(size_ascending)
272                         else
273                                 SORT_MACRO(size_descending)
274                         break;
275                 case SORT_DATE:
276                         if(sort_order == SORT_ASCENDING)
277                                 SORT_MACRO(date_ascending)
278                         else
279                                 SORT_MACRO(date_descending)
280                         break;
281                 case SORT_EXTENSION:
282                         if(sort_order == SORT_ASCENDING)
283                                 SORT_MACRO(ext_ascending)
284                         else
285                                 SORT_MACRO(ext_descending)
286                         break;
287         }
288
289         return 0;
290 }
291
292 int FileSystem::combine(ArrayList<FileItem*> *dir_list, ArrayList<FileItem*> *file_list)
293 {
294         int i;
295
296         sort_table(dir_list);
297         for(i = 0; i < dir_list->total; i++)
298         {
299                 this->dir_list.append(dir_list->values[i]);
300         }
301
302         sort_table(file_list);
303         for(i = 0; i < file_list->total; i++)
304         {
305                 this->dir_list.append(file_list->values[i]);
306         }
307         return 0;
308 }
309
310 void FileSystem::alphabetize()
311 {
312         sort_table(&dir_list);
313 }
314
315 int FileSystem::is_root_dir(char *path)
316 {
317         if(!strcmp(current_dir, "/")) return 1;
318         return 0;
319 }
320
321 int FileSystem::test_filter(FileItem *file)
322 {
323         char *filter1 = 0, *filter2 = filter, *subfilter1, *subfilter2;
324         int result = 0;
325         int done = 0, token_done;
326         int token_number = 0;
327
328 // Don't filter directories
329         if(file->is_dir) return 0;
330
331 // Empty filename string
332         if(!file->name) return 1;
333
334         do
335         {
336 // Get next token
337                 filter1 = strchr(filter2, '[');
338                 string[0] = 0;
339
340 // Get next filter
341                 if(filter1)
342                 {
343                         filter1++;
344                         filter2 = strchr(filter1, ']');
345
346                         if(filter2)
347                         {
348                                 int i;
349                                 for(i = 0; filter1 + i < filter2; i++)
350                                         string[i] = filter1[i];
351                                 string[i] = 0;
352                         }
353                         else
354                         {
355                                 strcpy(string, filter1);
356                                 done = 1;
357                         }
358                 }
359                 else
360                 {
361                         if(!token_number)
362                                 strcpy(string, filter);
363                         else
364                                 done = 1;
365                 }
366
367 // Process the token
368                 if(string[0] != 0)
369                 {
370                         char *path = file->name;
371                         subfilter1 = string;
372                         token_done = 0;
373                         result = 0;
374
375                         do
376                         {
377                                 string2[0] = 0;
378                                 subfilter2 = strchr(subfilter1, '*');
379
380                                 if(subfilter2)
381                                 {
382                                         int i;
383                                         for(i = 0; subfilter1 + i < subfilter2; i++)
384                                                 string2[i] = subfilter1[i];
385
386                                         string2[i] = 0;
387                                 }
388                                 else
389                                 {
390                                         strcpy(string2, subfilter1);
391                                         token_done = 1;
392                                 }
393
394                                 if(string2[0] != 0)
395                                 {
396 // Subfilter must exist at some later point in the string
397                                         if(subfilter1 > string)
398                                         {
399                                                 if(!strstr(path, string2))
400                                                 {
401                                                         result = 1;
402                                                         token_done = 1;
403                                                 }
404                                                 else
405                                                 path = strstr(path, string2) + strlen(string2);
406                                         }
407                                         else
408 // Subfilter must exist at this point in the string
409                                         {
410                                                 if(strncmp(path, string2, strlen(string2)))
411 //                                              if(strncasecmp(path, string2, strlen(string2)))
412                                                 {
413                                                         result = 1;
414                                                         token_done = 1;
415                                                 }
416                                                 else
417                                                 path += strlen(string2);
418                                         }
419
420 // String must terminate after subfilter
421                                         if(!subfilter2)
422                                         {
423                                                 if(*path != 0)
424                                                 {
425                                                         result = 1;
426                                                         token_done = 1;
427                                                 }
428                                         }
429                                 }
430                                 subfilter1 = subfilter2 + 1;
431 // Let pass if no subfilter
432                         }while(!token_done && !result);
433                 }
434                 token_number++;
435         }while(!done && result);
436
437         return result;
438 }
439
440
441 int FileSystem::update(const char *new_dir)
442 {
443         DIR *dirstream;
444         struct dirent64 *new_filename;
445         struct stat ostat;
446         struct tm *mod_time;
447         int include_this;
448         FileItem *new_file;
449         char full_path[BCTEXTLEN], name_only[BCTEXTLEN];
450         ArrayList<FileItem*>directories;
451         ArrayList<FileItem*>files;
452         int result = 0;
453
454         delete_directory();
455         if(new_dir != 0) strcpy(current_dir, new_dir);
456         dirstream = opendir(current_dir);
457         if(!dirstream) return 1;          // failed to open directory
458
459         while( (new_filename = readdir64(dirstream)) != 0 )
460         {
461                 include_this = 1;
462
463 // File is directory heirarchy
464                 if(!strcmp(new_filename->d_name, ".") ||
465                         !strcmp(new_filename->d_name, "..")) include_this = 0;
466
467 // File is hidden and we don't want all files
468                 if(include_this &&
469                         !show_all_files &&
470                         new_filename->d_name[0] == '.') include_this = 0;
471
472 // file not hidden
473                 if(include_this)
474                 {
475                         new_file = new FileItem;
476                         sprintf(full_path, "%s", current_dir);
477                         if(!is_root_dir(current_dir)) strcat(full_path, "/");
478                         strcat(full_path, new_filename->d_name);
479                         strcpy(name_only, new_filename->d_name);
480                         new_file->set_path(full_path);
481                         new_file->set_name(name_only);
482
483 // Get information about the file.
484                         if(!stat(full_path, &ostat))
485                         {
486                                 new_file->size = ostat.st_size;
487                                 mod_time = localtime(&(ostat.st_mtime));
488                                 new_file->month = mod_time->tm_mon + 1;
489                                 new_file->day = mod_time->tm_mday;
490                                 new_file->year = mod_time->tm_year + 1900;
491                                 new_file->calendar_time = ostat.st_mtime;
492
493                                 if(S_ISDIR(ostat.st_mode))
494                                 {
495                                         strcat(name_only, "/"); // is a directory
496                                         new_file->is_dir = 1;
497                                 }
498
499 // File is excluded from filter
500                                 if(include_this && test_filter(new_file)) include_this = 0;
501 //printf("FileSystem::update 3 %d %d\n", include_this, test_filter(new_file));
502
503 // File is not a directory and we just want directories
504                                 if(include_this && want_directory && !new_file->is_dir) include_this = 0;
505                         }
506                         else
507                         {
508                                 printf("FileSystem::update %s %s\n", full_path, strerror(errno));
509                                 include_this = 0;
510                         }
511
512 // add to list
513                         if(include_this)
514                         {
515                                 if(new_file->is_dir) directories.append(new_file);
516                                 else files.append(new_file);
517                         }
518                         else
519                                 delete new_file;
520                 }
521         }
522 //printf("FileSystem::update %d\n", __LINE__);
523
524         closedir(dirstream);
525 // combine the directories and files in the master list
526         combine(&directories, &files);
527 // remove pointers
528         directories.remove_all();
529         files.remove_all();
530 //printf("FileSystem::update %d\n", __LINE__);
531
532         return result;
533 // success
534 }
535
536 int FileSystem::set_filter(const char *new_filter)
537 {
538         strcpy(filter, new_filter);
539         return 0;
540 }
541
542 int FileSystem::set_show_all()
543 {
544         show_all_files = 1;
545         return 0;
546 }
547
548 int FileSystem::set_want_directory()
549 {
550         want_directory = 1;
551         return 0;
552 }
553
554 int FileSystem::is_dir(const char *path)      // return 0 if the text is a directory
555 {
556         if(!strlen(path)) return 0;
557
558         char new_dir[BCTEXTLEN];
559         struct stat ostat;    // entire name is a directory
560
561         strcpy(new_dir, path);
562         complete_path(new_dir);
563         if(!stat(new_dir, &ostat) && S_ISDIR(ostat.st_mode))
564                 return 1;
565         else
566                 return 0;
567 }
568
569 int FileSystem::create_dir(const char *new_dir_)
570 {
571         char new_dir[BCTEXTLEN];
572         strcpy(new_dir, new_dir_);
573         complete_path(new_dir);
574
575         mkdir(new_dir, S_IREAD | S_IWRITE | S_IEXEC);
576         return 0;
577 }
578
579 int FileSystem::parse_tildas(char *new_dir)
580 {
581         if(new_dir[0] == 0) return 1;
582
583 // Our home directory
584         if(new_dir[0] == '~')
585         {
586
587                 if(new_dir[1] == '/' || new_dir[1] == 0)
588                 {
589 // user's home directory
590                         char *home;
591                         char string[BCTEXTLEN];
592                         home = getenv("HOME");
593
594 // print starting after tilda
595                         if(home) sprintf(string, "%s%s", home, &new_dir[1]);
596                         strcpy(new_dir, string);
597                         return 0;
598                 }
599                 else
600 // Another user's home directory
601                 {
602                         char string[BCTEXTLEN], new_user[BCTEXTLEN];
603                         struct passwd *pw;
604                         int i, j;
605
606                         for(i = 1, j = 0; new_dir[i] != 0 && new_dir[i] != '/'; i++, j++)
607                         {                // copy user name
608                                 new_user[j] = new_dir[i];
609                         }
610                         new_user[j] = 0;
611
612                         setpwent();
613                         while( (pw = getpwent()) != 0 )
614                         {
615 // get info for user
616                                 if(!strcmp(pw->pw_name, new_user))
617                                 {
618 // print starting after tilda
619                                 sprintf(string, "%s%s", pw->pw_dir, &new_dir[i]);
620                                 strcpy(new_dir, string);
621                                 break;
622                         }
623                         }
624                         endpwent();
625                         return 0;
626                 }
627         }
628         return 0;
629 }
630
631 int FileSystem::parse_directories(char *new_dir)
632 {
633 //printf("FileSystem::parse_directories 1 %s\n", new_dir);
634         if(new_dir[0] != '/')
635         {
636 // extend path completely
637                 char string[BCTEXTLEN];
638 //printf("FileSystem::parse_directories 2 %s\n", current_dir);
639                 if(!strlen(current_dir))
640                 {
641 // no current directory
642                         strcpy(string, new_dir);
643                 }
644                 else
645                 if(!is_root_dir(current_dir))
646                 {
647 // current directory is not root
648                         if(current_dir[strlen(current_dir) - 1] == '/')
649 // current_dir already has ending /
650                         sprintf(string, "%s%s", current_dir, new_dir);
651                         else
652 // need ending /
653                         sprintf(string, "%s/%s", current_dir, new_dir);
654                 }
655                 else
656                         sprintf(string, "%s%s", current_dir, new_dir);
657
658 //printf("FileSystem::parse_directories 3 %s %s\n", new_dir, string);
659                 strcpy(new_dir, string);
660 //printf("FileSystem::parse_directories 4\n");
661         }
662         return 0;
663 }
664
665 int FileSystem::parse_dots(char *new_dir)
666 {
667 // recursively remove ..s
668         int changed = 1;
669         while(changed)
670         {
671                 int i, j, len;
672                 len = strlen(new_dir);
673                 changed = 0;
674                 for(i = 0, j = 1; !changed && j < len; i++, j++)
675                 {
676 // Got 2 dots
677                         if(new_dir[i] == '.' && new_dir[j] == '.')
678                         {
679 // Ignore if character after .. doesn't qualify
680                                 if(j + 1 < len &&
681                                         new_dir[j + 1] != ' ' &&
682                                         new_dir[j + 1] != '/')
683                                         continue;
684
685 // Ignore if character before .. doesn't qualify
686                                 if(i > 0 &&
687                                         new_dir[i - 1] != '/') continue;
688
689                                 changed = 1;
690                                 while(new_dir[i] != '/' && i > 0)
691                                 {
692 // look for first / before ..
693                                         i--;
694                                 }
695
696 // find / before this /
697                                 if(i > 0) i--;
698                                 while(new_dir[i] != '/' && i > 0)
699                                 {
700 // look for first / before first / before ..
701                                         i--;
702                                 }
703
704 // i now equals /first filename before ..
705 // look for first / after ..
706                                 while(new_dir[j] != '/' && j < len)
707                                 {
708                                         j++;
709                                 }
710
711 // j now equals /first filename after ..
712                                 while(j < len)
713                                 {
714                                         new_dir[i++] = new_dir[j++];
715                                 }
716
717                                 new_dir[i] = 0;
718 // default to root directory
719                                 if((new_dir[0]) == 0) sprintf(new_dir, "/");
720                                 break;
721                         }
722                 }
723         }
724         return 0;
725 }
726
727 int FileSystem::complete_path(char *filename)
728 {
729 //printf("FileSystem::complete_path 1\n");
730         if(!strlen(filename)) return 1;
731 //printf("FileSystem::complete_path 1\n");
732         parse_tildas(filename);
733 //printf("FileSystem::complete_path 1\n");
734         parse_directories(filename);
735 //printf("FileSystem::complete_path 1\n");
736         parse_dots(filename);
737 // don't add end slash since this requires checking if dir
738 //printf("FileSystem::complete_path 2\n");
739         return 0;
740 }
741
742 int FileSystem::extract_dir(char *out, const char *in)
743 {
744         strcpy(out, in);
745         if(!is_dir(in))
746         {
747 // complete string is not directory
748                 int i;
749
750                 complete_path(out);
751
752                 for(i = strlen(out); i > 0 && out[i - 1] != '/'; i--)
753                 {
754                         ;
755                 }
756                 if(i >= 0) out[i] = 0;
757         }
758         return 0;
759 }
760
761 int FileSystem::extract_name(char *out, const char *in, int test_dir)
762 {
763         int i;
764
765         if(test_dir && is_dir(in))
766                 out[0] = 0; // complete string is directory
767         else
768         {
769                 for(i = strlen(in)-1; i > 0 && in[i] != '/'; i--)
770                 {
771                         ;
772                 }
773                 if(in[i] == '/') i++;
774                 strcpy(out, &in[i]);
775         }
776         return 0;
777 }
778
779 int FileSystem::join_names(char *out, const char *dir_in, const char *name_in)
780 {
781         strcpy(out, dir_in);
782         int len = strlen(out);
783         int result = 0;
784
785         while(!result)
786                 if(len == 0 || out[len] != 0) result = 1; else len--;
787
788         if(len != 0)
789         {
790                 if(out[len] != '/') strcat(out, "/");
791         }
792
793         strcat(out, name_in);
794         return 0;
795 }
796
797 int64_t FileSystem::get_date(const char *filename)
798 {
799         struct stat file_status;
800         bzero(&file_status, sizeof(struct stat));
801         int result = stat(filename, &file_status);
802         return !result ? file_status.st_mtime : -1;
803 }
804
805 void FileSystem::set_date(const char *path, int64_t value)
806 {
807         struct utimbuf new_time;
808         new_time.actime = value;
809         new_time.modtime = value;
810         utime(path, &new_time);
811 }
812
813 int64_t FileSystem::get_size(char *filename)
814 {
815         struct stat file_status;
816         bzero(&file_status, sizeof(struct stat));
817         int result = stat(filename, &file_status);
818         return !result ? file_status.st_size : -1;
819 }
820
821 int FileSystem::change_dir(const char *new_dir, int update)
822 {
823         char new_dir_full[BCTEXTLEN];
824
825         strcpy(new_dir_full, new_dir);
826
827         complete_path(new_dir_full);
828 // cut ending slash
829         if(strcmp(new_dir_full, "/") &&
830                 new_dir_full[strlen(new_dir_full) - 1] == '/')
831                 new_dir_full[strlen(new_dir_full) - 1] = 0;
832
833         if(update)
834                 this->update(new_dir_full);
835         else
836         {
837                 strcpy(current_dir, new_dir_full);
838         }
839         return 0;
840 }
841
842 int FileSystem::set_current_dir(const char *new_dir)
843 {
844         strcpy(current_dir, new_dir);
845         return 0;
846 }
847
848 int FileSystem::add_end_slash(char *new_dir)
849 {
850         if(new_dir[strlen(new_dir) - 1] != '/') strcat(new_dir, "/");
851         return 0;
852 }
853
854 char* FileSystem::get_current_dir()
855 {
856         return current_dir;
857 }
858
859 int FileSystem::total_files()
860 {
861         return dir_list.total;
862 }
863
864
865 FileItem* FileSystem::get_entry(int entry)
866 {
867         return dir_list.values[entry];
868 }
869
870
871 // collapse ".",  "..", "//"  eg. x/./..//y = y
872 char *FileSystem::basepath(const char *path)
873 {
874         char fpath[BCTEXTLEN];
875         unsigned len = strlen(path);
876         if( len >= sizeof(fpath) ) return 0;
877         strcpy(fpath, path);
878         char *flat = cstrdup("");
879
880         int r = 0;
881         char *fn = fpath + len;
882         while( fn > fpath ) {
883                 while( --fn >= fpath )
884                         if( *fn == '/' ) { *fn = 0;  break; }
885                 fn = fn < fpath ? fpath : fn+1;
886                 if( !*fn || !strcmp(fn, ".") ) continue;
887                 if( !strcmp(fn, "..") ) { ++r; continue; }
888                 if( r < 0 ) continue;
889                 if( r > 0 ) { --r;  continue; }
890                 char *cp = cstrcat(3, "/",fn,flat);
891                 delete [] flat;  flat = cp;
892         }
893
894         if( *path != '/' ) {
895                 char *cp = cstrcat(2, ".",flat);
896                 delete [] flat;  flat = cp;
897         }
898
899         return flat;
900 }
901