#include #include #include #include #include #if _WIN32 #else #include #endif #include "hecl/Database.hpp" #include "hecl/Blender/BlenderConnection.hpp" namespace hecl { namespace Database { logvisor::Module LogModule("hecl::Database"); static const hecl::FourCC HECLfcc("HECL"); /********************************************** * Project::ConfigFile **********************************************/ static inline bool CheckNewLineAdvance(std::string::const_iterator& it) { if (*it == '\n') { it += 1; return true; } else if (*it == '\r') { if (*(it+1) == '\n') { it += 2; return true; } it += 1; return true; } return false; } Project::ConfigFile::ConfigFile(const Project& project, const SystemString& name, const SystemString& subdir) { m_filepath = project.m_rootPath.getAbsolutePath() + subdir + name; } std::vector& Project::ConfigFile::lockAndRead() { if (m_lockedFile) return m_lines; m_lockedFile = hecl::Fopen(m_filepath.c_str(), _S("a+"), FileLockType::Write); std::string mainString; char readBuf[1024]; size_t readSz; while ((readSz = fread(readBuf, 1, 1024, m_lockedFile))) mainString += std::string(readBuf, readSz); std::string::const_iterator begin = mainString.begin(); std::string::const_iterator end = mainString.begin(); m_lines.clear(); while (end != mainString.end()) { std::string::const_iterator origEnd = end; if (*end == '\0') break; else if (CheckNewLineAdvance(end)) { if (begin != origEnd) m_lines.push_back(std::string(begin, origEnd)); begin = end; continue; } ++end; } if (begin != end) m_lines.push_back(std::string(begin, end)); return m_lines; } void Project::ConfigFile::addLine(const std::string& line) { if (!checkForLine(line)) m_lines.push_back(line); } void Project::ConfigFile::removeLine(const std::string& refLine) { if (!m_lockedFile) { LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, "Project::ConfigFile::lockAndRead not yet called"); return; } for (auto it = m_lines.begin(); it != m_lines.end();) { if (!(*it).compare(refLine)) { it = m_lines.erase(it); continue; } ++it; } } bool Project::ConfigFile::checkForLine(const std::string& refLine) { if (!m_lockedFile) { LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, "Project::ConfigFile::lockAndRead not yet called"); return false; } for (const std::string& line : m_lines) if (!line.compare(refLine)) return true; return false; } void Project::ConfigFile::unlockAndDiscard() { if (!m_lockedFile) { LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, "Project::ConfigFile::lockAndRead not yet called"); return; } m_lines.clear(); fclose(m_lockedFile); m_lockedFile = NULL; } bool Project::ConfigFile::unlockAndCommit() { if (!m_lockedFile) { LogModule.reportSource(logvisor::Fatal, __FILE__, __LINE__, "Project::ConfigFile::lockAndRead not yet called"); return false; } SystemString newPath = m_filepath + _S(".part"); FILE* newFile = hecl::Fopen(newPath.c_str(), _S("w"), FileLockType::Write); bool fail = false; for (const std::string& line : m_lines) { if (fwrite(line.c_str(), 1, line.size(), newFile) != line.size()) { fail = true; break; } if (fwrite("\n", 1, 1, newFile) != 1) { fail = true; break; } } m_lines.clear(); fclose(newFile); fclose(m_lockedFile); m_lockedFile = NULL; if (fail) { #if HECL_UCS2 _wunlink(newPath.c_str()); #else unlink(newPath.c_str()); #endif return false; } else { #if HECL_UCS2 _wrename(newPath.c_str(), m_filepath.c_str()); #else rename(newPath.c_str(), m_filepath.c_str()); #endif return true; } } /********************************************** * Project **********************************************/ Project::Project(const ProjectRootPath& rootPath) : m_rootPath(rootPath), m_workRoot(*this, _S("")), m_dotPath(m_workRoot, _S(".hecl")), m_cookedRoot(m_dotPath, _S("cooked")), m_specs(*this, _S("specs")), m_paths(*this, _S("paths")), m_groups(*this, _S("groups")) { /* Stat for existing project directory (must already exist) */ Sstat myStat; if (hecl::Stat(m_rootPath.getAbsolutePath().c_str(), &myStat)) { LogModule.report(logvisor::Error, _S("unable to stat %s"), m_rootPath.getAbsolutePath().c_str()); return; } if (!S_ISDIR(myStat.st_mode)) { LogModule.report(logvisor::Error, _S("provided path must be a directory; '%s' isn't"), m_rootPath.getAbsolutePath().c_str()); return; } /* Create project directory structure */ m_dotPath.makeDir(); m_cookedRoot.makeDir(); /* Ensure beacon is valid or created */ ProjectPath beaconPath(m_dotPath, _S("beacon")); FILE* bf = hecl::Fopen(beaconPath.getAbsolutePath().c_str(), _S("a+b")); struct BeaconStruct { hecl::FourCC magic; uint32_t version; } beacon; #define DATA_VERSION 1 if (fread(&beacon, 1, sizeof(beacon), bf) != sizeof(beacon)) { fseek(bf, 0, SEEK_SET); beacon.magic = HECLfcc; beacon.version = SBig(DATA_VERSION); fwrite(&beacon, 1, sizeof(beacon), bf); } fclose(bf); if (beacon.magic != HECLfcc || SBig(beacon.version) != DATA_VERSION) { LogModule.report(logvisor::Fatal, "incompatible project version"); return; } /* Compile current dataspec */ rescanDataSpecs(); m_valid = true; } const ProjectPath& Project::getProjectCookedPath(const DataSpecEntry& spec) const { for (const ProjectDataSpec& sp : m_compiledSpecs) if (&sp.spec == &spec) return sp.cookedPath; LogModule.report(logvisor::Fatal, "Unable to find spec '%s'", spec.m_name); return m_cookedRoot; } bool Project::addPaths(const std::vector& paths) { m_paths.lockAndRead(); for (const ProjectPath& path : paths) m_paths.addLine(path.getRelativePathUTF8()); return m_paths.unlockAndCommit(); } bool Project::removePaths(const std::vector& paths, bool recursive) { std::vector& existingPaths = m_paths.lockAndRead(); if (recursive) { for (const ProjectPath& path : paths) { std::string recursiveBase = path.getRelativePathUTF8(); for (auto it = existingPaths.begin(); it != existingPaths.end();) { if (!(*it).compare(0, recursiveBase.size(), recursiveBase)) { it = existingPaths.erase(it); continue; } ++it; } } } else for (const ProjectPath& path : paths) m_paths.removeLine(path.getRelativePathUTF8()); return m_paths.unlockAndCommit(); } bool Project::addGroup(const hecl::ProjectPath& path) { m_groups.lockAndRead(); m_groups.addLine(path.getRelativePathUTF8()); return m_groups.unlockAndCommit(); } bool Project::removeGroup(const ProjectPath& path) { m_groups.lockAndRead(); m_groups.removeLine(path.getRelativePathUTF8()); return m_groups.unlockAndCommit(); } void Project::rescanDataSpecs() { m_compiledSpecs.clear(); m_specs.lockAndRead(); for (const DataSpecEntry* spec : DATA_SPEC_REGISTRY) { hecl::SystemString specStr(spec->m_name); SystemUTF8View specUTF8(specStr); m_compiledSpecs.push_back({*spec, ProjectPath(m_cookedRoot, hecl::SystemString(spec->m_name) + _S(".spec")), m_specs.checkForLine(specUTF8) ? true : false}); } m_specs.unlockAndDiscard(); } bool Project::enableDataSpecs(const std::vector& specs) { m_specs.lockAndRead(); for (const SystemString& spec : specs) { SystemUTF8View specView(spec); m_specs.addLine(specView); } bool result = m_specs.unlockAndCommit(); rescanDataSpecs(); return result; } bool Project::disableDataSpecs(const std::vector& specs) { m_specs.lockAndRead(); for (const SystemString& spec : specs) { SystemUTF8View specView(spec); m_specs.removeLine(specView); } bool result = m_specs.unlockAndCommit(); rescanDataSpecs(); return result; } class CookProgress { FProgress& m_progFunc; const SystemChar* m_dir = nullptr; const SystemChar* m_file = nullptr; int lidx = 0; float m_prog = 0.0; public: CookProgress(FProgress& progFunc) : m_progFunc(progFunc) {} void changeDir(const SystemChar* dir) {m_dir = dir; ++lidx;} void changeFile(const SystemChar* file, float prog) {m_file = file; m_prog = prog;} void reportFile(const DataSpecEntry* specEnt) { SystemString submsg(m_file); submsg += _S(" ("); submsg += specEnt->m_name; submsg += _S(')'); m_progFunc(m_dir, submsg.c_str(), lidx, m_prog); } void reportFile(const DataSpecEntry* specEnt, const SystemChar* extra) { SystemString submsg(m_file); submsg += _S(" ("); submsg += specEnt->m_name; submsg += _S(", "); submsg += extra; submsg += _S(')'); m_progFunc(m_dir, submsg.c_str(), lidx, m_prog); } void reportDirComplete() {m_progFunc(m_dir, nullptr, lidx, 1.0);} }; using SpecInst = std::pair>; static void VisitFile(const ProjectPath& path, bool force, bool fast, std::vector& specInsts, CookProgress& progress) { for (SpecInst& spec : specInsts) { if (spec.second->canCook(path, hecl::SharedBlenderToken)) { const DataSpecEntry* override = spec.second->overrideDataSpec(path, spec.first, hecl::SharedBlenderToken); if (!override) continue; ProjectPath cooked = path.getCookedPath(*override); if (fast) cooked = cooked.getWithExtension(_S(".fast")); if (force || cooked.getPathType() == ProjectPath::Type::None || path.getModtime() > cooked.getModtime()) { progress.reportFile(override); spec.second->doCook(path, cooked, fast, hecl::SharedBlenderToken, [&](const SystemChar* extra) { progress.reportFile(override, extra); }); } } } } static void VisitDirectory(const ProjectPath& dir, bool recursive, bool force, bool fast, std::vector& specInsts, CookProgress& progress) { std::map children; dir.getDirChildren(children); /* Pass 1: child file count */ int childFileCount = 0; for (auto& child : children) if (child.second.getPathType() == ProjectPath::Type::File) ++childFileCount; /* Pass 2: child files */ int progNum = 0; float progDenom = childFileCount; progress.changeDir(dir.getLastComponent()); for (auto& child : children) { if (child.second.getPathType() == ProjectPath::Type::File) { progress.changeFile(child.first.c_str(), progNum++/progDenom); VisitFile(child.second, force, fast, specInsts, progress); } } progress.reportDirComplete(); /* Pass 3: child directories */ if (recursive) { for (auto& child : children) { switch (child.second.getPathType()) { case ProjectPath::Type::Directory: { VisitDirectory(child.second, recursive, force, fast, specInsts, progress); break; } default: break; } } } } static void VisitGlob(const ProjectPath& path, bool recursive, bool force, bool fast, std::vector& specInsts, CookProgress& progress) { std::vector children; path.getGlobResults(children, path.getProject().getProjectRootPath().getAbsolutePath()); /* Pass 1: child file count */ int childFileCount = 0; for (ProjectPath& child : children) if (child.getPathType() == ProjectPath::Type::File) ++childFileCount; /* Pass 2: child files */ int progNum = 0; float progDenom = childFileCount; progress.changeDir(path.getLastComponent()); for (ProjectPath& child : children) { if (child.getPathType() == ProjectPath::Type::File) { progress.changeFile(child.getLastComponent(), progNum++/progDenom); VisitFile(child, force, fast, specInsts, progress); } } progress.reportDirComplete(); /* Pass 3: child directories */ if (recursive) for (ProjectPath& child : children) if (child.getPathType() == ProjectPath::Type::Directory) VisitDirectory(child, recursive, force, fast, specInsts, progress); } bool Project::cookPath(const ProjectPath& path, FProgress progress, bool recursive, bool force, bool fast) { /* Construct DataSpec instances for cooking */ std::vector specInsts; specInsts.reserve(m_compiledSpecs.size()); for (const ProjectDataSpec& spec : m_compiledSpecs) if (spec.active && spec.spec.m_factory) specInsts.emplace_back(&spec.spec, std::unique_ptr(spec.spec.m_factory(*this, DataSpecTool::Cook))); /* Iterate complete directory/file/glob list */ CookProgress cookProg(progress); switch (path.getPathType()) { case ProjectPath::Type::File: { cookProg.changeFile(path.getLastComponent(), 0.0); VisitFile(path, force, fast, specInsts, cookProg); break; } case ProjectPath::Type::Directory: { VisitDirectory(path, recursive, force, fast, specInsts, cookProg); break; } case ProjectPath::Type::Glob: { VisitGlob(path, recursive, force, fast, specInsts, cookProg); break; } default: break; } return true; } void Project::interruptCook() { } bool Project::cleanPath(const ProjectPath& path, bool recursive) { return false; } PackageDepsgraph Project::buildPackageDepsgraph(const ProjectPath& path) { return PackageDepsgraph(); } void Project::addBridgePathToCache(uint64_t id, const ProjectPath& path) { m_bridgePathCache[id] = path; } void Project::clearBridgePathCache() { m_bridgePathCache.clear(); } const ProjectPath* Project::lookupBridgePath(uint64_t id) const { auto search = m_bridgePathCache.find(id); if (search == m_bridgePathCache.cend()) return nullptr; return &search->second; } } }