sandbox
Loading...
Searching...
No Matches
virtual_filesystem.hpp
1// SPDX-License-Identifier: MIT
2#ifndef LIBSBX_FILESYSTEM_VIRTUAL_FILESYSTEM_HPP_
3#define LIBSBX_FILESYSTEM_VIRTUAL_FILESYSTEM_HPP_
4
5#include <unordered_map>
6#include <unordered_set>
7#include <vector>
8#include <string>
9#include <memory>
10#include <mutex>
11#include <algorithm>
12#include <optional>
13#include <functional>
14#include <filesystem>
15
16#include <libsbx/filesystem/filesystem.hpp>
17#include <libsbx/filesystem/file_base.hpp>
18#include <libsbx/filesystem/alias.hpp>
19
20namespace sbx::filesystem {
21
22class virtual_filesystem final {
23
24public:
25
26 using filesystem_list = std::vector<filesystem_ptr>;
27 using filesystem_map = std::unordered_map<alias, filesystem_list>;
28
29 virtual_filesystem() = default;
30
32 auto lock = std::scoped_lock{_mutex};
33
34 for (auto& [alias, list] : _filesystems) {
35 for (auto& filesystem : list) {
36 if (filesystem) {
37 filesystem->shutdown();
38 }
39 }
40 }
41 }
42
43 void add_filesystem(const alias& alias, const filesystem_ptr& filesystem) {
44 if (!filesystem) {
45 return;
46 }
47
48 auto lock = std::scoped_lock{_mutex};
49
50 _filesystems[alias].push_back(filesystem);
51
52 if (std::find(_sorted_alias.begin(), _sorted_alias.end(), alias) == _sorted_alias.end()) {
53 _sorted_alias.push_back(alias);
54 }
55
56 std::sort(_sorted_alias.begin(), _sorted_alias.end(), [](const filesystem::alias& lhs, const filesystem::alias& rhs) {
57 return lhs.length() > rhs.length();
58 });
59 }
60
61 void add_filesystem(std::string alias, const filesystem_ptr& filesystem) {
62 add_filesystem(filesystem::alias{std::move(alias)}, filesystem);
63 }
64
65 template<typename Type, typename... Args>
66 [[nodiscard]] auto create_filesystem(const alias& alias, Args&&... args) -> std::shared_ptr<Type> {
67 auto filesystem = std::make_shared<Type>(alias.string(), std::forward<Args>(args)...);
68
69 if (!filesystem || !filesystem->initialize()) {
70 return nullptr;
71 }
72
73 add_filesystem(alias, filesystem);
74
75 return filesystem;
76 }
77
78 template<typename Type, typename... Args>
79 [[nodiscard]] auto create_filesystem(std::string alias, Args&&... args) -> std::shared_ptr<Type> {
80 return create_filesystem<Type>(filesystem::alias{std::move(alias)}, std::forward<Args>(args)...);
81 }
82
83 auto remove_filesystem(const alias& alias, const filesystem_ptr& filesystem) -> void {
84 auto lock = std::scoped_lock{_mutex};
85
86 auto entry = _filesystems.find(alias);
87
88 if (entry == _filesystems.end()) {
89 return;
90 }
91
92 auto& list = entry->second;
93
94 list.erase(std::remove(list.begin(), list.end(), filesystem), list.end());
95
96 if (list.empty()) {
97 _filesystems.erase(entry);
98
99 _sorted_alias.erase(std::remove(_sorted_alias.begin(), _sorted_alias.end(), alias), _sorted_alias.end());
100 }
101 }
102
103 auto remove_filesystem(std::string alias, const filesystem_ptr& filesystem) -> void {
104 remove_filesystem(filesystem::alias{std::move(alias)}, filesystem);
105 }
106
107 [[nodiscard]] auto has_filesystem(const alias& alias, const filesystem_ptr& filesystem) const -> bool {
108 auto lock = std::scoped_lock{_mutex};
109
110 auto entry = _filesystems.find(alias);
111
112 if (entry == _filesystems.end()) {
113 return false;
114 }
115
116 const auto& list = entry->second;
117
118 return std::find(list.begin(), list.end(), filesystem) != list.end();
119 }
120
121 [[nodiscard]] auto has_filesystem(std::string alias, const filesystem_ptr& filesystem) const -> bool {
122 return has_filesystem(filesystem::alias{std::move(alias)}, filesystem);
123 }
124
125 void unregister_alias(const alias& alias) {
126 auto lock = std::scoped_lock{_mutex};
127
128 _filesystems.erase(alias);
129
130 _sorted_alias.erase(std::remove(_sorted_alias.begin(), _sorted_alias.end(), alias), _sorted_alias.end());
131 }
132
133 void unregister_alias(std::string alias) {
134 unregister_alias(filesystem::alias{std::move(alias)});
135 }
136
137 [[nodiscard]] auto is_alias_registered(const alias& alias) const -> bool {
138 auto lock = std::scoped_lock{_mutex};
139
140 return _filesystems.find(alias) != _filesystems.end();
141 }
142
143 [[nodiscard]] auto is_alias_registered(std::string alias) const -> bool {
144 return is_alias_registered(filesystem::alias{std::move(alias)});
145 }
146
147 [[nodiscard]] auto filesystems(const alias& alias) -> std::optional<std::reference_wrapper<const filesystem_list>> {
148 auto lock = std::scoped_lock{_mutex};
149
150 auto entry = _filesystems.find(alias);
151
152 if (entry != _filesystems.end()) {
153 return std::cref(entry->second);
154 }
155
156 return std::nullopt;
157 }
158
159 [[nodiscard]] auto filesystems(std::string alias) -> std::optional<std::reference_wrapper<const filesystem_list>> {
160 return filesystems(filesystem::alias{std::move(alias)});
161 }
162
163 [[nodiscard]] auto open_file(const std::string& path, file_base::mode mode) -> file_ptr {
164 auto lock = std::scoped_lock{_mutex};
165
166 auto result = _visit(path, [&](const filesystem_ptr& filesystem, [[maybe_unused]] auto is_main) -> std::optional<file_ptr> {
167 if (filesystem->exists(path)) {
168 if (auto file = filesystem->open_file(path, mode); file) {
169 return file;
170 }
171 } else if (!filesystem->is_read_only() && file_base::mode_has_flag(mode, file_base::mode::write)) {
172 if (auto file = filesystem->open_file(path, mode); file) {
173 return file;
174 }
175 }
176
177 return std::nullopt;
178 });
179
180 return result.value_or(nullptr);
181 }
182
183 [[nodiscard]] auto create_file(const std::string& path) -> file_ptr {
184 auto lock = std::scoped_lock{_mutex};
185
186 auto result = _visit(path, [&](const filesystem_ptr& filesystem, [[maybe_unused]] auto is_main) -> std::optional<file_ptr> {
187 if (!filesystem->is_read_only()) {
188 if (auto file = filesystem->create_file(path); file) {
189 return file;
190 }
191 }
192
193 return std::nullopt;
194 });
195
196 return result.value_or(nullptr);
197 }
198
199 [[nodiscard]] auto exists(const std::string& path) const -> bool {
200 auto lock = std::scoped_lock{_mutex};
201
202 auto result = _visit(path, [&](const filesystem_ptr& filesystem, [[maybe_unused]] auto is_main) -> std::optional<bool> {
203 if (filesystem->exists(path)) {
204 return true;
205 }
206
207 return std::nullopt;
208 });
209
210 return result.value_or(false);
211 }
212
213 [[nodiscard]] auto all_files() const -> std::vector<std::string> {
214 auto lock = std::scoped_lock{_mutex};
215
216 auto result = std::vector<std::string>{};
217 auto seen = std::unordered_set<std::string>{};
218
219 for (const auto& alias : _sorted_alias) {
220 auto entry = _filesystems.find(alias);
221
222 if (entry == _filesystems.end()) {
223 continue;
224 }
225
226 const auto& list = entry->second;
227
228 for (auto entry = list.rbegin(); entry != list.rend(); ++entry) {
229 auto filesystem = *entry;
230 const auto& files = filesystem->files();
231
232 for (const auto& info : files) {
233 const auto& path = info.virtual_path();
234
235 if (seen.emplace(path).second) {
236 result.push_back(path);
237 }
238 }
239 }
240 }
241
242 std::sort(result.begin(), result.end());
243
244 return result;
245 }
246
247 [[nodiscard]] auto native_path_of(const std::string& virtual_path) const -> std::filesystem::path {
248 auto lock = std::scoped_lock{_mutex};
249
250 for (const auto& alias : _sorted_alias) {
251 const auto& prefix = alias.string();
252
253 if (!virtual_path.starts_with(prefix)) {
254 continue;
255 }
256
257 auto entry = _filesystems.find(alias);
258
259 if (entry == _filesystems.end() || entry->second.empty()) {
260 continue;
261 }
262
263 const auto& base = entry->second.back()->base_path();
264 const auto tail = std::string_view{virtual_path}.substr(prefix.size());
265
266 auto result = std::filesystem::path{base};
267
268 if (!tail.empty()) {
269 result /= std::string{tail};
270 }
271
272 return result;
273 }
274
275 return std::filesystem::path{virtual_path};
276 }
277
278 [[nodiscard]] auto native_path_of(const std::filesystem::path& virtual_path) const -> std::filesystem::path {
279 return native_path_of(virtual_path.string());
280 }
281
282private:
283
284 template<typename Callback>
285 requires (std::is_invocable_v<Callback, const filesystem_ptr&, bool>)
286 auto _visit(const std::string& path, Callback&& callback) const -> std::invoke_result_t<Callback, const filesystem_ptr&, bool> {
287 using result_type = std::invoke_result_t<Callback, const filesystem_ptr&, bool>;
288
289 for (const auto& alias : _sorted_alias) {
290 if (!path.starts_with(alias.string())) {
291 continue;
292 }
293
294 auto entry = _filesystems.find(alias);
295
296 if (entry == _filesystems.end()) {
297 continue;
298 }
299
300 const auto& list = entry->second;
301
302 for (auto entry = list.rbegin(); entry != list.rend(); ++entry) {
303 auto filesystem = *entry;
304 auto is_main = (filesystem == list.front());
305 auto result = std::invoke(callback, filesystem, is_main);
306
307 if (result) {
308 return result;
309 }
310 }
311 }
312
313 return result_type{};
314 }
315
316 filesystem_map _filesystems;
317 std::vector<alias> _sorted_alias;
318
319 mutable std::mutex _mutex;
320
321}; // class virtual_filesystem
322
323using virtual_filesystem_ptr = std::shared_ptr<virtual_filesystem>;
324using virtual_filesystem_weak_ptr = std::weak_ptr<virtual_filesystem>;
325
326} // namespace sbx::filesystem
327
328#endif // LIBSBX_FILESYSTEM_VIRTUAL_FILESYSTEM_HPP_
Definition: alias.hpp:13
Definition: virtual_filesystem.hpp:22