first version of the util

This commit is contained in:
Bruno Carlin 2024-12-25 04:11:32 +01:00
commit 0db8655af4
Signed by: bcarlin
GPG key ID: 8E254EA0FFEB9B6D
8 changed files with 358 additions and 0 deletions

8
.editorconfig Normal file
View file

@ -0,0 +1,8 @@
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.v]
indent_style = tab

8
.gitattributes vendored Normal file
View file

@ -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

25
.gitignore vendored Normal file
View file

@ -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

36
src/cache.v Normal file
View file

@ -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
}

90
src/install.v Normal file
View file

@ -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
}

28
src/main.v Normal file
View file

@ -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)
}

156
src/uchromium.v Normal file
View file

@ -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
}

7
v.mod Normal file
View file

@ -0,0 +1,7 @@
Module {
name: 'setup-browser'
description: 'Utility to install browsers'
version: '0.0.0'
license: 'MIT'
dependencies: []
}