milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 5m6s
CI / frontend (push) Successful in 45s
CI / cross (push) Successful in 7m53s
CI / docker (push) Failing after 1h10m57s

This commit is contained in:
2026-08-07 18:20:30 +02:00
parent c50c6d285a
commit 6f67940995
82 changed files with 3167 additions and 3114 deletions
+24 -44
View File
@@ -25,35 +25,24 @@ const list_sql =
/// Every string in the result is a heap copy owned by `gpa`.
pub fn listBlocklistSources(database: *db.Db, gpa: Allocator) db.Error!std.ArrayList(model.BlocklistSource) {
var stmt = try database.prepare(list_sql);
defer stmt.deinit();
return crud.listRows(model.BlocklistSource, database, gpa, list_sql, readBlocklistSource);
}
var out: std.ArrayList(model.BlocklistSource) = .empty;
// `errdefer`s run in reverse: the free pass is declared last so it runs
// before the backing array is released.
errdefer out.deinit(gpa);
errdefer freeBlocklistSources(gpa, out.items);
while (try stmt.step()) {
const url = try stmt.columnTextAlloc(gpa, 0);
errdefer gpa.free(url);
const name = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(name);
try out.append(gpa, .{
.url = url,
.name = name,
.enabled = stmt.columnBool(2),
.is_suggested = stmt.columnBool(3),
});
}
return out;
fn readBlocklistSource(stmt: *db.Stmt, gpa: Allocator) db.Error!model.BlocklistSource {
const url = try stmt.columnTextAlloc(gpa, 0);
errdefer gpa.free(url);
const name = try stmt.columnTextAlloc(gpa, 1);
errdefer gpa.free(name);
return .{
.url = url,
.name = name,
.enabled = stmt.columnBool(2),
.is_suggested = stmt.columnBool(3),
};
}
pub fn freeBlocklistSources(gpa: Allocator, items: []const model.BlocklistSource) void {
for (items) |item| {
gpa.free(item.url);
gpa.free(item.name);
}
crud.freeRows(model.BlocklistSource, gpa, items);
}
const insert_sql =
@@ -125,21 +114,7 @@ const list_rows_sql = row_columns_sql ++ " ORDER BY url";
/// order `listBlocklistSources` uses. Every string is a heap copy owned by
/// `gpa`; free the whole list with `freeSourceRows` and then `deinit` the list.
pub fn listSourceRows(database: *db.Db, gpa: Allocator) db.Error!std.ArrayList(SourceRow) {
var stmt = try database.prepare(list_rows_sql);
defer stmt.deinit();
var out: std.ArrayList(SourceRow) = .empty;
// `errdefer`s run in reverse: the free pass is declared last so it runs
// before the backing array is released.
errdefer out.deinit(gpa);
errdefer freeSourceRows(gpa, out.items);
while (try stmt.step()) {
const row = try readSourceRow(&stmt, gpa);
errdefer freeSourceRow(gpa, row);
try out.append(gpa, row);
}
return out;
return crud.listRows(SourceRow, database, gpa, list_rows_sql, readSourceRow);
}
fn readSourceRow(stmt: *db.Stmt, gpa: Allocator) db.Error!SourceRow {
@@ -163,14 +138,13 @@ fn readSourceRow(stmt: *db.Stmt, gpa: Allocator) db.Error!SourceRow {
};
}
/// Frees `url`, `name` and the `checksum` payload when it is not null.
pub fn freeSourceRow(gpa: Allocator, row: SourceRow) void {
gpa.free(row.url);
gpa.free(row.name);
if (row.checksum) |value| gpa.free(value);
crud.freeRow(SourceRow, gpa, row);
}
pub fn freeSourceRows(gpa: Allocator, items: []const SourceRow) void {
for (items) |item| freeSourceRow(gpa, item);
crud.freeRows(SourceRow, gpa, items);
}
const update_stats_sql =
@@ -438,6 +412,12 @@ fn listSourceRowsUnderFailure(gpa: Allocator) !void {
var rows = try listSourceRows(&database, gpa);
defer rows.deinit(gpa);
defer freeSourceRows(gpa, rows.items);
// `checksum` is the one allocated optional in this directory. Without a
// non-null one in the result the injection never reaches its allocation and
// this test stops covering the shape it exists for. Row id 1 sorts last:
// `seedSources` inserts `c.example` first and the list orders by url.
try testing.expect(rows.items[2].checksum != null);
}
test "listSourceRows is leak-safe under allocation failure" {