166 lines
4.7 KiB
C++
166 lines
4.7 KiB
C++
/*
|
|
* Copyright 2016 - 2022 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 <nostalgia/core/typestore.hpp>
|
|
|
|
#include "nostalgiastudio_export.h"
|
|
|
|
namespace nostalgia::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::String> fileExt(const ox::String &path) noexcept {
|
|
const auto extStart = ox::find(path.crbegin(), path.crend(), '.').offset();
|
|
if (!extStart) {
|
|
return OxError(1, "Cannot open a file without valid extension.");
|
|
}
|
|
return path.substr(extStart + 1);
|
|
}
|
|
|
|
class NOSTALGIASTUDIO_EXPORT Project {
|
|
private:
|
|
ox::String m_path;
|
|
mutable core::TypeStore m_typeStore;
|
|
mutable ox::FileSystem *m_fs = nullptr;
|
|
ox::HashMap<ox::String, ox::Vector<ox::String>> m_fileExtFileMap;
|
|
|
|
public:
|
|
explicit Project(ox::FileSystem *fs, ox::String path) noexcept;
|
|
|
|
ox::Error create() noexcept;
|
|
|
|
[[nodiscard]]
|
|
ox::FileSystem *romFs() noexcept;
|
|
|
|
ox::Error mkdir(const ox::String &path) const noexcept;
|
|
|
|
/**
|
|
* Writes a MetalClaw object to the project at the given path.
|
|
*/
|
|
template<typename T>
|
|
ox::Error writeObj(const ox::String &path, const T *obj, ox::ClawFormat fmt = ox::ClawFormat::Metal) noexcept;
|
|
|
|
template<typename T>
|
|
ox::Result<T> loadObj(const ox::String &path) const noexcept;
|
|
|
|
ox::Result<ox::FileStat> stat(const ox::String &path) const noexcept;
|
|
|
|
[[nodiscard]]
|
|
bool exists(const ox::String& path) const noexcept;
|
|
|
|
template<typename Functor>
|
|
ox::Error subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&slot) const noexcept;
|
|
|
|
[[nodiscard]]
|
|
const ox::Vector<ox::String> &fileList(const char *ng) noexcept;
|
|
|
|
private:
|
|
void buildFileIndex() noexcept;
|
|
|
|
void indexFile(const ox::String &path) noexcept;
|
|
|
|
ox::Error writeBuff(const ox::String &path, const ox::Buffer &buff) noexcept;
|
|
|
|
ox::Result<ox::Buffer> loadBuff(const ox::String &path) const noexcept;
|
|
|
|
ox::Error lsProcDir(ox::Vector<ox::String> *paths, const ox::String &path) const noexcept;
|
|
|
|
ox::Result<ox::Vector<ox::String>> listFiles(const ox::String &path = "") const noexcept;
|
|
|
|
// signals
|
|
public:
|
|
ox::Signal<ox::Error(ProjectEvent, const ox::String&)> fileEvent;
|
|
ox::Signal<ox::Error(const ox::String&)> 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(const ox::String&)> fileRecognized;
|
|
ox::Signal<ox::Error(const ox::String&)> fileDeleted;
|
|
ox::Signal<ox::Error(const ox::String&)> fileUpdated;
|
|
|
|
};
|
|
|
|
template<typename T>
|
|
ox::Error Project::writeObj(const ox::String &path, const T *obj, ox::ClawFormat fmt) noexcept {
|
|
// write MetalClaw
|
|
oxRequireM(buff, ox::writeClaw(obj, fmt));
|
|
// write to FS
|
|
oxReturnError(writeBuff(path, buff));
|
|
// write type descriptor
|
|
if (m_typeStore.get<T>().error) {
|
|
oxReturnError(ox::buildTypeDef(&m_typeStore, obj));
|
|
}
|
|
// write out type store
|
|
static constexpr auto descPath = "/.nostalgia/type_descriptors";
|
|
oxReturnError(mkdir(descPath));
|
|
for (const auto &t : m_typeStore.typeList()) {
|
|
oxRequireM(typeOut, ox::writeClaw(t, ox::ClawFormat::Organic));
|
|
// replace garbage last character with new line
|
|
typeOut.back().value = '\n';
|
|
// write to FS
|
|
const auto typePath = ox::sfmt("{}/{}", descPath, buildTypeId(*t));
|
|
oxReturnError(writeBuff(typePath, typeOut));
|
|
}
|
|
fileUpdated.emit(path);
|
|
return OxError(0);
|
|
}
|
|
|
|
template<typename T>
|
|
ox::Result<T> Project::loadObj(const ox::String &path) const noexcept {
|
|
oxRequire(buff, loadBuff(path));
|
|
if constexpr (ox::is_same_v<T, ox::ModelObject>) {
|
|
return ox::readClaw(&m_typeStore, buff);
|
|
} else {
|
|
return ox::readClaw<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 (const auto &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 OxError(0);
|
|
}
|
|
|
|
}
|