mirror of
https://github.com/ModOrganizer2/mob.git
synced 2026-07-27 14:07:05 -07:00
396 lines
8.2 KiB
C++
396 lines
8.2 KiB
C++
#include "pch.h"
|
|
#include "op.h"
|
|
#include "utility.h"
|
|
#include "conf.h"
|
|
#include "context.h"
|
|
|
|
namespace mob::op
|
|
{
|
|
|
|
void do_touch(const context& cx, const fs::path& p);
|
|
void do_create_directories(const context& cx, const fs::path& p);
|
|
void do_delete_directory(const context& cx, const fs::path& p);
|
|
void do_delete_file(const context& cx, const fs::path& p);
|
|
void do_copy_file_to_dir(const context& cx, const fs::path& f, const fs::path& d);
|
|
void do_remove_readonly(const context& cx, const fs::path& p);
|
|
void do_rename(const context& cx, const fs::path& src, const fs::path& dest);
|
|
void check(const context& cx, const fs::path& p);
|
|
|
|
|
|
void touch(const context& cx, const fs::path& p)
|
|
{
|
|
cx.trace(context::fs, "touching " + p.string());
|
|
check(cx, p);
|
|
|
|
if (!conf::dry())
|
|
do_touch(cx ,p);
|
|
}
|
|
|
|
void create_directories(const context& cx, const fs::path& p)
|
|
{
|
|
cx.trace(context::fs, "creating dir " + p.string());
|
|
check(cx, p);
|
|
|
|
if (!conf::dry())
|
|
do_create_directories(cx, p);
|
|
}
|
|
|
|
void delete_directory(const context& cx, const fs::path& p, flags f)
|
|
{
|
|
cx.trace(context::fs, "deleting dir " + p.string());
|
|
check(cx, p);
|
|
|
|
if (!fs::exists(p))
|
|
{
|
|
if (f & optional)
|
|
{
|
|
cx.trace(context::fs,
|
|
"not deleting dir " + p.string() + ", doesn't exist");
|
|
|
|
return;
|
|
}
|
|
|
|
cx.bail_out(context::fs,
|
|
"can't delete dir " + p.string() + ", doesn't exist");
|
|
}
|
|
|
|
if (fs::exists(p) && !fs::is_directory(p))
|
|
cx.bail_out(context::fs, p.string() + " is not a dir");
|
|
|
|
if (!conf::dry())
|
|
do_delete_directory(cx, p);
|
|
}
|
|
|
|
void delete_file(const context& cx, const fs::path& p, flags f)
|
|
{
|
|
cx.trace(context::fs, "deleting file " + p.string());
|
|
check(cx, p);
|
|
|
|
if (!fs::exists(p))
|
|
{
|
|
if (f & optional)
|
|
{
|
|
cx.trace(context::fs,
|
|
"not deleting file " + p.string() + ", doesn't exist");
|
|
|
|
return;
|
|
}
|
|
|
|
cx.bail_out(context::fs,
|
|
"can't delete file " + p.string() + ", doesn't exist");
|
|
}
|
|
|
|
if (fs::exists(p) && !fs::is_regular_file(p))
|
|
{
|
|
cx.bail_out(context::fs,
|
|
"can't delete " + p.string() + ", not a file");
|
|
}
|
|
|
|
if (!conf::dry())
|
|
do_delete_file(cx, p);
|
|
}
|
|
|
|
void remove_readonly(const context& cx, const fs::path& first)
|
|
{
|
|
cx.trace(context::fs, "removing read-only from " + first.string());
|
|
check(cx, first);
|
|
|
|
if (!conf::dry())
|
|
{
|
|
if (fs::is_regular_file(first))
|
|
do_remove_readonly(cx, first);
|
|
|
|
for (auto&& p : fs::recursive_directory_iterator(first))
|
|
{
|
|
if (fs::is_regular_file(p))
|
|
do_remove_readonly(cx, p);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool is_source_better(
|
|
const context& cx, const fs::path& src, const fs::path& dest)
|
|
{
|
|
if (!fs::exists(dest))
|
|
{
|
|
cx.trace(context::fs,
|
|
"target " + dest.string() + " doesn't exist; copying");
|
|
|
|
return true;
|
|
}
|
|
|
|
std::error_code ec;
|
|
|
|
const auto src_size = fs::file_size(src, ec);
|
|
if (ec)
|
|
{
|
|
cx.warning(context::fs,
|
|
"failed to get size of " + src.string() + "; forcing copy");
|
|
|
|
return true;
|
|
}
|
|
|
|
const auto dest_size = fs::file_size(dest, ec);
|
|
if (ec)
|
|
{
|
|
cx.warning(context::fs,
|
|
"failed to get size of " + dest.string() + "; forcing copy");
|
|
|
|
return true;
|
|
}
|
|
|
|
if (src_size != dest_size)
|
|
{
|
|
cx.trace(
|
|
context::fs,
|
|
"src " + src.string() + " is " + std::to_string(src_size) + "), "
|
|
"dest " + dest.string() + " is " + std::to_string(dest_size) + "); "
|
|
"sizes different, copying");
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
const auto src_time = fs::last_write_time(src, ec);
|
|
if (ec)
|
|
{
|
|
cx.warning(context::fs,
|
|
"failed to get time of " + src.string() + "; forcing copy");
|
|
|
|
return true;
|
|
}
|
|
|
|
const auto dest_time = fs::last_write_time(dest, ec);
|
|
if (ec)
|
|
{
|
|
cx.warning(context::fs,
|
|
"failed to get time of " + dest.string() + "; forcing copy");
|
|
|
|
return true;
|
|
}
|
|
|
|
if (src_time > dest_time)
|
|
{
|
|
cx.trace(context::fs,
|
|
"src " + src.string() + " is newer than " + dest.string() + "; "
|
|
"copying");
|
|
|
|
return true;
|
|
}
|
|
|
|
// same size, same date
|
|
return false;
|
|
}
|
|
|
|
void rename(const context& cx, const fs::path& src, const fs::path& dest)
|
|
{
|
|
check(cx, src);
|
|
check(cx, dest);
|
|
|
|
if (fs::exists(dest))
|
|
{
|
|
cx.bail_out(context::fs,
|
|
"can't rename " + src.string() + " to " + dest.string() + ", "
|
|
"already exists");
|
|
}
|
|
|
|
cx.trace(context::fs, "renaming " + src.string() + " to " + dest.string());
|
|
do_rename(cx, src, dest);
|
|
}
|
|
|
|
void move_to_directory(
|
|
const context& cx, const fs::path& src, const fs::path& dest_dir)
|
|
{
|
|
check(cx, src);
|
|
check(cx, dest_dir);
|
|
|
|
const auto target = dest_dir / src.filename();
|
|
|
|
if (fs::exists(target))
|
|
{
|
|
cx.bail_out(context::fs,
|
|
"can't move " + src.string() + " to " + dest_dir.string() + ", " +
|
|
src.filename().string() + " already exists");
|
|
}
|
|
|
|
cx.trace(context::fs, "moving " + src.string() + " to " + target.string());
|
|
do_rename(cx, src, target);
|
|
}
|
|
|
|
void copy_file_to_dir_if_better(
|
|
const context& cx, const fs::path& file, const fs::path& dir, flags f)
|
|
{
|
|
check(cx, file);
|
|
check(cx, dir);
|
|
|
|
if (file.filename().string().find("*") == std::string::npos)
|
|
{
|
|
if (!conf::dry())
|
|
{
|
|
if (!fs::exists(file) || !fs::is_regular_file(file))
|
|
{
|
|
if (f & optional)
|
|
{
|
|
cx.trace(context::fs,
|
|
"not copying " + file.string() + ", not found but "
|
|
"optional");
|
|
|
|
return;
|
|
}
|
|
|
|
cx.bail_out(context::fs,
|
|
"can't copy " + file.string() + ", not a file");
|
|
}
|
|
|
|
if (fs::exists(dir) && !fs::is_directory(dir))
|
|
{
|
|
cx.bail_out(context::fs,
|
|
"can't copy to " + dir.string() + ", not a dir");
|
|
}
|
|
}
|
|
|
|
const auto target = dir / file.filename();
|
|
if (is_source_better(cx, file, target))
|
|
{
|
|
cx.trace(context::fs, file.string() + " -> " + dir.string());
|
|
|
|
if (!conf::dry())
|
|
do_copy_file_to_dir(cx, file, dir);
|
|
}
|
|
else
|
|
{
|
|
cx.trace(context::bypass,
|
|
"(skipped) " + file.string() + " -> " + dir.string());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cx.trace(context::fs, file.filename().string() + " is a glob");
|
|
|
|
// wildcard
|
|
const auto file_parent = file.parent_path();
|
|
const auto wildcard = file.filename().string();
|
|
|
|
for (auto&& e : fs::directory_iterator(file_parent))
|
|
{
|
|
const auto name = e.path().filename().string();
|
|
|
|
if (PathMatchSpecA(name.c_str(), wildcard.c_str()))
|
|
copy_file_to_dir_if_better(cx, e.path(), dir);
|
|
else
|
|
cx.trace(context::fs, name + " did not match " + wildcard);
|
|
}
|
|
}
|
|
}
|
|
|
|
void do_touch(const context& cx, const fs::path& p)
|
|
{
|
|
op::create_directories(cx, p.parent_path());
|
|
|
|
std::ofstream out(p);
|
|
if (!out)
|
|
cx.bail_out(context::fs, "failed to touch " + p.string());
|
|
}
|
|
|
|
void do_create_directories(const context& cx, const fs::path& p)
|
|
{
|
|
std::error_code ec;
|
|
fs::create_directories(p, ec);
|
|
|
|
if (ec)
|
|
cx.bail_out(context::fs, "can't create " + p.string(), ec);
|
|
}
|
|
|
|
void do_delete_directory(const context& cx, const fs::path& p)
|
|
{
|
|
std::error_code ec;
|
|
fs::remove_all(p, ec);
|
|
|
|
if (ec)
|
|
{
|
|
if (ec.value() == ERROR_ACCESS_DENIED)
|
|
{
|
|
cx.trace(
|
|
context::fs,
|
|
"got access denied trying to delete dir " + p.string() + ", "
|
|
"trying to remove read-only flag recursively");
|
|
|
|
remove_readonly(cx, p);
|
|
fs::remove_all(p, ec);
|
|
|
|
if (!ec)
|
|
return;
|
|
}
|
|
|
|
cx.bail_out(context::fs, "failed to delete " + p.string(), ec);
|
|
}
|
|
}
|
|
|
|
void do_delete_file(const context& cx, const fs::path& p)
|
|
{
|
|
std::error_code ec;
|
|
fs::remove(p, ec);
|
|
|
|
if (ec)
|
|
cx.bail_out(context::fs, "can't delete " + p.string(), ec);
|
|
}
|
|
|
|
void do_copy_file_to_dir(
|
|
const context& cx, const fs::path& f, const fs::path& d)
|
|
{
|
|
op::create_directories(cx, d);
|
|
|
|
std::error_code ec;
|
|
fs::copy_file(
|
|
f, d / f.filename(),
|
|
fs::copy_options::overwrite_existing, ec);
|
|
|
|
if (ec)
|
|
{
|
|
cx.bail_out(context::fs,
|
|
"can't copy " + f.string() + " to " + d.string(), ec);
|
|
}
|
|
}
|
|
|
|
void do_remove_readonly(const context& cx, const fs::path& p)
|
|
{
|
|
cx.trace(context::fs, "chmod +x " + p.string());
|
|
|
|
std::error_code ec;
|
|
fs::permissions(p, fs::perms::owner_write, fs::perm_options::add, ec);
|
|
|
|
if (ec)
|
|
{
|
|
cx.bail_out(context::fs,
|
|
"can't remove read-only flag on " + p.string(), ec);
|
|
}
|
|
}
|
|
|
|
void do_rename(const context& cx, const fs::path& src, const fs::path& dest)
|
|
{
|
|
std::error_code ec;
|
|
fs::rename(src, dest, ec);
|
|
|
|
if (ec)
|
|
{
|
|
cx.bail_out(context::fs,
|
|
"can't rename " + src.string() + " to " + dest.string(), ec);
|
|
}
|
|
}
|
|
|
|
void check(const context& cx, const fs::path& p)
|
|
{
|
|
if (p.empty())
|
|
cx.bail_out(context::fs, "path is empty");
|
|
|
|
if (p.native().starts_with(paths::prefix().native()))
|
|
return;
|
|
|
|
if (p.native().starts_with(paths::temp_dir().native()))
|
|
return;
|
|
|
|
cx.bail_out(context::fs, "path " + p.string() + " is outside prefix");
|
|
}
|
|
|
|
} // namespace
|