diff --git a/.gitignore b/.gitignore index ca2e2f8..3a6b11b 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ test/ # V compiler directory v/ + +# gdb log file +gdb.txt diff --git a/Makefile b/Makefile index 94b079b..b1b91f2 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,17 @@ vieter: $(SOURCES) .PHONY: debug debug: dvieter dvieter: $(SOURCES) - $(V) -keepc -cg -cc gcc -o dvieter $(SRC_DIR) + $(V_PATH) -showcc -keepc -cg -o dvieter $(SRC_DIR) + +.PHONY: gdb +gdb: dvieter + VIETER_API_KEY=test \ + VIETER_DOWNLOAD_DIR=data/downloads \ + VIETER_REPO_DIR=data/repo \ + VIETER_PKG_DIR=data/pkgs \ + VIETER_LOG_LEVEL=DEBUG \ + VIETER_REPOS_FILE=data/repos.json \ + gdb --args ./dvieter # Optimised production build .PHONY: prod @@ -39,6 +49,7 @@ run: vieter VIETER_REPO_DIR=data/repo \ VIETER_PKG_DIR=data/pkgs \ VIETER_LOG_LEVEL=DEBUG \ + VIETER_REPOS_FILE=data/repos.json \ ./vieter server .PHONY: run-prod diff --git a/src/build.v b/src/build.v index 0acca8f..8d6af14 100644 --- a/src/build.v +++ b/src/build.v @@ -6,7 +6,7 @@ import rand import time import os import json -import git +import server import env const container_build_dir = '/build' @@ -17,7 +17,7 @@ fn build() ? { // Read in the repos from a json file filename := os.join_path_single(conf.repo_dir, 'repos.json') txt := os.read_file(filename) ? - repos := json.decode([]git.GitRepo, txt) ? + repos := json.decode([]server.GitRepo, txt) ? mut commands := [ // Update repos & install required packages diff --git a/src/env.v b/src/env.v index 2cbcf63..1e601bd 100644 --- a/src/env.v +++ b/src/env.v @@ -17,6 +17,7 @@ pub: download_dir string api_key string repo_dir string + repos_file string } pub struct BuildConfig { @@ -42,7 +43,10 @@ fn get_env_var(field_name string) ?string { return error('Only one of $env_var_name or $env_file_name can be defined.') } - // If it's the env var itself, we return it + // If it's the env var itself, we return it. + // I'm pretty sure this also prevents variable ending in _FILE (e.g. + // VIETER_LOG_FILE) from being mistakingely read as an _FILE suffixed env + // var. if env_var != '' { return env_var } diff --git a/src/git.v b/src/git.v deleted file mode 100644 index 76d80d7..0000000 --- a/src/git.v +++ /dev/null @@ -1,7 +0,0 @@ -module git - -pub struct GitRepo { -pub: - url string [required] - branch string [required] -} diff --git a/src/repo/repo.v b/src/repo/repo.v index 1bf2d0c..f1419ac 100644 --- a/src/repo/repo.v +++ b/src/repo/repo.v @@ -2,17 +2,12 @@ module repo import os import package - -// Dummy struct to work around the fact that you can only share structs, maps & -// arrays -pub struct Dummy { - x int -} +import util // This struct manages a single repository. pub struct Repo { mut: - mutex shared Dummy + mutex shared util.Dummy pub: // Where to store repository files repo_dir string [required] diff --git a/src/server/git.v b/src/server/git.v new file mode 100644 index 0000000..6a1d929 --- /dev/null +++ b/src/server/git.v @@ -0,0 +1,100 @@ +module server + +import web +import os +import json + +const repos_file = 'repos.json' + +pub struct GitRepo { +pub: + url string [required] + branch string [required] +} + +fn read_repos(path string) ?[]GitRepo { + if !os.exists(path) { + mut f := os.create(path) ? + + defer { + f.close() + } + + f.write_string('{}') ? + + return [] + } + + content := os.read_file(path) ? + return json.decode([]GitRepo, content) +} + +fn write_repos(path string, repos []GitRepo) ? { + mut f := os.create(path) ? + + defer { + f.close() + } + + dump(repos) + value := json.encode(repos) + f.write_string(value) ? +} + +['/api/repos'; get] +pub fn (mut app App) get_repos() web.Result { + if !app.is_authorized() { + return app.text('Unauthorized.') + } + + repos := rlock app.git_mutex { + read_repos(app.conf.repos_file) or { + app.lerror('Failed to read repos file.') + + return app.server_error(500) + } + } + + return app.json(repos) +} + +['/api/repos'; post] +pub fn (mut app App) post_repo() web.Result { + if !app.is_authorized() { + return app.text('Unauthorized.') + } + + if !('url' in app.query && 'branch' in app.query) { + return app.server_error(400) + } + + new_repo := GitRepo{ + url: app.query['url'] + branch: app.query['branch'] + } + + mut repos := rlock app.git_mutex { + read_repos(app.conf.repos_file) or { + app.lerror('Failed to read repos file.') + + return app.server_error(500) + } + } + + // We need to check for duplicates + for r in repos { + if r == new_repo { + return app.text('Duplicate repository.') + } + } + + repos << new_repo + + lock app.git_mutex { + write_repos(app.conf.repos_file, repos) or { + return app.server_error(500) + } + } + + return app.ok('Repo added successfully.') +} diff --git a/src/server/routes.v b/src/server/routes.v index 872894e..7a0ee38 100644 --- a/src/server/routes.v +++ b/src/server/routes.v @@ -7,25 +7,6 @@ import time import rand import util -const prefixes = ['B', 'KB', 'MB', 'GB'] - -// pretty_bytes converts a byte count to human-readable version -fn pretty_bytes(bytes int) string { - mut i := 0 - mut n := f32(bytes) - - for n >= 1024 { - i++ - n /= 1024 - } - - return '${n:.2}${server.prefixes[i]}' -} - -fn is_pkg_name(s string) bool { - return s.contains('.pkg') -} - // healthcheck just returns a string, but can be used to quickly check if the // server is still responsive. ['/health'; get] @@ -65,7 +46,7 @@ fn (mut app App) put_package() web.Result { pkg_path = os.join_path_single(app.conf.download_dir, rand.uuid_v4()) } - app.ldebug("Uploading $length bytes (${pretty_bytes(length.int())}) to '$pkg_path'.") + app.ldebug("Uploading $length bytes (${util.pretty_bytes(length.int())}) to '$pkg_path'.") // This is used to time how long it takes to upload a file mut sw := time.new_stopwatch(time.StopWatchOptions{ auto_start: true }) diff --git a/src/server/server.v b/src/server/server.v index 3b16c63..ebb9f3b 100644 --- a/src/server/server.v +++ b/src/server/server.v @@ -12,9 +12,11 @@ const port = 8000 struct App { web.Context pub: - conf env.ServerConfig [required: web_global] + conf env.ServerConfig [required; web_global] pub mut: repo repo.Repo [required; web_global] + // This is used to claim the file lock on the repos file + git_mutex shared util.Dummy } pub fn server() ? { diff --git a/src/util.v b/src/util.v index d0fa984..65d5294 100644 --- a/src/util.v +++ b/src/util.v @@ -7,6 +7,14 @@ import crypto.sha256 const reader_buf_size = 1_000_000 +const prefixes = ['B', 'KB', 'MB', 'GB'] + +// Dummy struct to work around the fact that you can only share structs, maps & +// arrays +pub struct Dummy { + x int +} + [noreturn] pub fn exit_with_message(code int, msg string) { eprintln(msg) @@ -67,3 +75,17 @@ pub fn hash_file(path &string) ?(string, string) { return md5sum.checksum().hex(), sha256sum.checksum().hex() } + +// pretty_bytes converts a byte count to human-readable version +pub fn pretty_bytes(bytes int) string { + mut i := 0 + mut n := f32(bytes) + + for n >= 1024 { + i++ + n /= 1024 + } + + return '${n:.2}${util.prefixes[i]}' +} +