diff --git a/Userland/Applications/FileManager/main.cpp b/Userland/Applications/FileManager/main.cpp index 8f6ae2c1b2..7dbab9afb7 100644 --- a/Userland/Applications/FileManager/main.cpp +++ b/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()); } diff --git a/Userland/Services/FileOperation/main.cpp b/Userland/Services/FileOperation/main.cpp index 91f9856057..a4da956002 100644 --- a/Userland/Services/FileOperation/main.cpp +++ b/Userland/Services/FileOperation/main.cpp @@ -34,6 +34,8 @@ static int perform_delete(Vector const& sources); static int execute_work_items(Vector const& items); static void report_error(String message); static void report_warning(String message); +static AK::Result, 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 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 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 const& items) outln("FINISH"); return 0; } + +AK::Result, 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(); +}