Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2021-2022, Sam Atkins <atkinssj@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#include "FileUtils.h"
9#include "FileOperationProgressWidget.h"
10#include <AK/LexicalPath.h>
11#include <LibCore/DeprecatedFile.h>
12#include <LibCore/MimeData.h>
13#include <LibCore/System.h>
14#include <LibGUI/Event.h>
15#include <LibGUI/MessageBox.h>
16#include <unistd.h>
17
18namespace FileManager {
19
20HashTable<NonnullRefPtr<GUI::Window>> file_operation_windows;
21
22void delete_paths(Vector<DeprecatedString> const& paths, bool should_confirm, GUI::Window* parent_window)
23{
24 DeprecatedString message;
25 if (paths.size() == 1) {
26 message = DeprecatedString::formatted("Are you sure you want to delete {}?", LexicalPath::basename(paths[0]));
27 } else {
28 message = DeprecatedString::formatted("Are you sure you want to delete {} files?", paths.size());
29 }
30
31 if (should_confirm) {
32 auto result = GUI::MessageBox::show(parent_window,
33 message,
34 "Confirm deletion"sv,
35 GUI::MessageBox::Type::Warning,
36 GUI::MessageBox::InputType::OKCancel);
37 if (result == GUI::MessageBox::ExecResult::Cancel)
38 return;
39 }
40
41 if (run_file_operation(FileOperation::Delete, paths, {}, parent_window).is_error())
42 _exit(1);
43}
44
45ErrorOr<void> run_file_operation(FileOperation operation, Vector<DeprecatedString> const& sources, DeprecatedString const& destination, GUI::Window* parent_window)
46{
47 auto pipe_fds = TRY(Core::System::pipe2(0));
48
49 pid_t child_pid = TRY(Core::System::fork());
50
51 if (!child_pid) {
52 TRY(Core::System::close(pipe_fds[0]));
53 TRY(Core::System::dup2(pipe_fds[1], STDOUT_FILENO));
54
55 Vector<StringView> file_operation_args;
56 file_operation_args.append("/bin/FileOperation"sv);
57
58 switch (operation) {
59 case FileOperation::Copy:
60 file_operation_args.append("Copy"sv);
61 break;
62 case FileOperation::Move:
63 file_operation_args.append("Move"sv);
64 break;
65 case FileOperation::Delete:
66 file_operation_args.append("Delete"sv);
67 break;
68 default:
69 VERIFY_NOT_REACHED();
70 }
71
72 for (auto& source : sources)
73 file_operation_args.append(source.view());
74
75 if (operation != FileOperation::Delete)
76 file_operation_args.append(destination.view());
77
78 TRY(Core::System::exec(file_operation_args.first(), file_operation_args, Core::System::SearchInPath::Yes));
79 VERIFY_NOT_REACHED();
80 } else {
81 TRY(Core::System::close(pipe_fds[1]));
82 }
83
84 auto window = TRY(GUI::Window::try_create());
85 TRY(file_operation_windows.try_set(window));
86
87 switch (operation) {
88 case FileOperation::Copy:
89 window->set_title("Copying Files...");
90 break;
91 case FileOperation::Move:
92 window->set_title("Moving Files...");
93 break;
94 case FileOperation::Delete:
95 window->set_title("Deleting Files...");
96 break;
97 default:
98 VERIFY_NOT_REACHED();
99 }
100
101 auto pipe_input_file = TRY(Core::File::adopt_fd(pipe_fds[0], Core::File::OpenMode::Read));
102 auto buffered_pipe = TRY(Core::BufferedFile::create(move(pipe_input_file)));
103
104 (void)TRY(window->set_main_widget<FileOperationProgressWidget>(operation, move(buffered_pipe), pipe_fds[0]));
105 window->resize(320, 190);
106 if (parent_window)
107 window->center_within(*parent_window);
108 window->show();
109
110 return {};
111}
112
113ErrorOr<bool> handle_drop(GUI::DropEvent const& event, DeprecatedString const& destination, GUI::Window* window)
114{
115 bool has_accepted_drop = false;
116
117 if (!event.mime_data().has_urls())
118 return has_accepted_drop;
119 auto const urls = event.mime_data().urls();
120 if (urls.is_empty()) {
121 dbgln("No files to drop");
122 return has_accepted_drop;
123 }
124
125 auto const target = LexicalPath::canonicalized_path(destination);
126
127 if (!Core::DeprecatedFile::is_directory(target))
128 return has_accepted_drop;
129
130 Vector<DeprecatedString> paths_to_copy;
131 for (auto& url_to_copy : urls) {
132 if (!url_to_copy.is_valid() || url_to_copy.path() == target)
133 continue;
134 auto new_path = DeprecatedString::formatted("{}/{}", target, LexicalPath::basename(url_to_copy.path()));
135 if (url_to_copy.path() == new_path)
136 continue;
137
138 paths_to_copy.append(url_to_copy.path());
139 has_accepted_drop = true;
140 }
141
142 if (!paths_to_copy.is_empty())
143 TRY(run_file_operation(FileOperation::Copy, paths_to_copy, target, window));
144
145 return has_accepted_drop;
146}
147
148}