| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- using Invercargill;
- using Invercargill.DataStructures;
- using InvercargillJson;
- using Statum;
- using Usm;
- namespace UsmWeb {
- /** One package row in the overview and single-repo tables. */
- public class PackageRow : Object {
- public string name { get; set; default = ""; }
- public string version { get; set; default = ""; }
- public string summary { get; set; default = ""; }
- public string size { get; set; default = ""; }
- public string file { get; set; default = ""; }
- public string repo { get; set; default = ""; }
- }
- /**
- * One artifact row in a DCBA repository's browse table: the listing
- * entry's name/version plus the page links the row renders — the
- * detail page ({@link href}) and the raw `.dcba` download
- * ({@link download_href}).
- */
- public class ArtifactRow : Object {
- public string name { get; set; default = ""; }
- public string version { get; set; default = ""; }
- public string size { get; set; default = ""; }
- public string file { get; set; default = ""; }
- public string href { get; set; default = ""; }
- public string download_href { get; set; default = ""; }
- }
- /**
- * One repository summary in the overview: the stats card fields plus the
- * per-repository add-repository instruction fields ({@link base_uri},
- * {@link usmr_href}, {@link repos_d_snippet}).
- *
- * Both repository kinds render a card; {@link kind} (the
- * {@link RepositoryKind.to_string} wire name) picks which instruction
- * block and stats the card shows — a USM card carries its package count
- * and byte total, a DCBA card its artifact count and artifact byte
- * total — that total sums the listing's `size` fields, so artifacts
- * from listings written before the field existed (size 0) contribute
- * nothing while still counting toward {@link artifact_count}. The
- * `*_href`/`*_snippet` members are named for the USM card;
- * a DCBA summary fills them with the `repo.dcbar` / `usm dcba
- * add-repo` counterparts so the card markup stays one block with a
- * kind branch.
- */
- public class RepoSummary : Object {
- public string name { get; set; default = ""; }
- public string summary { get; set; default = ""; }
- public string kind { get; set; default = "usm"; }
- public int package_count { get; set; }
- public string total_size { get; set; default = ""; }
- public string href { get; set; default = ""; }
- public string key_fingerprint { get; set; default = ""; }
- public string base_uri { get; set; default = ""; }
- public string usmr_href { get; set; default = ""; }
- public string repos_d_snippet { get; set; default = ""; }
- public int artifact_count { get; set; }
- public string artifact_total_size { get; set; default = ""; }
- }
- /**
- * Authors the `packages` PAGE-slot state shared by the overview and
- * single-repo entrypoints and the search action: repository summaries,
- * the add-repository / install-USM instruction blocks, and the (possibly
- * query-filtered) package table. The lists are authored as JSON arrays
- * because collection-typed GObject properties are write-side only.
- */
- public class PackagesState : Object {
- public const string SLOT_TYPE = "packages";
- /**
- * Builds the slot state: a repo-scoped view when `repo_name` names a
- * repository (its overview card, instruction block and table rows),
- * otherwise the all-repositories overview (summary cards with
- * per-repository add instructions, and one combined table). Rows are
- * filtered by the case-insensitive `query` substring over name and
- * summary (an empty query keeps every entry).
- *
- * The overview lists repositories of **both** kinds as summary
- * cards, in discovery order, but only the USM source repositories
- * feed the package table and the package counts and byte totals —
- * a DCBA entry contributes a card (its artifact count and artifact
- * byte total from the `ARTIFACTS.usml` sizes) and nothing else, so
- * pre-built artifacts surface in the detail views, never in the
- * home package table.
- *
- * A repo-scoped build for a DCBA repository renders that
- * repository's browse view instead: the same overview-card
- * fields (carrying its artifact count and artifact byte total),
- * the `usm dcba add-repo` instruction and one
- * {@link ArtifactRow} per `ARTIFACTS.usml` entry. The query
- * never filters artifacts (search is source-package-only), so
- * those rows ignore `query` entirely and the page omits the
- * search form on the DCBA branch.
- */
- public static State build(RepositoryService repositories, UsmWebConfig config,
- ActionRegistry action_registry, string base_uri, string query, string? repo_name = null) throws Error {
- var repo_rows = new Vector<RepoSummary>();
- var package_rows = new Vector<PackageRow>();
- var dcba_artifact_rows = new Vector<ArtifactRow>();
- int64 total_bytes = 0;
- uint total_count = 0;
- uint matched = 0;
- foreach (var entry in repositories.entries()) {
- var repo = entry.repo();
- if (repo_name != null && repo.name != repo_name) {
- continue;
- }
- if (entry.kind == RepositoryKind.DCBA) {
- int64 artifact_bytes = 0;
- uint artifact_count = 0;
- var artifact_rows = new Vector<ArtifactRow>();
- if (repo_name != null) {
- foreach (var artifact_entry in entry.dcba_listing_entries()) {
- var file = Path.get_basename(artifact_entry.path);
- artifact_bytes += (int64) artifact_entry.size;
- artifact_count++;
- artifact_rows.add(new ArtifactRow() {
- name = artifact_entry.name,
- version = artifact_entry.version,
- size = artifact_entry.size > 0
- ? format_size((int64) artifact_entry.size)
- : "?",
- file = file,
- href = "/repo/" + repo.name + "/artifact/" + file,
- download_href = "/repo/" + repo.name + "/" + file
- });
- }
- dcba_artifact_rows.add_all(artifact_rows);
- } else {
- foreach (var artifact_entry in entry.dcba_listing_entries()) {
- artifact_bytes += (int64) artifact_entry.size;
- artifact_count++;
- }
- }
- repo_rows.add(new RepoSummary() {
- name = repo.name,
- summary = repo.summary,
- kind = RepositoryKind.DCBA.to_string(),
- artifact_count = (int) artifact_count,
- artifact_total_size = format_size(artifact_bytes),
- href = "/repo/" + repo.name,
- key_fingerprint = fingerprint(repo.key.to_hex()),
- base_uri = base_uri + "repo/" + repo.name + "/",
- usmr_href = base_uri + "repo/" + repo.name + "/repo.dcbar",
- repos_d_snippet = "sudo usm dcba add-repo %srepo/%s/repo.dcbar"
- .printf(base_uri, repo.name)
- });
- continue;
- }
- int64 repo_bytes = 0;
- uint repo_count = 0;
- var rows = new Vector<PackageRow>();
- foreach (var listing_entry in entry.listing_entries()) {
- var file = Path.get_basename(listing_entry.path);
- int64 size = file_size(entry.package_location(file));
- repo_bytes += size;
- repo_count++;
- if (!matches(listing_entry, query)) {
- continue;
- }
- rows.add(new PackageRow() {
- name = listing_entry.manifest.name,
- version = listing_entry.manifest.version.to_string(),
- summary = listing_entry.manifest.summary,
- size = format_size(size),
- file = file,
- repo = repo.name
- });
- }
- matched += rows.length;
- package_rows.add_all(rows);
- repo_rows.add(new RepoSummary() {
- name = repo.name,
- summary = repo.summary,
- kind = RepositoryKind.USM.to_string(),
- package_count = (int) repo_count,
- total_size = format_size(repo_bytes),
- href = "/repo/" + repo.name,
- key_fingerprint = fingerprint(repo.key.to_hex()),
- base_uri = base_uri + "repo/" + repo.name + "/",
- usmr_href = base_uri + "repo/" + repo.name + "/repo.usmr",
- repos_d_snippet = "sudo usm add-repo %srepo/%s/repo.usmr"
- .printf(base_uri, repo.name)
- });
- total_bytes += repo_bytes;
- total_count += repo_count;
- }
- if (repo_name != null && repo_rows.length == 0) {
- throw new UsmWebError.INVALID_CONFIGURATION(@"No repository named \"$repo_name\"");
- }
- var dict = new PropertyDictionary();
- if (repo_name == null) {
- dict.set_native<string>("site_name", config.display_name ?? "Repositories");
- dict.set_native<int>("repo_count", (int) repo_rows.length);
- dict.set_native<int>("package_count", (int) total_count);
- dict.set_native<string>("total_size", format_size(total_bytes));
- dict["repos"] = repo_list(repo_rows);
- } else {
- var summary = repo_rows.first();
- dict.set_native<string>("repo_name", summary.name);
- dict.set_native<string>("repo_summary", summary.summary);
- dict.set_native<string>("repo_kind", summary.kind);
- dict.set_native<string>("key_fingerprint", summary.key_fingerprint);
- dict.set_native<int>("package_count", summary.package_count);
- dict.set_native<string>("total_size", summary.total_size);
- dict.set_native<string>("base_uri", summary.base_uri);
- dict.set_native<string>("usmr_href", summary.usmr_href);
- dict.set_native<string>("repos_d_snippet", summary.repos_d_snippet);
- dict.set_native<int>("artifact_count", summary.artifact_count);
- dict.set_native<string>("artifact_total_size", summary.artifact_total_size);
- }
- dict["artifacts"] = artifact_list(dcba_artifact_rows);
- dict.set_native<string>("query", query);
- dict.set_native<string>("result_note", result_note(query, matched, total_count));
- dict.set_native<string>("installer_mode", config.installer.to_string());
- dict.set_native<string>("installer_href", installer_href(config, base_uri));
- dict.set_native<string>("installer_command", installer_command(config, base_uri));
- dict["search"] = action_registry.author<SearchPackagesAction>().to_element();
- dict["packages"] = list(package_rows);
- return new State() {
- type_name = SLOT_TYPE,
- public_data = dict,
- private_data = new PropertyDictionary()
- };
- }
- /** Case-insensitive substring match over name and summary. */
- public static bool matches(RepositoryListingEntry entry, string query) {
- if (query.length == 0) {
- return true;
- }
- var needle = query.casefold();
- return entry.manifest.name.casefold().contains(needle)
- || entry.manifest.summary.casefold().contains(needle);
- }
- /** Builds the package rows' JSON array (the {@link StateJson.list} pattern). */
- public static JsonElement list(Lot<PackageRow> rows) throws Error {
- var arr = new JsonArray();
- foreach (var row in rows) {
- arr.add(new JsonElement.from_properties(GObjectMapping.to_properties(row)));
- }
- return new JsonElement.from_elements(arr);
- }
- /** Builds the artifact rows' JSON array (the same pattern as {@link list}). */
- public static JsonElement artifact_list(Lot<ArtifactRow> rows) throws Error {
- var arr = new JsonArray();
- foreach (var row in rows) {
- arr.add(new JsonElement.from_properties(GObjectMapping.to_properties(row)));
- }
- return new JsonElement.from_elements(arr);
- }
- /** Builds the repository summaries' JSON array. */
- public static JsonElement repo_list(Lot<RepoSummary> rows) throws Error {
- var arr = new JsonArray();
- foreach (var row in rows) {
- arr.add(new JsonElement.from_properties(GObjectMapping.to_properties(row)));
- }
- return new JsonElement.from_elements(arr);
- }
- /** Groups a hex digest into space-separated 16-character chunks. */
- public static string fingerprint(string hex) {
- var builder = new StringBuilder();
- for (int i = 0; i < hex.length; i += 16) {
- if (i > 0) {
- builder.append_c(' ');
- }
- builder.append(hex[i:int.min(i + 16, hex.length)]);
- }
- return builder.str;
- }
- /** Human-readable byte size (KiB/MiB/GiB). */
- public static string format_size(int64 bytes) {
- if (bytes >= 1073741824) {
- return "%.2f GiB".printf(bytes / 1073741824.0);
- }
- if (bytes >= 1048576) {
- return "%.1f MiB".printf(bytes / 1048576.0);
- }
- if (bytes >= 1024) {
- return "%.1f KiB".printf(bytes / 1024.0);
- }
- return "%lld B".printf(bytes);
- }
- /** The link the install card points at (external URL or this app). */
- private static string installer_href(UsmWebConfig config, string base_uri) {
- if (config.installer == InstallerSource.URL) {
- return (!)config.installer_url;
- }
- return base_uri + "install-usm.sh";
- }
- /** The copy-paste one-liner the install card shows. */
- private static string installer_command(UsmWebConfig config, string base_uri) {
- return "curl -fsSL %s | sh".printf(installer_href(config, base_uri));
- }
- private static string result_note(string query, uint matched, uint total) {
- if (query.length == 0) {
- return "";
- }
- if (matched == 0) {
- return "No packages match \"%s\".".printf(query);
- }
- return "Showing %u of %u packages.".printf(matched, total);
- }
- private static int64 file_size(string path) {
- try {
- var info = File.new_for_path(path).query_info(
- FileAttribute.STANDARD_SIZE, FileQueryInfoFlags.NONE);
- return info.get_size();
- } catch (Error e) {
- return 0;
- }
- }
- }
- }
|