Browse Source

We can pull basic catalog info via HTTP

default_compile_flags
vector-of-bool 4 years ago
parent
commit
84eb94d9f7
10 changed files with 261 additions and 6 deletions
  1. BIN
      data/test-repo-1/data/neo-url/0.2.1.tar.gz
  2. BIN
      data/test-repo-1/repo.db
  3. +78
    -0
      src/dds.main.cpp
  4. +1
    -1
      src/dds/catalog/catalog.cpp
  5. +2
    -2
      src/dds/catalog/init_catalog.cpp
  6. +111
    -0
      src/dds/remote/remote.cpp
  7. +30
    -0
      src/dds/remote/remote.hpp
  8. +4
    -0
      src/dds/util/result.hpp
  9. +29
    -1
      tests/catalog/import_test.py
  10. +6
    -2
      tests/dds.py

BIN
data/test-repo-1/data/neo-url/0.2.1.tar.gz View File


BIN
data/test-repo-1/repo.db View File


+ 78
- 0
src/dds.main.cpp View File

@@ -3,6 +3,8 @@
#include <dds/catalog/get.hpp>
#include <dds/dym.hpp>
#include <dds/error/errors.hpp>
#include <dds/http/session.hpp>
#include <dds/remote/remote.hpp>
#include <dds/repo/repo.hpp>
#include <dds/repoman/repoman.hpp>
#include <dds/source/dist.hpp>
@@ -697,6 +699,80 @@ struct cli_repo {
}
} import_{*this};

struct {
cli_repo& parent;
args::Command cmd{parent.repo_group, "add", "Add a remote repository"};
common_flags _flags{cmd};

catalog_path_flag cat_path{cmd};

args::Positional<std::string> url{cmd,
"<url>",
"URL of a repository to add",
args::Options::Required};

args::Flag update{cmd, "update", "Update catalog contents immediately", {"update", 'U'}};

int run() {
return boost::leaf::try_handle_all( //
[&]() -> dds::result<int> {
try {
auto cat = cat_path.open();
auto repo = dds::remote_repository::connect(url.Get());
repo.store(cat.database());
if (update) {
repo.update_catalog(cat.database());
}
} catch (...) {
return dds::capture_exception();
}
return 0;
},
[&](neo::url_validation_error url_err, dds::e_url_string bad_url) {
dds_log(error, "Invalid URL [{}]: {}", bad_url.value, url_err.what());
return 1;
},
[&](const json5::parse_error& e, dds::e_http_url bad_url) {
dds_log(error,
"Error parsing JSON downloaded from URL [{}]: {}",
bad_url.value,
e.what());
return 1;
},
[](dds::e_sqlite3_error_exc e, dds::e_url_string url) {
dds_log(error,
"Error accessing remote database (From {}): {}",
url.value,
e.message);
return 1;
},
[](dds::e_sqlite3_error_exc e) {
dds_log(error, "Unexpected database error: {}", e.message);
return 1;
},
[&](dds::e_system_error_exc e, dds::e_http_connect conn) {
dds_log(error,
"Error opening connection to [{}:{}]: {}",
conn.host,
conn.port,
e.message);
return 1;
},
[](const std::exception& e) {
dds_log(error, "An unknown unhandled exception occurred: {}", e.what());
return 1;
},
[](dds::e_system_error_exc e) {
dds_log(error, "An unknown system_error occurred: {}", e.message);
return 42;
},
[](boost::leaf::diagnostic_info const& info) {
dds_log(error, "An unnknown error occurred? {}", info);
return 42;
});
}
} add{*this};

