287 lines
6.6 KiB
C++
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 */
|