diff --git a/efiler/fileops.cpp b/efiler/fileops.cpp index ca81fee..a62fbff 100644 --- a/efiler/fileops.cpp +++ b/efiler/fileops.cpp @@ -10,7 +10,7 @@ * See COPYING for details. */ -// This file implements copy / move / delete operation with files +// This file implements various operations with files (copy, delete...) // NOT TO BE CONFUSED WITH edelib::File.h !!! // Functions here usually call stuff from File.h, but also update // efiler interface, display warnings to user etc. @@ -24,7 +24,6 @@ #include #include #include -#include #include #include @@ -37,7 +36,7 @@ #include "EDE_FileView.h" #include "Util.h" #include "ede_ask.h" // replacement for fl_ask -#include "filesystem.h" // is_on_same_fs +#include "filesystem.h" // is_on_same_fs() Fl_Progress* cut_copy_progress; @@ -63,18 +62,23 @@ bool my_isdir(const char* path) { // wrapper around fl_filename_name() that also works with directories const char* my_filename_name(const char* path) { if (!my_isdir(path)) return fl_filename_name(path); + static char buffer[FL_PATH_MAX]; strncpy(buffer, path, FL_PATH_MAX); buffer[strlen(path)-1]='\0'; return fl_filename_name(buffer); } -// opposite to my_filename_name(), returns the first part (a.k.a. directory) +// opposite to fl_filename_name(), returns the first part of path (a.k.a. directory) const char* my_filename_dir(const char* path) { const char* name = my_filename_name(path); + + int pathlen = strlen(path)-strlen(name); + if (pathlen>=FL_PATH_MAX) pathlen=FL_PATH_MAX-1; + static char buffer[FL_PATH_MAX]; - strncpy(buffer, path, strlen(name)); - buffer[strlen(name)]='\0'; + strncpy(buffer, path, pathlen); + buffer[pathlen]='\0'; return buffer; // This is pointer to static buffer!! warning! } @@ -82,7 +86,6 @@ const char* my_filename_dir(const char* path) { // Execute cut or copy operation void do_cut_copy(bool m_copy) { - int num = view->size(); if (m_copy) operation = COPY; else operation = CUT; @@ -96,20 +99,19 @@ void do_cut_copy(bool m_copy) { for (int i=1; i<=num; i++) { view->ungray(i); if (view->selected(i)==1) { - char* t = (char*)view->path(i); - if (strncmp(t+strlen(t)-3, "/..", 3)==0) { + char* p = (char*)view->path(i); + if (strncmp(p+strlen(p)-3, "/..", 3)==0) { // Can't cut/copy the Up button ("..") free(buf); return; } - char* p = (char*)view->path(i); while (strlen(buf)+strlen(p)+8 >= bufsize) { bufsize+=10000; buf = (char*)realloc(buf,sizeof(char)*bufsize); } - strncat(buf, "file://", 7); + strcat(buf, "file://"); strncat(buf,p,strlen(p)); - strncat(buf, "\r\n", 1); + strcat(buf, "\r\n"); if (operation == CUT) view->gray(i); nselected++; } @@ -118,7 +120,9 @@ void do_cut_copy(bool m_copy) { int i=view->get_focus(); char* p = (char*)view->path(i); // an individual path should never be longer > 10000 chars! + strcat(buf, "file://"); strncat(buf,p,strlen(p)); + strcat(buf, "\r\n"); nselected=1; } @@ -136,59 +140,27 @@ void do_cut_copy(bool m_copy) { statusbar->copy_label(tsprintf(_("Selected %d items for copying"), nselected)); else statusbar->copy_label(tsprintf(_("Selected %d items for moving"), nselected)); + // TODO: add total selected size? would require a stat on each file + expanding directories } // Copy single file. Returns true if operation should continue // Note that at this point directories should be expanded into subdirectories etc. -// so when "copying" a directory we actually mean creating a new directory +// so when "copying" a directory we actually mean creating a new directory with same properties bool my_copy(const char* src, const char* dest) { FILE *fold, *fnew; int c; + // this shouldn't happen if (strcmp(src,dest)==0) - // this shouldn't happen return true; - if (edelib::file_exists(dest)) { - // if both src and dest are directories, do nothing - if (my_isdir(src) && my_isdir(dest)) - return true; - - int c = -1; - if (!overwrite_all && !skip_all) { - c = ede_choice_alert(tsprintf(_("File already exists: %s. What to do?"), dest), _("&Overwrite"), _("Over&write all"), _("&Skip"), _("Skip &all"), 0); - } - if (c==1) overwrite_all=true; - if (c==3) skip_all=true; - if (c==2 || skip_all) { - return true; - } - // At this point either c==0 (overwrite) or overwrite_all == true - - // copy directory over file - if (my_isdir(src)) { - int q = ede_choice_alert(tsprintf(_("You're trying to copy directory %s, but there is already a file:\n\t%s\nWhat to do?"),my_filename_name(src),dest), _("&Stop"), _("S&kip directory"), _("&Delete file"), 0); - if (q == 2) { - if (!edelib::file_remove(dest)) { - q = ede_choice_alert(tsprintf(_("Couldn't delete file %s."),my_filename_name(src)), _("&Stop"), _("&Continue"), 0); - } - } - if (q == 0) return false; - else if (q == 1) return true; - } - - // copy file over directory - // TODO: we will just skip this case because it's "impossible", - // but maybe there should be another warning - if (my_isdir(dest)) - return true; - } + // This case is already checked in do_paste() so it shouldn't happen here + if (edelib::file_exists(dest)) + return true; if (my_isdir(src)) { - if (my_isdir(dest)) - return true; // directory already exists, just continue copying // try to preserve permissions // FIXME: this is not entirely cross-platform (due to different headers on *BSD) struct stat buf; @@ -206,18 +178,20 @@ bool my_copy(const char* src, const char* dest) { } // edelib::file_writeable() returns false if dest doesn't exist - if (edelib::file_exists(dest) && !edelib::file_writeable(dest) ) { - int q = ede_choice_alert(tsprintf(_("File\n\t%s\nis not writeable! You probably don't have permission."),dest), _("&Stop"), _("&Continue"), 0); + if (edelib::file_exists(dest) && !edelib::file_writeable(dest)) { + int q = ede_choice_alert(tsprintf(_("You don't have permission to overwrite file\n\t%s"),dest), _("&Stop"), _("&Continue"), 0); if (q == 0) return true; else return false; + // this is redundant ATM cause dest is removed in do_paste() } if (!edelib::dir_exists(my_filename_dir(dest))) { + // Shouldn't happen, unless someone is deleting stuff behind our back int q = ede_choice_alert(tsprintf(_("Directory\n\t%s\ndoesn't exist."), my_filename_dir(dest)), _("&Stop"), _("&Continue"), 0); if (q == 0) return true; else return false; } if (!edelib::file_exists(dest) && !edelib::dir_writeable(my_filename_dir(dest))) { - int q = ede_choice_alert(tsprintf(_("Cannot create file in directory\n\t%s\nYou probably don't have permission."), my_filename_dir(dest)), _("&Stop"), _("&Continue"), 0); + int q = ede_choice_alert(tsprintf(_("Cannot create file in directory\n\t%s\nYou don't have permission."), my_filename_dir(dest)), _("&Stop"), _("&Continue"), 0); if (q == 0) return true; else return false; } @@ -249,24 +223,28 @@ bool my_copy(const char* src, const char* dest) { count=0; Fl::check(); } - if (stop_now) break; // this will leave a half-sized file... + + if (stop_now) { + ede_alert(tsprintf(_("Copying interrupted!\nFile %s is only half-copied and probably broken."), my_filename_name(dest))); + break; + } } - if (ferror(fold)) { - fclose(fold); // don't flush error buffer before time - fclose(fnew); - int q = ede_choice_alert(tsprintf(_("Error while reading file\n\t%s\n%s"), src, strerror(errno)), _("&Stop"), _("&Continue"), 0); - if (q == 0) return false; else return true; - } - if (ferror(fnew)) { - fclose(fold); // don't flush error buffer before time - fclose(fnew); - int q = ede_choice_alert(tsprintf(_("Error while writing file\n\t%s\n%s"), dest, strerror(errno)), _("&Stop"), _("&Continue"), 0); - if (q == 0) return false; else return true; - } + if (ferror(fold) || ferror(fnew)) { + // This is probably a filesystem error (such as ejected disk or bad sector) - fclose(fold); - fclose(fnew); + fclose(fold); // don't flush error buffer before calling ferror + fclose(fnew); + int q; + if (ferror(fold)) + q = ede_choice_alert(tsprintf(_("Error while reading file\n\t%s\n%s"), src, strerror(errno)), _("&Stop"), _("&Continue"), 0); + else + q = ede_choice_alert(tsprintf(_("Error while writing file\n\t%s\n%s"), dest, strerror(errno)), _("&Stop"), _("&Continue"), 0); + if (q == 0) return false; else return true; + } else { + fclose(fold); + fclose(fnew); + } // attempt to preserve permissions - if it fails, we don't care // FIXME: this is not entirely cross-platform (due to different headers on *BSD) @@ -292,10 +270,10 @@ bool expand_dirs(const char* src, char** &list, int &list_size, int &list_capaci list[list_size++] = strdup(src); if (my_isdir(src)) { // fl_filename_list makes sure that directories are appended with / - char new_src[PATH_MAX]; + char new_src[FL_PATH_MAX]; dirent **files; // FIXME: use same sort as used in view - // FIXME: detect errors on accessing folder + // FIXME: detect errors on accessing directory int num_files = fl_filename_list(src, &files, fl_casenumericsort); for (int i=0; id_name,"./")==0 || strcmp(files[i]->d_name,"../")==0) continue; @@ -309,16 +287,6 @@ bool expand_dirs(const char* src, char** &list, int &list_size, int &list_capaci } -// Callback for Stop button on progress window -void stop_copying_cb(Fl_Widget*,void* v) { - stop_now=true; - // Let's inform user that we're stopping... - Fl_Box* caption = (Fl_Box*)v; - caption->label(_("Stopping...")); - caption->redraw(); - caption->parent()->redraw(); -} - // Delete currently selected file(s) and directory(es) void do_delete() { @@ -340,11 +308,11 @@ void do_delete() { // Issue a warning int c; - if (list_size==1 && my_isdir(files_list[0])) { + if (list_size==1 && my_isdir(files_list[0])) c = ede_choice_alert(tsprintf(_("Are you sure that you want to delete directory\n\t%s\nincluding everything in it?"), files_list[0]), _("Do&n't delete"), _("&Delete"), 0); - } else if (list_size==1) { + else if (list_size==1) c = ede_choice_alert(tsprintf(_("Are you sure that you want to delete file %s ?"), my_filename_name(files_list[0])), _("Do&n't delete"), _("&Delete"), 0); - } else + else c = ede_choice_alert(tsprintf(_("Are you sure that you want to delete %d files and directories?"), list_size), _("Do&n't delete"), _("&Delete"), 0); if (c==1) { @@ -352,14 +320,14 @@ void do_delete() { for (int i=0; i=0; i--) if (my_isdir(files_list[i])) if (!edelib::dir_remove(files_list[i])) - ede_alert(tsprintf(_("Couldn't delete directory\n\t%s !\n%s"), files_list[i], strerror(errno))); + ede_alert(tsprintf(_("Couldn't delete directory\n\t%s\n%s"), files_list[i], strerror(errno))); // refresh directory listing - optimized for (int i=1; i<=view->size(); i++) @@ -374,40 +342,51 @@ void do_delete() { } -// Rename the file that has focus to the given name 'c' +// Rename the file that has focus to the given name 'newname' void do_rename(const char* newname) { int focus = view->get_focus(); - edelib::String oldname(fl_filename_name(view->path(focus))); // get filename + const char* oldname = fl_filename_name(view->path(focus)); // get filename - edelib::String oldpath(current_dir); - oldpath += oldname; - edelib::String newpath(current_dir); - newpath += newname; + char oldpath[FL_PATH_MAX], newpath[FL_PATH_MAX]; + snprintf(oldpath, FL_PATH_MAX-1, "%s%s", current_dir, oldname); + snprintf(newpath, FL_PATH_MAX-1, "%s%s", current_dir, newname); - if (edelib::file_exists(newpath.c_str())) + if (edelib::file_exists(newpath)) ede_alert(tsprintf(_("Filename already in use: %s"), newname)); - else if (!edelib::file_rename(oldpath.c_str(),newpath.c_str())) - ede_alert(tsprintf(_("Rename %s to %s failed!"), oldname.c_str(), newname)); + else if (!edelib::file_rename(oldpath,newpath)) + ede_alert(tsprintf(_("Rename %s to %s failed!"), oldname, newname)); else { // Insert new name into vline - // FIXME these methods shouldn't be exported by view + // FIXME text() methods shouldn't be exported by view! edelib::String vline = view->text(focus); vline = newname + vline.substr(vline.find(view->column_char(),0)); view->text(focus,vline.c_str()); - view->update_path(oldpath.c_str(),newpath.c_str()); + view->update_path(oldpath,newpath); } } +// Callback for Stop button on progress window (created in do_paste()) +void stop_copying_cb(Fl_Widget*,void* v) { + stop_now=true; + // Let's inform user that we're stopping... + Fl_Box* caption = (Fl_Box*)v; + caption->label(_("Stopping...")); + caption->redraw(); + caption->parent()->redraw(); +} + + + // Execute paste - this will copy or move files based on chosen operation // FIXME: if user cuts some files, then does dnd from another window, -// do_paste will assume operation=CUT and won't ask the user! -void do_paste(const char* t) { - FileItem **item_list=0; - int item_list_size=0; +// do_paste() will assume operation==CUT and won't ask the user! +// - Konqueror handles this by inventing a different mimetype for non-dnd buffer +// - Nautilus has the same bug! +void do_paste(const char* t) { char *to = (char*)t; if (!t || !fl_filename_isdir(t) || (strncmp(t+strlen(t)-3,"/..",3)==0)) to = current_dir; @@ -416,6 +395,7 @@ fprintf (stderr, "PASTE from '%s', to '%s', type=%d\n",(char*)Fl::event_text(),t if (!strchr(Fl::event_text(), '/')) return; // User is pasting something that isn't files + // TODO: create a text file? // Tokenize event text into an array of strings ("from") char* tmp = (char*)Fl::event_text(); @@ -434,7 +414,7 @@ fprintf (stderr, "PASTE from '%s', to '%s', type=%d\n",(char*)Fl::event_text(),t from[k] = (char*)malloc(sizeof(char) * (len+2)); strncpy(from[k],tmp,len); from[k][len]='\0'; - if (from[k][len-1] == '\r') { len--; from[k][len]='\0'; } + if (from[k][len-1] == '\r') from[k][--len]='\0'; // We accept both URIs (beginning with file://) and plain filename if (strncmp(from[k],"file://",7)==0) for (int i=0; i<=len-7; i++) @@ -498,184 +478,169 @@ fprintf (stderr, "from[%d]='%s'\n", k, from[k]); if (c==1) operation=COPY; else operation=CUT; } + + { // to silence goto + overwrite_all=false; skip_all=false; + stop_now=false; - // Moving files on same filesystem is trivial, just like rename - // We don't even need a progress bar - // OTOH maybe the two branches should be merged, lots of common code? - if (operation == CUT && is_on_same_fs(to, from[0])) { - // This list is used for updating mimetypes - if (to==current_dir) item_list = new FileItem*[count]; + // srcdir is root directory of from[] array + char *srcdir = strdup(my_filename_dir(from[0])); + if (strcmp(srcdir,to)==0) { + // This should never happen cause we already checked it... + ede_alert(_("You cannot copy a file onto itself!")); + free(srcdir); + goto FINISH; + } - for (int i=0; ilabel(_("Copying files")); + else + progress_window->label(_("Moving files")); - // At this point c==0 (Overwrite) or overwrite_all == true - unlink(dest); + progress_window->set_modal(); + progress_window->begin(); + Fl_Box* caption = new Fl_Box(20,20,310,25, _("Counting files in directories")); + caption->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE); + cut_copy_progress = new Fl_Progress(20,60,310,20); + Fl_Button* stop_button = new Fl_Button(145,100,60,40, _("&Stop")); + stop_button->callback(stop_copying_cb,caption); + progress_window->end(); + progress_window->show(); - } else if (to==current_dir) { - // Update interface - add file to list - FileItem *item = new FileItem; - item->name = fl_filename_name(from[i]); - item->realpath = dest; - if (fl_filename_isdir(dest)) { - item->icon = "folder"; - item->description = "Directory"; - // item->name += "/"; - } else { - item->icon = "unknown"; - item->description = "Unknown"; - } - item_list[item_list_size++] = item; - view->add(item); // don't bother with sorting, that would be too complex - } + // Set ProgressBar range + cut_copy_progress->minimum(0); + cut_copy_progress->maximum(count); + cut_copy_progress->value(0); - rename(from[i],dest); - free(dest); - } + // Count files in directories + int list_size = 0, list_capacity = 1000; + char** files_list = (char**)malloc(sizeof(char**)*list_capacity); + for (int i=0; ivalue(i+1); + Fl::check(); // check to see if user pressed Stop + } - // - // Real file moving / copying using recursive algorithm - // + if (stop_now) { // user pressed stop while counting, cleanup and exit + for (int i=0; iminimum(0); + cut_copy_progress->maximum(list_size); + cut_copy_progress->value(0); + char dest[FL_PATH_MAX]; - // Create srcdir string - char *srcdir = strdup(from[0]); - char *p = strrchr(srcdir,'/'); - if (*(p+1) == '\0') { // slash is last character - find one before - *p = '\0'; - p = strrchr(srcdir,'/'); - } - *(p+1) = '\0'; - if (strcmp(srcdir,to)==0) { - // This should never happen cause we already checked it... - ede_alert(_("You cannot copy a file onto itself!")); - free(srcdir); - goto FINISH; - } + // This list is used for updating mimetypes after the copying is finished + FileItem **item_list=0; + int item_list_size=0; + if (strncmp(to,current_dir,strlen(to))==0) + // avoid malloc if current dir isn't inside the scope of paste + item_list = new FileItem*[list_size]; + + + for (int i=0; ilabel(_("Copying files")); + caption->copy_label(tsprintf(_("Copying %d of %d files to %s"), i, list_size, to)); else - progress_window->label(_("Moving files")); + caption->copy_label(tsprintf(_("Moving %d of %d files to %s"), i, list_size, to)); + caption->redraw(); - progress_window->set_modal(); - progress_window->begin(); - Fl_Box* caption = new Fl_Box(20,20,310,25, _("Counting files in directories")); - caption->align(FL_ALIGN_LEFT|FL_ALIGN_INSIDE); - cut_copy_progress = new Fl_Progress(20,60,310,20); - Fl_Button* stop_button = new Fl_Button(145,100,60,40, _("&Stop")); - stop_button->callback(stop_copying_cb,caption); - progress_window->end(); - progress_window->show(); + if (edelib::file_exists(dest)) { - // Set ProgressBar range - cut_copy_progress->minimum(0); - cut_copy_progress->maximum(count); - cut_copy_progress->value(0); + // if both src and dest are directories, do nothing + if (my_isdir(src) && my_isdir(dest)) + continue; - // Count files in directories - int list_size = 0, list_capacity = 1000; - char** files_list = (char**)malloc(sizeof(char**)*list_capacity); - for (int i=0; ivalue(i+1); - Fl::check(); // check to see if user pressed Stop - } + // copy file over directory + // TODO: we will just skip this case because it's "impossible", + // but maybe there should be another warning + if (my_isdir(dest)) + continue; + + // copy directory over file + if (my_isdir(src)) { + int q = ede_choice_alert(tsprintf(_("You're trying to copy directory\n\t%s\nbut there is already a file with this name. What to do?"),dest), _("&Stop"), _("S&kip directory"), _("&Delete file"), 0); + if (q == 0) break; + else if (q == 1) continue; + // else q==2 (delete file) - if (stop_now) { // user pressed stop while counting, cleanup and exit - for (int i=0; iminimum(0); - cut_copy_progress->maximum(list_size); - cut_copy_progress->value(0); - - // This list is used for updating mimetypes - if (to==current_dir) item_list = new FileItem*[list_size]; - - for (int i=0; icopy_label(tsprintf(_("Copying %d of %d files to %s"), i, list_size, to)); - else - caption->copy_label(tsprintf(_("Moving %d of %d files to %s"), i, list_size, to)); - caption->redraw(); - - // Check if it already exists - if (edelib::file_exists(dest)) { + // copy file over file + } else { int c = -1; - if (!overwrite_all && !skip_all) { - // here was choice_alert + if (!overwrite_all && !skip_all) c = ede_choice_alert(tsprintf(_("File with name\n\t%s\nalready exists. What to do?"), dest), _("&Overwrite"), _("Over&write all"), _("&Skip"), _("Skip &all")); - } + if (c==1) overwrite_all=true; if (c==3) skip_all=true; if (c==2 || skip_all) continue; // go to next entry // At this point c==0 (Overwrite) or overwrite_all == true - unlink(dest); - } else if (to==current_dir) { - // Update interface - add file to list - FileItem *item = new FileItem; - item->name = my_filename_name(dest); - item->realpath = dest; - if (my_isdir(dest)) { - item->icon = "folder"; - item->description = "Directory"; - // item->name += "/"; - } else { - item->icon = "unknown"; - item->description = "Unknown"; - } - item_list[item_list_size++] = item; - view->add(item); // don't bother with sorting, that would be too complex } - if (stop_now || !my_copy(files_list[i], dest)) - break; + if (!edelib::file_remove(dest)) { + int q = ede_choice_alert(tsprintf(_("Couldn't remove file\n\t%s"),dest), _("&Stop"), _("&Continue"), 0); + if (q==0) break; + else continue; + } + // Update interface - add file to list (if we are viewing the destination directory) + } else if (strcmp(current_dir,my_filename_dir(dest))==0) { + FileItem *item = new FileItem; + item->name = my_filename_name(dest); + item->realpath = dest; + if (my_isdir(dest)) { + item->icon = "folder"; + item->description = "Directory"; + // item->name += "/"; + } else { + item->icon = "unknown"; + item->description = "Unknown"; + } + item_list[item_list_size++] = item; + view->add(item); // don't bother with sorting, that would be too complex + } + + // Moving on same filesystem is actually rename + if (operation == CUT && is_on_same_fs(to, from[0])) + edelib::file_rename(src,dest); + // this is extremely fast, user won't have time to click on stop ;) + + else { + if (stop_now || !my_copy(src, dest)) + break; // Delete file after moving - if (operation == CUT) unlink(files_list[i]); + if (operation == CUT) edelib::file_remove(src); cut_copy_progress->value(cut_copy_progress->value()+1); Fl::check(); // check to see if user pressed Stop } - progress_window->hide(); + } + progress_window->hide(); - // Cleanup memory - for (int i=0; i0) { view->redraw(); - for (int i=view->size(),j=0; jsize(),j=0; jselect(i,1); } + } // scoping to silence goto + // Cleanup memory and exit FINISH: for (int i=0; i