3 * Copyright (C) 2010 Adam Williams <broadcast at earthling dot net>
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.
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.
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
21 #include "bcsignals.h"
31 #include <sys/types.h>
37 #include "filesystem.h"
46 FileItem::FileItem(char *path, char *name, int is_dir,
47 int64_t size, int month, int day, int year,
48 int64_t calendar_time, int item_no)
50 this->path = new char[strlen(path)];
51 this->name = new char[strlen(name)];
52 if(this->path) strcpy(this->path, path);
53 if(this->name) strcpy(this->name, name);
54 this->is_dir = is_dir;
59 this->calendar_time = calendar_time;
60 this->item_no = item_no;
70 if(this->path) delete [] this->path;
71 if(this->name) delete [] this->name;
84 int FileItem::set_path(char *path)
86 if(this->path) delete [] this->path;
87 this->path = new char[strlen(path) + 1];
88 strcpy(this->path, path);
92 int FileItem::set_name(char *name)
94 if(this->name) delete [] this->name;
95 this->name = new char[strlen(name) + 1];
96 strcpy(this->name, name);
100 const char* FileItem::get_path()
105 const char* FileItem::get_name()
110 int FileItem::get_is_dir()
117 FileSystem::FileSystem()
120 (void)getcwd(current_dir, BCTEXTLEN);
123 FileSystem::~FileSystem()
128 int FileSystem::reset_parameters()
133 strcpy(current_dir, "");
134 sort_order = SORT_ASCENDING;
135 sort_field = SORT_PATH;
139 int FileSystem::delete_directory()
141 dir_list.remove_all_objects();
145 int FileSystem::set_sort_order(int value)
147 this->sort_order = value;
151 int FileSystem::set_sort_field(int field)
153 this->sort_field = field;
157 // filename.with.dots.extension
159 // extension.dots.with.filename
161 int FileSystem::dot_reverse_filename(char *out, const char *in)
163 int i, i2, j=0, lastdot;
164 lastdot = strlen(in);
165 for ( i=strlen(in); --i >= 0; ) {
174 if (in[++i] != '.') {
175 while (i < lastdot) out[j++] = in[i++];
181 int FileSystem::path_ascending(const void *ptr1, const void *ptr2)
183 FileItem *item1 = *(FileItem**)ptr1;
184 FileItem *item2 = *(FileItem**)ptr2;
185 //printf("path_ascending %p %p\n", ptr1, ptr2);
186 int ret = strcasecmp(item1->name, item2->name);
187 if( ret != 0 ) return ret;
188 return item1->item_no - item2->item_no;
191 int FileSystem::path_descending(const void *ptr1, const void *ptr2)
193 FileItem *item1 = *(FileItem**)ptr1;
194 FileItem *item2 = *(FileItem**)ptr2;
195 int ret = strcasecmp(item2->name, item1->name);
196 if( ret != 0 ) return ret;
197 return item2->item_no - item1->item_no;
201 int FileSystem::size_ascending(const void *ptr1, const void *ptr2)
203 FileItem *item1 = *(FileItem**)ptr1;
204 FileItem *item2 = *(FileItem**)ptr2;
205 return item1->size == item2->size ?
206 item1->item_no - item2->item_no :
207 item1->size > item2->size;
210 int FileSystem::size_descending(const void *ptr1, const void *ptr2)
212 FileItem *item1 = *(FileItem**)ptr1;
213 FileItem *item2 = *(FileItem**)ptr2;
214 return item2->size == item1->size ?
215 item2->item_no - item1->item_no :
216 item2->size > item1->size;
220 int FileSystem::date_ascending(const void *ptr1, const void *ptr2)
222 FileItem *item1 = *(FileItem**)ptr1;
223 FileItem *item2 = *(FileItem**)ptr2;
224 return item1->calendar_time == item2->calendar_time ?
225 item1->item_no - item2->item_no :
226 item1->calendar_time > item2->calendar_time;
229 int FileSystem::date_descending(const void *ptr1, const void *ptr2)
231 FileItem *item1 = *(FileItem**)ptr1;
232 FileItem *item2 = *(FileItem**)ptr2;
233 return item2->calendar_time == item1->calendar_time ?
234 item2->item_no - item1->item_no :
235 item2->calendar_time > item1->calendar_time;
238 int FileSystem::ext_ascending(const void *ptr1, const void *ptr2)
240 FileItem *item1 = *(FileItem**)ptr1;
241 FileItem *item2 = *(FileItem**)ptr2;
242 char *ext1 = strrchr(item1->name,'.');
243 if( !ext1 ) ext1 = item1->name;
244 char *ext2 = strrchr(item2->name,'.');
245 if( !ext2 ) ext2 = item2->name;
246 int ret = strcasecmp(ext1, ext2);
247 if( ret ) return ret;
248 if( item1->item_no >= 0 && item2->item_no >= 0 )
249 return item1->item_no - item2->item_no;
250 char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
251 dot_reverse_filename(dotreversedname1,item1->name);
252 dot_reverse_filename(dotreversedname2,item2->name);
253 return strcasecmp(dotreversedname1, dotreversedname2);
256 int FileSystem::ext_descending(const void *ptr1, const void *ptr2)
258 FileItem *item1 = *(FileItem**)ptr1;
259 FileItem *item2 = *(FileItem**)ptr2;
260 char *ext1 = strrchr(item1->name,'.');
261 if( !ext1 ) ext1 = item1->name;
262 char *ext2 = strrchr(item2->name,'.');
263 if( !ext2 ) ext2 = item2->name;
264 int ret = strcasecmp(ext2, ext1);
265 if( ret ) return ret;
266 if( item2->item_no >= 0 && item1->item_no >= 0 )
267 return item2->item_no - item1->item_no;
268 char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
269 dot_reverse_filename(dotreversedname1,item1->name);
270 dot_reverse_filename(dotreversedname2,item2->name);
271 return strcasecmp(dotreversedname2, dotreversedname1);
274 int FileSystem::sort_table(ArrayList<FileItem*> *dir_list)
276 if(!dir_list || !dir_list->size()) return 0;
277 static int (*cmpr[][2])(const void *ptr1, const void *ptr2) = {
278 { &path_ascending, &path_descending },
279 { &size_ascending, &size_descending },
280 { &date_ascending, &date_descending },
281 { &ext_ascending, &ext_descending },
284 qsort(dir_list->values,
285 dir_list->size(), sizeof(FileItem*),
286 cmpr[sort_field][sort_order]);
291 int FileSystem::combine(ArrayList<FileItem*> *dir_list, ArrayList<FileItem*> *file_list)
295 sort_table(dir_list);
296 for(i = 0; i < dir_list->total; i++)
298 this->dir_list.append(dir_list->values[i]);
301 sort_table(file_list);
302 for(i = 0; i < file_list->total; i++)
304 this->dir_list.append(file_list->values[i]);
309 void FileSystem::alphabetize()
311 sort_table(&dir_list);
314 int FileSystem::is_root_dir(char *path)
316 if(!strcmp(current_dir, "/")) return 1;
320 int FileSystem::test_filter(FileItem *file)
322 char *filter1 = 0, *filter2 = filter, *subfilter1, *subfilter2;
324 int done = 0, token_done;
325 int token_number = 0;
327 // Don't filter directories
328 if(file->is_dir) return 0;
330 // Empty filename string
331 if(!file->name) return 1;
336 filter1 = strchr(filter2, '[');
343 filter2 = strchr(filter1, ']');
348 for(i = 0; filter1 + i < filter2; i++)
349 string[i] = filter1[i];
354 strcpy(string, filter1);
361 strcpy(string, filter);
369 char *path = file->name;
377 subfilter2 = strchr(subfilter1, '*');
382 for(i = 0; subfilter1 + i < subfilter2; i++)
383 string2[i] = subfilter1[i];
389 strcpy(string2, subfilter1);
395 // Subfilter must exist at some later point in the string
396 if(subfilter1 > string)
398 if(!strstr(path, string2))
404 path = strstr(path, string2) + strlen(string2);
407 // Subfilter must exist at this point in the string
409 if(strncmp(path, string2, strlen(string2)))
410 // if(strncasecmp(path, string2, strlen(string2)))
416 path += strlen(string2);
419 // String must terminate after subfilter
429 subfilter1 = subfilter2 + 1;
430 // Let pass if no subfilter
431 }while(!token_done && !result);
434 }while(!done && result);
440 int FileSystem::scan_directory(const char *new_dir)
443 strcpy(current_dir, new_dir);
444 DIR *dirstream = opendir(current_dir);
445 if( !dirstream ) return 1; // failed to open directory
447 struct dirent64 *new_filename;
448 while( (new_filename = readdir64(dirstream)) != 0 ) {
449 FileItem *new_file = 0;
450 int include_this = 1;
452 // File is directory heirarchy
453 if(!strcmp(new_filename->d_name, ".") ||
454 !strcmp(new_filename->d_name, "..")) include_this = 0;
456 // File is hidden and we don't want all files
459 new_filename->d_name[0] == '.') include_this = 0;
464 new_file = new FileItem;
465 char full_path[BCTEXTLEN], name_only[BCTEXTLEN];
466 sprintf(full_path, "%s", current_dir);
467 if(!is_root_dir(current_dir)) strcat(full_path, "/");
468 strcat(full_path, new_filename->d_name);
469 strcpy(name_only, new_filename->d_name);
470 new_file->set_path(full_path);
471 new_file->set_name(name_only);
473 // Get information about the file.
475 if(!stat(full_path, &ostat))
477 new_file->size = ostat.st_size;
478 struct tm *mod_time = localtime(&(ostat.st_mtime));
479 new_file->month = mod_time->tm_mon + 1;
480 new_file->day = mod_time->tm_mday;
481 new_file->year = mod_time->tm_year + 1900;
482 new_file->calendar_time = ostat.st_mtime;
484 if(S_ISDIR(ostat.st_mode))
486 strcat(name_only, "/"); // is a directory
487 new_file->is_dir = 1;
490 // File is excluded from filter
491 if(include_this && test_filter(new_file)) include_this = 0;
492 //printf("FileSystem::update 3 %d %d\n", include_this, test_filter(new_file));
494 // File is not a directory and we just want directories
495 if(include_this && want_directory && !new_file->is_dir) include_this = 0;
499 printf("FileSystem::update %s %s\n", full_path, strerror(errno));
505 dir_list.append(new_file);
513 int FileSystem::update(const char *new_dir)
516 int result = scan_directory(new_dir);
517 // combine the directories and files in the master list
518 return !result ? update_sort() : result;
521 int FileSystem::update_sort()
523 ArrayList<FileItem*> directories, files;
524 for( int i=0; i< dir_list.size(); ++i ) {
525 FileItem *item = dir_list[i];
527 (item->is_dir ? &directories : &files)->append(item);
529 dir_list.remove_all();
530 // combine the directories and files in the master list
531 combine(&directories, &files);
537 int FileSystem::set_filter(const char *new_filter)
539 strcpy(filter, new_filter);
543 int FileSystem::set_show_all()
549 int FileSystem::set_want_directory()
555 int FileSystem::is_dir(const char *path) // return 0 if the text is a directory
557 if(!strlen(path)) return 0;
559 char new_dir[BCTEXTLEN];
560 struct stat ostat; // entire name is a directory
562 strcpy(new_dir, path);
563 complete_path(new_dir);
564 if(!stat(new_dir, &ostat) && S_ISDIR(ostat.st_mode))
570 int FileSystem::create_dir(const char *new_dir_)
572 char new_dir[BCTEXTLEN];
573 strcpy(new_dir, new_dir_);
574 complete_path(new_dir);
576 mkdir(new_dir, S_IREAD | S_IWRITE | S_IEXEC);
580 int FileSystem::parse_tildas(char *new_dir)
582 if(new_dir[0] == 0) return 1;
584 // Our home directory
585 if(new_dir[0] == '~')
588 if(new_dir[1] == '/' || new_dir[1] == 0)
590 // user's home directory
592 char string[BCTEXTLEN];
593 home = getenv("HOME");
595 // print starting after tilda
596 if(home) sprintf(string, "%s%s", home, &new_dir[1]);
597 strcpy(new_dir, string);
601 // Another user's home directory
603 char string[BCTEXTLEN], new_user[BCTEXTLEN];
607 for(i = 1, j = 0; new_dir[i] != 0 && new_dir[i] != '/'; i++, j++)
609 new_user[j] = new_dir[i];
614 while( (pw = getpwent()) != 0 )
617 if(!strcmp(pw->pw_name, new_user))
619 // print starting after tilda
620 sprintf(string, "%s%s", pw->pw_dir, &new_dir[i]);
621 strcpy(new_dir, string);
632 int FileSystem::parse_directories(char *new_dir)
634 //printf("FileSystem::parse_directories 1 %s\n", new_dir);
635 if(new_dir[0] != '/')
637 // extend path completely
638 char string[BCTEXTLEN];
639 //printf("FileSystem::parse_directories 2 %s\n", current_dir);
640 if(!strlen(current_dir))
642 // no current directory
643 strcpy(string, new_dir);
646 if(!is_root_dir(current_dir))
648 // current directory is not root
649 if(current_dir[strlen(current_dir) - 1] == '/')
650 // current_dir already has ending /
651 sprintf(string, "%s%s", current_dir, new_dir);
654 sprintf(string, "%s/%s", current_dir, new_dir);
657 sprintf(string, "%s%s", current_dir, new_dir);
659 //printf("FileSystem::parse_directories 3 %s %s\n", new_dir, string);
660 strcpy(new_dir, string);
661 //printf("FileSystem::parse_directories 4\n");
666 int FileSystem::parse_dots(char *new_dir)
668 // recursively remove ..s
673 len = strlen(new_dir);
675 for(i = 0, j = 1; !changed && j < len; i++, j++)
678 if(new_dir[i] == '.' && new_dir[j] == '.')
680 // Ignore if character after .. doesn't qualify
682 new_dir[j + 1] != ' ' &&
683 new_dir[j + 1] != '/')
686 // Ignore if character before .. doesn't qualify
688 new_dir[i - 1] != '/') continue;
691 while(new_dir[i] != '/' && i > 0)
693 // look for first / before ..
697 // find / before this /
699 while(new_dir[i] != '/' && i > 0)
701 // look for first / before first / before ..
705 // i now equals /first filename before ..
706 // look for first / after ..
707 while(new_dir[j] != '/' && j < len)
712 // j now equals /first filename after ..
715 new_dir[i++] = new_dir[j++];
719 // default to root directory
720 if((new_dir[0]) == 0) sprintf(new_dir, "/");
728 int FileSystem::complete_path(char *filename)
730 //printf("FileSystem::complete_path 1\n");
731 if(!strlen(filename)) return 1;
732 //printf("FileSystem::complete_path 1\n");
733 parse_tildas(filename);
734 //printf("FileSystem::complete_path 1\n");
735 parse_directories(filename);
736 //printf("FileSystem::complete_path 1\n");
737 parse_dots(filename);
738 // don't add end slash since this requires checking if dir
739 //printf("FileSystem::complete_path 2\n");
743 int FileSystem::extract_dir(char *out, const char *in)
748 // complete string is not directory
753 for(i = strlen(out); i > 0 && out[i - 1] != '/'; i--)
757 if(i >= 0) out[i] = 0;
762 int FileSystem::extract_name(char *out, const char *in, int test_dir)
766 if(test_dir && is_dir(in))
767 out[0] = 0; // complete string is directory
770 for(i = strlen(in)-1; i > 0 && in[i] != '/'; i--)
774 if(in[i] == '/') i++;
780 int FileSystem::join_names(char *out, const char *dir_in, const char *name_in)
783 int len = strlen(out);
787 if(len == 0 || out[len] != 0) result = 1; else len--;
791 if(out[len] != '/') strcat(out, "/");
794 strcat(out, name_in);
798 int64_t FileSystem::get_date(const char *filename)
800 struct stat file_status;
801 bzero(&file_status, sizeof(struct stat));
802 int result = stat(filename, &file_status);
803 return !result ? file_status.st_mtime : -1;
806 void FileSystem::set_date(const char *path, int64_t value)
808 struct utimbuf new_time;
809 new_time.actime = value;
810 new_time.modtime = value;
811 utime(path, &new_time);
814 int64_t FileSystem::get_size(char *filename)
816 struct stat file_status;
817 bzero(&file_status, sizeof(struct stat));
818 int result = stat(filename, &file_status);
819 return !result ? file_status.st_size : -1;
822 int FileSystem::change_dir(const char *new_dir, int update)
824 char new_dir_full[BCTEXTLEN];
826 strcpy(new_dir_full, new_dir);
828 complete_path(new_dir_full);
830 if(strcmp(new_dir_full, "/") &&
831 new_dir_full[strlen(new_dir_full) - 1] == '/')
832 new_dir_full[strlen(new_dir_full) - 1] = 0;
835 this->update(new_dir_full);
838 strcpy(current_dir, new_dir_full);
843 int FileSystem::set_current_dir(const char *new_dir)
845 strcpy(current_dir, new_dir);
849 int FileSystem::add_end_slash(char *new_dir)
851 if(new_dir[strlen(new_dir) - 1] != '/') strcat(new_dir, "/");
855 char* FileSystem::get_current_dir()
860 int FileSystem::total_files()
862 return dir_list.total;
866 FileItem* FileSystem::get_entry(int entry)
868 return dir_list.values[entry];
872 // collapse ".", "..", "//" eg. x/./..//y = y
873 char *FileSystem::basepath(const char *path)
875 char fpath[BCTEXTLEN];
876 unsigned len = strlen(path);
877 if( len >= sizeof(fpath) ) return 0;
879 char *flat = cstrdup("");
882 char *fn = fpath + len;
883 while( fn > fpath ) {
884 while( --fn >= fpath )
885 if( *fn == '/' ) { *fn = 0; break; }
886 fn = fn < fpath ? fpath : fn+1;
887 if( !*fn || !strcmp(fn, ".") ) continue;
888 if( !strcmp(fn, "..") ) { ++r; continue; }
889 if( r < 0 ) continue;
890 if( r > 0 ) { --r; continue; }
891 char *cp = cstrcat(3, "/",fn,flat);
892 delete [] flat; flat = cp;
896 char *cp = cstrcat(2, ".",flat);
897 delete [] flat; flat = cp;