fxos/lib/project.cpp

287 lines
6.6 KiB
C++

#include <fxos/project.h>
#include <fxos/util/log.h>
#include <filesystem>
#include <algorithm>
#include <string>
#include <fmt/core.h>
using namespace std::literals;
namespace fs = std::filesystem;
namespace FxOS {
//=== Project ===//
Project::Project()
{
m_name = "unnamed_project";
}
void Project::setName(std::string const &new_name)
{
m_name = new_name;
setDirty();
}
void Project::setPath(std::string const &new_path)
{
m_path = new_path;
}
bool Project::canSave() const
{
return m_path != "";
}
bool Project::isDirty() const
{
return m_dirty;
}
void Project::setDirty()
{
m_dirty = true;
}
std::string Project::generateBinaryName(std::string const &base) const
{
if(base != "" && !m_binaries.count(base))
return base;
std::string const &prefix = (base == "") ? "unnamed" : base;
int counter = 0;
while(true) {
std::string name = fmt::format("{}_{}", prefix, counter);
if(!m_binaries.count(name))
return name;
counter++;
}
}
std::string Project::createBinary(std::string const &name)
{
std::string trueName = generateBinaryName(name);
/* Create a new default-constructed entry in the map. */
m_binaries[trueName];
setDirty();
return trueName;
}
Binary *Project::getBinary(std::string const &name)
{
auto it = m_binaries.find(name);
return (it == m_binaries.end()) ? nullptr : &it->second;
}
Binary const *Project::getBinary(std::string const &name) const
{
auto it = m_binaries.find(name);
return (it == m_binaries.end()) ? nullptr : &it->second;
}
void Project::renameBinary(
std::string const &old_name, std::string const &new_name)
{
if(!m_binaries.count(old_name))
return;
auto node = m_binaries.extract(old_name);
node.key() = new_name;
m_binaries.insert(std::move(node));
setDirty();
}
void Project::removeBinary(std::string const &name)
{
if(m_binaries.erase(name))
setDirty();
}
BSON Project::serializeMetadata() const
{
BSON binaries = BSON::mkArray(m_binaries.size());
int i = 0;
for(auto const &[name, _]: m_binaries) {
binaries[i] = BSON::mkString(name);
i++;
}
return BSON::mkDocument({
{"*", BSON::mkString("Project")},
{"name", BSON::mkString(m_name)},
{"binaries", std::move(binaries)},
});
}
bool Project::save()
{
fs::path path(m_path);
if(path == "") {
FxOS_log(ERR, "Project “%s” has no path", m_name.c_str());
return false;
}
std::error_code ec;
fs::create_directories(path, ec);
if(ec) {
FxOS_log(ERR, "Could not create path to '%s': %s", path.c_str(),
ec.message());
return false;
}
fs::path metadata_path = path / "project";
FILE *metadata = fopen(metadata_path.c_str(), "w");
if(!metadata) {
FxOS_log(ERR, "cannot write '%s': %m", metadata_path.c_str());
return false;
}
serializeMetadata().serialize(metadata);
fclose(metadata);
/* Remove old binaries' folders. */
for(auto const &entry: fs::directory_iterator(path)) {
fs::path const &path = entry.path();
if(path.extension() == ".bin" && m_binaries.count(path.stem()) == 0)
fs::remove(path);
}
for(auto const &[name, binary]: m_binaries) {
fs::path bin_path = path / (name + ".bin");
FILE *bin = fopen(bin_path.c_str(), "w");
if(!bin) {
FxOS_log(ERR, "cannot write '%s': %m", bin_path.c_str());
continue;
}
binary.serialize().serialize(bin);
fclose(bin);
}
m_dirty = false;
return true;
}
bool Project::load(std::string const &path0)
{
fs::path path = path0;
BSON metadata
= BSON::loadDocumentFromFile(path / "project", true, true, "Project");
if(metadata.isNull())
return false;
m_path = path;
m_name = metadata["name"].getString();
m_dirty = false;
if(!metadata.hasField("binaries"))
return true;
for(size_t i = 0; i < metadata["binaries"].size(); i++) {
std::string name = metadata["binaries"][i].getString();
BSON bin = BSON::loadDocumentFromFile(
path / (name + ".bin"), true, true, "Binary");
if(bin.isNull())
continue;
assert(m_binaries.count(name) == 0);
m_binaries[name];
m_binaries[name].deserialize(bin);
}
return true;
}
//=== RecentProjects ===//
RecentProjects::RecentProjectEntry::RecentProjectEntry(BSON const &b)
{
assert(b["*"].getStringReadOnly() == "RecentProjectEntry"s);
name = b["name"].getStringReadOnly();
path = b["path"].getStringReadOnly();
utime = b["utime"].getI64();
}
BSON RecentProjects::RecentProjectEntry::serialize() const
{
return BSON::mkDocument({
{"*", BSON::mkString("RecentProjectEntry")},
{"name", BSON::mkString(name)},
{"path", BSON::mkString(path)},
{"utime", BSON::mkI64(utime)},
});
}
BSON RecentProjects::serialize() const
{
BSON b = BSON::mkArray(m_entries.size());
for(uint i = 0; i < m_entries.size(); i++)
b[i] = m_entries[i].serialize();
return b;
}
void RecentProjects::deserialize(BSON const &b)
{
assert(b.isArray());
for(uint i = 0; i < b.size(); i++)
m_entries.push_back(RecentProjectEntry(b[i]));
}
std::string RecentProjects::pathFromName(std::string const &name) const
{
for(auto &entry: m_entries) {
if(entry.name == name)
return entry.path;
}
return "";
}
std::string RecentProjects::mostRecentPath() const
{
if(!m_entries.size())
return "";
return std::max_element(m_entries.begin(), m_entries.end(),
[](RecentProjectEntry const &e1, RecentProjectEntry const &e2) {
return e1.utime < e2.utime;
})
->path;
}
void RecentProjects::remove(std::string const &path)
{
auto it = m_entries.begin();
while(it != m_entries.end()) {
if(it->path == path)
it = m_entries.erase(it);
else
++it;
}
}
void RecentProjects::touch(Project const &p)
{
remove(p.path());
struct RecentProjectEntry entry;
entry.name = p.name();
entry.path = p.path();
entry.utime = time(NULL);
m_entries.insert(m_entries.begin(), entry);
}
void RecentProjects::refresh()
{
auto it = std::remove_if(
m_entries.begin(), m_entries.end(), [](RecentProjectEntry const &e) {
std::error_code rc;
fs::file_status st = fs::status(e.path, rc);
return rc || st.type() != fs::file_type::directory;
});
m_entries.erase(it, m_entries.end());
}
} /* namespace FxOS */