a466fff71a
- main.cpp: registrar info About via fn_ui::about_window_set_info - views.cpp: nueva columna "Git" en la tabla Apps (remote/local/-) - data.h/cpp + data_http.cpp: AppRow gana repo_url + dir_path - views.cpp: actions bar (Reindex / + Add / Reload / inbox) y modal Add - views.cpp: tab Projects con tree + detalle anidado - data_http.cpp: load_projects_http, load_project_detail_http, http_post_* - http_client.cpp: SO_RCVTIMEO en Windows como DWORD ms (timeout 5 ms bug) - CMakeLists: limpieza de srcs duplicados con fn_framework - app.md: notas operativas y estado actual Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
243 lines
7.9 KiB
C++
243 lines
7.9 KiB
C++
#include "data.h"
|
|
#include <sqlite3.h>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <shlobj.h>
|
|
#endif
|
|
|
|
// Helper: execute a query and call row_fn for each row
|
|
template<typename F>
|
|
static bool query(sqlite3* db, const char* sql, F row_fn) {
|
|
sqlite3_stmt* stmt = nullptr;
|
|
if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {
|
|
fprintf(stderr, "SQL error: %s\n query: %s\n", sqlite3_errmsg(db), sql);
|
|
return false;
|
|
}
|
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
|
row_fn(stmt);
|
|
}
|
|
sqlite3_finalize(stmt);
|
|
return true;
|
|
}
|
|
|
|
static std::string col_str(sqlite3_stmt* s, int i) {
|
|
const char* t = reinterpret_cast<const char*>(sqlite3_column_text(s, i));
|
|
return t ? t : "";
|
|
}
|
|
|
|
static int col_int(sqlite3_stmt* s, int i) {
|
|
return sqlite3_column_int(s, i);
|
|
}
|
|
|
|
void RegistryData::prepare_chart_data() {
|
|
lang_labels.clear(); lang_values.clear();
|
|
for (auto& lc : by_lang) {
|
|
lang_labels.push_back(lc.lang);
|
|
lang_values.push_back(static_cast<float>(lc.count));
|
|
}
|
|
|
|
domain_labels.clear(); domain_values.clear();
|
|
for (auto& dc : by_domain) {
|
|
domain_labels.push_back(dc.domain);
|
|
domain_values.push_back(static_cast<float>(dc.count));
|
|
}
|
|
|
|
kind_labels.clear(); kind_values.clear();
|
|
for (auto& kc : by_kind) {
|
|
kind_labels.push_back(kc.kind);
|
|
kind_values.push_back(static_cast<float>(kc.count));
|
|
}
|
|
|
|
date_labels.clear(); date_values.clear();
|
|
for (auto& dc : by_date) {
|
|
date_labels.push_back(dc.date);
|
|
date_values.push_back(static_cast<float>(dc.count));
|
|
}
|
|
}
|
|
|
|
// Copy file src to dst. Returns true on success.
|
|
static bool copy_file(const char* src, const char* dst) {
|
|
#ifdef _WIN32
|
|
return CopyFileA(src, dst, FALSE) != 0;
|
|
#else
|
|
FILE* in = fopen(src, "rb");
|
|
if (!in) return false;
|
|
FILE* out = fopen(dst, "wb");
|
|
if (!out) { fclose(in); return false; }
|
|
char buf[65536];
|
|
size_t n;
|
|
while ((n = fread(buf, 1, sizeof(buf), in)) > 0)
|
|
fwrite(buf, 1, n, out);
|
|
fclose(in);
|
|
fclose(out);
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
// On Windows, if the path is a network/UNC path (like \\wsl.localhost\...),
|
|
// copy the DB to a local temp file so SQLite locking works correctly.
|
|
// Returns the path to use (may be the original or a temp copy).
|
|
static std::string ensure_local_db(const char* db_path) {
|
|
#ifdef _WIN32
|
|
// Detect UNC / network paths
|
|
if (strncmp(db_path, "\\\\", 2) == 0 || strncmp(db_path, "//", 2) == 0) {
|
|
char temp_dir[MAX_PATH] = {0};
|
|
GetTempPathA(MAX_PATH, temp_dir);
|
|
std::string local_path = std::string(temp_dir) + "fn_registry_dashboard.db";
|
|
|
|
fprintf(stdout, "Copying DB to local temp: %s\n", local_path.c_str());
|
|
if (copy_file(db_path, local_path.c_str())) {
|
|
return local_path;
|
|
}
|
|
fprintf(stderr, "Warning: failed to copy DB locally, using original path\n");
|
|
}
|
|
#endif
|
|
return db_path;
|
|
}
|
|
|
|
bool load_registry_data(const char* db_path, RegistryData& out) {
|
|
std::string effective_path = ensure_local_db(db_path);
|
|
|
|
sqlite3* db = nullptr;
|
|
if (sqlite3_open_v2(effective_path.c_str(), &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) {
|
|
fprintf(stderr, "Cannot open %s: %s\n", effective_path.c_str(), sqlite3_errmsg(db));
|
|
return false;
|
|
}
|
|
|
|
sqlite3_busy_timeout(db, 3000);
|
|
|
|
// --- Counts ---
|
|
query(db, "SELECT COUNT(*) FROM functions", [&](sqlite3_stmt* s) {
|
|
out.stats.total_functions = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM types", [&](sqlite3_stmt* s) {
|
|
out.stats.total_types = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM apps", [&](sqlite3_stmt* s) {
|
|
out.stats.total_apps = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM analysis", [&](sqlite3_stmt* s) {
|
|
out.stats.total_analysis = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM unit_tests", [&](sqlite3_stmt* s) {
|
|
out.stats.total_unit_tests = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM proposals", [&](sqlite3_stmt* s) {
|
|
out.stats.total_proposals = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM functions WHERE tested = 1", [&](sqlite3_stmt* s) {
|
|
out.stats.tested_functions = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM functions WHERE purity = 'pure'", [&](sqlite3_stmt* s) {
|
|
out.stats.pure_functions = col_int(s, 0);
|
|
});
|
|
query(db, "SELECT COUNT(*) FROM functions WHERE purity = 'impure'", [&](sqlite3_stmt* s) {
|
|
out.stats.impure_functions = col_int(s, 0);
|
|
});
|
|
|
|
// --- By language ---
|
|
out.by_lang.clear();
|
|
query(db, "SELECT lang, COUNT(*) as cnt FROM functions GROUP BY lang ORDER BY cnt DESC",
|
|
[&](sqlite3_stmt* s) {
|
|
out.by_lang.push_back({col_str(s, 0), col_int(s, 1)});
|
|
});
|
|
|
|
// --- By domain ---
|
|
out.by_domain.clear();
|
|
query(db, "SELECT domain, COUNT(*) as cnt FROM functions GROUP BY domain ORDER BY cnt DESC",
|
|
[&](sqlite3_stmt* s) {
|
|
out.by_domain.push_back({col_str(s, 0), col_int(s, 1)});
|
|
});
|
|
|
|
// --- By kind ---
|
|
out.by_kind.clear();
|
|
query(db, "SELECT kind, COUNT(*) as cnt FROM functions GROUP BY kind ORDER BY cnt DESC",
|
|
[&](sqlite3_stmt* s) {
|
|
out.by_kind.push_back({col_str(s, 0), col_int(s, 1)});
|
|
});
|
|
|
|
// --- By date (last 30 days) ---
|
|
out.by_date.clear();
|
|
query(db,
|
|
"SELECT date(created_at) as d, COUNT(*) as cnt FROM functions "
|
|
"WHERE created_at >= date('now', '-30 days') "
|
|
"GROUP BY d ORDER BY d",
|
|
[&](sqlite3_stmt* s) {
|
|
out.by_date.push_back({col_str(s, 0), col_int(s, 1)});
|
|
});
|
|
|
|
// --- Recent functions (last 20) ---
|
|
out.recent_funcs.clear();
|
|
query(db,
|
|
"SELECT id, name, lang, domain, kind, purity, description, created_at, tested "
|
|
"FROM functions ORDER BY created_at DESC LIMIT 20",
|
|
[&](sqlite3_stmt* s) {
|
|
FunctionRow r;
|
|
r.id = col_str(s, 0);
|
|
r.name = col_str(s, 1);
|
|
r.lang = col_str(s, 2);
|
|
r.domain = col_str(s, 3);
|
|
r.kind = col_str(s, 4);
|
|
r.purity = col_str(s, 5);
|
|
r.description = col_str(s, 6);
|
|
r.created_at = col_str(s, 7);
|
|
r.tested = col_int(s, 8) != 0;
|
|
out.recent_funcs.push_back(std::move(r));
|
|
});
|
|
|
|
// --- Apps ---
|
|
out.apps.clear();
|
|
query(db,
|
|
"SELECT id, name, lang, domain, description, framework, repo_url, dir_path FROM apps ORDER BY name",
|
|
[&](sqlite3_stmt* s) {
|
|
AppRow r;
|
|
r.id = col_str(s, 0);
|
|
r.name = col_str(s, 1);
|
|
r.lang = col_str(s, 2);
|
|
r.domain = col_str(s, 3);
|
|
r.description = col_str(s, 4);
|
|
r.framework = col_str(s, 5);
|
|
r.repo_url = col_str(s, 6);
|
|
r.dir_path = col_str(s, 7);
|
|
out.apps.push_back(std::move(r));
|
|
});
|
|
|
|
// --- Analysis ---
|
|
out.analyses.clear();
|
|
query(db,
|
|
"SELECT id, name, lang, domain, description FROM analysis ORDER BY name",
|
|
[&](sqlite3_stmt* s) {
|
|
AnalysisRow r;
|
|
r.id = col_str(s, 0);
|
|
r.name = col_str(s, 1);
|
|
r.lang = col_str(s, 2);
|
|
r.domain = col_str(s, 3);
|
|
r.description = col_str(s, 4);
|
|
out.analyses.push_back(std::move(r));
|
|
});
|
|
|
|
// --- Types ---
|
|
out.types.clear();
|
|
query(db,
|
|
"SELECT id, name, lang, domain, algebraic, description FROM types ORDER BY name",
|
|
[&](sqlite3_stmt* s) {
|
|
TypeRow r;
|
|
r.id = col_str(s, 0);
|
|
r.name = col_str(s, 1);
|
|
r.lang = col_str(s, 2);
|
|
r.domain = col_str(s, 3);
|
|
r.algebraic = col_str(s, 4);
|
|
r.description = col_str(s, 5);
|
|
out.types.push_back(std::move(r));
|
|
});
|
|
|
|
sqlite3_close(db);
|
|
|
|
out.prepare_chart_data();
|
|
return true;
|
|
}
|