303 lines
11 KiB
C++
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
|