180 lines
4.9 KiB
C++
180 lines
4.9 KiB
C++
/*
|
|
* Copyright 2016 - 2024 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/mc/mc.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,
|
|
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,
|
|
FileDeleted,
|
|
FileUpdated,
|
|
};
|
|
|
|
[[nodiscard]]
|
|
constexpr ox::Result<ox::StringView> fileExt(ox::CRStringView path) noexcept {
|
|
auto const extStart = ox::find(path.crbegin(), path.crend(), '.').offset();
|
|
if (!extStart) {
|
|
return OxError(1, "Cannot open a file without 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 {
|
|
private:
|
|
keel::Context &m_ctx;
|
|
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::String path, ox::CRStringView projectDataDir);
|
|
|
|
ox::Error create() noexcept;
|
|
|
|
[[nodiscard]]
|
|
ox::String const&projectPath() const noexcept;
|
|
|
|
[[nodiscard]]
|
|
ox::FileSystem *romFs() noexcept;
|
|
|
|
ox::Error mkdir(ox::CRStringView path) const noexcept;
|
|
|
|
/**
|
|
* Writes a MetalClaw object to the project at the given path.
|
|
*/
|
|
template<typename T>
|
|
ox::Error writeObj(
|
|
ox::CRStringView path,
|
|
T const&obj,
|
|
ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept;
|
|
|
|
template<typename T>
|
|
ox::Result<T> loadObj(ox::CRStringView path) const noexcept;
|
|
|
|
ox::Result<ox::FileStat> stat(ox::CRStringView path) const noexcept;
|
|
|
|
[[nodiscard]]
|
|
bool exists(ox::CRStringView 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::CRStringView ext) noexcept;
|
|
|
|
ox::Error writeTypeStore() noexcept;
|
|
|
|
private:
|
|
void buildFileIndex() noexcept;
|
|
|
|
void indexFile(ox::CRStringView path) noexcept;
|
|
|
|
ox::Error writeBuff(ox::CRStringView path, ox::Buffer const&buff) noexcept;
|
|
|
|
ox::Result<ox::Buffer> loadBuff(ox::CRStringView path) const noexcept;
|
|
|
|
ox::Error lsProcDir(ox::Vector<ox::String> *paths, ox::CRStringView path) const noexcept;
|
|
|
|
ox::Result<ox::Vector<ox::String>> listFiles(ox::CRStringView path = "") const noexcept;
|
|
|
|
// signals
|
|
public:
|
|
ox::Signal<ox::Error(ProjectEvent, ox::CRStringView)> fileEvent;
|
|
ox::Signal<ox::Error(ox::CRStringView)> 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::CRStringView)> fileRecognized;
|
|
ox::Signal<ox::Error(ox::CRStringView)> fileDeleted;
|
|
ox::Signal<ox::Error(ox::CRStringView)> fileUpdated;
|
|
|
|
};
|
|
|
|
template<typename T>
|
|
ox::Error Project::writeObj(ox::CRStringView path, T const&obj, ox::ClawFormat fmt) noexcept {
|
|
oxRequireM(buff, ox::writeClaw(obj, fmt));
|
|
// write to FS
|
|
oxReturnError(mkdir(parentDir(path)));
|
|
oxReturnError(writeBuff(path, buff));
|
|
// write type descriptor
|
|
if (m_typeStore.get<T>().error) {
|
|
oxReturnError(ox::buildTypeDef(&m_typeStore, &obj));
|
|
}
|
|
oxRequire(desc, m_typeStore.get<T>());
|
|
auto const descExists = m_fs.stat(ox::sfmt("{}/{}", m_typeDescPath, buildTypeId(*desc))).error != 0;
|
|
if (!descExists) {
|
|
oxReturnError(writeTypeStore());
|
|
}
|
|
oxReturnError(keel::setAsset(m_ctx, path, obj));
|
|
fileUpdated.emit(path);
|
|
return {};
|
|
}
|
|
|
|
template<typename T>
|
|
ox::Result<T> Project::loadObj(ox::CRStringView path) const noexcept {
|
|
oxRequire(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::FileAdded:
|
|
connect(this, &Project::fileAdded, tgt, slot);
|
|
break;
|
|
case ProjectEvent::FileRecognized:
|
|
{
|
|
oxRequire(files, listFiles());
|
|
for (auto const&f : files) {
|
|
slot(f);
|
|
}
|
|
connect(this, &Project::fileRecognized, tgt, slot);
|
|
break;
|
|
}
|
|
case ProjectEvent::FileDeleted:
|
|
connect(this, &Project::fileDeleted, tgt, slot);
|
|
break;
|
|
case ProjectEvent::FileUpdated:
|
|
connect(this, &Project::fileUpdated, tgt, slot);
|
|
break;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
}
|