Blender V4.3
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
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_system.asset_catalog_definition_file"};
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
155 : file_path(file_path)
156{
157}
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 char directory[PATH_MAX];
191 BLI_path_split_dir_part(dest_file_path.c_str(), directory, sizeof(directory));
192 if (!ensure_directory_exists(directory)) {
193 /* TODO(Sybren): pass errors to the UI somehow. */
194 return false;
195 }
196
197 fstream output(dest_file_path, std::ios::out);
198
199 /* TODO(@sybren): remember the line ending style that was originally read, then use that to write
200 * the file again. */
201
202 /* Write the header. */
203 output << HEADER;
204 output << "" << std::endl;
205 output << VERSION_MARKER << SUPPORTED_VERSION << std::endl;
206 output << "" << std::endl;
207
208 /* Write the catalogs, ordered by path (primary) and UUID (secondary). */
209 AssetCatalogOrderedSet catalogs_by_path;
210 for (const AssetCatalog *catalog : catalogs_.values()) {
211 if (catalog->flags.is_deleted) {
212 continue;
213 }
214 catalogs_by_path.insert(catalog);
215 }
216
217 for (const AssetCatalog *catalog : catalogs_by_path) {
218 output << catalog->catalog_id << ":" << catalog->path << ":" << catalog->simple_name
219 << std::endl;
220 }
221 output.close();
222 return !output.bad();
223}
224
226 const CatalogFilePath &directory_path) const
227{
228 /* TODO(@sybren): design a way to get such errors presented to users (or ensure that they never
229 * occur). */
230 if (directory_path.empty()) {
231 std::cerr
232 << "AssetCatalogService: no asset library root configured, unable to ensure it exists."
233 << std::endl;
234 return false;
235 }
236
237 if (BLI_exists(directory_path.data())) {
238 if (!BLI_is_dir(directory_path.data())) {
239 std::cerr << "AssetCatalogService: " << directory_path
240 << " exists but is not a directory, this is not a supported situation."
241 << std::endl;
242 return false;
243 }
244
245 /* Root directory exists, work is done. */
246 return true;
247 }
248
249 /* Ensure the root directory exists. */
250 std::error_code err_code;
251 if (!BLI_dir_create_recursive(directory_path.data())) {
252 std::cerr << "AssetCatalogService: error creating directory " << directory_path << ": "
253 << err_code << std::endl;
254 return false;
255 }
256
257 /* Root directory has been created, work is done. */
258 return true;
259}
260
261std::unique_ptr<AssetCatalogDefinitionFile> AssetCatalogDefinitionFile::copy_and_remap(
262 const OwningAssetCatalogMap &catalogs, const OwningAssetCatalogMap &deleted_catalogs) const
263{
264 auto copy = std::make_unique<AssetCatalogDefinitionFile>(*this);
265 copy->catalogs_.clear();
266
267 /* Remap pointers of the copy from the original AssetCatalogCollection to the given one. */
268 for (CatalogID catalog_id : catalogs_.keys()) {
269 /* The catalog can be in the regular or the deleted map. */
270 const std::unique_ptr<AssetCatalog> *remapped_catalog_uptr_ptr = catalogs.lookup_ptr(
271 catalog_id);
272 if (remapped_catalog_uptr_ptr) {
273 copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
274 continue;
275 }
276
277 remapped_catalog_uptr_ptr = deleted_catalogs.lookup_ptr(catalog_id);
278 if (remapped_catalog_uptr_ptr) {
279 copy->catalogs_.add_new(catalog_id, remapped_catalog_uptr_ptr->get());
280 continue;
281 }
282
283 BLI_assert_msg(false, "A CDF should only reference known catalogs.");
284 }
285
286 return copy;
287}
288
289} // namespace blender::asset_system
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:350
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:391
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:433
int BLI_rename_overwrite(const char *from, const char *to) ATTR_NONNULL()
Definition fileops_c.cc:505
#define PATH_MAX
Definition BLI_fileops.h:30
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:182
static CLG_LogRef LOG
#define output
const Value * lookup_ptr(const Key &key) const
Definition BLI_map.hh:484
static constexpr int64_t not_found
constexpr bool is_empty() const
constexpr StringRef substr(int64_t start, int64_t size) 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)
#define LOG(severity)
Definition log.h:33
std::set< const AssetCatalog *, AssetCatalogLessThan > AssetCatalogOrderedSet
static void copy(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node)
__int64 int64_t
Definition stdint.h:89
Universally Unique Identifier according to RFC4122.