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 {};
}
}