#include "Database.h" #include #include #include namespace Romeo { void DB_Init(Database& db, 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; Arena* TempArena; }; static void ProcessFile(ScanContext& ctx, const 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(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(relStart - pPath); baseLen -= (isHeader ? 2 : 4); FileEntry* entry = nullptr; for (FileEntry& e : ctx.Db->Entries) { 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(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 = StringCopy(ctx.Db->DbArena, path); entry->LastModifiedHeader = lastModified; } else { entry->CppPath = StringCopy(ctx.Db->DbArena, path); entry->LastModifiedCpp = lastModified; } } static void ScanDirectoryRecursive(ScanContext& ctx, const String& currentDir) { size_t searchPathLen = currentDir.Size + 3; // "\*" + null char* searchPathBuf = ArenaPushArray(ctx.TempArena, searchPathLen JULIET_DEBUG_PARAM("RomeoDbSearchPathBuf")); snprintf(searchPathBuf, searchPathLen, "%s\\*", CStr(currentDir)); 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(ctx.TempArena, static_cast(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(ctx.TempArena, static_cast(uLen) JULIET_DEBUG_PARAM("RomeoDbUFileName")); WideCharToMultiByte(CP_UTF8, 0, findData.cFileName, -1, uFileName, uLen, nullptr, nullptr); size_t fullPathLen = currentDir.Size + 1 + static_cast(uLen); // "\" + null char* fullPathBuf = ArenaPushArray(ctx.TempArena, fullPathLen JULIET_DEBUG_PARAM("RomeoDbFullPathBuf")); snprintf(fullPathBuf, fullPathLen, "%s\\%s", CStr(currentDir), uFileName); 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, Arena* tempArena, const 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)); } String srcDir = StringCopy(tempArena, 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)); } String incDir = StringCopy(tempArena, WrapString(incDirBuf)); ScanDirectoryRecursive(ctx, srcDir); ScanDirectoryRecursive(ctx, incDir); } bool DB_Save(const Database& db, const 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.Str, 1, static_cast(hLen), file); uint64_t cLen = entry.CppPath.Size; fwrite(&cLen, sizeof(uint64_t), 1, file); if (cLen > 0) fwrite(entry.CppPath.Str, 1, static_cast(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.Str, 1, static_cast(dLen), file); } fclose(file); return true; } bool DB_Load(Database& db, const 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.Str = ArenaPushArray(db.DbArena, static_cast(hLen + 1) JULIET_DEBUG_PARAM("DbLoadHeader")); entry.HeaderPath.Size = static_cast(hLen); fread(entry.HeaderPath.Str, 1, static_cast(hLen), file); entry.HeaderPath.Str[hLen] = '\0'; } uint64_t cLen = 0; if (fread(&cLen, sizeof(uint64_t), 1, file) != 1) break; if (cLen > 0) { entry.CppPath.Str = ArenaPushArray(db.DbArena, static_cast(cLen + 1) JULIET_DEBUG_PARAM("DbLoadCpp")); entry.CppPath.Size = static_cast(cLen); fread(entry.CppPath.Str, 1, static_cast(cLen), file); entry.CppPath.Str[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.Str = ArenaPushArray(db.DbArena, static_cast(dLen + 1) JULIET_DEBUG_PARAM("DbLoadDesc")); entry.Description.Size = static_cast(dLen); fread(entry.Description.Str, 1, static_cast(dLen), file); entry.Description.Str[dLen] = '\0'; } db.Entries.PushBack(entry); } fclose(file); return true; } } // namespace Romeo