From 0db8655af4d36666cb19371816817b56699137be Mon Sep 17 00:00:00 2001 From: Bruno Carlin Date: Wed, 25 Dec 2024 04:11:32 +0100 Subject: [PATCH] first version of the util --- .editorconfig | 8 +++ .gitattributes | 8 +++ .gitignore | 25 ++++++++ src/cache.v | 36 +++++++++++ src/install.v | 90 ++++++++++++++++++++++++++++ src/main.v | 28 +++++++++ src/uchromium.v | 156 ++++++++++++++++++++++++++++++++++++++++++++++++ v.mod | 7 +++ 8 files changed, 358 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 src/cache.v create mode 100644 src/install.v create mode 100644 src/main.v create mode 100644 src/uchromium.v create mode 100644 v.mod diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..01072ca --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.v] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9a98968 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +* text=auto eol=lf +*.bat eol=crlf + +*.v linguist-language=V +*.vv linguist-language=V +*.vsh linguist-language=V +v.mod linguist-language=V +.vdocignore linguist-language=ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d23613e --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Binaries for programs and plugins +main +setup-browser +*.exe +*.exe~ +*.so +*.dylib +*.dll + +# Ignore binary output folders +bin/ + +# Ignore common editor/system specific metadata +.DS_Store +.idea/ +.vscode/ +*.iml + +# ENV +.env + +# vweb and database +*.db +*.js +cache diff --git a/src/cache.v b/src/cache.v new file mode 100644 index 0000000..60523de --- /dev/null +++ b/src/cache.v @@ -0,0 +1,36 @@ +module main + +import log +import os + +@[noinit] +struct Cache { + base_path string +} + +fn Cache.new(base_path string) !Cache { + log.info('using cache dir: ${base_path}') + os.mkdir_all(base_path) or { return error('failed to create cache directory ${base_path}') } + + return Cache{ + base_path: base_path + } +} + +fn Cache.default() !Cache { + base_path := os.getenv_opt('XDG_CACHE_HOME') or { os.getenv('HOME') + '.cache' } + cache_dir := os.join_path(base_path, 'setup-browser') + return Cache.new(cache_dir) +} + +fn (c Cache) path(name ...string) !string { + dir := os.join_path(c.base_path, ...name) + os.mkdir_all(dir) or { return error('failed to create directory ${dir}') } + return dir +} + +fn (c Cache) file(name ...string) !string { + file := os.join_path(c.base_path, ...name) + os.mkdir_all(os.dir(file)) or { return error('failed to create directory ${os.dir(file)}') } + return file +} diff --git a/src/install.v b/src/install.v new file mode 100644 index 0000000..a37ae60 --- /dev/null +++ b/src/install.v @@ -0,0 +1,90 @@ +module main + +import cli +import log +import os + +const install_command = cli.Command{ + name: 'install' + description: 'Installs a browser' + execute: execute_install + required_args: 1 + flags: [ + cli.Flag{ + flag: .bool + name: 'verbose' + abbrev: 'v' + description: 'Enable debug logging' + }, + cli.Flag{ + flag: .string + name: 'cache-path' + abbrev: 'c' + description: 'Path to cache directory' + }, + cli.Flag{ + flag: .string + name: 'install-to' + abbrev: 'i' + description: 'Path to which the binary is linked' + }, + ] +} + +fn execute_install(cmd cli.Command) ! { + if cmd.flags.get_bool('debug') or { false } { + log.set_level(.debug) + } + + browser, version := parse_target(cmd.args[0]) or { + return error('failed to parse target: ${err}') + } + + cache_path := cmd.flags.get_string('cache-path') or { '' } + cache := match cache_path { + '' { Cache.default() or { return error('failed to create default cache: ${err}') } } + else { Cache.new(cache_path) or { return error('failed to create cache: ${err}') } } + } + + install_path := cmd.flags.get_string('install-to') or { '' } + install_to := match install_path { + '' { + cache.file('bin', 'uchromium') or { + return error('failed to create default install dir: ${err}') + } + } + else { + install_dir := os.dir(install_path) + os.mkdir_all(install_dir) or { + return error('failed to create install dir ${install_dir}: ${err}') + } + install_path + } + } + + log.info('using install path: ${install_path}') + + prov := Provider.for_browser(browser, cache) or { + return error('failed to get provider: ${err}') + } + + return prov.install(version, install_to) +} + +enum BrowserType { + uchromium +} + +fn parse_target(target string) !(BrowserType, string) { + parts := target.split_n(':', 2) + + name, version := match parts.len { + 1 { parts[0], 'latest' } + 2 { parts[0], parts[1] } + else { return error('unexpected target ${target}') } + } + + browser := BrowserType.from(name) or { return error('unexpected browser name ${name}') } + + return browser, version +} diff --git a/src/main.v b/src/main.v new file mode 100644 index 0000000..2ec11ef --- /dev/null +++ b/src/main.v @@ -0,0 +1,28 @@ +module main + +import cli +import os + +fn main() { + mut app := cli.Command{ + name: 'setup-browser' + description: 'Set-up browsers from various sources' + version: '0.0.1' + defaults: struct { + man: false + help: true + version: true + } + } + + app.execute = fn [app] (cmd cli.Command) ! { + println(app.help_message()) + // cli.print_help_for_command(cmd) or { eprintln('failed to print help for command') } + return + } + + app.add_command(install_command) + + app.setup() + app.parse(os.args) +} diff --git a/src/uchromium.v b/src/uchromium.v new file mode 100644 index 0000000..869d89c --- /dev/null +++ b/src/uchromium.v @@ -0,0 +1,156 @@ +module main + +import net.http +import log +import os +import math + +interface Provider { + install(version string, install_to string) ! +} + +fn Provider.for_browser(browser_type BrowserType, cache Cache) !Provider { + return match browser_type { + .uchromium { Provider(UChromium.new(cache)!) } + } +} + +const uchromium_repo = 'https://github.com/ungoogled-software/ungoogled-chromium-binaries.git' + +struct UChromiumVersion { + url string + md5 string + sha1 string + sha256 string +} + +fn UChromiumVersion.from_file(file string) !UChromiumVersion { + lines := os.read_lines(file) or { return error('failed to read file ${file}: ${err}') } + mut data := map[string]string{} + + for line in lines { + parts := line.split_any(' =').filter(it != '') + if parts.len != 2 { + continue + } + + data[parts[0]] = parts[1] or { '' } + } + + log.debug('data: ${data}') + + return UChromiumVersion{ + url: data['url'] + md5: data['md5'] + sha1: data['sha1'] + sha256: data['sha256'] + } +} + +@[noinit] +struct UChromium { + cache Cache +} + +fn UChromium.new(cache Cache) !UChromium { + return UChromium{ + cache: cache + } +} + +fn (u UChromium) repo_path() !string { + return u.cache.path('uchromium-repo') +} + +fn (u UChromium) set_repo() ! { + repo_path := u.repo_path()! + cmd := match os.is_dir(repo_path) { + false { + log.info('cloning ${uchromium_repo} to ${repo_path}') + 'git clone ${uchromium_repo} ${repo_path}' + } + true { + log.info('updating ${repo_path}') + 'git -C ${repo_path} pull' + } + } + + os.execute_opt(cmd) or { + return error('failed to sync ${uchromium_repo} to ${repo_path}: ${err}') + } + + return +} + +fn (u UChromium) find_version(version string) !UChromiumVersion { + base_dir := os.join_path(u.repo_path()!, 'config', 'platforms', 'appimage', '64bit') + log.debug('base_dir: ${base_dir}') + + mut versions := os.glob(base_dir + '/*.ini') or { + return error('failed to list versions: ${err}') + } + log.debug('versions: ${versions}') + + versions.sort_with_compare(fn (a &string, b &string) int { + a_parts := a.split_any('.-/') + b_parts := b.split_any('.-/') + + for i in 0 .. math.min(a_parts.len, b_parts.len) { + return if a_parts[i].int() < b_parts[i].int() { + -1 + } else if a_parts[i].int() > b_parts[i].int() { + 1 + } else { + continue + } + } + + return 0 + }) + + log.debug('sorted versions: ${versions}') + + version_file := match version { + 'latest' { + versions.last() + } + else { + cand := os.join_path(base_dir, '${version}.ini') + if cand in versions { + cand + } else { + return error('version not found: ${version}') + } + } + } + + return UChromiumVersion.from_file(version_file) +} + +fn (u UChromium) download(version UChromiumVersion) !string { + dest := u.cache.file('dl', os.base(version.url))! + log.info('downloading ${version.url} to ${dest}') + + if os.is_file(dest) { + log.info('file already exists') + return dest + } + + http.download_file(version.url, dest) or { + return error('failed to download ${version.url}: ${err}') + } + + return dest +} + +fn (u UChromium) install(version string, install_to string) ! { + u.set_repo()! + v := u.find_version(version)! + log.info('version ${version} found') + dest := u.download(v)! + os.chmod(dest, 0o777)! + os.rm(install_to) or {} + os.cp(dest, install_to)! + + return +} diff --git a/v.mod b/v.mod new file mode 100644 index 0000000..e57ffd5 --- /dev/null +++ b/v.mod @@ -0,0 +1,7 @@ +Module { + name: 'setup-browser' + description: 'Utility to install browsers' + version: '0.0.0' + license: 'MIT' + dependencies: [] +}