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