Blender V5.0
asset_catalog_definition_file.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <iostream>
10
11#include "BLI_fileops.hh"
12#include "BLI_path_utils.hh"
13
14#include "CLG_log.h"
15
17
18static CLG_LogRef LOG = {"asset.catalog"};
19
20namespace blender::asset_system {
21
23const std::string AssetCatalogDefinitionFile::VERSION_MARKER = "VERSION ";
24
25const std::string AssetCatalogDefinitionFile::HEADER =
26 "# This is an Asset Catalog Definition file for Blender.\n"
27 "#\n"
28 "# Empty lines and lines starting with `#` will be ignored.\n"
29 "# The first non-ignored line should be the version indicator.\n"
30 "# Other lines are of the format \"UUID:catalog/path/for/assets:simple catalog name\"\n";
31
33{
34 return catalogs_.contains(catalog_id);
35}
36
38{
39 catalogs_.add_new(catalog->catalog_id, catalog);
40}
41
43{
44 catalogs_.add_overwrite(catalog->catalog_id, catalog);
45}
46
48{
49 catalogs_.remove(catalog_id);
50}
51
53 const CatalogFilePath &catalog_definition_file_path,
54 AssetCatalogParsedFn catalog_loaded_callback)
55{
56 fstream infile(catalog_definition_file_path, std::ios::in);
57
58 if (!infile.is_open()) {
59 CLOG_ERROR(&LOG, "%s: unable to open file", catalog_definition_file_path.c_str());
60 return;
61 }
62 bool seen_version_number = false;
63 std::string line;
64 while (std::getline(infile, line)) {
65 const StringRef trimmed_line = StringRef(line).trim();
66 if (trimmed_line.is_empty() || trimmed_line[0] == '#') {
67 continue;
68 }
69
70 if (!seen_version_number) {
71 /* The very first non-ignored line should be the version declaration. */
72 const bool is_valid_version = this->parse_version_line(trimmed_line);
73 if (!is_valid_version) {
74 std::cerr << catalog_definition_file_path
75 << ": first line should be version declaration; ignoring file." << std::endl;
76 break;
77 }
78 seen_version_number = true;
79 continue;
80 }
81
82 std::unique_ptr<AssetCatalog> catalog = this->parse_catalog_line(trimmed_line);
83 if (!catalog) {
84 continue;
85 }
86
87 AssetCatalog *non_owning_ptr = catalog.get();
88 const bool keep_catalog = catalog_loaded_callback(std::move(catalog));
89 if (!keep_catalog) {
90 continue;
91 }
92
93 /* The AssetDefinitionFile should include this catalog when writing it back to disk. */
94 this->add_overwrite(non_owning_ptr);
95 }
96}
97
99{
100 if (!line.startswith(VERSION_MARKER)) {
101 return false;
102 }
103
104 const std::string version_string = line.substr(VERSION_MARKER.length());
105 const int file_version = std::atoi(version_string.c_str());
106
107 /* No versioning, just a blunt check whether it's the right one. */
108 return file_version == SUPPORTED_VERSION;
109}
110
111std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(const StringRef line)
112{
113 const char delim = ':';
114 const int64_t first_delim = line.find_first_of(delim);
115 if (first_delim == StringRef::not_found) {
116 std::cerr << "Invalid catalog line in " << this->file_path << ": " << line << std::endl;
117 return std::unique_ptr<AssetCatalog>(nullptr);
118 }
119
120 /* Parse the catalog ID. */
121 const std::string id_as_string = line.substr(0, first_delim).trim();
122 bUUID catalog_id;
123 const bool uuid_parsed_ok = BLI_uuid_parse_string(&catalog_id, id_as_string.c_str());
124 if (!uuid_parsed_ok) {
125 std::cerr << "Invalid UUID in " << this->file_path << ": " << line << std::endl;
126 return std::unique_ptr<AssetCatalog>(nullptr);
127 }
128
129 /* Parse the path and simple name. */
130 const StringRef path_and_simple_name = line.substr(first_delim + 1);
131 const int64_t second_delim = path_and_simple_name.find_first_of(delim);
132
133 std::string path_in_file;
134 std::string simple_name;
135 if (second_delim == 0) {
136 /* Delimiter as first character means there is no path. These lines are to be ignored. */
137 return std::unique_ptr<AssetCatalog>(nullptr);
138 }
139
140 if (second_delim == StringRef::not_found) {
141 /* No delimiter means no simple name, just treat it as all "path". */
142 path_in_file = path_and_simple_name;
143 simple_name = "";
144 }
145 else {
146 path_in_file = path_and_simple_name.substr(0, second_delim);
147 simple_name = path_and_simple_name.substr(second_delim + 1).trim();
148 }
149
150 AssetCatalogPath catalog_path = path_in_file;
151 return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name);
152}
153
158
160{
161 BLI_assert_msg(!this->file_path.empty(), "Writing to CDF requires its file path to be known");
162 return this->write_to_disk(this->file_path);
163}
164
166{
167 const CatalogFilePath writable_path = dest_file_path + ".writing";
168 const CatalogFilePath backup_path = dest_file_path + "~";
169
170 if (!this->write_to_disk_unsafe(writable_path)) {
171 /* TODO: communicate what went wrong. */
172 return false;
173 }
174 if (BLI_exists(dest_file_path.c_str())) {
175 if (BLI_rename_overwrite(dest_file_path.c_str(), backup_path.c_str())) {
176 /* TODO: communicate what went wrong. */
177 return false;
178 }
179 }
180 if (BLI_rename_overwrite(writable_path.c_str(), dest_file_path.c_str())) {
181 /* TODO: communicate what went wrong. */
182 return false;
183 }
184
185 return true;
186}
187
189{
190 return BLI_exists(this->file_path.c_str());
191}
192
194{
195 char directory[PATH_MAX];
196 BLI_path_split_dir_part(dest_file_path.c_str(), directory, sizeof(directory));
197 if (!ensure_directory_exists(directory)) {
198 /* TODO(Sybren): pass errors to the UI somehow. */
199 return false;
200 }
201
202 fstream output(dest_file_path, std::ios::out);
203
204 /* TODO(@sybren): remember the line ending style that was originally read, then use that to write
205 * the file again. */
206
207 /* Write the header. */
208 output << HEADER;
209 output << "" << std::endl;
210 output << VERSION_MARKER << SUPPORTED_VERSION << std::endl;
211 output << "" << std::endl;
212
213 /* Write the catalogs, ordered by path (primary) and UUID (secondary). */
214 AssetCatalogOrderedSet catalogs_by_path;
215 for (const AssetCatalog *catalog : catalogs_.values()) {
216 if (catalog->flags.is_deleted) {
217 continue;
218 }
219 catalogs_by_path.insert(catalog);
220 }
221
222 for (const AssetCatalog *catalog : catalogs_by_path) {
223 output << catalog->catalog_id << ":" << catalog->path << ":" << catalog->simple_name
224 << std::endl;
225 }
226 output.close();
227 return !output.bad();
228}
229
231 const CatalogFilePath &directory_path) const
232{
233 /* TODO(@sybren): design a way to get such errors presented to users (or ensure that they never
234 * occur). */
235 if (directory_path.empty()) {
236 std::cerr
237 << "AssetCatalogService: no asset library root configured, unable to ensure it exists."
238 << std::endl;
239 return false;
240 }
241
242 if (BLI_exists(directory_path.data())) {
243 if (!BLI_is_dir(directory_path.data())) {
244 std::cerr << "AssetCatalogService: " << directory_path
245 << " exists but is not a directory, this is not a supported situation."
246 << std::endl;
247 return false;
248 }
249
250 /* Root directory exists, work is done. */
251 return true;
252 }
253
254 /* Ensure the root directory exists. */
255 std::error_code err_code;
256 if (!BLI_dir_create_recursive(directory_path.data())) {
257 std::cerr << "AssetCatalogService: error creating directory " << directory_path << ": "
258 << err_code << std::endl;
259 return false;
260 }
261
262 /* Root directory has been created, work is done. */
263 return true;
264}
265
266std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogDefinitionFile::copy_and_remap(
267 const OwningAssetCatalogMap &catalogs, const OwningAssetCatalogMap &deleted_catalogs) const
268{
269 auto copy = std::make_unique<AssetCatalogDefinitionFile>(*this);
270 copy->catalogs_.clear();
271
272 /* Remap pointers of the copy from the original AssetCatalogCollection to the given one. */
273 for (CatalogID catalog_id : catalogs_.keys()) {
274 /* The catalog can be in the regular or the deleted map. */
275 const std::unique_ptr<AssetCatalog> *remapped_catalog_uptr_ptr = catalogs.lookup_ptr(
276 catalog_id);
277 if (remapped_catalog_uptr_ptr) {
278 copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
279 continue;
280 }
281
282 remapped_catalog_uptr_ptr = deleted_catalogs.lookup_ptr(catalog_id);
283 if (remapped_catalog_uptr_ptr) {
284 copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
285 continue;
286 }
287
288 BLI_assert_msg(false, "A CDF should only reference known catalogs.");
289 }
290
291 return copy;
292}
293
294} // namespace blender::asset_system
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:360
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:414
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:443
int BLI_rename_overwrite(const char *from, const char *to) ATTR_NONNULL()
Definition fileops_c.cc:528
#define PATH_MAX
Definition BLI_fileops.h:26
File and directory operations.
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) ATTR_NONNULL()
Definition uuid.cc:112
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:188
long long int int64_t
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:508
static constexpr int64_t not_found
constexpr bool is_empty() const
constexpr StringRef substr(int64_t start, int64_t size) const
constexpr bool startswith(StringRef prefix) const
constexpr int64_t find_first_of(StringRef chars, int64_t pos=0) const
constexpr StringRef trim() const
bool ensure_directory_exists(const CatalogFilePath &directory_path) const
void parse_catalog_file(const CatalogFilePath &catalog_definition_file_path, AssetCatalogParsedFn catalog_loaded_callback)
std::unique_ptr< AssetCatalogDefinitionFile > copy_and_remap(const OwningAssetCatalogMap &catalogs, const OwningAssetCatalogMap &deleted_catalogs) const
bool write_to_disk_unsafe(const CatalogFilePath &dest_file_path) const
std::unique_ptr< AssetCatalog > parse_catalog_line(StringRef line)
FunctionRef< bool(std::unique_ptr< AssetCatalog >)> AssetCatalogParsedFn
#define output
#define LOG(level)
Definition log.h:97
Map< CatalogID, std::unique_ptr< AssetCatalog > > OwningAssetCatalogMap
std::set< const AssetCatalog *, AssetCatalogLessThan > AssetCatalogOrderedSet
static void copy(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node)