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(const char *path, const char *name, int is_dir,
47 int64_t size, time_t mtime, int item_no)
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;
56 this->item_no = item_no;
66 if(this->path) delete [] this->path;
67 if(this->name) delete [] this->name;
77 int FileItem::set_path(char *path)
79 if(this->path) delete [] this->path;
80 this->path = new char[strlen(path) + 1];
81 strcpy(this->path, path);
85 int FileItem::set_name(char *name)
87 if(this->name) delete [] this->name;
88 this->name = new char[strlen(name) + 1];
89 strcpy(this->name, name);
95 FileSystem::FileSystem()
98 (void)getcwd(current_dir, BCTEXTLEN);
101 FileSystem::~FileSystem()
106 int FileSystem::reset_parameters()
111 strcpy(current_dir, "");
112 sort_order = SORT_ASCENDING;
113 sort_field = SORT_PATH;
117 int FileSystem::delete_directory()
119 dir_list.remove_all_objects();
123 int FileSystem::set_sort_order(int value)
125 this->sort_order = value;
129 int FileSystem::set_sort_field(int field)
131 this->sort_field = field;
135 // filename.with.dots.extension
137 // extension.dots.with.filename
139 int FileSystem::dot_reverse_filename(char *out, const char *in)
141 int i, i2, j=0, lastdot;
142 lastdot = strlen(in);
143 for ( i=strlen(in); --i >= 0; ) {
152 if (in[++i] != '.') {
153 while (i < lastdot) out[j++] = in[i++];
159 int FileSystem::path_ascending(const void *ptr1, const void *ptr2)
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;
169 int FileSystem::path_descending(const void *ptr1, const void *ptr2)
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;
179 int FileSystem::size_ascending(const void *ptr1, const void *ptr2)
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;
188 int FileSystem::size_descending(const void *ptr1, const void *ptr2)
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;
198 int FileSystem::date_ascending(const void *ptr1, const void *ptr2)
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;
207 int FileSystem::date_descending(const void *ptr1, const void *ptr2)
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;
216 int FileSystem::ext_ascending(const void *ptr1, const void *ptr2)
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);
234 int FileSystem::ext_descending(const void *ptr1, const void *ptr2)
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);
252 int FileSystem::sort_table(ArrayList<FileItem*> *dir_list)
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 },
262 qsort(dir_list->values,
263 dir_list->size(), sizeof(FileItem*),
264 cmpr[sort_field][sort_order]);
269 int FileSystem::combine(ArrayList<FileItem*> *dir_list, ArrayList<FileItem*> *file_list)
273 sort_table(dir_list);
274 for(i = 0; i < dir_list->total; i++)
276 this->dir_list.append(dir_list->values[i]);
279 sort_table(file_list);
280 for(i = 0; i < file_list->total; i++)
282 this->dir_list.append(file_list->values[i]);
287 int FileSystem::test_filter(const char *url, const char *filter)
289 char string[BCTEXTLEN], string2[BCTEXTLEN];
290 const char *filter1 = 0, *filter2 = filter;
291 int done = 0, result = 0, token_number = 0;
293 do { // Get next token
294 filter1 = strchr(filter2, '[');
296 if( filter1 ) { // Get next filter
297 filter2 = strchr(++filter1, ']');
299 int n = filter2 - filter1;
300 for( int i=0; i<n; ++i ) string[i] = filter1[i];
304 strcpy(string, filter1);
310 strcpy(string, filter);
315 if( string[0] ) { // Process the token
316 const char *path = url;
317 const char *subfilter1 = string, *subfilter2 = 0;
322 subfilter2 = strchr(subfilter1, '*');
324 int n = subfilter2 - subfilter1;
325 for( int i=0; i<n; ++i ) string2[i] = subfilter1[i];
329 strcpy(string2, subfilter1);
333 if( string2[0] ) { // Subfilter exists
334 if( subfilter1 > string ) {
335 const char *cp = strstr(path, string2);
337 result = token_done = 1;
339 path = cp + strlen(string2);
341 else { // Subfilter exists
342 if( strncmp(path, string2, strlen(string2)) ) // strncasecmp?
343 result = token_done = 1;
345 path += strlen(string2);
348 // String must terminate after subfilter
351 result = token_done = 1;
354 subfilter1 = subfilter2 + 1;
355 // Let pass if no subfilter
356 } while( !token_done && !result );
359 } while( !done && result );
364 int FileSystem::test_filter(FileItem *file)
366 // Don't filter directories
367 if( file->is_dir ) return 0;
368 if( !file->name ) return 1;
369 return test_filter(file->name, this->filter);
372 int FileSystem::scan_directory(const char *new_dir)
375 strcpy(current_dir, new_dir);
376 DIR *dirstream = opendir(current_dir);
377 if( !dirstream ) return 1; // failed to open directory
379 struct dirent64 *new_filename;
380 while( (new_filename = readdir64(dirstream)) != 0 ) {
381 FileItem *new_file = 0;
382 int include_this = 1;
384 // File is directory heirarchy
385 if(!strcmp(new_filename->d_name, ".") ||
386 !strcmp(new_filename->d_name, "..")) include_this = 0;
388 // File is hidden and we don't want all files
391 new_filename->d_name[0] == '.') include_this = 0;
396 new_file = new FileItem;
397 char full_path[BCTEXTLEN], name_only[BCTEXTLEN];
398 sprintf(full_path, "%s", current_dir);
399 add_end_slash(full_path);
400 strcat(full_path, new_filename->d_name);
401 strcpy(name_only, new_filename->d_name);
402 new_file->set_path(full_path);
403 new_file->set_name(name_only);
405 // Get information about the file.
407 if(!stat(full_path, &ostat))
409 new_file->size = ostat.st_size;
410 new_file->mtime = ostat.st_mtime;
412 if(S_ISDIR(ostat.st_mode))
414 strcat(name_only, "/"); // is a directory
415 new_file->is_dir = 1;
418 // File is excluded from filter
419 if(include_this && test_filter(new_file)) include_this = 0;
420 //printf("FileSystem::update 3 %d %d\n", include_this, test_filter(new_file));
422 // File is not a directory and we just want directories
423 if(include_this && want_directory && !new_file->is_dir) include_this = 0;
427 printf("FileSystem::update %s %s\n", full_path, strerror(errno));
433 dir_list.append(new_file);
442 int FileSystem::update(const char *new_dir)
445 int result = scan_directory(new_dir);
446 // combine the directories and files in the master list
447 return !result ? update_sort() : result;
450 int FileSystem::update_sort()
452 ArrayList<FileItem*> directories, files;
453 for( int i=0; i< dir_list.size(); ++i ) {
454 FileItem *item = dir_list[i];
456 (item->is_dir ? &directories : &files)->append(item);
458 dir_list.remove_all();
459 // combine the directories and files in the master list
460 combine(&directories, &files);
466 int FileSystem::set_filter(const char *new_filter)
468 strcpy(filter, new_filter);
472 int FileSystem::set_show_all()
478 int FileSystem::set_want_directory()
484 int FileSystem::is_dir(const char *path) // return 0 if the text is a directory
486 if(!strlen(path)) return 0;
488 char new_dir[BCTEXTLEN];
489 struct stat ostat; // entire name is a directory
491 strcpy(new_dir, path);
492 complete_path(new_dir);
493 if(!stat(new_dir, &ostat) && S_ISDIR(ostat.st_mode))
499 int FileSystem::create_dir(const char *new_dir_)
501 char new_dir[BCTEXTLEN];
502 strcpy(new_dir, new_dir_);
503 complete_path(new_dir);
504 return mkdir(new_dir, S_IRWXU | S_IRWXG | S_IRWXO);
507 int FileSystem::parse_tildas(char *new_dir)
509 if(new_dir[0] == 0) return 1;
511 // Our home directory
512 if(new_dir[0] == '~')
515 if(new_dir[1] == '/' || new_dir[1] == 0)
517 // user's home directory
519 char string[BCTEXTLEN];
520 home = getenv("HOME");
522 // print starting after tilda
523 if(home) sprintf(string, "%s%s", home, &new_dir[1]);
524 strcpy(new_dir, string);
528 // Another user's home directory
530 char string[BCTEXTLEN], new_user[BCTEXTLEN];
534 for(i = 1, j = 0; new_dir[i] != 0 && new_dir[i] != '/'; i++, j++)
536 new_user[j] = new_dir[i];
541 while( (pw = getpwent()) != 0 )
544 if(!strcmp(pw->pw_name, new_user))
546 // print starting after tilda
547 sprintf(string, "%s%s", pw->pw_dir, &new_dir[i]);
548 strcpy(new_dir, string);
559 int FileSystem::parse_directories(char *new_dir)
561 //printf("FileSystem::parse_directories 1 %s\n", new_dir);
562 if( *new_dir != '/' && current_dir[0] ) { // expand to abs path
563 char string[BCTEXTLEN];
564 strcpy(string, current_dir);
565 add_end_slash(string);
566 strcat(string, new_dir);
567 strcpy(new_dir, string);
572 int FileSystem::parse_dots(char *new_dir)
574 // recursively remove ..s
579 len = strlen(new_dir);
581 for(i = 0, j = 1; !changed && j < len; i++, j++)
584 if(new_dir[i] == '.' && new_dir[j] == '.')
586 // Ignore if character after .. doesn't qualify
588 new_dir[j + 1] != ' ' &&
589 new_dir[j + 1] != '/')
592 // Ignore if character before .. doesn't qualify
594 new_dir[i - 1] != '/') continue;
597 while(new_dir[i] != '/' && i > 0)
599 // look for first / before ..
603 // find / before this /
605 while(new_dir[i] != '/' && i > 0)
607 // look for first / before first / before ..
611 // i now equals /first filename before ..
612 // look for first / after ..
613 while(new_dir[j] != '/' && j < len)
618 // j now equals /first filename after ..
621 new_dir[i++] = new_dir[j++];
625 // default to root directory
626 if((new_dir[0]) == 0) sprintf(new_dir, "/");
634 int FileSystem::complete_path(char *filename)
636 //printf("FileSystem::complete_path 1\n");
637 if(!strlen(filename)) return 1;
638 //printf("FileSystem::complete_path 1\n");
639 parse_tildas(filename);
640 //printf("FileSystem::complete_path 1\n");
641 parse_directories(filename);
642 //printf("FileSystem::complete_path 1\n");
643 parse_dots(filename);
644 // don't add end slash since this requires checking if dir
645 //printf("FileSystem::complete_path 2\n");
649 int FileSystem::extract_dir(char *out, const char *in)
654 // complete string is not directory
659 for(i = strlen(out); i > 0 && out[i - 1] != '/'; i--)
663 if(i >= 0) out[i] = 0;
668 int FileSystem::extract_name(char *out, const char *in, int test_dir)
672 if(test_dir && is_dir(in))
673 out[0] = 0; // complete string is directory
676 for(i = strlen(in)-1; i > 0 && in[i] != '/'; i--)
680 if(in[i] == '/') i++;
686 int FileSystem::join_names(char *out, const char *dir_in, const char *name_in)
689 int len = strlen(out);
693 if(len == 0 || out[len] != 0) result = 1; else len--;
697 if(out[len] != '/') strcat(out, "/");
700 strcat(out, name_in);
704 int64_t FileSystem::get_date(const char *filename)
706 struct stat file_status;
707 bzero(&file_status, sizeof(struct stat));
708 int result = stat(filename, &file_status);
709 return !result ? file_status.st_mtime : -1;
712 void FileSystem::set_date(const char *path, int64_t value)
714 struct utimbuf new_time;
715 new_time.actime = value;
716 new_time.modtime = value;
717 utime(path, &new_time);
720 int64_t FileSystem::get_size(char *filename)
722 struct stat file_status;
723 bzero(&file_status, sizeof(struct stat));
724 int result = stat(filename, &file_status);
725 return !result ? file_status.st_size : -1;
728 int FileSystem::change_dir(const char *new_dir, int update)
730 char new_dir_full[BCTEXTLEN];
732 strcpy(new_dir_full, new_dir);
734 complete_path(new_dir_full);
736 if(strcmp(new_dir_full, "/") &&
737 new_dir_full[strlen(new_dir_full) - 1] == '/')
738 new_dir_full[strlen(new_dir_full) - 1] = 0;
741 this->update(new_dir_full);
744 strcpy(current_dir, new_dir_full);
749 int FileSystem::set_current_dir(const char *new_dir)
751 strcpy(current_dir, new_dir);
755 int FileSystem::add_end_slash(char *new_dir)
757 if(new_dir[strlen(new_dir) - 1] != '/') strcat(new_dir, "/");
762 // collapse ".", "..", "//" eg. x/./..//y = y
763 char *FileSystem::basepath(const char *path)
765 char fpath[BCTEXTLEN];
766 unsigned len = strlen(path);
767 if( len >= sizeof(fpath) ) return 0;
769 char *flat = cstrdup("");
772 char *fn = fpath + len;
773 while( fn > fpath ) {
774 while( --fn >= fpath )
775 if( *fn == '/' ) { *fn = 0; break; }
776 fn = fn < fpath ? fpath : fn+1;
777 if( !*fn || !strcmp(fn, ".") ) continue;
778 if( !strcmp(fn, "..") ) { ++r; continue; }
779 if( r < 0 ) continue;
780 if( r > 0 ) { --r; continue; }
781 char *cp = cstrcat(3, "/",fn,flat);
782 delete [] flat; flat = cp;
786 char *cp = cstrcat(2, ".",flat);
787 delete [] flat; flat = cp;