|
@@ -4,61 +4,121 @@ using Usm;
|
|
|
|
|
|
|
|
namespace UsmWeb {
|
|
namespace UsmWeb {
|
|
|
|
|
|
|
|
|
|
+ /** The kind of a discovered repository directory. */
|
|
|
|
|
+ public enum RepositoryKind {
|
|
|
|
|
+ /** A USM source repository: `.usmr` + `public/PACKAGES.usml` + `.usmc` packages. */
|
|
|
|
|
+ USM,
|
|
|
|
|
+ /** A DCBA artifact repository: `.dcbar` + `public/ARTIFACTS.usml` + `.dcba` artifacts. */
|
|
|
|
|
+ DCBA;
|
|
|
|
|
+
|
|
|
|
|
+ /** The wire/display name used on generated pages. */
|
|
|
|
|
+ public string to_string() {
|
|
|
|
|
+ switch (this) {
|
|
|
|
|
+ case RepositoryKind.USM:
|
|
|
|
|
+ return "usm";
|
|
|
|
|
+ case RepositoryKind.DCBA:
|
|
|
|
|
+ return "dcba";
|
|
|
|
|
+ default:
|
|
|
|
|
+ assert_not_reached();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
- * One on-disk USM repository discovered under the configured parent
|
|
|
|
|
- * directory: a subdirectory holding a `.usmr` beside a `public/`
|
|
|
|
|
- * directory, built on libusm via {@link Usm.Repository.from_file} and
|
|
|
|
|
- * {@link Usm.RepositoryListing.from_stream}.
|
|
|
|
|
|
|
+ * One on-disk repository discovered under the configured parent
|
|
|
|
|
+ * directory, built on libusm via {@link Usm.Repository.from_file}.
|
|
|
|
|
+ *
|
|
|
|
|
+ * A USM entry is a subdirectory holding a `.usmr` beside a `public/`
|
|
|
|
|
+ * directory with `PACKAGES.usml`; a DCBA entry is the artifact-repo
|
|
|
|
|
+ * counterpart holding a `.dcbar` (the same JSON shape) beside a
|
|
|
|
|
+ * `public/` directory with `ARTIFACTS.usml` and `.dcba` files. The
|
|
|
|
|
+ * two kinds share the loading machinery but expose disjoint members;
|
|
|
|
|
+ * reaching for a member of the other kind throws instead of
|
|
|
|
|
+ * silently answering nonsense.
|
|
|
*
|
|
*
|
|
|
- * Each entry tracks its `.usmr` and `public/PACKAGES.usml` mtimes
|
|
|
|
|
- * independently, so one repository can be rebuilt and re-read without
|
|
|
|
|
- * disturbing the others.
|
|
|
|
|
|
|
+ * Each entry tracks its descriptor (`.usmr`/`.dcbar`) and listing
|
|
|
|
|
+ * (`PACKAGES.usml`/`ARTIFACTS.usml`) mtimes independently, so one
|
|
|
|
|
+ * repository can be rebuilt and re-read without disturbing the
|
|
|
|
|
+ * others, and both file roles reload when their own mtime moves.
|
|
|
*/
|
|
*/
|
|
|
public class RepositoryEntry : Object {
|
|
public class RepositoryEntry : Object {
|
|
|
|
|
|
|
|
/** The repository's directory (absolute). */
|
|
/** The repository's directory (absolute). */
|
|
|
public string dir_path { get; private set; }
|
|
public string dir_path { get; private set; }
|
|
|
|
|
|
|
|
- /** The absolute path of the `.usmr` inside {@link dir_path}. */
|
|
|
|
|
- public string usmr_path { get; private set; }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The absolute path of the `.usmr` (USM entries) or `.dcbar`
|
|
|
|
|
+ * (DCBA entries) inside {@link dir_path}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string descriptor_path { get; private set; }
|
|
|
|
|
+
|
|
|
|
|
+ /** This repository's kind (source or artifact). */
|
|
|
|
|
+ public RepositoryKind kind { get; private set; }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Compatibility alias for {@link descriptor_path} — the endpoint
|
|
|
|
|
+ * serving `repo.usmr` reads the raw descriptor through this name.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string usmr_path {
|
|
|
|
|
+ get { return descriptor_path; }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
private Repository? repository;
|
|
private Repository? repository;
|
|
|
private RepositoryListing? listing;
|
|
private RepositoryListing? listing;
|
|
|
- private int64 usmr_mtime;
|
|
|
|
|
|
|
+ private DcbaListing? dcba_listing;
|
|
|
|
|
+ private int64 descriptor_mtime;
|
|
|
private int64 listing_mtime;
|
|
private int64 listing_mtime;
|
|
|
|
|
|
|
|
- public RepositoryEntry(string dir_path, string usmr_path) {
|
|
|
|
|
|
|
+ public RepositoryEntry.usm(string dir_path, string usmr_path) {
|
|
|
this.dir_path = dir_path;
|
|
this.dir_path = dir_path;
|
|
|
- this.usmr_path = usmr_path;
|
|
|
|
|
|
|
+ this.kind = RepositoryKind.USM;
|
|
|
|
|
+ this.descriptor_path = usmr_path;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** Whether both the `.usmr` and the listing are currently loaded. */
|
|
|
|
|
|
|
+ public RepositoryEntry.dcba(string dir_path, string dcbar_path) {
|
|
|
|
|
+ this.dir_path = dir_path;
|
|
|
|
|
+ this.kind = RepositoryKind.DCBA;
|
|
|
|
|
+ this.descriptor_path = dcbar_path;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Whether the descriptor and this kind's listing are currently loaded. */
|
|
|
public bool is_loaded {
|
|
public bool is_loaded {
|
|
|
- get { return repository != null && listing != null; }
|
|
|
|
|
|
|
+ get {
|
|
|
|
|
+ if (kind == RepositoryKind.DCBA) {
|
|
|
|
|
+ return repository != null && dcba_listing != null;
|
|
|
|
|
+ }
|
|
|
|
|
+ return repository != null && listing != null;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Reloads the `.usmr` and listing when either file changed on disk
|
|
|
|
|
- * (or was never loaded); a failure throws while keeping the
|
|
|
|
|
|
|
+ * Reloads the descriptor and listing when either file changed on
|
|
|
|
|
+ * disk (or was never loaded); a failure throws while keeping the
|
|
|
* previously loaded snapshot, so an in-flight rebuild cannot lose
|
|
* previously loaded snapshot, so an in-flight rebuild cannot lose
|
|
|
* what is already held.
|
|
* what is already held.
|
|
|
*/
|
|
*/
|
|
|
public void ensure_loaded() throws Error {
|
|
public void ensure_loaded() throws Error {
|
|
|
- int64 current_usmr_mtime = file_mtime(usmr_path);
|
|
|
|
|
- int64 current_listing_mtime = file_mtime(listing_location());
|
|
|
|
|
|
|
+ int64 current_descriptor_mtime = file_mtime(descriptor_path);
|
|
|
|
|
+ int64 current_listing_mtime = file_mtime(current_listing_path());
|
|
|
if (is_loaded
|
|
if (is_loaded
|
|
|
- && current_usmr_mtime == usmr_mtime
|
|
|
|
|
|
|
+ && current_descriptor_mtime == descriptor_mtime
|
|
|
&& current_listing_mtime == listing_mtime) {
|
|
&& current_listing_mtime == listing_mtime) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (repository == null || current_usmr_mtime != usmr_mtime) {
|
|
|
|
|
- repository = new Repository.from_file(usmr_path);
|
|
|
|
|
- usmr_mtime = current_usmr_mtime;
|
|
|
|
|
|
|
+ if (repository == null || current_descriptor_mtime != descriptor_mtime) {
|
|
|
|
|
+ repository = new Repository.from_file(descriptor_path);
|
|
|
|
|
+ descriptor_mtime = current_descriptor_mtime;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (listing == null || current_listing_mtime != listing_mtime) {
|
|
|
|
|
- var file = File.new_for_path(listing_location());
|
|
|
|
|
|
|
+ if (kind == RepositoryKind.DCBA) {
|
|
|
|
|
+ if (dcba_listing == null || current_listing_mtime != listing_mtime) {
|
|
|
|
|
+ var file = File.new_for_path(current_listing_path());
|
|
|
|
|
+ dcba_listing = new DcbaListing.from_stream(new DataInputStream(file.read()));
|
|
|
|
|
+ listing_mtime = current_listing_mtime;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (listing == null || current_listing_mtime != listing_mtime) {
|
|
|
|
|
+ var file = File.new_for_path(current_listing_path());
|
|
|
listing = new RepositoryListing.from_stream(new DataInputStream(file.read()));
|
|
listing = new RepositoryListing.from_stream(new DataInputStream(file.read()));
|
|
|
listing_mtime = current_listing_mtime;
|
|
listing_mtime = current_listing_mtime;
|
|
|
}
|
|
}
|
|
@@ -70,13 +130,35 @@ namespace UsmWeb {
|
|
|
return (!)repository;
|
|
return (!)repository;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** The loaded `PACKAGES.usml` entries, in listing order. */
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The loaded `PACKAGES.usml` entries, in listing order. USM
|
|
|
|
|
+ * entries only — DCBA entries answer with
|
|
|
|
|
+ * {@link dcba_listing_entries}.
|
|
|
|
|
+ */
|
|
|
public Enumerable<RepositoryListingEntry> listing_entries() throws Error {
|
|
public Enumerable<RepositoryListingEntry> listing_entries() throws Error {
|
|
|
|
|
+ if (kind == RepositoryKind.DCBA) {
|
|
|
|
|
+ throw_wrong_kind("PACKAGES.usml entries");
|
|
|
|
|
+ }
|
|
|
ensure_loaded();
|
|
ensure_loaded();
|
|
|
return ((!)listing).entries;
|
|
return ((!)listing).entries;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** The listing entry whose package file is `file`, or null. */
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The loaded `ARTIFACTS.usml` entries, in listing order. DCBA
|
|
|
|
|
+ * entries only — USM entries answer with {@link listing_entries}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public Enumerable<DcbaListingEntry> dcba_listing_entries() throws Error {
|
|
|
|
|
+ if (kind != RepositoryKind.DCBA) {
|
|
|
|
|
+ throw_wrong_kind("ARTIFACTS.usml entries");
|
|
|
|
|
+ }
|
|
|
|
|
+ ensure_loaded();
|
|
|
|
|
+ return ((!)dcba_listing).entries;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The listing entry whose package file is `file`, or null. USM
|
|
|
|
|
+ * entries only.
|
|
|
|
|
+ */
|
|
|
public RepositoryListingEntry? find_entry(string file) throws Error {
|
|
public RepositoryListingEntry? find_entry(string file) throws Error {
|
|
|
foreach (var entry in listing_entries()) {
|
|
foreach (var entry in listing_entries()) {
|
|
|
if (Path.get_basename(entry.path) == file) {
|
|
if (Path.get_basename(entry.path) == file) {
|
|
@@ -86,16 +168,102 @@ namespace UsmWeb {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** The absolute path of the verbatim-served `PACKAGES.usml`. */
|
|
|
|
|
- public string listing_location() {
|
|
|
|
|
- return Path.build_filename(dir_path, "public", "PACKAGES.usml");
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The artifact entry whose package name and version match `name`
|
|
|
|
|
+ * and `version` exactly (a plain string comparison on both, per
|
|
|
|
|
+ * the DCBA specification — listing strings never mutate through
|
|
|
|
|
+ * a {@link Usm.Version} round trip), or null when this repository
|
|
|
|
|
+ * carries no artifact for that package. DCBA entries only.
|
|
|
|
|
+ */
|
|
|
|
|
+ public DcbaListingEntry? find_dcba_artifact(string name, string version) throws Error {
|
|
|
|
|
+ foreach (var entry in dcba_listing_entries()) {
|
|
|
|
|
+ if (entry.name == name && entry.version == version) {
|
|
|
|
|
+ return entry;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The absolute path of the verbatim-served `PACKAGES.usml`. USM
|
|
|
|
|
+ * entries only — DCBA entries answer with
|
|
|
|
|
+ * {@link artifacts_listing_location}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string listing_location() throws Error {
|
|
|
|
|
+ if (kind == RepositoryKind.DCBA) {
|
|
|
|
|
+ throw_wrong_kind("PACKAGES.usml location");
|
|
|
|
|
+ }
|
|
|
|
|
+ return Path.build_filename(dir_path, "public",
|
|
|
|
|
+ Usm.RepositoryClient.LISTING_NAME);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The absolute path of the verbatim-served `ARTIFACTS.usml`. DCBA
|
|
|
|
|
+ * entries only — USM entries answer with {@link listing_location}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string artifacts_listing_location() throws Error {
|
|
|
|
|
+ if (kind != RepositoryKind.DCBA) {
|
|
|
|
|
+ throw_wrong_kind("ARTIFACTS.usml location");
|
|
|
|
|
+ }
|
|
|
|
|
+ return Path.build_filename(dir_path, "public",
|
|
|
|
|
+ Usm.RepositoryClient.DCBA_LISTING_NAME);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** The absolute path of a package archive inside `public/`. */
|
|
|
|
|
- public string package_location(string file) {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The path of this kind's listing file, unguarded: both kinds
|
|
|
|
|
+ * keep their listing under `public/`, so {@link ensure_loaded}
|
|
|
|
|
+ * watches one location per kind.
|
|
|
|
|
+ */
|
|
|
|
|
+ private string current_listing_path() {
|
|
|
|
|
+ var listing_name = kind == RepositoryKind.DCBA
|
|
|
|
|
+ ? Usm.RepositoryClient.DCBA_LISTING_NAME
|
|
|
|
|
+ : Usm.RepositoryClient.LISTING_NAME;
|
|
|
|
|
+ return Path.build_filename(dir_path, "public", listing_name);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The actual descriptor filename inside the repository
|
|
|
|
|
+ * directory (`name.usmr` or `name.dcbar`), for messages and
|
|
|
|
|
+ * sibling-route naming.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string descriptor_basename() {
|
|
|
|
|
+ return Path.get_basename(descriptor_path);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The absolute path of a package archive inside `public/`. USM
|
|
|
|
|
+ * entries only — DCBA entries answer with
|
|
|
|
|
+ * {@link artifact_location}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string package_location(string file) throws Error {
|
|
|
|
|
+ if (kind == RepositoryKind.DCBA) {
|
|
|
|
|
+ throw_wrong_kind("package location");
|
|
|
|
|
+ }
|
|
|
|
|
+ return Path.build_filename(dir_path, "public", file);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The absolute path of a `.dcba` artifact inside `public/`. DCBA
|
|
|
|
|
+ * entries only — USM entries answer with {@link package_location}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public string artifact_location(string file) throws Error {
|
|
|
|
|
+ if (kind != RepositoryKind.DCBA) {
|
|
|
|
|
+ throw_wrong_kind("artifact location");
|
|
|
|
|
+ }
|
|
|
return Path.build_filename(dir_path, "public", file);
|
|
return Path.build_filename(dir_path, "public", file);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Raises the cross-kind misuse error: every member documented as
|
|
|
|
|
+ * kind-specific guards against being reached through the wrong
|
|
|
|
|
+ * kind, so a DCBA entry never answers for a `PACKAGES.usml` and
|
|
|
|
|
+ * vice versa.
|
|
|
|
|
+ */
|
|
|
|
|
+ private void throw_wrong_kind(string member) throws Error {
|
|
|
|
|
+ throw new UsmWebError.INVALID_CONFIGURATION(
|
|
|
|
|
+ @"Repository at \"$dir_path\" is a $kind repository — $member is only available for $(kind == RepositoryKind.DCBA ? RepositoryKind.USM : RepositoryKind.DCBA) repositories");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
private static int64 file_mtime(string path) {
|
|
private static int64 file_mtime(string path) {
|
|
|
try {
|
|
try {
|
|
|
var info = File.new_for_path(path).query_info(
|
|
var info = File.new_for_path(path).query_info(
|
|
@@ -108,22 +276,33 @@ namespace UsmWeb {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Read-side access to the on-disk USM repositories this app browses and
|
|
|
|
|
- * serves.
|
|
|
|
|
|
|
+ * Read-side access to the on-disk repositories this app browses and
|
|
|
|
|
+ * serves, in a mixed layout.
|
|
|
*
|
|
*
|
|
|
* The repositories live under {@link UsmWebConfig.repo_dir}: every
|
|
* The repositories live under {@link UsmWebConfig.repo_dir}: every
|
|
|
- * subdirectory containing a `.usmr` is discovered by scanning, or only
|
|
|
|
|
- * those named by {@link UsmWebConfig.repositories} are used when that
|
|
|
|
|
- * list is configured. A repository is addressed by the name in its
|
|
|
|
|
- * `.usmr`, which is also its routing segment (`/repo/<name>/…`). A
|
|
|
|
|
- * legacy single-repository layout with the `.usmr` directly inside the
|
|
|
|
|
- * configured directory is still served as one repository.
|
|
|
|
|
|
|
+ * subdirectory containing a `.usmr` is a USM source repository
|
|
|
|
|
+ * (`.usmc` packages listed in `public/PACKAGES.usml`) and every
|
|
|
|
|
+ * subdirectory containing a `.dcbar` is a DCBA artifact repository
|
|
|
|
|
+ * (`.dcba` files listed in `public/ARTIFACTS.usml`); both are
|
|
|
|
|
+ * discovered by the same scan, or only those named by
|
|
|
|
|
+ * {@link UsmWebConfig.repositories} are used when that list is
|
|
|
|
|
+ * configured — the list applies to both kinds. A repository is
|
|
|
|
|
+ * addressed by the name in its descriptor, which is also its routing
|
|
|
|
|
+ * segment (`/repo/<name>/…`). A directory holding both a `.usmr`
|
|
|
|
|
+ * and a `.dcbar` is a configuration error. A legacy
|
|
|
|
|
+ * single-repository layout with the `.usmr` directly inside the
|
|
|
|
|
+ * configured directory is still served as one USM repository.
|
|
|
|
|
+ *
|
|
|
|
|
+ * {@link entries} returns every loadable repository of either kind,
|
|
|
|
|
+ * in discovery order; {@link usm_entries} and {@link dcba_entries}
|
|
|
|
|
+ * filter it by kind for callers that iterate one world at a time
|
|
|
|
|
+ * (the home package table and search only see source repositories).
|
|
|
*
|
|
*
|
|
|
* Discovery re-runs on every {@link ensure_loaded}, so repositories
|
|
* Discovery re-runs on every {@link ensure_loaded}, so repositories
|
|
|
* added or removed on disk are picked up without a restart; each
|
|
* added or removed on disk are picked up without a restart; each
|
|
|
- * repository's `.usmr` and `PACKAGES.usml` reload independently when
|
|
|
|
|
- * their mtimes change. A repository that fails to (re)load is skipped
|
|
|
|
|
- * with a warning rather than taking the whole app down.
|
|
|
|
|
|
|
+ * repository's descriptor and listing reload independently when
|
|
|
|
|
+ * their mtimes change. A repository that fails to (re)load is
|
|
|
|
|
+ * skipped with a warning rather than taking the whole app down.
|
|
|
*/
|
|
*/
|
|
|
public class RepositoryService : Object {
|
|
public class RepositoryService : Object {
|
|
|
|
|
|
|
@@ -193,10 +372,41 @@ namespace UsmWeb {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * The repository whose `.usmr` is named `name`, or null when no
|
|
|
|
|
- * loaded repository carries that name. A discovered repository that
|
|
|
|
|
- * cannot be read surfaces its error (so callers can 500 rather than
|
|
|
|
|
- * 404) unless another repository matches cleanly first.
|
|
|
|
|
|
|
+ * The currently loadable USM source repositories, in discovery
|
|
|
|
|
+ * order — the home package table, search and package detail
|
|
|
|
|
+ * flows iterate these. Same load/throw contract as
|
|
|
|
|
+ * {@link entries}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public Enumerable<RepositoryEntry> usm_entries() throws Error {
|
|
|
|
|
+ var result = new Vector<RepositoryEntry>();
|
|
|
|
|
+ foreach (var entry in entries()) {
|
|
|
|
|
+ if (entry.kind == RepositoryKind.USM) {
|
|
|
|
|
+ result.add(entry);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The currently loadable DCBA artifact repositories, in discovery
|
|
|
|
|
+ * order. Same load/throw contract as {@link entries}.
|
|
|
|
|
+ */
|
|
|
|
|
+ public Enumerable<RepositoryEntry> dcba_entries() throws Error {
|
|
|
|
|
+ var result = new Vector<RepositoryEntry>();
|
|
|
|
|
+ foreach (var entry in entries()) {
|
|
|
|
|
+ if (entry.kind == RepositoryKind.DCBA) {
|
|
|
|
|
+ result.add(entry);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The repository whose descriptor is named `name`, or null when
|
|
|
|
|
+ * no loaded repository carries that name. A discovered repository
|
|
|
|
|
+ * that cannot be read surfaces its error (so callers can 500
|
|
|
|
|
+ * rather than 404) unless another repository matches cleanly
|
|
|
|
|
+ * first.
|
|
|
*/
|
|
*/
|
|
|
public RepositoryEntry? find(string name) throws Error {
|
|
public RepositoryEntry? find(string name) throws Error {
|
|
|
ensure_loaded();
|
|
ensure_loaded();
|
|
@@ -226,9 +436,9 @@ namespace UsmWeb {
|
|
|
/**
|
|
/**
|
|
|
* Discovers repository directories: the configured subdirectories
|
|
* Discovers repository directories: the configured subdirectories
|
|
|
* when {@link UsmWebConfig.repositories} names them, otherwise every
|
|
* when {@link UsmWebConfig.repositories} names them, otherwise every
|
|
|
- * subdirectory holding a `.usmr`, falling back to the legacy
|
|
|
|
|
- * single-repository layout. Already-known directories keep their
|
|
|
|
|
- * entry (and loaded snapshot).
|
|
|
|
|
|
|
+ * subdirectory holding a `.usmr` or a `.dcbar`, falling back to the
|
|
|
|
|
+ * legacy single-repository layout (`.usmr` only). Already-known
|
|
|
|
|
+ * directories keep their entry (and loaded snapshot).
|
|
|
*/
|
|
*/
|
|
|
private Vector<RepositoryEntry> discover() throws Error {
|
|
private Vector<RepositoryEntry> discover() throws Error {
|
|
|
var result = new Vector<RepositoryEntry>();
|
|
var result = new Vector<RepositoryEntry>();
|
|
@@ -260,15 +470,15 @@ namespace UsmWeb {
|
|
|
}
|
|
}
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- var usmr_path = find_usmr(child_path);
|
|
|
|
|
- if (usmr_path == null) {
|
|
|
|
|
|
|
+ var descriptor = find_descriptor(child_path);
|
|
|
|
|
+ if (descriptor == null) {
|
|
|
if (config.repositories.length > 0) {
|
|
if (config.repositories.length > 0) {
|
|
|
throw new UsmWebError.INVALID_CONFIGURATION(
|
|
throw new UsmWebError.INVALID_CONFIGURATION(
|
|
|
- @"\"$name\" from the configured repositories list has no *.usmr under \"$(config.repo_dir)\"");
|
|
|
|
|
|
|
+ @"\"$name\" from the configured repositories list has no *.usmr or *.dcbar under \"$(config.repo_dir)\"");
|
|
|
}
|
|
}
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- result.add(entry_for(child_path, (!)usmr_path));
|
|
|
|
|
|
|
+ result.add(entry_for(child_path, (!)descriptor));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (result.length == 0) {
|
|
if (result.length == 0) {
|
|
@@ -286,14 +496,21 @@ namespace UsmWeb {
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** Reuses the entry for `dir_path` when discovery finds it again. */
|
|
|
|
|
- private RepositoryEntry entry_for(string dir_path, string usmr_path) {
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Reuses the entry for `dir_path` when discovery finds it again.
|
|
|
|
|
+ * A directory whose descriptor kind changed on disk (a `.dcbar`
|
|
|
|
|
+ * replaced by a `.usmr` between discoveries) gets a fresh entry
|
|
|
|
|
+ * so no stale snapshot of the old kind lingers.
|
|
|
|
|
+ */
|
|
|
|
|
+ private RepositoryEntry entry_for(string dir_path, string descriptor_path) {
|
|
|
RepositoryEntry? existing = null;
|
|
RepositoryEntry? existing = null;
|
|
|
entries_by_dir.try_get(dir_path, out existing);
|
|
entries_by_dir.try_get(dir_path, out existing);
|
|
|
- if (existing != null) {
|
|
|
|
|
|
|
+ if (existing != null && ((!)existing).kind == kind_of((!)descriptor_path)) {
|
|
|
return (!)existing;
|
|
return (!)existing;
|
|
|
}
|
|
}
|
|
|
- var created = new RepositoryEntry(dir_path, usmr_path);
|
|
|
|
|
|
|
+ var created = kind_of((!)descriptor_path) == RepositoryKind.DCBA
|
|
|
|
|
+ ? new RepositoryEntry.dcba(dir_path, descriptor_path)
|
|
|
|
|
+ : new RepositoryEntry.usm(dir_path, descriptor_path);
|
|
|
entries_by_dir.set(dir_path, created);
|
|
entries_by_dir.set(dir_path, created);
|
|
|
return created;
|
|
return created;
|
|
|
}
|
|
}
|
|
@@ -311,14 +528,34 @@ namespace UsmWeb {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The first `*.usmr` or `*.dcbar` directly inside `dir`, or null
|
|
|
|
|
+ * when there is none. A directory holding both is a
|
|
|
|
|
+ * configuration error.
|
|
|
|
|
+ */
|
|
|
|
|
+ private static string? find_descriptor(string dir) throws Error {
|
|
|
|
|
+ string? usmr = find_suffix(dir, ".usmr");
|
|
|
|
|
+ string? dcbar = find_suffix(dir, ".dcbar");
|
|
|
|
|
+ if (usmr != null && dcbar != null) {
|
|
|
|
|
+ throw new UsmWebError.INVALID_CONFIGURATION(
|
|
|
|
|
+ @"\"$dir\" holds both a .usmr and a .dcbar — a repository directory must be either a USM source repository or a DCBA artifact repository, not both");
|
|
|
|
|
+ }
|
|
|
|
|
+ return usmr ?? dcbar;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/** The first `*.usmr` directly inside `dir`, or null when there is none. */
|
|
/** The first `*.usmr` directly inside `dir`, or null when there is none. */
|
|
|
private static string? find_usmr(string dir) {
|
|
private static string? find_usmr(string dir) {
|
|
|
|
|
+ return find_suffix(dir, ".usmr");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** The first file ending in `suffix` directly inside `dir`, or null. */
|
|
|
|
|
+ private static string? find_suffix(string dir, string suffix) {
|
|
|
try {
|
|
try {
|
|
|
var handle = Dir.open(dir);
|
|
var handle = Dir.open(dir);
|
|
|
string? name = null;
|
|
string? name = null;
|
|
|
string? found = null;
|
|
string? found = null;
|
|
|
while ((name = handle.read_name()) != null) {
|
|
while ((name = handle.read_name()) != null) {
|
|
|
- if (name.has_suffix(".usmr")) {
|
|
|
|
|
|
|
+ if (name.has_suffix(suffix)) {
|
|
|
found = name;
|
|
found = name;
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -328,5 +565,10 @@ namespace UsmWeb {
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /** The kind implied by a descriptor path's extension. */
|
|
|
|
|
+ private static RepositoryKind kind_of(string descriptor_path) {
|
|
|
|
|
+ return descriptor_path.has_suffix(".dcbar") ? RepositoryKind.DCBA : RepositoryKind.USM;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|