wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
project_directory.cpp
Go to the documentation of this file.
2
3#include <format>
4#include <fstream>
5#include <mutex>
6
8
9namespace wowlib::fs {
10 namespace {
11 namespace fsys = std::filesystem;
12 }
13
15 std::unique_lock lock{other._mtx};
16 _root = std::move(other._root);
17 _index = std::move(other._index);
18 }
19
21 if (this != &other) {
22 std::scoped_lock lock{_mtx, other._mtx};
23 _root = std::move(other._root);
24 _index = std::move(other._index);
25 }
26 return *this;
27 }
28
30 std::error_code ec;
31 fsys::create_directories(root, ec);
32 if (ec)
34 std::format("cannot create project directory '{}': {}", root.string(), ec.message()));
35
36 root = fsys::absolute(root, ec);
37 if (ec)
39 std::format("cannot resolve project directory '{}': {}", root.string(), ec.message()));
40
41 ProjectDirectory result;
42 result._root = std::move(root);
43 result._indexTreeLocked(); // sole owner, no readers yet
44 return result;
45 }
46
47 void ProjectDirectory::_indexTreeLocked() {
48 _index.clear();
49
50 std::error_code ec;
51 for (fsys::recursive_directory_iterator it{_root, ec}, end; !ec && it != end; it.increment(ec)) {
52 if (!it->is_regular_file(ec)) continue;
53
54 auto relative = fsys::relative(it->path(), _root, ec);
55 if (ec) continue;
56
57 _index.insert_or_assign(normalizePath(relative.generic_string()), it->path());
58 }
59 }
60
62 std::unique_lock lock{_mtx};
63 _indexTreeLocked();
64 }
65
66 std::optional<std::filesystem::path> ProjectDirectory::resolve(std::string_view path) const {
67 const std::string canonical = normalizePath(path);
68 std::shared_lock lock{_mtx};
69 const auto it = _index.find(canonical);
70 return it == _index.end() ? std::nullopt : std::optional{it->second};
71 }
72
73 bool ProjectDirectory::exists(std::string_view path) const {
74 return resolve(path).has_value();
75 }
76
77 Result<FileBuffer> ProjectDirectory::read(std::string_view path) const {
78 const auto onDisk = resolve(path);
79 if (!onDisk)
81 std::format("'{}' is not in the project directory", normalizePath(path)));
82
83 std::ifstream file{*onDisk, std::ios::binary | std::ios::ate};
84 if (!file)
85 return makeError(ErrorCode::IoError, std::format("cannot open '{}'", onDisk->string()));
86
87 FileBuffer buffer(static_cast<std::size_t>(file.tellg()));
88 file.seekg(0);
89 if (!buffer.empty() && !file.read(reinterpret_cast<char*>(buffer.data()),
90 static_cast<std::streamsize>(buffer.size())))
91 return makeError(ErrorCode::IoError, std::format("I/O error reading '{}'", onDisk->string()));
92
93 return buffer;
94 }
95
96 Result<void> ProjectDirectory::write(std::string_view path, std::span<const std::byte> content) {
97 const std::string canonical = normalizePath(path);
98 if (canonical.empty()) return makeError(ErrorCode::InvalidPath, "cannot write an empty path");
99
100 const fsys::path onDisk = _root / toNativeRelative(canonical);
101
102 std::error_code ec;
103 fsys::create_directories(onDisk.parent_path(), ec);
104 if (ec)
106 std::format("cannot create directories for '{}': {}", onDisk.string(), ec.message()));
107
108 std::ofstream file{onDisk, std::ios::binary | std::ios::trunc};
109 if (!file)
110 return makeError(ErrorCode::IoError, std::format("cannot write '{}'", onDisk.string()));
111
112 if (!content.empty())
113 file.write(reinterpret_cast<const char*>(content.data()), static_cast<std::streamsize>(content.size()));
114 if (!file.flush())
115 return makeError(ErrorCode::IoError, std::format("I/O error writing '{}'", onDisk.string()));
116
117 std::unique_lock lock{_mtx};
118 _index.insert_or_assign(canonical, onDisk);
119 return {};
120 }
121
122 std::size_t ProjectDirectory::size() const {
123 std::shared_lock lock{_mtx};
124 return _index.size();
125 }
126}
Result< void > write(std::string_view path, std::span< const std::byte > content)
Write (create or overwrite) a file under the overlay, creating intermediate directories,...
void rescan()
Rebuild the index from disk, picking up files changed by other tools.
ProjectDirectory & operator=(const ProjectDirectory &)=delete
std::optional< std::filesystem::path > resolve(std::string_view path) const
The real on-disk location of a client-internal path, if the overlay carries it.
static Result< ProjectDirectory > open(std::filesystem::path root)
Open (and index) a project directory; it is created if missing.
Result< FileBuffer > read(std::string_view path) const
Read an overlay file into memory.
bool exists(std::string_view path) const
Whether the overlay carries a file.
const std::filesystem::path & root() const
std::string normalizePath(std::string_view path)
Canonicalize a client-internal file path.
Definition path.cpp:4
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
Definition error.hpp:100
std::string toNativeRelative(std::string_view canonical)
Convert a canonical path to a forward-slash relative path for use on the native filesystem (project-d...
Definition path.cpp:23
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
Definition error.hpp:107
@ FileNotFound
The file exists nowhere in the overlay or storage.
Definition error.hpp:22
@ InvalidPath
A path that cannot be normalized/used.
Definition error.hpp:29
@ IoError
Generic filesystem I/O failure (project directory).
Definition error.hpp:30
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
Definition buffer.hpp:16
Client-internal path canonicalization.
The project-directory overlay: a local directory acting as the ultimate patch over any client storage...