Files
Juliet/Romeo/src/Database.cpp
T
2026-07-22 17:15:52 -04:00

303 lines
11 KiB
C++

#include "Database.h"
#include <Core/HAL/Filesystem/Filesystem.h>
#include <Core/Memory/MemoryArena.h>
#include <Windows.h>
namespace Romeo
{
void DB_Init(Database& db, Juliet::Arena* arena)
{
printf("Entering DB_Init...\n");
db.DbArena = arena;
db.Entries.Create(arena JULIET_DEBUG_ONLY(, "RomeoDB"));
printf("Exiting DB_Init...\n");
}
struct ScanContext
{
Database* Db;
Juliet::Arena* TempArena;
};
static void ProcessFile(ScanContext& ctx, const Juliet::String& path, const WIN32_FIND_DATAW& findData)
{
// Simple filter: only .h and .cpp
const char* pPath = CStr(path);
size_t len = StringLength(path);
if (len < 3)
{
return;
}
bool isHeader = false;
bool isCpp = false;
if (pPath[len - 2] == '.' && pPath[len - 1] == 'h')
{
isHeader = true;
}
else if (len >= 4 && pPath[len - 4] == '.' && pPath[len - 3] == 'c' && pPath[len - 2] == 'p' && pPath[len - 1] == 'p')
{
isCpp = true;
}
if (!isHeader && !isCpp)
{
return;
}
uint64_t lastModified = (static_cast<uint64_t>(findData.ftLastWriteTime.dwHighDateTime) << 32) | findData.ftLastWriteTime.dwLowDateTime;
// Try to find if we already have it in the DB (ignoring \include\ or \src\ offset)
const char* relStart = pPath;
if (const char* p1 = strstr(pPath, "\\include\\"))
{
relStart = p1 + 9;
}
else if (const char* p2 = strstr(pPath, "\\src\\"))
{
relStart = p2 + 5;
}
size_t baseLen = len - static_cast<size_t>(relStart - pPath);
baseLen -= (isHeader ? 2 : 4);
FileEntry* entry = nullptr;
for (FileEntry& e : ctx.Db->Entries)
{
Juliet::String dbPath = e.HeaderPath.Size > 0 ? e.HeaderPath : e.CppPath;
if (dbPath.Size > 0)
{
const char* pDbPath = CStr(dbPath);
const char* dbRelStart = pDbPath;
if (const char* p1 = strstr(pDbPath, "\\include\\"))
{
dbRelStart = p1 + 9;
}
else if (const char* p2 = strstr(pDbPath, "\\src\\"))
{
dbRelStart = p2 + 5;
}
bool dbIsHeader = (pDbPath[dbPath.Size - 1] == 'h');
size_t dbBaseLen = dbPath.Size - static_cast<size_t>(dbRelStart - pDbPath);
dbBaseLen -= (dbIsHeader ? 2 : 4);
if (baseLen == dbBaseLen && strncmp(relStart, dbRelStart, baseLen) == 0)
{
entry = &e;
break;
}
}
}
if (!entry)
{
FileEntry newEntry = {};
ctx.Db->Entries.PushBack(newEntry);
entry = ctx.Db->Entries.Back();
}
if (isHeader)
{
entry->HeaderPath = Juliet::StringCopy(ctx.Db->DbArena, path);
entry->LastModifiedHeader = lastModified;
}
else
{
entry->CppPath = Juliet::StringCopy(ctx.Db->DbArena, path);
entry->LastModifiedCpp = lastModified;
}
}
static void ScanDirectoryRecursive(ScanContext& ctx, const Juliet::String& currentDir)
{
size_t searchPathLen = currentDir.Size + 3; // "\*" + null
char* searchPathBuf = ArenaPushArray<char>(ctx.TempArena, searchPathLen JULIET_DEBUG_PARAM("RomeoDbSearchPathBuf"));
snprintf(searchPathBuf, searchPathLen, "%s\\*", CStr(currentDir));
Juliet::String searchPath = {searchPathBuf, searchPathLen - 1};
// Convert to wide string for Win32
int wLen = MultiByteToWideChar(CP_UTF8, 0, CStr(searchPath), -1, nullptr, 0);
if (wLen <= 0)
{
return;
}
wchar_t* wSearchPath = ArenaPushArray<wchar_t>(ctx.TempArena, static_cast<size_t>(wLen) JULIET_DEBUG_PARAM("RomeoDbWSearch"));
MultiByteToWideChar(CP_UTF8, 0, CStr(searchPath), -1, wSearchPath, wLen);
WIN32_FIND_DATAW findData;
HANDLE hFind = FindFirstFileExW(wSearchPath, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, 0);
if (hFind == INVALID_HANDLE_VALUE)
{
return;
}
do
{
if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0)
{
continue;
}
// Convert filename back to UTF8
int uLen = WideCharToMultiByte(CP_UTF8, 0, findData.cFileName, -1, nullptr, 0, nullptr, nullptr);
char* uFileName = ArenaPushArray<char>(ctx.TempArena, static_cast<size_t>(uLen) JULIET_DEBUG_PARAM("RomeoDbUFileName"));
WideCharToMultiByte(CP_UTF8, 0, findData.cFileName, -1, uFileName, uLen, nullptr, nullptr);
size_t fullPathLen = currentDir.Size + 1 + static_cast<size_t>(uLen); // "\" + null
char* fullPathBuf = ArenaPushArray<char>(ctx.TempArena, fullPathLen JULIET_DEBUG_PARAM("RomeoDbFullPathBuf"));
snprintf(fullPathBuf, fullPathLen, "%s\\%s", CStr(currentDir), uFileName);
Juliet::String fullPath = {fullPathBuf, fullPathLen - 1};
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
// Skip external/build dirs
if (strcmp(uFileName, "External") != 0 && strcmp(uFileName, "Intermediate") != 0 && strcmp(uFileName, "bin") != 0 && strcmp(uFileName, ".git") != 0)
{
ScanDirectoryRecursive(ctx, fullPath);
}
}
else
{
ProcessFile(ctx, fullPath, findData);
}
} while (FindNextFileW(hFind, &findData) != 0);
FindClose(hFind);
}
void DB_ScanWorkspace(Database& db, Juliet::Arena* tempArena, const Juliet::String& rootDir)
{
Assert(tempArena != nullptr);
ScanContext ctx;
ctx.Db = &db;
ctx.TempArena = tempArena;
// Check if src and include are directly under rootDir or under rootDir\Juliet
char srcDirBuf[512];
snprintf(srcDirBuf, sizeof(srcDirBuf), "%s\\src", CStr(rootDir));
DWORD srcAttribs = GetFileAttributesA(srcDirBuf);
if (srcAttribs == INVALID_FILE_ATTRIBUTES || !(srcAttribs & FILE_ATTRIBUTE_DIRECTORY))
{
snprintf(srcDirBuf, sizeof(srcDirBuf), "%s\\Juliet\\src", CStr(rootDir));
}
Juliet::String srcDir = Juliet::StringCopy(tempArena, Juliet::WrapString(srcDirBuf));
char incDirBuf[512];
snprintf(incDirBuf, sizeof(incDirBuf), "%s\\include", CStr(rootDir));
DWORD incAttribs = GetFileAttributesA(incDirBuf);
if (incAttribs == INVALID_FILE_ATTRIBUTES || !(incAttribs & FILE_ATTRIBUTE_DIRECTORY))
{
snprintf(incDirBuf, sizeof(incDirBuf), "%s\\Juliet\\include", CStr(rootDir));
}
Juliet::String incDir = Juliet::StringCopy(tempArena, Juliet::WrapString(incDirBuf));
ScanDirectoryRecursive(ctx, srcDir);
ScanDirectoryRecursive(ctx, incDir);
}
bool DB_Save(const Database& db, const Juliet::String& filePath)
{
FILE* file = nullptr;
if (fopen_s(&file, CStr(filePath), "wb") != 0 || !file)
{
return false;
}
uint64_t entryCount = db.Entries.Size();
fwrite(&entryCount, sizeof(uint64_t), 1, file);
for (const FileEntry& entry : db.Entries)
{
uint64_t hLen = entry.HeaderPath.Size;
fwrite(&hLen, sizeof(uint64_t), 1, file);
if (hLen > 0) fwrite(entry.HeaderPath.Data, 1, static_cast<size_t>(hLen), file);
uint64_t cLen = entry.CppPath.Size;
fwrite(&cLen, sizeof(uint64_t), 1, file);
if (cLen > 0) fwrite(entry.CppPath.Data, 1, static_cast<size_t>(cLen), file);
fwrite(&entry.LastModifiedHeader, sizeof(uint64_t), 1, file);
fwrite(&entry.LastModifiedCpp, sizeof(uint64_t), 1, file);
fwrite(&entry.LastMarkdownCreatedTime, sizeof(uint64_t), 1, file);
fwrite(&entry.LastAIGeneratedTime, sizeof(uint64_t), 1, file);
uint64_t dLen = entry.Description.Size;
fwrite(&dLen, sizeof(uint64_t), 1, file);
if (dLen > 0) fwrite(entry.Description.Data, 1, static_cast<size_t>(dLen), file);
}
fclose(file);
return true;
}
bool DB_Load(Database& db, const Juliet::String& filePath)
{
FILE* file = nullptr;
if (fopen_s(&file, CStr(filePath), "rb") != 0 || !file)
{
return false;
}
db.Entries.Clear();
uint64_t entryCount = 0;
if (fread(&entryCount, sizeof(uint64_t), 1, file) != 1)
{
fclose(file);
return false;
}
for (uint64_t i = 0; i < entryCount; ++i)
{
FileEntry entry = {};
uint64_t hLen = 0;
if (fread(&hLen, sizeof(uint64_t), 1, file) != 1) break;
if (hLen > 0)
{
entry.HeaderPath.Data = ArenaPushArray<char>(db.DbArena, static_cast<size_t>(hLen + 1) JULIET_DEBUG_PARAM("DbLoadHeader"));
entry.HeaderPath.Size = static_cast<size_t>(hLen);
fread(entry.HeaderPath.Data, 1, static_cast<size_t>(hLen), file);
entry.HeaderPath.Data[hLen] = '\0';
}
uint64_t cLen = 0;
if (fread(&cLen, sizeof(uint64_t), 1, file) != 1) break;
if (cLen > 0)
{
entry.CppPath.Data = ArenaPushArray<char>(db.DbArena, static_cast<size_t>(cLen + 1) JULIET_DEBUG_PARAM("DbLoadCpp"));
entry.CppPath.Size = static_cast<size_t>(cLen);
fread(entry.CppPath.Data, 1, static_cast<size_t>(cLen), file);
entry.CppPath.Data[cLen] = '\0';
}
fread(&entry.LastModifiedHeader, sizeof(uint64_t), 1, file);
fread(&entry.LastModifiedCpp, sizeof(uint64_t), 1, file);
fread(&entry.LastMarkdownCreatedTime, sizeof(uint64_t), 1, file);
fread(&entry.LastAIGeneratedTime, sizeof(uint64_t), 1, file);
uint64_t dLen = 0;
if (fread(&dLen, sizeof(uint64_t), 1, file) != 1) break;
if (dLen > 0)
{
entry.Description.Data = ArenaPushArray<char>(db.DbArena, static_cast<size_t>(dLen + 1) JULIET_DEBUG_PARAM("DbLoadDesc"));
entry.Description.Size = static_cast<size_t>(dLen);
fread(entry.Description.Data, 1, static_cast<size_t>(dLen), file);
entry.Description.Data[dLen] = '\0';
}
db.Entries.PushBack(entry);
}
fclose(file);
return true;
}
} // namespace Romeo