Преглед на файлове

FileManager+FileOperation: Implement (and use) 'Move' command

`FileOperation Move ...` is now used for cut-and-paste in the
FileManager.
Sam Atkins преди 4 години
родител
ревизия
0a62d517fd
променени са 2 файла, в които са добавени 145 реда и са изтрити 25 реда
  1. 12 2
      Userland/Applications/FileManager/FileUtils.cpp
  2. 133 23
      Userland/Services/FileOperation/main.cpp

+ 12 - 2
Userland/Applications/FileManager/FileUtils.cpp

@@ -71,7 +71,7 @@ void delete_paths(Vector<String> const& paths, bool should_confirm, GUI::Window*
     }
 }
 
-void run_file_operation([[maybe_unused]] FileOperation operation, Vector<String> const& sources, String const& destination, GUI::Window* parent_window)
+void run_file_operation(FileOperation operation, Vector<String> 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<String>
 
         Vector<char const*> 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());

+ 133 - 23
Userland/Services/FileOperation/main.cpp

@@ -12,11 +12,14 @@
 #include <LibCore/File.h>
 #include <sched.h>
 #include <sys/stat.h>
+#include <unistd.h>
 
 struct WorkItem {
     enum class Type {
         CreateDirectory,
+        DeleteDirectory,
         CopyFile,
+        MoveFile,
     };
     Type type;
     String source;
@@ -25,6 +28,7 @@ struct WorkItem {
 };
 
 static int perform_copy(Vector<String> const& sources, String const& destination);
+static int perform_move(Vector<String> const& sources, String const& destination);
 static int execute_work_items(Vector<WorkItem> 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<WorkItem>& items)
+static bool collect_copy_work_items(String const& source, String const& destination, Vector<WorkItem>& 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<String> const& sources, String const& destination)
     Vector<WorkItem> 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<WorkItem>& 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<String> const& sources, String const& destination)
+{
+    Vector<WorkItem> items;
+
+    for (auto& source : sources) {
+        if (!collect_move_work_items(source, destination, items))
             return 1;
     }
 
@@ -127,28 +194,16 @@ int execute_work_items(Vector<WorkItem> const& items)
             outln("PROGRESS {} {} {} {} {} {} {}", i, items.size(), executed_work_bytes, total_work_bytes, item_done, item.size, item.source);
         };
 
-        switch (item.type) {
-
-        case WorkItem::Type::CreateDirectory: {
-            outln("MKDIR {}", item.destination);
-            if (mkdir(item.destination.characters(), 0755) < 0 && errno != EEXIST) {
-                auto original_errno = errno;
-                report_error(String::formatted("mkdir: {}", strerror(original_errno)));
-                return 1;
-            }
-            break;
-        }
-
-        case WorkItem::Type::CopyFile: {
-            auto source_file_or_error = Core::File::open(item.source, Core::OpenMode::ReadOnly);
+        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: {}", item.source, source_file_or_error.error()));
-                return 1;
+                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(item.destination, (Core::OpenMode)(Core::OpenMode::WriteOnly | Core::OpenMode::Truncate));
+            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: {}", item.destination, destination_file_or_error.error()));
-                return 1;
+                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();
@@ -160,7 +215,7 @@ int execute_work_items(Vector<WorkItem> const& items)
                     break;
                 if (!destination_file.write(buffer)) {
                     report_warning(String::formatted("Failed to write to destination file: {}", destination_file.error_string()));
-                    return 1;
+                    return false;
                 }
                 item_done += buffer.size();
                 executed_work_bytes += buffer.size();
@@ -171,6 +226,61 @@ int execute_work_items(Vector<WorkItem> const& items)
                 sched_yield();
             }
             print_progress();
+            return true;
+        };
+
+        switch (item.type) {
+
+        case WorkItem::Type::CreateDirectory: {
+            outln("MKDIR {}", item.destination);
+            if (mkdir(item.destination.characters(), 0755) < 0 && errno != EEXIST) {
+                auto original_errno = errno;
+                report_error(String::formatted("mkdir: {}", strerror(original_errno)));
+                return 1;
+            }
+            break;
+        }
+
+        case WorkItem::Type::DeleteDirectory: {
+            if (rmdir(item.source.characters()) < 0) {
+                auto original_errno = errno;
+                report_error(String::formatted("rmdir: {}", strerror(original_errno)));
+                return 1;
+            }
+            break;
+        }
+
+        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();
+                continue;
+            }
+
+            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;
         }