Ver código fonte

FileOperation: Deduplicate destination file names on copy

When there is a file with the same name in the destination directory,
FileManager overwrites that file without any warning. With this change,
such a file will be automatically renamed to "emoji-2.txt", for example.

Also, currently there is a check in FileManager that makes copy and
paste of a file in the same directory no-op. This change removes that
check, because it is no longer a problem.
Tetsui Ohkubo 4 anos atrás
pai
commit
eb326db028

+ 0 - 5
Userland/Applications/FileManager/main.cpp

@@ -174,11 +174,6 @@ void do_paste(String const& target_directory, GUI::Window* window)
             dbgln("Cannot paste URI {}", uri_as_string);
             continue;
         }
-
-        auto new_path = String::formatted("{}/{}", target_directory, url.basename());
-        if (url.path() == new_path)
-            continue;
-
         source_paths.append(url.path());
     }
 

+ 40 - 1
Userland/Services/FileOperation/main.cpp

@@ -34,6 +34,8 @@ static int perform_delete(Vector<String> const& sources);
 static int execute_work_items(Vector<WorkItem> const& items);
 static void report_error(String message);
 static void report_warning(String message);
+static AK::Result<AK::NonnullRefPtr<Core::File>, AK::OSError> open_destination_file(String const& destination);
+static String deduplicate_destination_file_name(String const& destination);
 
 int main(int argc, char** argv)
 {
@@ -259,7 +261,8 @@ int execute_work_items(Vector<WorkItem> const& items)
                 report_warning(String::formatted("Failed to open {} for reading: {}", source, source_file_or_error.error()));
                 return false;
             }
-            auto destination_file_or_error = Core::File::open(destination, (Core::OpenMode)(Core::OpenMode::WriteOnly | Core::OpenMode::Truncate));
+            // FIXME: When the file already exists, let the user choose the next action instead of renaming it by default.
+            auto destination_file_or_error = open_destination_file(destination);
             if (destination_file_or_error.is_error()) {
                 report_warning(String::formatted("Failed to open {} for write: {}", destination, destination_file_or_error.error()));
                 return false;
@@ -292,6 +295,7 @@ int execute_work_items(Vector<WorkItem> const& items)
 
         case WorkItem::Type::CreateDirectory: {
             outln("MKDIR {}", item.destination);
+            // FIXME: Support deduplication like open_destination_file() when the directory already exists.
             if (mkdir(item.destination.characters(), 0755) < 0 && errno != EEXIST) {
                 auto original_errno = errno;
                 report_error(String::formatted("mkdir: {}", strerror(original_errno)));
@@ -365,3 +369,38 @@ int execute_work_items(Vector<WorkItem> const& items)
     outln("FINISH");
     return 0;
 }
+
+AK::Result<AK::NonnullRefPtr<Core::File>, AK::OSError> open_destination_file(String const& destination)
+{
+    auto destination_file_or_error = Core::File::open(destination, (Core::OpenMode)(Core::OpenMode::WriteOnly | Core::OpenMode::Truncate | Core::OpenMode::MustBeNew));
+    if (destination_file_or_error.is_error() && destination_file_or_error.error().error() == EEXIST) {
+        return open_destination_file(deduplicate_destination_file_name(destination));
+    }
+    return destination_file_or_error;
+}
+
+String deduplicate_destination_file_name(String const& destination)
+{
+    LexicalPath destination_path(destination);
+    auto title_without_counter = destination_path.title();
+    size_t next_counter = 1;
+
+    auto last_hyphen_index = title_without_counter.find_last('-');
+    if (last_hyphen_index.has_value()) {
+        auto counter_string = title_without_counter.substring_view(*last_hyphen_index + 1);
+        auto last_counter = counter_string.to_uint();
+        if (last_counter.has_value()) {
+            next_counter = *last_counter + 1;
+            title_without_counter = title_without_counter.substring_view(0, *last_hyphen_index);
+        }
+    }
+
+    StringBuilder basename;
+    basename.appendff("{}-{}", title_without_counter, next_counter);
+    if (!destination_path.extension().is_empty()) {
+        basename.append(".");
+        basename.append(destination_path.extension());
+    }
+
+    return LexicalPath::join(destination_path.dirname(), basename.to_string()).string();
+}