Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / guicast / filesystem.C
diff --git a/cinelerra-5.1/guicast/filesystem.C b/cinelerra-5.1/guicast/filesystem.C
new file mode 100644 (file)
index 0000000..0f03530
--- /dev/null
@@ -0,0 +1,901 @@
+/*
+ * CINELERRA
+ * Copyright (C) 2010 Adam Williams <broadcast at earthling dot net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#include "bcsignals.h"
+#include "cstrdup.h"
+#include <dirent.h>
+#include <errno.h>
+#include <pwd.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+
+#include "filesystem.h"
+
+FileItem::FileItem()
+{
+       path = 0;
+       name = 0;
+       reset();
+}
+
+FileItem::FileItem(char *path,
+       char *name,
+       int is_dir,
+       int64_t size,
+       int month,
+       int day,
+       int year,
+       int64_t calendar_time)
+{
+       this->path = new char[strlen(path)];
+       this->name = new char[strlen(name)];
+       if(this->path) strcpy(this->path, path);
+       if(this->name) strcpy(this->name, name);
+       this->is_dir = is_dir;
+       this->size = size;
+       this->month = month;
+       this->day = day;
+       this->year = year;
+       this->calendar_time = calendar_time;
+}
+
+FileItem::~FileItem()
+{
+       reset();
+}
+
+int FileItem::reset()
+{
+       if(this->path) delete [] this->path;
+       if(this->name) delete [] this->name;
+       path = 0;
+       name = 0;
+       is_dir = 0;
+       size = 0;
+       month = 0;
+       day = 0;
+       year = 0;
+       calendar_time = 0;
+       return 0;
+}
+
+int FileItem::set_path(char *path)
+{
+       if(this->path) delete [] this->path;
+       this->path = new char[strlen(path) + 1];
+       strcpy(this->path, path);
+       return 0;
+}
+
+int FileItem::set_name(char *name)
+{
+       if(this->name) delete [] this->name;
+       this->name = new char[strlen(name) + 1];
+       strcpy(this->name, name);
+       return 0;
+}
+
+const char* FileItem::get_path()
+{
+       return path;
+}
+
+const char* FileItem::get_name()
+{
+       return name;
+}
+
+int FileItem::get_is_dir()
+{
+       return is_dir;
+}
+
+
+
+FileSystem::FileSystem()
+{
+       reset_parameters();
+       (void)getcwd(current_dir, BCTEXTLEN);
+}
+
+FileSystem::~FileSystem()
+{
+       delete_directory();
+}
+
+int FileSystem::reset_parameters()
+{
+       show_all_files = 0;
+       want_directory = 0;
+       strcpy(filter, "");
+       strcpy(current_dir, "");
+       sort_order = SORT_ASCENDING;
+       sort_field = SORT_PATH;
+       return 0;
+}
+
+int FileSystem::delete_directory()
+{
+       for(int i = 0; i < dir_list.total; i++)
+       {
+               delete dir_list.values[i];
+       }
+       dir_list.remove_all();
+       return 0;
+}
+
+int FileSystem::set_sort_order(int value)
+{
+       this->sort_order = value;
+       return 0;
+}
+
+int FileSystem::set_sort_field(int field)
+{
+       this->sort_field = field;
+       return 0;
+}
+
+// filename.with.dots.extension
+//   becomes
+// extension.dots.with.filename
+
+int FileSystem::dot_reverse_filename(char *out, const char *in)
+{
+       int i, i2, j=0, lastdot;
+       lastdot = strlen(in);
+       for ( i=strlen(in); --i >= 0; ) {
+               if (in[i] == '.') {
+                       i2 = i+1;
+                               while (i2 < lastdot)
+                                       out[j++] = in[i2++];
+                       out[j++] = in[i];
+                       lastdot = i;
+               }
+       }
+       if (in[++i] != '.') {
+               while (i < lastdot) out[j++] = in[i++];
+       }
+       out[j++] = '\0';
+       return 0;
+}
+
+int FileSystem::path_ascending(const void *ptr1, const void *ptr2)
+{
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+//printf("path_ascending %p %p\n", ptr1, ptr2);
+       return strcasecmp(item1->name, item2->name);
+}
+
+int FileSystem::path_descending(const void *ptr1, const void *ptr2)
+{
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+       return strcasecmp(item2->name, item1->name);
+}
+
+
+int FileSystem::size_ascending(const void *ptr1, const void *ptr2)
+{
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+       return item1->size >= item2->size;
+}
+
+int FileSystem::size_descending(const void *ptr1, const void *ptr2)
+{
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+       return item1->size <= item2->size;
+}
+
+
+int FileSystem::date_ascending(const void *ptr1, const void *ptr2)
+{
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+       return item1->calendar_time >= item2->calendar_time;
+}
+
+int FileSystem::date_descending(const void *ptr1, const void *ptr2)
+{
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+       return item1->calendar_time <= item2->calendar_time;
+}
+
+int FileSystem::ext_ascending(const void *ptr1, const void *ptr2)
+{
+       char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+       dot_reverse_filename(dotreversedname1,item1->name);
+       dot_reverse_filename(dotreversedname2,item2->name);
+       return strcasecmp(dotreversedname1, dotreversedname2);
+}
+
+int FileSystem::ext_descending(const void *ptr1, const void *ptr2)
+{
+       char dotreversedname1[BCTEXTLEN], dotreversedname2[BCTEXTLEN];
+       FileItem *item1 = *(FileItem**)ptr1;
+       FileItem *item2 = *(FileItem**)ptr2;
+       dot_reverse_filename(dotreversedname1,item1->name);
+       dot_reverse_filename(dotreversedname2,item2->name);
+       return strcasecmp(dotreversedname2, dotreversedname1);
+}
+
+int FileSystem::sort_table(ArrayList<FileItem*> *dir_list)
+{
+#define SORT_MACRO(compare) \
+       qsort(dir_list->values, dir_list->size(), sizeof(FileItem*), compare);
+
+       if(!dir_list || !dir_list->size()) return 0;
+
+//printf("FileSystem::sort_table %p\n", dir_list->values);
+       switch(sort_field)
+       {
+               case SORT_PATH:
+                       if(sort_order == SORT_ASCENDING)
+                               SORT_MACRO(path_ascending)
+                       else
+                               SORT_MACRO(path_descending)
+                       break;
+               case SORT_SIZE:
+                       if(sort_order == SORT_ASCENDING)
+                               SORT_MACRO(size_ascending)
+                       else
+                               SORT_MACRO(size_descending)
+                       break;
+               case SORT_DATE:
+                       if(sort_order == SORT_ASCENDING)
+                               SORT_MACRO(date_ascending)
+                       else
+                               SORT_MACRO(date_descending)
+                       break;
+               case SORT_EXTENSION:
+                       if(sort_order == SORT_ASCENDING)
+                               SORT_MACRO(ext_ascending)
+                       else
+                               SORT_MACRO(ext_descending)
+                       break;
+       }
+
+       return 0;
+}
+
+int FileSystem::combine(ArrayList<FileItem*> *dir_list, ArrayList<FileItem*> *file_list)
+{
+       int i;
+
+       sort_table(dir_list);
+       for(i = 0; i < dir_list->total; i++)
+       {
+               this->dir_list.append(dir_list->values[i]);
+       }
+
+       sort_table(file_list);
+       for(i = 0; i < file_list->total; i++)
+       {
+               this->dir_list.append(file_list->values[i]);
+       }
+       return 0;
+}
+
+void FileSystem::alphabetize()
+{
+       sort_table(&dir_list);
+}
+
+int FileSystem::is_root_dir(char *path)
+{
+       if(!strcmp(current_dir, "/")) return 1;
+       return 0;
+}
+
+int FileSystem::test_filter(FileItem *file)
+{
+       char *filter1 = 0, *filter2 = filter, *subfilter1, *subfilter2;
+       int result = 0;
+       int done = 0, token_done;
+       int token_number = 0;
+
+// Don't filter directories
+       if(file->is_dir) return 0;
+
+// Empty filename string
+       if(!file->name) return 1;
+
+       do
+       {
+// Get next token
+               filter1 = strchr(filter2, '[');
+               string[0] = 0;
+
+// Get next filter
+               if(filter1)
+               {
+                       filter1++;
+                       filter2 = strchr(filter1, ']');
+
+                       if(filter2)
+                       {
+                               int i;
+                               for(i = 0; filter1 + i < filter2; i++)
+                                       string[i] = filter1[i];
+                               string[i] = 0;
+                       }
+                       else
+                       {
+                               strcpy(string, filter1);
+                               done = 1;
+                       }
+               }
+               else
+               {
+                       if(!token_number)
+                               strcpy(string, filter);
+                       else
+                               done = 1;
+               }
+
+// Process the token
+               if(string[0] != 0)
+               {
+                       char *path = file->name;
+                       subfilter1 = string;
+                       token_done = 0;
+                       result = 0;
+
+                       do
+                       {
+                               string2[0] = 0;
+                               subfilter2 = strchr(subfilter1, '*');
+
+                               if(subfilter2)
+                               {
+                                       int i;
+                                       for(i = 0; subfilter1 + i < subfilter2; i++)
+                                               string2[i] = subfilter1[i];
+
+                                       string2[i] = 0;
+                               }
+                               else
+                               {
+                                       strcpy(string2, subfilter1);
+                                       token_done = 1;
+                               }
+
+                               if(string2[0] != 0)
+                               {
+// Subfilter must exist at some later point in the string
+                                       if(subfilter1 > string)
+                                       {
+                                               if(!strstr(path, string2))
+                                               {
+                                                       result = 1;
+                                                       token_done = 1;
+                                               }
+                                               else
+                                               path = strstr(path, string2) + strlen(string2);
+                                       }
+                                       else
+// Subfilter must exist at this point in the string
+                                       {
+                                               if(strncmp(path, string2, strlen(string2)))
+//                                             if(strncasecmp(path, string2, strlen(string2)))
+                                               {
+                                                       result = 1;
+                                                       token_done = 1;
+                                               }
+                                               else
+                                               path += strlen(string2);
+                                       }
+
+// String must terminate after subfilter
+                                       if(!subfilter2)
+                                       {
+                                               if(*path != 0)
+                                               {
+                                                       result = 1;
+                                                       token_done = 1;
+                                               }
+                                       }
+                               }
+                               subfilter1 = subfilter2 + 1;
+// Let pass if no subfilter
+                       }while(!token_done && !result);
+               }
+               token_number++;
+       }while(!done && result);
+
+       return result;
+}
+
+
+int FileSystem::update(const char *new_dir)
+{
+       DIR *dirstream;
+       struct dirent64 *new_filename;
+       struct stat ostat;
+       struct tm *mod_time;
+       int include_this;
+       FileItem *new_file;
+       char full_path[BCTEXTLEN], name_only[BCTEXTLEN];
+       ArrayList<FileItem*>directories;
+       ArrayList<FileItem*>files;
+       int result = 0;
+
+       delete_directory();
+       if(new_dir != 0) strcpy(current_dir, new_dir);
+       dirstream = opendir(current_dir);
+       if(!dirstream) return 1;          // failed to open directory
+
+       while( (new_filename = readdir64(dirstream)) != 0 )
+       {
+               include_this = 1;
+
+// File is directory heirarchy
+               if(!strcmp(new_filename->d_name, ".") ||
+                       !strcmp(new_filename->d_name, "..")) include_this = 0;
+
+// File is hidden and we don't want all files
+               if(include_this &&
+                       !show_all_files &&
+                       new_filename->d_name[0] == '.') include_this = 0;
+
+// file not hidden
+               if(include_this)
+       {
+                       new_file = new FileItem;
+                       sprintf(full_path, "%s", current_dir);
+                       if(!is_root_dir(current_dir)) strcat(full_path, "/");
+                       strcat(full_path, new_filename->d_name);
+                       strcpy(name_only, new_filename->d_name);
+                       new_file->set_path(full_path);
+                       new_file->set_name(name_only);
+
+// Get information about the file.
+                       if(!stat(full_path, &ostat))
+                       {
+                               new_file->size = ostat.st_size;
+                               mod_time = localtime(&(ostat.st_mtime));
+                               new_file->month = mod_time->tm_mon + 1;
+                               new_file->day = mod_time->tm_mday;
+                               new_file->year = mod_time->tm_year + 1900;
+                               new_file->calendar_time = ostat.st_mtime;
+
+                               if(S_ISDIR(ostat.st_mode))
+                               {
+                                       strcat(name_only, "/"); // is a directory
+                                       new_file->is_dir = 1;
+                               }
+
+// File is excluded from filter
+                               if(include_this && test_filter(new_file)) include_this = 0;
+//printf("FileSystem::update 3 %d %d\n", include_this, test_filter(new_file));
+
+// File is not a directory and we just want directories
+                               if(include_this && want_directory && !new_file->is_dir) include_this = 0;
+                       }
+                       else
+                       {
+                               printf("FileSystem::update %s %s\n", full_path, strerror(errno));
+                               include_this = 0;
+                       }
+
+// add to list
+                       if(include_this)
+                       {
+                               if(new_file->is_dir) directories.append(new_file);
+                               else files.append(new_file);
+                       }
+                       else
+                               delete new_file;
+               }
+       }
+//printf("FileSystem::update %d\n", __LINE__);
+
+       closedir(dirstream);
+// combine the directories and files in the master list
+       combine(&directories, &files);
+// remove pointers
+       directories.remove_all();
+       files.remove_all();
+//printf("FileSystem::update %d\n", __LINE__);
+
+       return result;
+// success
+}
+
+int FileSystem::set_filter(const char *new_filter)
+{
+       strcpy(filter, new_filter);
+       return 0;
+}
+
+int FileSystem::set_show_all()
+{
+       show_all_files = 1;
+       return 0;
+}
+
+int FileSystem::set_want_directory()
+{
+       want_directory = 1;
+       return 0;
+}
+
+int FileSystem::is_dir(const char *path)      // return 0 if the text is a directory
+{
+       if(!strlen(path)) return 0;
+
+       char new_dir[BCTEXTLEN];
+       struct stat ostat;    // entire name is a directory
+
+       strcpy(new_dir, path);
+       complete_path(new_dir);
+       if(!stat(new_dir, &ostat) && S_ISDIR(ostat.st_mode))
+               return 1;
+       else
+               return 0;
+}
+
+int FileSystem::create_dir(const char *new_dir_)
+{
+       char new_dir[BCTEXTLEN];
+       strcpy(new_dir, new_dir_);
+       complete_path(new_dir);
+
+       mkdir(new_dir, S_IREAD | S_IWRITE | S_IEXEC);
+       return 0;
+}
+
+int FileSystem::parse_tildas(char *new_dir)
+{
+       if(new_dir[0] == 0) return 1;
+
+// Our home directory
+       if(new_dir[0] == '~')
+       {
+
+               if(new_dir[1] == '/' || new_dir[1] == 0)
+               {
+// user's home directory
+                       char *home;
+                       char string[BCTEXTLEN];
+                       home = getenv("HOME");
+
+// print starting after tilda
+                       if(home) sprintf(string, "%s%s", home, &new_dir[1]);
+                       strcpy(new_dir, string);
+                       return 0;
+               }
+               else
+// Another user's home directory
+               {
+                       char string[BCTEXTLEN], new_user[BCTEXTLEN];
+                       struct passwd *pw;
+                       int i, j;
+
+                       for(i = 1, j = 0; new_dir[i] != 0 && new_dir[i] != '/'; i++, j++)
+                       {                // copy user name
+                               new_user[j] = new_dir[i];
+                       }
+                       new_user[j] = 0;
+
+                       setpwent();
+                       while( (pw = getpwent()) != 0 )
+                       {
+// get info for user
+                               if(!strcmp(pw->pw_name, new_user))
+                               {
+// print starting after tilda
+                               sprintf(string, "%s%s", pw->pw_dir, &new_dir[i]);
+                               strcpy(new_dir, string);
+                               break;
+                       }
+                       }
+                       endpwent();
+                       return 0;
+               }
+       }
+       return 0;
+}
+
+int FileSystem::parse_directories(char *new_dir)
+{
+//printf("FileSystem::parse_directories 1 %s\n", new_dir);
+       if(new_dir[0] != '/')
+       {
+// extend path completely
+               char string[BCTEXTLEN];
+//printf("FileSystem::parse_directories 2 %s\n", current_dir);
+               if(!strlen(current_dir))
+               {
+// no current directory
+                       strcpy(string, new_dir);
+               }
+               else
+               if(!is_root_dir(current_dir))
+               {
+// current directory is not root
+                       if(current_dir[strlen(current_dir) - 1] == '/')
+// current_dir already has ending /
+                       sprintf(string, "%s%s", current_dir, new_dir);
+                       else
+// need ending /
+                       sprintf(string, "%s/%s", current_dir, new_dir);
+               }
+               else
+                       sprintf(string, "%s%s", current_dir, new_dir);
+
+//printf("FileSystem::parse_directories 3 %s %s\n", new_dir, string);
+               strcpy(new_dir, string);
+//printf("FileSystem::parse_directories 4\n");
+       }
+       return 0;
+}
+
+int FileSystem::parse_dots(char *new_dir)
+{
+// recursively remove ..s
+       int changed = 1;
+       while(changed)
+       {
+               int i, j, len;
+               len = strlen(new_dir);
+               changed = 0;
+               for(i = 0, j = 1; !changed && j < len; i++, j++)
+               {
+// Got 2 dots
+                       if(new_dir[i] == '.' && new_dir[j] == '.')
+                       {
+// Ignore if character after .. doesn't qualify
+                               if(j + 1 < len &&
+                                       new_dir[j + 1] != ' ' &&
+                                       new_dir[j + 1] != '/')
+                                       continue;
+
+// Ignore if character before .. doesn't qualify
+                               if(i > 0 &&
+                                       new_dir[i - 1] != '/') continue;
+
+                               changed = 1;
+                               while(new_dir[i] != '/' && i > 0)
+                               {
+// look for first / before ..
+                                       i--;
+                               }
+
+// find / before this /
+                               if(i > 0) i--;
+                               while(new_dir[i] != '/' && i > 0)
+                               {
+// look for first / before first / before ..
+                                       i--;
+                               }
+
+// i now equals /first filename before ..
+// look for first / after ..
+                               while(new_dir[j] != '/' && j < len)
+                               {
+                                       j++;
+                               }
+
+// j now equals /first filename after ..
+                               while(j < len)
+                               {
+                                       new_dir[i++] = new_dir[j++];
+                               }
+
+                               new_dir[i] = 0;
+// default to root directory
+                               if((new_dir[0]) == 0) sprintf(new_dir, "/");
+                               break;
+                       }
+               }
+       }
+       return 0;
+}
+
+int FileSystem::complete_path(char *filename)
+{
+//printf("FileSystem::complete_path 1\n");
+       if(!strlen(filename)) return 1;
+//printf("FileSystem::complete_path 1\n");
+       parse_tildas(filename);
+//printf("FileSystem::complete_path 1\n");
+       parse_directories(filename);
+//printf("FileSystem::complete_path 1\n");
+       parse_dots(filename);
+// don't add end slash since this requires checking if dir
+//printf("FileSystem::complete_path 2\n");
+       return 0;
+}
+
+int FileSystem::extract_dir(char *out, const char *in)
+{
+       strcpy(out, in);
+       if(!is_dir(in))
+       {
+// complete string is not directory
+               int i;
+
+               complete_path(out);
+
+               for(i = strlen(out); i > 0 && out[i - 1] != '/'; i--)
+               {
+                       ;
+               }
+               if(i >= 0) out[i] = 0;
+       }
+       return 0;
+}
+
+int FileSystem::extract_name(char *out, const char *in, int test_dir)
+{
+       int i;
+
+       if(test_dir && is_dir(in))
+               out[0] = 0; // complete string is directory
+       else
+       {
+               for(i = strlen(in)-1; i > 0 && in[i] != '/'; i--)
+               {
+                       ;
+               }
+               if(in[i] == '/') i++;
+               strcpy(out, &in[i]);
+       }
+       return 0;
+}
+
+int FileSystem::join_names(char *out, const char *dir_in, const char *name_in)
+{
+       strcpy(out, dir_in);
+       int len = strlen(out);
+       int result = 0;
+
+       while(!result)
+               if(len == 0 || out[len] != 0) result = 1; else len--;
+
+       if(len != 0)
+       {
+               if(out[len] != '/') strcat(out, "/");
+       }
+
+       strcat(out, name_in);
+       return 0;
+}
+
+int64_t FileSystem::get_date(const char *filename)
+{
+       struct stat file_status;
+       bzero(&file_status, sizeof(struct stat));
+       int result = stat(filename, &file_status);
+       return !result ? file_status.st_mtime : -1;
+}
+
+void FileSystem::set_date(const char *path, int64_t value)
+{
+       struct utimbuf new_time;
+       new_time.actime = value;
+       new_time.modtime = value;
+       utime(path, &new_time);
+}
+
+int64_t FileSystem::get_size(char *filename)
+{
+       struct stat file_status;
+       bzero(&file_status, sizeof(struct stat));
+       int result = stat(filename, &file_status);
+       return !result ? file_status.st_size : -1;
+}
+
+int FileSystem::change_dir(const char *new_dir, int update)
+{
+       char new_dir_full[BCTEXTLEN];
+
+       strcpy(new_dir_full, new_dir);
+
+       complete_path(new_dir_full);
+// cut ending slash
+       if(strcmp(new_dir_full, "/") &&
+               new_dir_full[strlen(new_dir_full) - 1] == '/')
+               new_dir_full[strlen(new_dir_full) - 1] = 0;
+
+       if(update)
+               this->update(new_dir_full);
+       else
+       {
+               strcpy(current_dir, new_dir_full);
+       }
+       return 0;
+}
+
+int FileSystem::set_current_dir(const char *new_dir)
+{
+       strcpy(current_dir, new_dir);
+       return 0;
+}
+
+int FileSystem::add_end_slash(char *new_dir)
+{
+       if(new_dir[strlen(new_dir) - 1] != '/') strcat(new_dir, "/");
+       return 0;
+}
+
+char* FileSystem::get_current_dir()
+{
+       return current_dir;
+}
+
+int FileSystem::total_files()
+{
+       return dir_list.total;
+}
+
+
+FileItem* FileSystem::get_entry(int entry)
+{
+       return dir_list.values[entry];
+}
+
+
+// collapse ".",  "..", "//"  eg. x/./..//y = y
+char *FileSystem::basepath(const char *path)
+{
+       char fpath[BCTEXTLEN];
+       unsigned len = strlen(path);
+       if( len >= sizeof(fpath) ) return 0;
+       strcpy(fpath, path);
+       char *flat = cstrdup("");
+
+       int r = 0;
+       char *fn = fpath + len;
+       while( fn > fpath ) {
+               while( --fn >= fpath )
+                       if( *fn == '/' ) { *fn = 0;  break; }
+               fn = fn < fpath ? fpath : fn+1;
+               if( !*fn || !strcmp(fn, ".") ) continue;
+               if( !strcmp(fn, "..") ) { ++r; continue; }
+               if( r < 0 ) continue;
+               if( r > 0 ) { --r;  continue; }
+               char *cp = cstrcat(3, "/",fn,flat);
+               delete [] flat;  flat = cp;
+       }
+
+       if( *path != '/' ) {
+               char *cp = cstrcat(2, ".",flat);
+               delete [] flat;  flat = cp;
+       }
+
+       return flat;
+}
+