diff --git a/Userland/Applications/FileManager/FileUtils.cpp b/Userland/Applications/FileManager/FileUtils.cpp index fcf250a64b..3ccce5fa4f 100644 --- a/Userland/Applications/FileManager/FileUtils.cpp +++ b/Userland/Applications/FileManager/FileUtils.cpp @@ -71,7 +71,7 @@ void delete_paths(Vector const& paths, bool should_confirm, GUI::Window* } } -void run_file_operation([[maybe_unused]] FileOperation operation, Vector const& sources, String const& destination, GUI::Window* parent_window) +void run_file_operation(FileOperation operation, Vector const& sources, String const& destination, GUI::Window* parent_window) { int pipe_fds[2]; if (pipe(pipe_fds) < 0) { @@ -97,7 +97,17 @@ void run_file_operation([[maybe_unused]] FileOperation operation, Vector Vector file_operation_args; file_operation_args.append("/bin/FileOperation"); - file_operation_args.append("Copy"); + + switch (operation) { + case FileOperation::Copy: + file_operation_args.append("Copy"); + break; + case FileOperation::Cut: + file_operation_args.append("Move"); + break; + default: + VERIFY_NOT_REACHED(); + } for (auto& source : sources) file_operation_args.append(source.characters()); diff --git a/Userland/Services/FileOperation/main.cpp b/Userland/Services/FileOperation/main.cpp index 49b22a7a81..1156db5ca5 100644 --- a/Userland/Services/FileOperation/main.cpp +++ b/Userland/Services/FileOperation/main.cpp @@ -12,11 +12,14 @@ #include #include #include +#include struct WorkItem { enum class Type { CreateDirectory, + DeleteDirectory, CopyFile, + MoveFile, }; Type type; String source; @@ -25,6 +28,7 @@ struct WorkItem { }; static int perform_copy(Vector const& sources, String const& destination); +static int perform_move(Vector const& sources, String const& destination); static int execute_work_items(Vector const& items); static void report_error(String message); static void report_warning(String message); @@ -43,6 +47,8 @@ int main(int argc, char** argv) if (operation == "Copy") return perform_copy(sources, destination); + if (operation == "Move") + return perform_move(sources, destination); report_warning(String::formatted("Unknown operation '{}'", operation)); return 0; @@ -58,7 +64,7 @@ static void report_warning(String message) outln("WARN {}", message); } -static bool collect_work_items(String const& source, String const& destination, Vector& items) +static bool collect_copy_work_items(String const& source, String const& destination, Vector& items) { struct stat st = {}; if (stat(source.characters(), &st) < 0) { @@ -89,7 +95,7 @@ static bool collect_work_items(String const& source, String const& destination, Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir); while (dt.has_next()) { auto name = dt.next_path(); - if (!collect_work_items( + if (!collect_copy_work_items( String::formatted("{}/{}", source, name), String::formatted("{}/{}", destination, LexicalPath::basename(source)), items)) { @@ -105,7 +111,68 @@ int perform_copy(Vector const& sources, String const& destination) Vector items; for (auto& source : sources) { - if (!collect_work_items(source, destination, items)) + if (!collect_copy_work_items(source, destination, items)) + return 1; + } + + return execute_work_items(items); +} + +static bool collect_move_work_items(String const& source, String const& destination, Vector& items) +{ + struct stat st = {}; + if (stat(source.characters(), &st) < 0) { + auto original_errno = errno; + report_error(String::formatted("stat: {}", strerror(original_errno))); + return false; + } + + if (!S_ISDIR(st.st_mode)) { + // It's a file. + items.append(WorkItem { + .type = WorkItem::Type::MoveFile, + .source = source, + .destination = String::formatted("{}/{}", destination, LexicalPath(source).basename()), + .size = st.st_size, + }); + return true; + } + + // It's a directory. + items.append(WorkItem { + .type = WorkItem::Type::CreateDirectory, + .source = {}, + .destination = String::formatted("{}/{}", destination, LexicalPath(source).basename()), + .size = 0, + }); + + Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir); + while (dt.has_next()) { + auto name = dt.next_path(); + if (!collect_move_work_items( + String::formatted("{}/{}", source, name), + String::formatted("{}/{}", destination, LexicalPath(source).basename()), + items)) { + return false; + } + } + + items.append(WorkItem { + .type = WorkItem::Type::DeleteDirectory, + .source = source, + .destination = {}, + .size = 0, + }); + + return true; +} + +int perform_move(Vector const& sources, String const& destination) +{ + Vector items; + + for (auto& source : sources) { + if (!collect_move_work_items(source, destination, items)) return 1; } @@ -127,6 +194,41 @@ int execute_work_items(Vector const& items) outln("PROGRESS {} {} {} {} {} {} {}", i, items.size(), executed_work_bytes, total_work_bytes, item_done, item.size, item.source); }; + auto copy_file = [&](String const& source, String const& destination) { + auto source_file_or_error = Core::File::open(source, Core::OpenMode::ReadOnly); + if (source_file_or_error.is_error()) { + 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)); + if (destination_file_or_error.is_error()) { + report_warning(String::formatted("Failed to open {} for write: {}", destination, destination_file_or_error.error())); + return false; + } + auto& source_file = *source_file_or_error.value(); + auto& destination_file = *destination_file_or_error.value(); + + while (true) { + print_progress(); + auto buffer = source_file.read(65536); + if (buffer.is_empty()) + break; + if (!destination_file.write(buffer)) { + report_warning(String::formatted("Failed to write to destination file: {}", destination_file.error_string())); + return false; + } + item_done += buffer.size(); + executed_work_bytes += buffer.size(); + print_progress(); + // FIXME: Remove this once the kernel is smart enough to schedule other threads + // while we're doing heavy I/O. Right now, copying a large file will totally + // starve the rest of the system. + sched_yield(); + } + print_progress(); + return true; + }; + switch (item.type) { case WorkItem::Type::CreateDirectory: { @@ -139,38 +241,46 @@ int execute_work_items(Vector const& items) break; } - case WorkItem::Type::CopyFile: { - auto source_file_or_error = Core::File::open(item.source, Core::OpenMode::ReadOnly); - if (source_file_or_error.is_error()) { - report_warning(String::formatted("Failed to open {} for reading: {}", item.source, source_file_or_error.error())); + case WorkItem::Type::DeleteDirectory: { + if (rmdir(item.source.characters()) < 0) { + auto original_errno = errno; + report_error(String::formatted("rmdir: {}", strerror(original_errno))); return 1; } - auto destination_file_or_error = Core::File::open(item.destination, (Core::OpenMode)(Core::OpenMode::WriteOnly | Core::OpenMode::Truncate)); - if (destination_file_or_error.is_error()) { - report_warning(String::formatted("Failed to open {} for write: {}", item.destination, destination_file_or_error.error())); - return 1; - } - auto& source_file = *source_file_or_error.value(); - auto& destination_file = *destination_file_or_error.value(); + break; + } - while (true) { + case WorkItem::Type::CopyFile: { + if (!copy_file(item.source, item.destination)) + return 1; + + break; + } + + case WorkItem::Type::MoveFile: { + if (rename(item.source.characters(), item.destination.characters()) == 0) { + item_done += item.size; + executed_work_bytes += item.size; print_progress(); - auto buffer = source_file.read(65536); - if (buffer.is_empty()) - break; - if (!destination_file.write(buffer)) { - report_warning(String::formatted("Failed to write to destination file: {}", destination_file.error_string())); - return 1; - } - item_done += buffer.size(); - executed_work_bytes += buffer.size(); - print_progress(); - // FIXME: Remove this once the kernel is smart enough to schedule other threads - // while we're doing heavy I/O. Right now, copying a large file will totally - // starve the rest of the system. - sched_yield(); + continue; } - print_progress(); + + auto original_errno = errno; + if (original_errno != EXDEV) { + report_warning(String::formatted("Failed to move {}: {}", item.source, strerror(original_errno))); + return 1; + } + + // EXDEV means we have to copy the file data and then remove the original + if (!copy_file(item.source, item.destination)) + return 1; + + if (unlink(item.source.characters()) < 0) { + auto original_errno = errno; + report_error(String::formatted("unlink: {}", strerror(original_errno))); + return 1; + } + break; }