Asset Catalog: introduce AssetCatalogPath
class
So far we have used `std::string` for asset catalog paths. Some operations are better described on a dedicated class for this, though. This commits switches catalog paths from using `std::string` to a dedicated `blender::bke::AssetCatalogPath` class. The `using CatalogPath = AssetCatalogPath` alias is still there, and will be removed in a following cleanup commit. New `AssetCatalogPath` code reviewed by @severin in D12710.
This commit is contained in:
@@ -30,6 +30,8 @@
|
||||
#include "BLI_uuid.h"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BKE_asset_catalog_path.hh"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
@@ -38,7 +40,7 @@
|
||||
namespace blender::bke {
|
||||
|
||||
using CatalogID = bUUID;
|
||||
using CatalogPath = std::string;
|
||||
using CatalogPath = AssetCatalogPath;
|
||||
using CatalogPathComponent = std::string;
|
||||
/* Would be nice to be able to use `std::filesystem::path` for this, but it's currently not
|
||||
* available on the minimum macOS target version. */
|
||||
@@ -52,7 +54,6 @@ class AssetCatalogTree;
|
||||
* directory hierarchy). */
|
||||
class AssetCatalogService {
|
||||
public:
|
||||
static const char PATH_SEPARATOR;
|
||||
static const CatalogFilePath DEFAULT_CATALOG_FILENAME;
|
||||
|
||||
public:
|
||||
@@ -297,15 +298,6 @@ class AssetCatalog {
|
||||
bool is_deleted = false;
|
||||
} flags;
|
||||
|
||||
/**
|
||||
* \return true only if this catalog's path is contained within the given path.
|
||||
* When this catalog's path is equal to the given path, return true as well.
|
||||
*
|
||||
* Note that non-normalized paths (so for example starting or ending with a slash) are not
|
||||
* supported, and result in undefined behavior.
|
||||
*/
|
||||
bool is_contained_in(const CatalogPath &other_path) const;
|
||||
|
||||
/**
|
||||
* Create a new Catalog with the given path, auto-generating a sensible catalog simple-name.
|
||||
*
|
||||
@@ -313,7 +305,6 @@ class AssetCatalog {
|
||||
* `AssetCatalog`'s path differ from the given one.
|
||||
*/
|
||||
static std::unique_ptr<AssetCatalog> from_path(const CatalogPath &path);
|
||||
static CatalogPath cleanup_path(const CatalogPath &path);
|
||||
|
||||
protected:
|
||||
/** Generate a sensible catalog ID for the given path. */
|
||||
|
138
source/blender/blenkernel/BKE_asset_catalog_path.hh
Normal file
138
source/blender/blenkernel/BKE_asset_catalog_path.hh
Normal file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __cplusplus
|
||||
# error This is a C++ header.
|
||||
#endif
|
||||
|
||||
#include "BLI_function_ref.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "BLI_sys_types.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
/**
|
||||
* Location of an Asset Catalog in the catalog tree, denoted by slash-separated path components.
|
||||
*
|
||||
* Each path component is a string that is not allowed to have slashes or colons. The latter is to
|
||||
* make things easy to save in the colon-delimited Catalog Definition File format.
|
||||
*
|
||||
* The path of a catalog determines where in the catalog hierarchy the catalog is shown. Examples
|
||||
* are "Characters/Ellie/Poses/Hand" or "Kitbash/City/Skyscrapers". The path looks like a
|
||||
* filesystem path, with a few differences:
|
||||
*
|
||||
* - Only slashes are used as path component separators.
|
||||
* - All paths are absolute, so there is no need for a leading slash.
|
||||
*
|
||||
* See https://wiki.blender.org/wiki/Source/Architecture/Asset_System/Catalogs
|
||||
*
|
||||
* Paths are stored as byte sequences, and assumed to be UTF-8.
|
||||
*/
|
||||
class AssetCatalogPath {
|
||||
friend std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The path itself, such as "Agents/Secret/327".
|
||||
*/
|
||||
std::string path_;
|
||||
|
||||
public:
|
||||
static const char SEPARATOR;
|
||||
|
||||
AssetCatalogPath() = delete;
|
||||
AssetCatalogPath(StringRef path);
|
||||
AssetCatalogPath(const std::string &path);
|
||||
AssetCatalogPath(const char *path);
|
||||
AssetCatalogPath(const AssetCatalogPath &other_path);
|
||||
AssetCatalogPath(AssetCatalogPath &&other_path) noexcept;
|
||||
~AssetCatalogPath() = default;
|
||||
|
||||
uint64_t hash() const;
|
||||
uint64_t length() const; /* Length of the path in bytes. */
|
||||
|
||||
/** C-string representation of the path. */
|
||||
const char *c_str() const;
|
||||
const std::string &str() const;
|
||||
|
||||
/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
|
||||
* Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
|
||||
bool operator==(const AssetCatalogPath &other_path) const;
|
||||
bool operator!=(const AssetCatalogPath &other_path) const;
|
||||
bool operator<(const AssetCatalogPath &other_path) const;
|
||||
AssetCatalogPath &operator=(const AssetCatalogPath &other_path) = default;
|
||||
AssetCatalogPath &operator=(AssetCatalogPath &&other_path) = default;
|
||||
|
||||
/** Concatenate two paths, returning the new path. */
|
||||
AssetCatalogPath operator/(const AssetCatalogPath &path_to_append) const;
|
||||
|
||||
/* False when the path is empty, true otherwise. */
|
||||
operator bool() const;
|
||||
|
||||
/**
|
||||
* Clean up the path. This ensures:
|
||||
* - Every path component is stripped of its leading/trailing spaces.
|
||||
* - Empty components (caused by double slashes or leading/trailing slashes) are removed.
|
||||
* - Invalid characters are replaced with valid ones.
|
||||
*/
|
||||
[[nodiscard]] AssetCatalogPath cleanup() const;
|
||||
|
||||
/**
|
||||
* \return true only if the given path is a parent of this catalog's path.
|
||||
* When this catalog's path is equal to the given path, return true as well.
|
||||
* In other words, this defines a weak subset.
|
||||
*
|
||||
* True: "some/path/there" is contained in "some/path" and "some".
|
||||
* False: "path/there" is not contained in "some/path/there".
|
||||
*
|
||||
* Note that non-cleaned-up paths (so for example starting or ending with a
|
||||
* slash) are not supported, and result in undefined behavior.
|
||||
*/
|
||||
bool is_contained_in(const AssetCatalogPath &other_path) const;
|
||||
|
||||
/**
|
||||
* Change the initial part of the path from `from_path` to `to_path`.
|
||||
* If this path does not start with `from_path`, return an empty path as result.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* AssetCatalogPath path("some/path/to/some/catalog");
|
||||
* path.rebase("some/path", "new/base") -> "new/base/to/some/catalog"
|
||||
*/
|
||||
AssetCatalogPath rebase(const AssetCatalogPath &from_path,
|
||||
const AssetCatalogPath &to_path) const;
|
||||
|
||||
/** Call the callback function for each path component, in left-to-right order. */
|
||||
using ComponentIteratorFn = FunctionRef<void(StringRef component_name, bool is_last_component)>;
|
||||
void iterate_components(ComponentIteratorFn callback) const;
|
||||
|
||||
protected:
|
||||
/** Strip leading/trailing spaces and replace disallowed characters. */
|
||||
static std::string cleanup_component(StringRef component_name);
|
||||
};
|
||||
|
||||
/** Output the path as string. */
|
||||
std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append);
|
||||
|
||||
} // namespace blender::bke
|
@@ -85,6 +85,7 @@ set(SRC
|
||||
intern/armature_update.c
|
||||
intern/asset.cc
|
||||
intern/asset_catalog.cc
|
||||
intern/asset_catalog_path.cc
|
||||
intern/asset_library.cc
|
||||
intern/attribute.c
|
||||
intern/attribute_access.cc
|
||||
@@ -306,6 +307,7 @@ set(SRC
|
||||
BKE_armature.hh
|
||||
BKE_asset.h
|
||||
BKE_asset_catalog.hh
|
||||
BKE_asset_catalog_path.hh
|
||||
BKE_asset_library.h
|
||||
BKE_asset_library.hh
|
||||
BKE_attribute.h
|
||||
@@ -789,6 +791,7 @@ if(WITH_GTESTS)
|
||||
intern/action_test.cc
|
||||
intern/armature_test.cc
|
||||
intern/asset_catalog_test.cc
|
||||
intern/asset_catalog_path_test.cc
|
||||
intern/asset_library_test.cc
|
||||
intern/asset_test.cc
|
||||
intern/cryptomatte_test.cc
|
||||
|
@@ -38,7 +38,6 @@
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
const char AssetCatalogService::PATH_SEPARATOR = '/';
|
||||
const CatalogFilePath AssetCatalogService::DEFAULT_CATALOG_FILENAME = "blender_assets.cats.txt";
|
||||
|
||||
/* For now this is the only version of the catalog definition files that is supported.
|
||||
@@ -115,12 +114,12 @@ void AssetCatalogService::update_catalog_path(CatalogID catalog_id,
|
||||
|
||||
for (auto &catalog_uptr : catalogs_.values()) {
|
||||
AssetCatalog *cat = catalog_uptr.get();
|
||||
if (!cat->is_contained_in(old_cat_path)) {
|
||||
|
||||
const CatalogPath new_path = cat->path.rebase(old_cat_path, new_catalog_path);
|
||||
if (!new_path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const CatalogPath path_suffix = cat->path.substr(old_cat_path.length());
|
||||
cat->path = new_catalog_path + path_suffix;
|
||||
cat->path = new_path;
|
||||
}
|
||||
|
||||
this->rebuild_tree();
|
||||
@@ -319,8 +318,7 @@ CatalogFilePath AssetCatalogService::find_suitable_cdf_path_for_writing(
|
||||
/* - There's no definition file next to the .blend file.
|
||||
* -> Ask the asset library API for an appropriate location. */
|
||||
char suitable_root_path[PATH_MAX];
|
||||
BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(),
|
||||
suitable_root_path);
|
||||
BKE_asset_library_find_suitable_root_path_from_path(blend_file_path.c_str(), suitable_root_path);
|
||||
char asset_lib_cdf_path[PATH_MAX];
|
||||
BLI_path_join(asset_lib_cdf_path,
|
||||
sizeof(asset_lib_cdf_path),
|
||||
@@ -382,9 +380,9 @@ StringRef AssetCatalogTreeItem::get_name() const
|
||||
|
||||
CatalogPath AssetCatalogTreeItem::catalog_path() const
|
||||
{
|
||||
std::string current_path = name_;
|
||||
CatalogPath current_path = name_;
|
||||
for (const AssetCatalogTreeItem *parent = parent_; parent; parent = parent->parent_) {
|
||||
current_path = parent->name_ + AssetCatalogService::PATH_SEPARATOR + current_path;
|
||||
current_path = CatalogPath(parent->name_) / current_path;
|
||||
}
|
||||
return current_path;
|
||||
}
|
||||
@@ -405,32 +403,6 @@ bool AssetCatalogTreeItem::has_children() const
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Iterate over path components, calling \a callback for each component. E.g. "just/some/path"
|
||||
* iterates over "just", then "some" then "path".
|
||||
*/
|
||||
static void iterate_over_catalog_path_components(
|
||||
const CatalogPath &path,
|
||||
FunctionRef<void(StringRef component_name, bool is_last_component)> callback)
|
||||
{
|
||||
const char *next_slash_ptr;
|
||||
|
||||
for (const char *path_component = path.data(); path_component && path_component[0];
|
||||
/* Jump to one after the next slash if there is any. */
|
||||
path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) {
|
||||
next_slash_ptr = BLI_path_slash_find(path_component);
|
||||
|
||||
const bool is_last_component = next_slash_ptr == nullptr;
|
||||
/* Note that this won't be null terminated. */
|
||||
const StringRef component_name = is_last_component ?
|
||||
path_component :
|
||||
StringRef(path_component,
|
||||
next_slash_ptr - path_component);
|
||||
|
||||
callback(component_name, is_last_component);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
|
||||
{
|
||||
const AssetCatalogTreeItem *parent = nullptr;
|
||||
@@ -438,30 +410,29 @@ void AssetCatalogTree::insert_item(const AssetCatalog &catalog)
|
||||
* added to (if not there yet). */
|
||||
AssetCatalogTreeItem::ChildMap *current_item_children = &root_items_;
|
||||
|
||||
BLI_assert_msg(!ELEM(catalog.path[0], '/', '\\'),
|
||||
BLI_assert_msg(!ELEM(catalog.path.str()[0], '/', '\\'),
|
||||
"Malformed catalog path; should not start with a separator");
|
||||
|
||||
const CatalogID nil_id{};
|
||||
|
||||
iterate_over_catalog_path_components(
|
||||
catalog.path, [&](StringRef component_name, const bool is_last_component) {
|
||||
/* Insert new tree element - if no matching one is there yet! */
|
||||
auto [key_and_item, was_inserted] = current_item_children->emplace(
|
||||
component_name,
|
||||
AssetCatalogTreeItem(
|
||||
component_name, is_last_component ? catalog.catalog_id : nil_id, parent));
|
||||
AssetCatalogTreeItem &item = key_and_item->second;
|
||||
catalog.path.iterate_components([&](StringRef component_name, const bool is_last_component) {
|
||||
/* Insert new tree element - if no matching one is there yet! */
|
||||
auto [key_and_item, was_inserted] = current_item_children->emplace(
|
||||
component_name,
|
||||
AssetCatalogTreeItem(
|
||||
component_name, is_last_component ? catalog.catalog_id : nil_id, parent));
|
||||
AssetCatalogTreeItem &item = key_and_item->second;
|
||||
|
||||
/* If full path of this catalog already exists as parent path of a previously read catalog,
|
||||
* we can ensure this tree item's UUID is set here. */
|
||||
if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) {
|
||||
item.catalog_id_ = catalog.catalog_id;
|
||||
}
|
||||
/* If full path of this catalog already exists as parent path of a previously read catalog,
|
||||
* we can ensure this tree item's UUID is set here. */
|
||||
if (is_last_component && BLI_uuid_is_nil(item.catalog_id_)) {
|
||||
item.catalog_id_ = catalog.catalog_id;
|
||||
}
|
||||
|
||||
/* Walk further into the path (no matter if a new item was created or not). */
|
||||
parent = &item;
|
||||
current_item_children = &item.children_;
|
||||
});
|
||||
/* Walk further into the path (no matter if a new item was created or not). */
|
||||
parent = &item;
|
||||
current_item_children = &item.children_;
|
||||
});
|
||||
}
|
||||
|
||||
void AssetCatalogTree::foreach_item(AssetCatalogTreeItem::ItemIterFn callback)
|
||||
@@ -592,7 +563,7 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con
|
||||
const StringRef path_and_simple_name = line.substr(first_delim + 1);
|
||||
const int64_t second_delim = path_and_simple_name.find_first_of(delim);
|
||||
|
||||
CatalogPath catalog_path;
|
||||
std::string path_in_file;
|
||||
std::string simple_name;
|
||||
if (second_delim == 0) {
|
||||
/* Delimiter as first character means there is no path. These lines are to be ignored. */
|
||||
@@ -601,16 +572,16 @@ std::unique_ptr<AssetCatalog> AssetCatalogDefinitionFile::parse_catalog_line(con
|
||||
|
||||
if (second_delim == StringRef::not_found) {
|
||||
/* No delimiter means no simple name, just treat it as all "path". */
|
||||
catalog_path = path_and_simple_name;
|
||||
path_in_file = path_and_simple_name;
|
||||
simple_name = "";
|
||||
}
|
||||
else {
|
||||
catalog_path = path_and_simple_name.substr(0, second_delim);
|
||||
path_in_file = path_and_simple_name.substr(0, second_delim);
|
||||
simple_name = path_and_simple_name.substr(second_delim + 1).trim();
|
||||
}
|
||||
|
||||
catalog_path = AssetCatalog::cleanup_path(catalog_path);
|
||||
return std::make_unique<AssetCatalog>(catalog_id, catalog_path, simple_name);
|
||||
CatalogPath catalog_path = path_in_file;
|
||||
return std::make_unique<AssetCatalog>(catalog_id, catalog_path.cleanup(), simple_name);
|
||||
}
|
||||
|
||||
bool AssetCatalogDefinitionFile::write_to_disk() const
|
||||
@@ -724,7 +695,7 @@ AssetCatalog::AssetCatalog(const CatalogID catalog_id,
|
||||
|
||||
std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const CatalogPath &path)
|
||||
{
|
||||
const CatalogPath clean_path = cleanup_path(path);
|
||||
const CatalogPath clean_path = path.cleanup();
|
||||
const CatalogID cat_id = BLI_uuid_generate_random();
|
||||
const std::string simple_name = sensible_simple_name_for_path(clean_path);
|
||||
auto catalog = std::make_unique<AssetCatalog>(cat_id, clean_path, simple_name);
|
||||
@@ -733,8 +704,8 @@ std::unique_ptr<AssetCatalog> AssetCatalog::from_path(const CatalogPath &path)
|
||||
|
||||
std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path)
|
||||
{
|
||||
std::string name = path;
|
||||
std::replace(name.begin(), name.end(), AssetCatalogService::PATH_SEPARATOR, '-');
|
||||
std::string name = path.str();
|
||||
std::replace(name.begin(), name.end(), CatalogPath::SEPARATOR, '-');
|
||||
if (name.length() < MAX_NAME - 1) {
|
||||
return name;
|
||||
}
|
||||
@@ -744,33 +715,4 @@ std::string AssetCatalog::sensible_simple_name_for_path(const CatalogPath &path)
|
||||
return "..." + name.substr(name.length() - 60);
|
||||
}
|
||||
|
||||
CatalogPath AssetCatalog::cleanup_path(const CatalogPath &path)
|
||||
{
|
||||
/* TODO(@sybren): maybe go over each element of the path, and trim those? */
|
||||
CatalogPath clean_path = StringRef(path).trim().trim(AssetCatalogService::PATH_SEPARATOR).trim();
|
||||
return clean_path;
|
||||
}
|
||||
|
||||
bool AssetCatalog::is_contained_in(const CatalogPath &other_path) const
|
||||
{
|
||||
if (other_path.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->path == other_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* To be a child path of 'other_path', our path must be at least a separator and another
|
||||
* character longer. */
|
||||
if (this->path.length() < other_path.length() + 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const StringRef this_path(this->path);
|
||||
const bool prefix_ok = this_path.startswith(other_path);
|
||||
const char next_char = this_path[other_path.length()];
|
||||
return prefix_ok && next_char == AssetCatalogService::PATH_SEPARATOR;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
||||
|
220
source/blender/blenkernel/intern/asset_catalog_path.cc
Normal file
220
source/blender/blenkernel/intern/asset_catalog_path.cc
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* \ingroup bke
|
||||
*/
|
||||
|
||||
#include "BKE_asset_catalog_path.hh"
|
||||
|
||||
#include "BLI_path_util.h"
|
||||
|
||||
namespace blender::bke {
|
||||
|
||||
const char AssetCatalogPath::SEPARATOR = '/';
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(const std::string &path) : path_(path)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(StringRef path) : path_(path)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(const char *path) : path_(path)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(const AssetCatalogPath &other_path) : path_(other_path.path_)
|
||||
{
|
||||
}
|
||||
|
||||
AssetCatalogPath::AssetCatalogPath(AssetCatalogPath &&other_path) noexcept
|
||||
: path_(std::move(other_path.path_))
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t AssetCatalogPath::hash() const
|
||||
{
|
||||
std::hash<std::string> hasher{};
|
||||
return hasher(this->path_);
|
||||
}
|
||||
|
||||
uint64_t AssetCatalogPath::length() const
|
||||
{
|
||||
return this->path_.length();
|
||||
}
|
||||
|
||||
const char *AssetCatalogPath::c_str() const
|
||||
{
|
||||
return this->path_.c_str();
|
||||
}
|
||||
|
||||
const std::string &AssetCatalogPath::str() const
|
||||
{
|
||||
return this->path_;
|
||||
}
|
||||
|
||||
/* In-class operators, because of the implicit `AssetCatalogPath(StringRef)` constructor.
|
||||
* Otherwise `string == string` could cast both sides to `AssetCatalogPath`. */
|
||||
bool AssetCatalogPath::operator==(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
return this->path_ == other_path.path_;
|
||||
}
|
||||
|
||||
bool AssetCatalogPath::operator!=(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
return !(*this == other_path);
|
||||
}
|
||||
|
||||
bool AssetCatalogPath::operator<(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
return this->path_ < other_path.path_;
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::operator/(const AssetCatalogPath &path_to_append) const
|
||||
{
|
||||
/* `"" / "path"` or `"path" / ""` should just result in `"path"` */
|
||||
if (!*this) {
|
||||
return path_to_append;
|
||||
}
|
||||
if (!path_to_append) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::stringstream new_path;
|
||||
new_path << this->path_ << SEPARATOR << path_to_append.path_;
|
||||
return AssetCatalogPath(new_path.str());
|
||||
}
|
||||
|
||||
AssetCatalogPath::operator bool() const
|
||||
{
|
||||
return !this->path_.empty();
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const AssetCatalogPath &path_to_append)
|
||||
{
|
||||
stream << path_to_append.path_;
|
||||
return stream;
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::cleanup() const
|
||||
{
|
||||
std::stringstream clean_components;
|
||||
bool first_component_seen = false;
|
||||
|
||||
this->iterate_components([&clean_components, &first_component_seen](StringRef component_name,
|
||||
bool /*is_last_component*/) {
|
||||
const std::string clean_component = cleanup_component(component_name);
|
||||
|
||||
if (clean_component.empty()) {
|
||||
/* These are caused by leading, trailing, or double slashes. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* If a previous path component has been streamed already, we need a path separator. This
|
||||
* cannot use the `is_last_component` boolean, because the last component might be skipped due
|
||||
* to the condition above. */
|
||||
if (first_component_seen) {
|
||||
clean_components << SEPARATOR;
|
||||
}
|
||||
first_component_seen = true;
|
||||
|
||||
clean_components << clean_component;
|
||||
});
|
||||
|
||||
return AssetCatalogPath(clean_components.str());
|
||||
}
|
||||
|
||||
std::string AssetCatalogPath::cleanup_component(StringRef component)
|
||||
{
|
||||
std::string cleaned = component.trim();
|
||||
/* Replace colons with something else, as those are used in the CDF file as delimiter. */
|
||||
std::replace(cleaned.begin(), cleaned.end(), ':', '-');
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
bool AssetCatalogPath::is_contained_in(const AssetCatalogPath &other_path) const
|
||||
{
|
||||
if (!other_path) {
|
||||
/* The empty path contains all other paths. */
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this->path_ == other_path.path_) {
|
||||
/* Weak is-in relation: equal paths contain each other. */
|
||||
return true;
|
||||
}
|
||||
|
||||
/* To be a child path of 'other_path', our path must be at least a separator and another
|
||||
* character longer. */
|
||||
if (this->length() < other_path.length() + 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Create StringRef to be able to use .startswith(). */
|
||||
const StringRef this_path(this->path_);
|
||||
const bool prefix_ok = this_path.startswith(other_path.path_);
|
||||
const char next_char = this_path[other_path.length()];
|
||||
return prefix_ok && next_char == SEPARATOR;
|
||||
}
|
||||
|
||||
void AssetCatalogPath::iterate_components(ComponentIteratorFn callback) const
|
||||
{
|
||||
const char *next_slash_ptr;
|
||||
|
||||
for (const char *path_component = this->path_.data(); path_component && path_component[0];
|
||||
/* Jump to one after the next slash if there is any. */
|
||||
path_component = next_slash_ptr ? next_slash_ptr + 1 : nullptr) {
|
||||
next_slash_ptr = BLI_path_slash_find(path_component);
|
||||
|
||||
const bool is_last_component = next_slash_ptr == nullptr;
|
||||
/* Note that this won't be null terminated. */
|
||||
const StringRef component_name = is_last_component ?
|
||||
path_component :
|
||||
StringRef(path_component,
|
||||
next_slash_ptr - path_component);
|
||||
|
||||
callback(component_name, is_last_component);
|
||||
}
|
||||
}
|
||||
|
||||
AssetCatalogPath AssetCatalogPath::rebase(const AssetCatalogPath &from_path,
|
||||
const AssetCatalogPath &to_path) const
|
||||
{
|
||||
if (!from_path) {
|
||||
if (!to_path) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
return to_path / *this;
|
||||
}
|
||||
|
||||
if (!this->is_contained_in(from_path)) {
|
||||
return AssetCatalogPath("");
|
||||
}
|
||||
|
||||
if (*this == from_path) {
|
||||
/* Early return, because otherwise the length+1 below is going to cause problems. */
|
||||
return to_path;
|
||||
}
|
||||
|
||||
/* When from_path = "abcd", we need to skip "abcd/" to get the rest of the path, hence the +1. */
|
||||
const StringRef suffix = StringRef(this->path_).substr(from_path.length() + 1);
|
||||
const AssetCatalogPath path_suffix(suffix);
|
||||
return to_path / path_suffix;
|
||||
}
|
||||
|
||||
} // namespace blender::bke
|
234
source/blender/blenkernel/intern/asset_catalog_path_test.cc
Normal file
234
source/blender/blenkernel/intern/asset_catalog_path_test.cc
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* The Original Code is Copyright (C) 2020 Blender Foundation
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
#include "BKE_asset_catalog_path.hh"
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "testing/testing.h"
|
||||
|
||||
namespace blender::bke::tests {
|
||||
|
||||
TEST(AssetCatalogPathTest, construction)
|
||||
{
|
||||
AssetCatalogPath from_char_literal("the/path");
|
||||
|
||||
const std::string str_const = "the/path";
|
||||
AssetCatalogPath from_string_constant(str_const);
|
||||
|
||||
std::string str_variable = "the/path";
|
||||
AssetCatalogPath from_string_variable(str_variable);
|
||||
|
||||
std::string long_string = "this is a long/string/with/a/path in the middle";
|
||||
StringRef long_string_ref(long_string);
|
||||
StringRef middle_bit = long_string_ref.substr(10, 23);
|
||||
AssetCatalogPath from_string_ref(middle_bit);
|
||||
EXPECT_EQ(from_string_ref, "long/string/with/a/path");
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, length)
|
||||
{
|
||||
const AssetCatalogPath one("1");
|
||||
EXPECT_EQ(1, one.length());
|
||||
|
||||
const AssetCatalogPath empty("");
|
||||
EXPECT_EQ(0, empty.length());
|
||||
|
||||
const AssetCatalogPath utf8("some/родитель");
|
||||
EXPECT_EQ(21, utf8.length()) << "13 characters should be 21 bytes.";
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, comparison_operators)
|
||||
{
|
||||
const AssetCatalogPath empty("");
|
||||
const AssetCatalogPath the_path("the/path");
|
||||
const AssetCatalogPath the_path_child("the/path/child");
|
||||
const AssetCatalogPath unrelated_path("unrelated/path");
|
||||
const AssetCatalogPath other_instance_same_path("the/path");
|
||||
|
||||
EXPECT_LT(empty, the_path);
|
||||
EXPECT_LT(the_path, the_path_child);
|
||||
EXPECT_LT(the_path, unrelated_path);
|
||||
|
||||
EXPECT_EQ(empty, empty) << "Identical empty instances should compare equal.";
|
||||
EXPECT_EQ(empty, "") << "Comparison to empty string should be possible.";
|
||||
EXPECT_EQ(the_path, the_path) << "Identical non-empty instances should compare equal.";
|
||||
EXPECT_EQ(the_path, "the/path") << "Comparison to string should be possible.";
|
||||
EXPECT_EQ(the_path, other_instance_same_path)
|
||||
<< "Different instances with equal path should compare equal.";
|
||||
|
||||
EXPECT_NE(the_path, the_path_child);
|
||||
EXPECT_NE(the_path, unrelated_path);
|
||||
EXPECT_NE(the_path, empty);
|
||||
|
||||
EXPECT_FALSE(empty);
|
||||
EXPECT_TRUE(the_path);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, move_semantics)
|
||||
{
|
||||
AssetCatalogPath source_path("source/path");
|
||||
EXPECT_TRUE(source_path);
|
||||
|
||||
AssetCatalogPath dest_path = std::move(source_path);
|
||||
EXPECT_FALSE(source_path);
|
||||
EXPECT_TRUE(dest_path);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, concatenation)
|
||||
{
|
||||
AssetCatalogPath some_parent("some/родитель");
|
||||
AssetCatalogPath child = some_parent / "ребенок";
|
||||
|
||||
EXPECT_EQ(some_parent, "some/родитель")
|
||||
<< "Appending a child path should not modify the parent.";
|
||||
EXPECT_EQ(child, "some/родитель/ребенок");
|
||||
|
||||
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
|
||||
EXPECT_EQ(appended_compound_path, "some/родитель/ребенок/внук");
|
||||
|
||||
AssetCatalogPath empty("");
|
||||
AssetCatalogPath child_of_the_void = empty / "child";
|
||||
EXPECT_EQ(child_of_the_void, "child")
|
||||
<< "Appending to an empty path should not create an initial slash.";
|
||||
|
||||
AssetCatalogPath parent_of_the_void = some_parent / empty;
|
||||
EXPECT_EQ(parent_of_the_void, "some/родитель")
|
||||
<< "Prepending to an empty path should not create a trailing slash.";
|
||||
|
||||
std::string subpath = "child";
|
||||
AssetCatalogPath concatenated_with_string = some_parent / subpath;
|
||||
EXPECT_EQ(concatenated_with_string, "some/родитель/child");
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, hashable)
|
||||
{
|
||||
AssetCatalogPath path("heyyyyy");
|
||||
|
||||
std::set<AssetCatalogPath> path_std_set;
|
||||
path_std_set.insert(path);
|
||||
|
||||
blender::Set<AssetCatalogPath> path_blender_set;
|
||||
path_blender_set.add(path);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, stream_operator)
|
||||
{
|
||||
AssetCatalogPath path("путь/в/Пермь");
|
||||
std::stringstream sstream;
|
||||
sstream << path;
|
||||
EXPECT_EQ("путь/в/Пермь", sstream.str());
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, is_contained_in)
|
||||
{
|
||||
const AssetCatalogPath catpath("simple/path/child");
|
||||
EXPECT_FALSE(catpath.is_contained_in("unrelated"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("sim"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/pathx"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/path/c"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/path/child/grandchild"));
|
||||
EXPECT_FALSE(catpath.is_contained_in("simple/path/"))
|
||||
<< "Non-normalized paths are not expected to work.";
|
||||
|
||||
EXPECT_TRUE(catpath.is_contained_in(""));
|
||||
EXPECT_TRUE(catpath.is_contained_in("simple"));
|
||||
EXPECT_TRUE(catpath.is_contained_in("simple/path"));
|
||||
|
||||
/* Test with some UTF8 non-ASCII characters. */
|
||||
AssetCatalogPath some_parent("some/родитель");
|
||||
AssetCatalogPath child = some_parent / "ребенок";
|
||||
|
||||
EXPECT_TRUE(child.is_contained_in(some_parent));
|
||||
EXPECT_TRUE(child.is_contained_in("some"));
|
||||
|
||||
AssetCatalogPath appended_compound_path = some_parent / "ребенок/внук";
|
||||
EXPECT_TRUE(appended_compound_path.is_contained_in(some_parent));
|
||||
EXPECT_TRUE(appended_compound_path.is_contained_in(child));
|
||||
|
||||
/* Test "going up" directory-style. */
|
||||
AssetCatalogPath child_with_dotdot = some_parent / "../../other/hierarchy/part";
|
||||
EXPECT_TRUE(child_with_dotdot.is_contained_in(some_parent))
|
||||
<< "dotdot path components should have no meaning";
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, cleanup)
|
||||
{
|
||||
AssetCatalogPath ugly_path("/ some / родитель / ");
|
||||
AssetCatalogPath clean_path = ugly_path.cleanup();
|
||||
|
||||
EXPECT_EQ(AssetCatalogPath("/ some / родитель / "), ugly_path)
|
||||
<< "cleanup should not modify the path instance itself";
|
||||
|
||||
EXPECT_EQ(AssetCatalogPath("some/родитель"), clean_path);
|
||||
|
||||
AssetCatalogPath double_slashed("some//родитель");
|
||||
EXPECT_EQ(AssetCatalogPath("some/родитель"), double_slashed.cleanup());
|
||||
|
||||
AssetCatalogPath with_colons("some/key:subkey=value/path");
|
||||
EXPECT_EQ(AssetCatalogPath("some/key-subkey=value/path"), with_colons.cleanup());
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, iterate_components)
|
||||
{
|
||||
AssetCatalogPath path("путь/в/Пермь");
|
||||
Vector<std::pair<std::string, bool>> seen_components;
|
||||
|
||||
path.iterate_components([&seen_components](StringRef component_name, bool is_last_component) {
|
||||
std::pair<std::string, bool> parameter_pair = std::make_pair<std::string, bool>(
|
||||
component_name, bool(is_last_component));
|
||||
seen_components.append(parameter_pair);
|
||||
});
|
||||
|
||||
ASSERT_EQ(3, seen_components.size());
|
||||
|
||||
EXPECT_EQ("путь", seen_components[0].first);
|
||||
EXPECT_EQ("в", seen_components[1].first);
|
||||
EXPECT_EQ("Пермь", seen_components[2].first);
|
||||
|
||||
EXPECT_FALSE(seen_components[0].second);
|
||||
EXPECT_FALSE(seen_components[1].second);
|
||||
EXPECT_TRUE(seen_components[2].second);
|
||||
}
|
||||
|
||||
TEST(AssetCatalogPathTest, rebase)
|
||||
{
|
||||
AssetCatalogPath path("some/path/to/some/catalog");
|
||||
EXPECT_EQ(path.rebase("some/path", "new/base"), "new/base/to/some/catalog");
|
||||
EXPECT_EQ(path.rebase("", "new/base"), "new/base/some/path/to/some/catalog");
|
||||
|
||||
EXPECT_EQ(path.rebase("some/path/to/some/catalog", "some/path/to/some/catalog"),
|
||||
"some/path/to/some/catalog")
|
||||
<< "Rebasing to itself should not change the path.";
|
||||
|
||||
EXPECT_EQ(path.rebase("path/to", "new/base"), "")
|
||||
<< "Non-matching base path should return empty string to indicate 'NO'.";
|
||||
|
||||
/* Empty strings should be handled without crashing or other nasty side-effects. */
|
||||
AssetCatalogPath empty("");
|
||||
EXPECT_EQ(empty.rebase("path/to", "new/base"), "");
|
||||
EXPECT_EQ(empty.rebase("", "new/base"), "new/base");
|
||||
EXPECT_EQ(empty.rebase("", ""), "");
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
@@ -106,7 +106,7 @@ class AssetCatalogTest : public testing::Test {
|
||||
EXPECT_EQ(expected_filename, actual_item.get_name());
|
||||
/* Does the computed number of parents match? */
|
||||
EXPECT_EQ(expected_path.parent_count, actual_item.count_parents());
|
||||
EXPECT_EQ(expected_path.name, actual_item.catalog_path());
|
||||
EXPECT_EQ(expected_path.name, actual_item.catalog_path().str());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,21 +186,21 @@ TEST_F(AssetCatalogTest, load_single_file)
|
||||
AssetCatalog *poses_ellie = service.find_catalog(UUID_POSES_ELLIE);
|
||||
ASSERT_NE(nullptr, poses_ellie);
|
||||
EXPECT_EQ(UUID_POSES_ELLIE, poses_ellie->catalog_id);
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path);
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
|
||||
EXPECT_EQ("POSES_ELLIE", poses_ellie->simple_name);
|
||||
|
||||
/* Test white-space stripping and support in the path. */
|
||||
AssetCatalog *poses_whitespace = service.find_catalog(UUID_POSES_ELLIE_WHITESPACE);
|
||||
ASSERT_NE(nullptr, poses_whitespace);
|
||||
EXPECT_EQ(UUID_POSES_ELLIE_WHITESPACE, poses_whitespace->catalog_id);
|
||||
EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path);
|
||||
EXPECT_EQ("character/Ellie/poselib/white space", poses_whitespace->path.str());
|
||||
EXPECT_EQ("POSES_ELLIE WHITESPACE", poses_whitespace->simple_name);
|
||||
|
||||
/* Test getting a UTF-8 catalog ID. */
|
||||
AssetCatalog *poses_ruzena = service.find_catalog(UUID_POSES_RUZENA);
|
||||
ASSERT_NE(nullptr, poses_ruzena);
|
||||
EXPECT_EQ(UUID_POSES_RUZENA, poses_ruzena->catalog_id);
|
||||
EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path);
|
||||
EXPECT_EQ("character/Ružena/poselib", poses_ruzena->path.str());
|
||||
EXPECT_EQ("POSES_RUŽENA", poses_ruzena->simple_name);
|
||||
}
|
||||
|
||||
@@ -588,7 +588,7 @@ TEST_F(AssetCatalogTest, create_first_catalog_from_scratch)
|
||||
AssetCatalog *written_cat = loaded_service.find_catalog(cat->catalog_id);
|
||||
ASSERT_NE(nullptr, written_cat);
|
||||
EXPECT_EQ(written_cat->catalog_id, cat->catalog_id);
|
||||
EXPECT_EQ(written_cat->path, cat->path);
|
||||
EXPECT_EQ(written_cat->path, cat->path.str());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, create_catalog_after_loading_file)
|
||||
@@ -640,7 +640,7 @@ TEST_F(AssetCatalogTest, create_catalog_path_cleanup)
|
||||
AssetCatalog *cat = service.create_catalog(" /some/path / ");
|
||||
|
||||
EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
|
||||
EXPECT_EQ("some/path", cat->path);
|
||||
EXPECT_EQ("some/path", cat->path.str());
|
||||
EXPECT_EQ("some-path", cat->simple_name);
|
||||
}
|
||||
|
||||
@@ -652,7 +652,7 @@ TEST_F(AssetCatalogTest, create_catalog_simple_name)
|
||||
|
||||
EXPECT_FALSE(BLI_uuid_is_nil(cat->catalog_id));
|
||||
EXPECT_EQ("production/Spite Fright/Characters/Victora/Pose Library/Approved/Body Parts/Hands",
|
||||
cat->path);
|
||||
cat->path.str());
|
||||
EXPECT_EQ("...ht-Characters-Victora-Pose Library-Approved-Body Parts-Hands", cat->simple_name);
|
||||
}
|
||||
|
||||
@@ -733,12 +733,12 @@ TEST_F(AssetCatalogTest, update_catalog_path)
|
||||
EXPECT_EQ(orig_cat->catalog_id, renamed_cat->catalog_id)
|
||||
<< "Changing the path should not change the catalog ID.";
|
||||
|
||||
EXPECT_EQ("charlib/Ružena", renamed_cat->path)
|
||||
EXPECT_EQ("charlib/Ružena", renamed_cat->path.str())
|
||||
<< "Changing the path should change the path. Surprise.";
|
||||
|
||||
EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path)
|
||||
EXPECT_EQ("charlib/Ružena/hand", service.find_catalog(UUID_POSES_RUZENA_HAND)->path.str())
|
||||
<< "Changing the path should update children.";
|
||||
EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path)
|
||||
EXPECT_EQ("charlib/Ružena/face", service.find_catalog(UUID_POSES_RUZENA_FACE)->path.str())
|
||||
<< "Changing the path should update children.";
|
||||
}
|
||||
|
||||
@@ -775,7 +775,7 @@ TEST_F(AssetCatalogTest, merge_catalog_files)
|
||||
|
||||
/* When there are overlaps, the in-memory (i.e. last-saved) paths should win. */
|
||||
const AssetCatalog *ruzena_face = loaded_service.find_catalog(UUID_POSES_RUZENA_FACE);
|
||||
EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path);
|
||||
EXPECT_EQ("character/Ružena/poselib/face", ruzena_face->path.str());
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, backups)
|
||||
@@ -846,21 +846,4 @@ TEST_F(AssetCatalogTest, order_by_path)
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AssetCatalogTest, is_contained_in)
|
||||
{
|
||||
const AssetCatalog cat(BLI_uuid_generate_random(), "simple/path/child", "");
|
||||
|
||||
EXPECT_FALSE(cat.is_contained_in("unrelated"));
|
||||
EXPECT_FALSE(cat.is_contained_in("sim"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/pathx"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/path/c"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/path/child/grandchild"));
|
||||
EXPECT_FALSE(cat.is_contained_in("simple/path/"))
|
||||
<< "Non-normalized paths are not expected to work.";
|
||||
|
||||
EXPECT_TRUE(cat.is_contained_in(""));
|
||||
EXPECT_TRUE(cat.is_contained_in("simple"));
|
||||
EXPECT_TRUE(cat.is_contained_in("simple/path"));
|
||||
}
|
||||
|
||||
} // namespace blender::bke::tests
|
||||
|
@@ -49,7 +49,7 @@ TEST(AssetLibraryTest, load_and_free_c_functions)
|
||||
const bUUID uuid_poses_ellie("df60e1f6-2259-475b-93d9-69a1b4a8db78");
|
||||
AssetCatalog *poses_ellie = service->find_catalog(uuid_poses_ellie);
|
||||
ASSERT_NE(nullptr, poses_ellie) << "unable to find POSES_ELLIE catalog";
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path);
|
||||
EXPECT_EQ("character/Ellie/poselib", poses_ellie->path.str());
|
||||
|
||||
BKE_asset_library_free(library_c_ptr);
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
|
||||
#include "BKE_asset_catalog.hh"
|
||||
#include "BKE_asset_catalog_path.hh"
|
||||
#include "BKE_asset_library.hh"
|
||||
|
||||
#include "BLI_string_utils.h"
|
||||
@@ -33,17 +34,10 @@ struct CatalogUniqueNameFnData {
|
||||
StringRef parent_path;
|
||||
};
|
||||
|
||||
static std::string to_full_path(StringRef parent_path, StringRef name)
|
||||
{
|
||||
return parent_path.is_empty() ?
|
||||
std::string(name) :
|
||||
std::string(parent_path) + AssetCatalogService::PATH_SEPARATOR + name;
|
||||
}
|
||||
|
||||
static bool catalog_name_exists_fn(void *arg, const char *name)
|
||||
{
|
||||
CatalogUniqueNameFnData &fn_data = *static_cast<CatalogUniqueNameFnData *>(arg);
|
||||
std::string fullpath = to_full_path(fn_data.parent_path, name);
|
||||
CatalogPath fullpath = CatalogPath(fn_data.parent_path) / name;
|
||||
return fn_data.catalog_service.find_catalog_by_path(fullpath);
|
||||
}
|
||||
|
||||
@@ -70,7 +64,7 @@ AssetCatalog *ED_asset_catalog_add(::AssetLibrary *library,
|
||||
}
|
||||
|
||||
std::string unique_name = catalog_name_ensure_unique(*catalog_service, name, parent_path);
|
||||
std::string fullpath = to_full_path(parent_path, unique_name);
|
||||
CatalogPath fullpath = CatalogPath(parent_path) / unique_name;
|
||||
|
||||
return catalog_service->create_catalog(fullpath);
|
||||
}
|
||||
|
Reference in New Issue
Block a user