233 lines
6.5 KiB
C++
233 lines
6.5 KiB
C++
/*
|
|
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <ox/claw/read.hpp>
|
|
#include <ox/claw/write.hpp>
|
|
#include <ox/event/signal.hpp>
|
|
#include <ox/fs/fs.hpp>
|
|
#include <ox/model/descwrite.hpp>
|
|
#include <ox/std/hashmap.hpp>
|
|
|
|
#include <keel/typestore.hpp>
|
|
#include <keel/media.hpp>
|
|
|
|
namespace studio {
|
|
|
|
enum class ProjectEvent {
|
|
None,
|
|
DirAdded,
|
|
FileAdded,
|
|
// FileRecognized is triggered for all matching files upon a new
|
|
// subscription to a section of the project and upon the addition of a file.
|
|
FileRecognized,
|
|
DirDeleted,
|
|
FileDeleted,
|
|
FileUpdated,
|
|
FileMoved,
|
|
};
|
|
|
|
[[nodiscard]]
|
|
constexpr ox::Result<ox::StringView> fileExt(ox::StringViewCR path) noexcept {
|
|
auto const extStart = ox::find(path.crbegin(), path.crend(), '.').offset();
|
|
if (!extStart) {
|
|
return ox::Error(1, "file path does not have valid extension");
|
|
}
|
|
return substr(path, extStart + 1);
|
|
}
|
|
|
|
[[nodiscard]]
|
|
constexpr ox::StringView parentDir(ox::StringView path) noexcept {
|
|
if (path.len() && path[path.len() - 1] == '/') {
|
|
path = substr(path, 0, path.len() - 1);
|
|
}
|
|
auto const extStart = ox::find(path.crbegin(), path.crend(), '/').offset();
|
|
return substr(path, 0, extStart);
|
|
}
|
|
|
|
class Project: public ox::SignalHandler {
|
|
private:
|
|
ox::SmallMap<ox::String, ox::Optional<ox::ClawFormat>> m_typeFmt;
|
|
keel::Context &m_kctx;
|
|
ox::String m_path;
|
|
ox::String m_projectDataDir;
|
|
ox::String m_typeDescPath;
|
|
mutable keel::TypeStore m_typeStore;
|
|
ox::FileSystem &m_fs;
|
|
ox::HashMap<ox::String, ox::Vector<ox::String>> m_fileExtFileMap;
|
|
|
|
public:
|
|
explicit Project(keel::Context &ctx, ox::StringParam path, ox::StringViewCR projectDataDir);
|
|
|
|
ox::Error create() noexcept;
|
|
|
|
[[nodiscard]]
|
|
ox::String const &projectPath() const noexcept;
|
|
|
|
[[nodiscard]]
|
|
ox::FileSystem &romFs() noexcept;
|
|
|
|
ox::Error mkdir(ox::StringViewCR path) const noexcept;
|
|
|
|
/**
|
|
* Writes a MetalClaw object to the project at the given path.
|
|
*/
|
|
template<typename T>
|
|
ox::Error writeObj(
|
|
ox::StringViewCR path,
|
|
T const &obj,
|
|
ox::ClawFormat fmt) noexcept;
|
|
|
|
/**
|
|
* Writes a MetalClaw object to the project at the given path.
|
|
*/
|
|
template<typename T>
|
|
ox::Error writeObj(
|
|
ox::StringViewCR path,
|
|
T const &obj) noexcept;
|
|
|
|
template<typename T>
|
|
ox::Result<T> loadObj(ox::StringViewCR path) const noexcept;
|
|
|
|
ox::Result<ox::FileStat> stat(ox::StringViewCR path) const noexcept;
|
|
|
|
ox::Error copyItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept;
|
|
|
|
ox::Error moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept;
|
|
|
|
ox::Error moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept;
|
|
|
|
ox::Error deleteItem(ox::StringViewCR path) noexcept;
|
|
|
|
[[nodiscard]]
|
|
keel::Context &kctx() noexcept {
|
|
return m_kctx;
|
|
}
|
|
|
|
[[nodiscard]]
|
|
keel::Context const &kctx() const noexcept {
|
|
return m_kctx;
|
|
}
|
|
|
|
[[nodiscard]]
|
|
bool exists(ox::StringViewCR path) const noexcept;
|
|
|
|
template<typename Functor>
|
|
ox::Error subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&slot) const noexcept;
|
|
|
|
[[nodiscard]]
|
|
ox::Vector<ox::String> const &fileList(ox::StringViewCR ext) noexcept;
|
|
|
|
ox::Error writeTypeStore() noexcept;
|
|
|
|
private:
|
|
void buildFileIndex() noexcept;
|
|
|
|
void indexFile(ox::StringViewCR path) noexcept;
|
|
|
|
ox::Error writeBuff(ox::StringViewCR path, ox::BufferView const &buff) noexcept;
|
|
|
|
ox::Result<ox::Buffer> loadBuff(ox::StringViewCR path) const noexcept;
|
|
|
|
ox::Error lsProcDir(ox::Vector<ox::String> &paths, ox::StringViewCR path) const noexcept;
|
|
|
|
ox::Result<ox::Vector<ox::String>> listFiles(ox::StringViewCR path = "") const noexcept;
|
|
|
|
// signals
|
|
public:
|
|
ox::Signal<ox::Error(ProjectEvent, ox::StringViewCR)> fileEvent;
|
|
ox::Signal<ox::Error(ox::StringViewCR)> dirAdded;
|
|
ox::Signal<ox::Error(ox::StringViewCR)> fileAdded;
|
|
// FileRecognized is triggered for all matching files upon a new
|
|
// subscription to a section of the project and upon the addition of a
|
|
// file.
|
|
ox::Signal<ox::Error(ox::StringViewCR)> fileRecognized;
|
|
ox::Signal<ox::Error(ox::StringViewCR)> fileDeleted;
|
|
ox::Signal<ox::Error(ox::StringViewCR)> dirDeleted;
|
|
ox::Signal<ox::Error(ox::StringViewCR path, ox::UUID const &id)> fileUpdated;
|
|
ox::Signal<ox::Error(ox::StringViewCR oldPath, ox::StringViewCR newPath, ox::UUID const &id)> fileMoved;
|
|
|
|
};
|
|
|
|
template<typename T>
|
|
ox::Error Project::writeObj(ox::StringViewCR path, T const &obj, ox::ClawFormat fmt) noexcept {
|
|
OX_REQUIRE_M(buff, ox::writeClaw(obj, fmt));
|
|
if (fmt == ox::ClawFormat::Organic) {
|
|
buff.pop_back();
|
|
}
|
|
// write to FS
|
|
OX_RETURN_ERROR(mkdir(parentDir(path)));
|
|
OX_RETURN_ERROR(writeBuff(path, buff));
|
|
// write type descriptor
|
|
if (m_typeStore.get<T>().error) {
|
|
OX_RETURN_ERROR(ox::buildTypeDef(m_typeStore, obj));
|
|
}
|
|
OX_REQUIRE(desc, m_typeStore.get<T>());
|
|
auto const descPath = ox::sfmt("{}/{}", m_typeDescPath, buildTypeId(*desc));
|
|
auto const descExists = m_fs.exists(descPath);
|
|
if (!descExists) {
|
|
OX_RETURN_ERROR(writeTypeStore());
|
|
}
|
|
OX_RETURN_ERROR(keel::reloadAsset(m_kctx, path));
|
|
OX_REQUIRE(uuid, pathToUuid(m_kctx, path));
|
|
fileUpdated.emit(path, uuid);
|
|
return {};
|
|
}
|
|
|
|
template<typename T>
|
|
ox::Error Project::writeObj(ox::StringViewCR path, T const &obj) noexcept {
|
|
OX_REQUIRE(ext, fileExt(path));
|
|
auto const fmt = m_typeFmt[ext].or_value(ox::ClawFormat::Metal);
|
|
return writeObj(path, obj, fmt);
|
|
}
|
|
|
|
template<typename T>
|
|
ox::Result<T> Project::loadObj(ox::StringViewCR path) const noexcept {
|
|
OX_REQUIRE(buff, loadBuff(path));
|
|
if constexpr(ox::is_same_v<T, ox::ModelObject>) {
|
|
return keel::readAsset(m_typeStore, buff);
|
|
} else {
|
|
return keel::readAsset<T>(buff);
|
|
}
|
|
}
|
|
|
|
template<typename Functor>
|
|
ox::Error Project::subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&slot) const noexcept {
|
|
switch (e) {
|
|
case ProjectEvent::None:
|
|
break;
|
|
case ProjectEvent::DirAdded:
|
|
connect(this, &Project::dirAdded, tgt, slot);
|
|
break;
|
|
case ProjectEvent::FileAdded:
|
|
connect(this, &Project::fileAdded, tgt, slot);
|
|
break;
|
|
case ProjectEvent::FileRecognized:
|
|
{
|
|
OX_REQUIRE(files, listFiles());
|
|
for (auto const &f : files) {
|
|
slot(f);
|
|
}
|
|
connect(this, &Project::fileRecognized, tgt, slot);
|
|
break;
|
|
}
|
|
case ProjectEvent::DirDeleted:
|
|
connect(this, &Project::dirDeleted, tgt, slot);
|
|
break;
|
|
case ProjectEvent::FileDeleted:
|
|
connect(this, &Project::fileDeleted, tgt, slot);
|
|
break;
|
|
case ProjectEvent::FileUpdated:
|
|
connect(this, &Project::fileUpdated, tgt, slot);
|
|
break;
|
|
case ProjectEvent::FileMoved:
|
|
connect(this, &Project::fileMoved, tgt, slot);
|
|
break;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
}
|