1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 05:07:45 +00:00

Services: Port FileOperation to LibMain

This commit is contained in:
Lenny Maiorani 2022-02-12 18:36:15 -07:00 committed by Andreas Kling
parent b455363ce7
commit f5fd2f3857
2 changed files with 69 additions and 122 deletions

View file

@ -8,4 +8,4 @@ set(SOURCES
) )
serenity_bin(FileOperation) serenity_bin(FileOperation)
target_link_libraries(FileOperation LibCore) target_link_libraries(FileOperation LibCore LibMain)

View file

@ -7,9 +7,13 @@
#include <AK/Format.h> #include <AK/Format.h>
#include <AK/LexicalPath.h> #include <AK/LexicalPath.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <LibCore/ArgsParser.h> #include <LibCore/ArgsParser.h>
#include <LibCore/DirIterator.h> #include <LibCore/DirIterator.h>
#include <LibCore/File.h> #include <LibCore/File.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
#include <sched.h> #include <sched.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
@ -28,16 +32,16 @@ struct WorkItem {
off_t size; off_t size;
}; };
static int perform_copy(Vector<StringView> const& sources, String const& destination); static void report_warning(StringView message);
static int perform_move(Vector<StringView> const& sources, String const& destination); static void report_error(StringView message);
static int perform_delete(Vector<StringView> const& sources); static ErrorOr<int> perform_copy(Vector<StringView> const& sources, String const& destination);
static int execute_work_items(Vector<WorkItem> const& items); static ErrorOr<int> perform_move(Vector<StringView> const& sources, String const& destination);
static void report_error(String message); static ErrorOr<int> perform_delete(Vector<StringView> const& sources);
static void report_warning(String message); static ErrorOr<int> execute_work_items(Vector<WorkItem> const& items);
static ErrorOr<NonnullRefPtr<Core::File>> open_destination_file(String const& destination); static ErrorOr<NonnullRefPtr<Core::File>> open_destination_file(String const& destination);
static String deduplicate_destination_file_name(String const& destination); static String deduplicate_destination_file_name(String const& destination);
int main(int argc, char** argv) ErrorOr<int> serenity_main(Main::Arguments arguments)
{ {
String operation; String operation;
Vector<StringView> paths; Vector<StringView> paths;
@ -45,46 +49,38 @@ int main(int argc, char** argv)
Core::ArgsParser args_parser; Core::ArgsParser args_parser;
args_parser.add_positional_argument(operation, "Operation: either 'Copy', 'Move' or 'Delete'", "operation", Core::ArgsParser::Required::Yes); args_parser.add_positional_argument(operation, "Operation: either 'Copy', 'Move' or 'Delete'", "operation", Core::ArgsParser::Required::Yes);
args_parser.add_positional_argument(paths, "Source paths, followed by a destination if applicable", "paths", Core::ArgsParser::Required::Yes); args_parser.add_positional_argument(paths, "Source paths, followed by a destination if applicable", "paths", Core::ArgsParser::Required::Yes);
args_parser.parse(argc, argv); args_parser.parse(arguments);
if (operation == "Delete") if (operation == "Delete")
return perform_delete(paths); return perform_delete(paths);
String destination = paths.take_last(); String destination = paths.take_last();
if (paths.is_empty()) { if (paths.is_empty())
report_warning("At least one source and destination are required"); return Error::from_string_literal("At least one source and destination are required");
return 1;
}
if (operation == "Copy") if (operation == "Copy")
return perform_copy(paths, destination); return perform_copy(paths, destination);
if (operation == "Move") if (operation == "Move")
return perform_move(paths, destination); return perform_move(paths, destination);
report_warning(String::formatted("Unknown operation '{}'", operation)); // FIXME: Return the formatted string directly. There is no way to do this right now without the temporary going out of scope and being destroyed.
return 0; report_error(String::formatted("Unknown operation '{}'", operation));
return Error::from_string_literal("Unknown operation");
} }
static void report_error(String message) static void report_warning(StringView message)
{
outln("ERROR {}", message);
}
static void report_warning(String message)
{ {
outln("WARN {}", message); outln("WARN {}", message);
} }
static bool collect_copy_work_items(String const& source, String const& destination, Vector<WorkItem>& items) static void report_error(StringView message)
{ {
struct stat st = {}; outln("ERROR {}", message);
if (lstat(source.characters(), &st) < 0) {
auto original_errno = errno;
report_error(String::formatted("stat: {}", strerror(original_errno)));
return false;
} }
if (!S_ISDIR(st.st_mode)) { static ErrorOr<int> collect_copy_work_items(String const& source, String const& destination, Vector<WorkItem>& items)
{
if (auto const st = TRY(Core::System::lstat(source)); !S_ISDIR(st.st_mode)) {
// It's a file. // It's a file.
items.append(WorkItem { items.append(WorkItem {
.type = WorkItem::Type::CopyFile, .type = WorkItem::Type::CopyFile,
@ -92,7 +88,7 @@ static bool collect_copy_work_items(String const& source, String const& destinat
.destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(), .destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(),
.size = st.st_size, .size = st.st_size,
}); });
return true; return 0;
} }
// It's a directory. // It's a directory.
@ -106,39 +102,29 @@ static bool collect_copy_work_items(String const& source, String const& destinat
Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir); Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir);
while (dt.has_next()) { while (dt.has_next()) {
auto name = dt.next_path(); auto name = dt.next_path();
if (!collect_copy_work_items( TRY(collect_copy_work_items(
LexicalPath::join(source, name).string(), LexicalPath::join(source, name).string(),
LexicalPath::join(destination, LexicalPath::basename(source)).string(), LexicalPath::join(destination, LexicalPath::basename(source)).string(),
items)) { items));
return false;
}
} }
return true; return 0;
} }
int perform_copy(Vector<StringView> const& sources, String const& destination) ErrorOr<int> perform_copy(Vector<StringView> const& sources, String const& destination)
{ {
Vector<WorkItem> items; Vector<WorkItem> items;
for (auto& source : sources) { for (auto& source : sources) {
if (!collect_copy_work_items(source, destination, items)) TRY(collect_copy_work_items(source, destination, items));
return 1;
} }
return execute_work_items(items); return execute_work_items(items);
} }
static bool collect_move_work_items(String const& source, String const& destination, Vector<WorkItem>& items) static ErrorOr<int> collect_move_work_items(String const& source, String const& destination, Vector<WorkItem>& items)
{ {
struct stat st = {}; if (auto const st = TRY(Core::System::lstat(source)); !S_ISDIR(st.st_mode)) {
if (lstat(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. // It's a file.
items.append(WorkItem { items.append(WorkItem {
.type = WorkItem::Type::MoveFile, .type = WorkItem::Type::MoveFile,
@ -146,7 +132,7 @@ static bool collect_move_work_items(String const& source, String const& destinat
.destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(), .destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(),
.size = st.st_size, .size = st.st_size,
}); });
return true; return 0;
} }
// It's a directory. // It's a directory.
@ -160,12 +146,10 @@ static bool collect_move_work_items(String const& source, String const& destinat
Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir); Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir);
while (dt.has_next()) { while (dt.has_next()) {
auto name = dt.next_path(); auto name = dt.next_path();
if (!collect_move_work_items( TRY(collect_move_work_items(
LexicalPath::join(source, name).string(), LexicalPath::join(source, name).string(),
LexicalPath::join(destination, LexicalPath::basename(source)).string(), LexicalPath::join(destination, LexicalPath::basename(source)).string(),
items)) { items));
return false;
}
} }
items.append(WorkItem { items.append(WorkItem {
@ -175,31 +159,23 @@ static bool collect_move_work_items(String const& source, String const& destinat
.size = 0, .size = 0,
}); });
return true; return 0;
} }
int perform_move(Vector<StringView> const& sources, String const& destination) ErrorOr<int> perform_move(Vector<StringView> const& sources, String const& destination)
{ {
Vector<WorkItem> items; Vector<WorkItem> items;
for (auto& source : sources) { for (auto& source : sources) {
if (!collect_move_work_items(source, destination, items)) TRY(collect_move_work_items(source, destination, items));
return 1;
} }
return execute_work_items(items); return execute_work_items(items);
} }
static bool collect_delete_work_items(String const& source, Vector<WorkItem>& items) static ErrorOr<int> collect_delete_work_items(String const& source, Vector<WorkItem>& items)
{ {
struct stat st = {}; if (auto const st = TRY(Core::System::lstat(source)); !S_ISDIR(st.st_mode)) {
if (lstat(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. // It's a file.
items.append(WorkItem { items.append(WorkItem {
.type = WorkItem::Type::DeleteFile, .type = WorkItem::Type::DeleteFile,
@ -207,15 +183,14 @@ static bool collect_delete_work_items(String const& source, Vector<WorkItem>& it
.destination = {}, .destination = {},
.size = st.st_size, .size = st.st_size,
}); });
return true; return 0;
} }
// It's a directory. // It's a directory.
Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir); Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir);
while (dt.has_next()) { while (dt.has_next()) {
auto name = dt.next_path(); auto name = dt.next_path();
if (!collect_delete_work_items(LexicalPath::join(source, name).string(), items)) TRY(collect_delete_work_items(LexicalPath::join(source, name).string(), items));
return false;
} }
items.append(WorkItem { items.append(WorkItem {
@ -225,22 +200,21 @@ static bool collect_delete_work_items(String const& source, Vector<WorkItem>& it
.size = 0, .size = 0,
}); });
return true; return 0;
} }
int perform_delete(Vector<StringView> const& sources) ErrorOr<int> perform_delete(Vector<StringView> const& sources)
{ {
Vector<WorkItem> items; Vector<WorkItem> items;
for (auto& source : sources) { for (auto& source : sources) {
if (!collect_delete_work_items(source, items)) TRY(collect_delete_work_items(source, items));
return 1;
} }
return execute_work_items(items); return execute_work_items(items);
} }
int execute_work_items(Vector<WorkItem> const& items) ErrorOr<int> execute_work_items(Vector<WorkItem> const& items)
{ {
off_t total_work_bytes = 0; off_t total_work_bytes = 0;
for (auto& item : items) for (auto& item : items)
@ -255,29 +229,20 @@ 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); 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 copy_file = [&](String const& source, String const& destination) -> ErrorOr<int> {
auto source_file_or_error = Core::File::open(source, Core::OpenMode::ReadOnly); auto source_file = TRY(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;
}
// FIXME: When the file already exists, let the user choose the next action instead of renaming it by default. // 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); auto destination_file = TRY(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;
}
auto& source_file = *source_file_or_error.value();
auto& destination_file = *destination_file_or_error.value();
while (true) { while (true) {
print_progress(); print_progress();
auto buffer = source_file.read(65536); auto buffer = source_file->read(65536);
if (buffer.is_empty()) if (buffer.is_empty())
break; break;
if (!destination_file.write(buffer)) { if (auto result = destination_file->write(buffer); !result) {
report_warning(String::formatted("Failed to write to destination file: {}", destination_file.error_string())); // FIXME: Return the formatted string directly. There is no way to do this right now without the temporary going out of scope and being destroyed.
return false; report_warning(String::formatted("Failed to write to destination file: {}", destination_file->error_string()));
return result;
} }
item_done += buffer.size(); item_done += buffer.size();
executed_work_bytes += buffer.size(); executed_work_bytes += buffer.size();
@ -288,7 +253,7 @@ int execute_work_items(Vector<WorkItem> const& items)
sched_yield(); sched_yield();
} }
print_progress(); print_progress();
return true; return 0;
}; };
switch (item.type) { switch (item.type) {
@ -296,27 +261,18 @@ int execute_work_items(Vector<WorkItem> const& items)
case WorkItem::Type::CreateDirectory: { case WorkItem::Type::CreateDirectory: {
outln("MKDIR {}", item.destination); outln("MKDIR {}", item.destination);
// FIXME: Support deduplication like open_destination_file() when the directory already exists. // FIXME: Support deduplication like open_destination_file() when the directory already exists.
if (mkdir(item.destination.characters(), 0755) < 0 && errno != EEXIST) { if (mkdir(item.destination.characters(), 0755) < 0 && errno != EEXIST)
auto original_errno = errno; return Error::from_syscall("mkdir", -errno);
report_error(String::formatted("mkdir: {}", strerror(original_errno)));
return 1;
}
break; break;
} }
case WorkItem::Type::DeleteDirectory: { case WorkItem::Type::DeleteDirectory: {
if (rmdir(item.source.characters()) < 0) { TRY(Core::System::rmdir(item.source));
auto original_errno = errno;
report_error(String::formatted("rmdir: {}", strerror(original_errno)));
return 1;
}
break; break;
} }
case WorkItem::Type::CopyFile: { case WorkItem::Type::CopyFile: {
if (!copy_file(item.source, item.destination)) TRY(copy_file(item.source, item.destination));
return 1;
break; break;
} }
@ -337,19 +293,14 @@ int execute_work_items(Vector<WorkItem> const& items)
} }
if (original_errno != EXDEV) { if (original_errno != EXDEV) {
// FIXME: Return the formatted string directly. There is no way to do this right now without the temporary going out of scope and being destroyed.
report_warning(String::formatted("Failed to move {}: {}", item.source, strerror(original_errno))); report_warning(String::formatted("Failed to move {}: {}", item.source, strerror(original_errno)));
return 1; return Error::from_errno(original_errno);
} }
// EXDEV means we have to copy the file data and then remove the original // EXDEV means we have to copy the file data and then remove the original
if (!copy_file(item.source, item.destination)) TRY(copy_file(item.source, item.destination));
return 1; TRY(Core::System::unlink(item.source));
if (unlink(item.source.characters()) < 0) {
auto original_errno = errno;
report_error(String::formatted("unlink: {}", strerror(original_errno)));
return 1;
}
break; break;
} }
@ -357,11 +308,7 @@ int execute_work_items(Vector<WorkItem> const& items)
} }
case WorkItem::Type::DeleteFile: { case WorkItem::Type::DeleteFile: {
if (unlink(item.source.characters()) < 0) { TRY(Core::System::unlink(item.source));
auto original_errno = errno;
report_error(String::formatted("unlink: {}", strerror(original_errno)));
return 1;
}
item_done += item.size; item_done += item.size;
executed_work_bytes += item.size; executed_work_bytes += item.size;