struct {
cli_repo& parent;
args::Command cmd{parent.repo_group, "init", "Initialize a directory as a repository"};
@@ -720,6 +796,8 @@ struct cli_repo {
return init.run();
} else if (import_.cmd) {
return import_.run();
} else if (add.cmd) {
return add.run();
} else {
assert(false);
std::terminate();

+ 1
- 1
src/dds/catalog/catalog.cpp View File

@@ -81,7 +81,7 @@ void migrate_repodb_3(nsql::database& db) {
db.exec(R"(
CREATE TABLE dds_cat_remotes (
remote_id INTEGER PRIMARY KEY AUTOINCREMENT,
ident TEXT NOT NULL UNIQUE,
name TEXT NOT NULL UNIQUE,
gen_ident TEXT NOT NULL,
remote_url TEXT NOT NULL
);

+ 2
- 2
src/dds/catalog/init_catalog.cpp
File diff suppressed because it is too large
View File


+ 111
- 0
src/dds/remote/remote.cpp View File

@@ -0,0 +1,111 @@
#include "./remote.hpp"

#include <dds/error/errors.hpp>
#include <dds/http/session.hpp>
#include <dds/temp.hpp>
#include <dds/util/result.hpp>

#include <neo/sqlite3/exec.hpp>
#include <neo/sqlite3/single.hpp>
#include <neo/sqlite3/transaction.hpp>
#include <neo/url.hpp>
#include <neo/utility.hpp>

using namespace dds;
namespace nsql = neo::sqlite3;

namespace {

struct remote_db {
temporary_dir _tempdir;
nsql::database db;

static remote_db download_and_open(neo::url const& url) {
neo_assert(expects,
url.host.has_value(),
"URL does not have a hostname??",
url.to_string());
auto sess = url.scheme == "https"
? http_session::connect_ssl(*url.host, url.port_or_default_port_or(443))
: http_session::connect(*url.host, url.port_or_default_port_or(80));

auto tempdir = temporary_dir::create();
auto repo_db_dl = tempdir.path() / "repo.db";
fs::create_directories(tempdir.path());
sess.download_file(
{
.method = "GET",
.path = url.path,
},
repo_db_dl);

auto db = nsql::open(repo_db_dl.string());
return {tempdir, std::move(db)};
}

static remote_db download_and_open_for_base(neo::url url) {
auto repo_url = url;
repo_url.path = fs::path(url.path).append("repo.db").string();
return download_and_open(repo_url);
}

static remote_db download_and_open_for_base(std::string_view url_str) {
return download_and_open_for_base(neo::url::parse(url_str));
}
};

} // namespace

remote_repository remote_repository::connect(std::string_view url_str) {
DDS_E_SCOPE(e_url_string{std::string(url_str)});
const auto url = neo::url::parse(url_str);

auto db = remote_db::download_and_open_for_base(url);
auto name_st = db.db.prepare("SELECT name FROM dds_repo_meta");
auto [name] = nsql::unpack_single<std::string>(name_st);

remote_repository ret;
ret._base_url = url;
ret._name = name;
return ret;
}

void remote_repository::store(nsql::database_ref db) {
auto st = db.prepare(R"(
INSERT INTO dds_cat_remotes (name, gen_ident, remote_url)
VALUES (?, ?, ?)
)");
nsql::exec(st, _name, "[placeholder]", _base_url.to_string());
}

void remote_repository::update_catalog(nsql::database_ref db) {
auto rdb = remote_db::download_and_open_for_base(_base_url);

auto db_path = rdb._tempdir.path() / "repo.db";

auto rid_st = db.prepare("SELECT remote_id FROM dds_cat_remotes WHERE name = ?");
rid_st.bindings()[1] = _name;
auto [remote_id] = nsql::unpack_single<std::int64_t>(rid_st);

nsql::transaction_guard tr{db};
nsql::exec(db.prepare("ATTACH DATABASE ? AS remote"), db_path.string());
nsql::exec( //
db.prepare(R"(
DELETE FROM dds_cat_pkgs
WHERE remote_id = ?
)"),
remote_id);
nsql::exec( //
db.prepare(R"(
INSERT INTO dds_cat_pkgs
(name, version, description, remote_url, remote_id)
SELECT
name,
version,
description,
printf('dds:%s/%s', name, version),
?1
FROM remote.dds_repo_packages
)"),
remote_id);
}

+ 30
- 0
src/dds/remote/remote.hpp View File

@@ -0,0 +1,30 @@
#pragma once

#include <dds/util/fs.hpp>
#include <dds/util/result.hpp>

#include <neo/concepts.hpp>
#include <neo/sqlite3/database.hpp>
#include <neo/url.hpp>

#include <string_view>
#include <variant>

namespace dds {

class remote_repository {
std::string _name;
neo::url _base_url;

remote_repository() = default;

public:
static remote_repository connect(std::string_view url);

// const repository_manifest& manifest() const noexcept;

void store(neo::sqlite3::database_ref);
void update_catalog(neo::sqlite3::database_ref);
};

} // namespace dds

+ 4
- 0
src/dds/util/result.hpp View File

@@ -30,6 +30,10 @@ struct e_sqlite3_error_exc {
std::error_code code;
};

struct e_url_string {
std::string value;
};

/**
* @brief Capture currently in-flight special exceptions as new error object. Works around a bug in
* Boost.LEAF when catching std::system error.

+ 29
- 1
tests/catalog/import_test.py View File

@@ -67,6 +67,21 @@ def http_import_server():
httpd.shutdown()


@pytest.yield_fixture
def http_repo_server():
handler = partial(
DirectoryServingHTTPRequestHandler,
dir=Path.cwd() / 'data/test-repo-1')
addr = ('0.0.0.0', 4646)
pool = ThreadPoolExecutor()
with HTTPServer(addr, handler) as httpd:
pool.submit(lambda: httpd.serve_forever(poll_interval=0.1))
try:
yield
finally:
httpd.shutdown()


def test_import_http(dds: DDS, http_import_server):
dds.repo_dir.mkdir(parents=True, exist_ok=True)
dds.run(
@@ -74,8 +89,21 @@ def test_import_http(dds: DDS, http_import_server):
'repo',
dds.repo_dir_arg,
'import',
'https://github.com/vector-of-bool/neo-buffer/archive/0.4.2.tar.gz?dds_strpcmp=1',
'http://localhost:8000/neo-buffer-0.4.2.tar.gz',
],
cwd=dds.repo_dir,
)
assert dds.repo_dir.joinpath('neo-buffer@0.4.2').is_dir()


def test_repo_add(dds: DDS, http_repo_server):
dds.repo_dir.mkdir(parents=True, exist_ok=True)
dds.run([
'repo',
dds.repo_dir_arg,
'add',
dds.catalog_path_arg,
'http://localhost:4646',
'--update',
])
# dds.build_deps(['neo-url@0.2.1'])

+ 6
- 2
tests/dds.py View File

@@ -69,13 +69,17 @@ class DDS:
def project_dir_arg(self) -> str:
return f'--project-dir={self.source_root}'

@property
def catalog_path_arg(self) -> str:
return f'--catalog={self.catalog_path}'

def build_deps(self, args: proc.CommandLine, *,
toolchain: str = None) -> subprocess.CompletedProcess:
return self.run([
'build-deps',
f'--toolchain={toolchain or self.default_builtin_toolchain}',
f'--catalog={self.catalog_path}',
f'--repo-dir={self.repo_dir}',
self.catalog_path_arg,
self.repo_dir_arg,
f'--out={self.deps_build_dir}',
f'--lmi-path={self.lmi_path}',
args,

Loading…
Cancel
Save