Files
nostalgia/src/olympic/studio/modlib/src/project.cpp
Gary Talent 9f040392c7
All checks were successful
Build / build (push) Successful in 1m18s
[olympic,nostalgia] Cleanup style
2025-06-24 01:59:22 -05:00

254 lines
7.1 KiB
C++

/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <algorithm>
#include <filesystem>
#include <ox/std/std.hpp>
#include <keel/module.hpp>
#include <studio/project.hpp>
namespace studio {
static_assert(fileExt("main.c").value == "c");
static_assert(fileExt("a.b.c").value == "c");
static_assert(parentDir("/a/b/c") == "/a/b");
static_assert(parentDir("/a/b/c/") == "/a/b");
static void generateTypes(ox::TypeStore &ts) noexcept {
for (auto const mod : keel::modules()) {
for (auto gen : mod->types()) {
oxLogError(gen(ts));
}
}
}
static ox::Result<ox::Vector<ox::String>> listAllRecursive(ox::FileSystem &fs, ox::StringViewCR path) {
// there really isn't much recourse if this function fails, just log it and move on
auto [out, outErr] = fs.ls(path);
oxLogError(outErr);
for (auto const &p : out) {
auto const [stat, statErr] = fs.stat(path);
if (statErr) {
oxLogError(statErr);
continue;
}
if (stat.fileType == ox::FileType::Directory) {
OX_REQUIRE_M(l, listAllRecursive(fs, sfmt("{}/{}", path, p)));
for (auto &c : l) {
out.emplace_back(std::move(c));
}
}
}
return std::move(out);
}
[[nodiscard]]
static constexpr bool isParentOf(ox::StringViewCR parent, ox::StringViewCR child) noexcept {
if (endsWith(child, "/")) {
return beginsWith(child, parent);
}
return beginsWith(sfmt("{}/", child), parent);
}
Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir):
m_kctx(ctx),
m_path(std::move(path)),
m_projectDataDir(projectDataDir),
m_typeDescPath(ox::sfmt("/{}/type_descriptors", m_projectDataDir)),
m_typeStore(*m_kctx.rom, ox::sfmt("/{}/type_descriptors", projectDataDir)),
m_fs(*m_kctx.rom) {
oxTracef("studio", "Project: {}", m_path);
generateTypes(m_typeStore);
if constexpr(ox::defines::Debug) {
OX_THROW_ERROR(writeTypeStore());
}
buildFileIndex();
}
ox::Error Project::create() noexcept {
std::error_code ec;
std::filesystem::create_directory(m_path.toStdString(), ec);
return ox::Error(static_cast<ox::ErrorCode>(ec.value()), "PassThroughFS: mkdir failed");
}
ox::String const &Project::projectPath() const noexcept {
return m_path;
}
ox::FileSystem &Project::romFs() noexcept {
return m_fs;
}
ox::Error Project::mkdir(ox::StringViewCR path) const noexcept {
auto [stat, err] = m_fs.stat(path);
if (err) {
OX_RETURN_ERROR(m_fs.mkdir(path, true));
dirAdded.emit(path);
OX_RETURN_ERROR(m_fs.stat(path).moveTo(stat));
}
return stat.fileType == ox::FileType::Directory ?
ox::Error{} : ox::Error{1, "path exists as normal file"};
}
ox::Result<ox::FileStat> Project::stat(ox::StringViewCR path) const noexcept {
return m_fs.stat(path);
}
ox::Error Project::copyItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
OX_REQUIRE_M(buff, loadBuff(src));
OX_REQUIRE(id, keel::regenerateUuidHeader(buff));
createUuidMapping(m_kctx, dest, id);
OX_RETURN_ERROR(writeBuff(dest, ox::BufferView{buff} + keel::K1HdrSz));
return {};
}
ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
OX_RETURN_ERROR(m_fs.move(src, dest));
OX_RETURN_ERROR(keel::updatePath(m_kctx, src, dest));
OX_REQUIRE(uuid, keel::pathToUuid(m_kctx, dest));
fileMoved.emit(src, dest, uuid);
return {};
}
ox::Error Project::moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept {
if (isParentOf(src, dest)) {
return ox::Error{1, "cannot move parent to a child directory"};
}
OX_REQUIRE(files, listAllRecursive(m_fs, src));
OX_RETURN_ERROR(m_fs.move(src, dest));
fileMoved.emit(src, dest, ox::UUID{});
for (auto const &op : files) {
auto const name =
substr(op, ox::find(op.rbegin(), op.rend(), '/').offset() + 1);
auto const np = sfmt("{}/{}", dest, name);
OX_RETURN_ERROR(keel::updatePath(m_kctx, op, np));
OX_REQUIRE(uuid, keel::pathToUuid(m_kctx, dest));
fileMoved.emit(src, dest, uuid);
}
return {};
}
ox::Error Project::deleteItem(ox::StringViewCR path) noexcept {
OX_REQUIRE(stat, m_fs.stat(path));
if (stat.fileType == ox::FileType::Directory) {
bool partialRemoval{};
OX_REQUIRE(members, m_fs.ls(path));
for (auto const &p : members) {
partialRemoval = m_fs.remove(ox::sfmt("{}/{}", path, p)) || partialRemoval;
}
if (partialRemoval) {
return ox::Error{1, "failed to remove one or more directory members"};
}
auto const err = m_fs.remove(path);
if (!err) {
dirDeleted.emit(path);
}
return err;
} else {
auto const err = m_fs.remove(path);
if (!err) {
fileDeleted.emit(path);
}
return err;
}
}
bool Project::exists(ox::StringViewCR path) const noexcept {
return m_fs.stat(path).error == 0;
}
ox::Vector<ox::String> const &Project::fileList(ox::StringViewCR ext) noexcept {
return m_fileExtFileMap[ext];
}
ox::Error Project::writeTypeStore() noexcept {
// write all descriptors because we don't know which types T depends on
OX_RETURN_ERROR(mkdir(m_typeDescPath));
for (auto const &t: m_typeStore.typeList()) {
OX_REQUIRE(typeOut, ox::writeClaw(*t, ox::ClawFormat::Organic));
// write to FS
auto const typePath = ox::sfmt("{}/{}", m_typeDescPath, buildTypeId(*t));
OX_RETURN_ERROR(writeBuff(typePath, {typeOut.data(), typeOut.size() - 1}));
}
return {};
}
void Project::buildFileIndex() noexcept {
auto [files, err] = listFiles();
if (err) {
oxLogError(err);
return;
}
m_fileExtFileMap.clear();
std::sort(files.begin(), files.end());
for (auto const &file : files) {
if (!beginsWith(file, ox::sfmt("/.{}/", m_projectDataDir))) {
indexFile(file);
}
}
}
void Project::indexFile(ox::StringViewCR path) noexcept {
auto const [ext, err] = fileExt(path);
if (err) {
return;
}
m_fileExtFileMap[ext].emplace_back(path);
}
ox::Error Project::writeBuff(ox::StringViewCR path, ox::BufferView const &buff) noexcept {
constexpr auto HdrSz = 40;
ox::Buffer outBuff;
outBuff.reserve(buff.size() + HdrSz);
ox::BufferWriter writer(&outBuff);
auto const [uuid, err] = pathToUuid(m_kctx, path);
if (!err) {
OX_RETURN_ERROR(keel::writeUuidHeader(writer, uuid));
}
OX_RETURN_ERROR(writer.write(buff.data(), buff.size()));
auto const newFile = m_fs.stat(path).error != 0;
OX_RETURN_ERROR(m_fs.write(path, outBuff.data(), outBuff.size(), ox::FileType::NormalFile));
if (newFile) {
fileAdded.emit(path);
indexFile(path);
} else {
fileUpdated.emit(path, uuid);
}
return {};
}
ox::Result<ox::Buffer> Project::loadBuff(ox::StringViewCR path) const noexcept {
return m_fs.read(path);
}
ox::Error Project::lsProcDir(ox::Vector<ox::String> &paths, ox::StringViewCR path) const noexcept {
OX_REQUIRE(files, m_fs.ls(path));
for (auto const &name : files) {
auto fullPath = ox::sfmt("{}/{}", path, name);
OX_REQUIRE(stat, m_fs.stat(ox::StringView(fullPath)));
switch (stat.fileType) {
case ox::FileType::NormalFile:
paths.emplace_back(std::move(fullPath));
break;
case ox::FileType::Directory:
OX_RETURN_ERROR(lsProcDir(paths, fullPath));
break;
case ox::FileType::None:
break;
}
}
return {};
}
ox::Result<ox::Vector<ox::String>> Project::listFiles(ox::StringViewCR path) const noexcept {
ox::Vector<ox::String> paths;
OX_RETURN_ERROR(lsProcDir(paths, path));
return paths;
}
}