diff --git a/src/archive/archive.v b/src/archive/archive.v new file mode 100644 index 0000000..c280a42 --- /dev/null +++ b/src/archive/archive.v @@ -0,0 +1,55 @@ +module archive + +import os + +// Returns the .PKGINFO file's contents & the list of files. +pub fn pkg_info(pkg_path string) ?(string, []string) { + if !os.is_file(pkg_path) { + return error("'$pkg_path' doesn't exist or isn't a file.") + } + + a := C.archive_read_new() + entry := C.archive_entry_new() + mut r := 0 + + // Sinds 2020, all newly built Arch packages use zstd + C.archive_read_support_filter_zstd(a) + // The content should always be a tarball + C.archive_read_support_format_tar(a) + + // TODO find out where does this 10240 come from + r = C.archive_read_open_filename(a, &char(pkg_path.str), 10240) + defer { + C.archive_read_free(a) + } + + if r != C.ARCHIVE_OK { + return error('Failed to open package.') + } + + // We iterate over every header in search of the .PKGINFO one + mut buf := voidptr(0) + mut files := []string{} + for C.archive_read_next_header(a, &entry) == C.ARCHIVE_OK { + pathname := C.archive_entry_pathname(entry) + + ignored_names := [c'.BUILDINFO', c'.INSTALL', c'.MTREE', c'.PKGINFO', c'.CHANGELOG'] + if ignored_names.all(C.strcmp(it, pathname) != 0) { + unsafe { + files << cstring_to_vstring(pathname) + } + } + + if C.strcmp(pathname, c'.PKGINFO') == 0 { + size := C.archive_entry_size(entry) + + // TODO can this unsafe block be avoided? + buf = unsafe { malloc(size) } + C.archive_read_data(a, voidptr(buf), size) + } else { + C.archive_read_data_skip(a) + } + } + + return unsafe { cstring_to_vstring(&char(buf)) }, files +} diff --git a/src/archive/bindings.v b/src/archive/bindings.v new file mode 100644 index 0000000..26af13f --- /dev/null +++ b/src/archive/bindings.v @@ -0,0 +1,50 @@ +module archive + +#flag -larchive + +#include "archive.h" + +struct C.archive {} + +// Create a new archive struct +fn C.archive_read_new() &C.archive + +// Configure the archive to work with zstd compression +fn C.archive_read_support_filter_zstd(&C.archive) + +// Configure the archive to work with a tarball content +fn C.archive_read_support_format_tar(&C.archive) + +// Open an archive for reading +fn C.archive_read_open_filename(&C.archive, &char, int) int + +// Go to next entry header in archive +fn C.archive_read_next_header(&C.archive, &&C.archive_entry) int + +// Skip reading the current entry +fn C.archive_read_data_skip(&C.archive) + +// Free an archive +fn C.archive_read_free(&C.archive) int + +// Read an archive entry's contents into a pointer +fn C.archive_read_data(&C.archive, voidptr, int) + +#include "archive_entry.h" + +struct C.archive_entry {} + +// Create a new archive_entry struct +fn C.archive_entry_new() &C.archive_entry + +// Get the filename of the given entry +fn C.archive_entry_pathname(&C.archive_entry) &char + +// Get an entry's file size +// Note: this function actually returns an i64, but as this can't be used as an arugment to malloc, we'll just roll with it & assume an entry is never bigger than 4 gigs +fn C.archive_entry_size(&C.archive_entry) int + +#include + +// Compare two C strings; 0 means they're equal +fn C.strcmp(&char, &char) int diff --git a/src/main.v b/src/main.v index ca35c9d..9d9e541 100644 --- a/src/main.v +++ b/src/main.v @@ -5,6 +5,7 @@ import os import log import io import pkg +import archive import repo const port = 8000 @@ -109,5 +110,7 @@ fn main() { return } // println(info) - print(res) + println(res.info) + print(res.files) + println(res.info.to_desc()) } diff --git a/src/pkg.v b/src/pkg.v index bcd2724..1bb1abc 100644 --- a/src/pkg.v +++ b/src/pkg.v @@ -1,65 +1,14 @@ module pkg +import archive import time -import os -#flag -larchive - -#include "archive.h" - -struct C.archive {} - -// Create a new archive struct -fn C.archive_read_new() &C.archive - -// Configure the archive to work with zstd compression -fn C.archive_read_support_filter_zstd(&C.archive) - -// Configure the archive to work with a tarball content -fn C.archive_read_support_format_tar(&C.archive) - -// Open an archive for reading -fn C.archive_read_open_filename(&C.archive, &char, int) int - -// Go to next entry header in archive -fn C.archive_read_next_header(&C.archive, &&C.archive_entry) int - -// Skip reading the current entry -fn C.archive_read_data_skip(&C.archive) - -// Free an archive -fn C.archive_read_free(&C.archive) int - -// Read an archive entry's contents into a pointer -fn C.archive_read_data(&C.archive, voidptr, int) - -#include "archive_entry.h" - -struct C.archive_entry {} - -// Create a new archive_entry struct -fn C.archive_entry_new() &C.archive_entry - -// Get the filename of the given entry -fn C.archive_entry_pathname(&C.archive_entry) &char - -// Get an entry's file size -// Note: this function actually returns an i64, but as this can't be used as an arugment to malloc, we'll just roll with it & assume an entry is never bigger than 4 gigs -fn C.archive_entry_size(&C.archive_entry) int - -#include - -// Compare two C strings; 0 means they're equal -fn C.strcmp(&char, &char) int - -// Represents a read archive struct Pkg { pub: info PkgInfo [required] files []string [required] } -// Represents the contents of a .PKGINFO file struct PkgInfo { mut: // Single values @@ -89,7 +38,6 @@ mut: checkdepends []string } -// parse_pkg_info_string parses a PkgInfo object from a string fn parse_pkg_info_string(pkg_info_str &string) ?PkgInfo { mut pkg_info := PkgInfo{} @@ -141,57 +89,9 @@ fn parse_pkg_info_string(pkg_info_str &string) ?PkgInfo { return pkg_info } -// read_pkg extracts the file list & .PKGINFO contents from an archive -// NOTE: this command currently only supports zstd-compressed tarballs pub fn read_pkg(pkg_path string) ?Pkg { - if !os.is_file(pkg_path) { - return error("'$pkg_path' doesn't exist or isn't a file.") - } - - a := C.archive_read_new() - entry := C.archive_entry_new() - mut r := 0 - - // Sinds 2020, all newly built Arch packages use zstd - C.archive_read_support_filter_zstd(a) - // The content should always be a tarball - C.archive_read_support_format_tar(a) - - // TODO find out where does this 10240 come from - r = C.archive_read_open_filename(a, &char(pkg_path.str), 10240) - defer { - C.archive_read_free(a) - } - - if r != C.ARCHIVE_OK { - return error('Failed to open package.') - } - - mut buf := voidptr(0) - mut files := []string{} - - for C.archive_read_next_header(a, &entry) == C.ARCHIVE_OK { - pathname := C.archive_entry_pathname(entry) - - ignored_names := [c'.BUILDINFO', c'.INSTALL', c'.MTREE', c'.PKGINFO', c'.CHANGELOG'] - if ignored_names.all(C.strcmp(it, pathname) != 0) { - unsafe { - files << cstring_to_vstring(pathname) - } - } - - if C.strcmp(pathname, c'.PKGINFO') == 0 { - size := C.archive_entry_size(entry) - - // TODO can this unsafe block be avoided? - buf = unsafe { malloc(size) } - C.archive_read_data(a, voidptr(buf), size) - } else { - C.archive_read_data_skip(a) - } - } - - pkg_info := parse_pkg_info_string(unsafe { cstring_to_vstring(&char(buf)) }) ? + pkg_info_str, files := archive.pkg_info(pkg_path) ? + pkg_info := parse_pkg_info_string(pkg_info_str) ? return Pkg{ info: pkg_info @@ -199,7 +99,7 @@ pub fn read_pkg(pkg_path string) ?Pkg { } } -// to_desc returns a desc file valid string representation +// Represent a PkgInfo struct as a desc file pub fn (p &PkgInfo) to_desc() string { // TODO calculate md5 & sha256 instead of believing the file mut desc := '' diff --git a/src/repo.v b/src/repo.v index f3ebfd4..ae76cc9 100644 --- a/src/repo.v +++ b/src/repo.v @@ -1,6 +1,7 @@ module repo import os +import archive const pkgs_subpath = 'pkgs' @@ -21,37 +22,35 @@ pub: pkg_dir string [required] } -// contains returns whether the repository contains the given package. +// Returns whether the repository contains the given package. pub fn (r &Repo) contains(pkg string) bool { return os.exists(os.join_path(r.repo_dir, 'files', pkg)) } -// add adds the given package to the repo. If false, the package was already +// Adds the given package to the repo. If false, the package was already // present in the repository. pub fn (r &Repo) add(pkg string) ?bool { return false } -// generate re-generates the db & files archives. +// Re-generate the db & files archives. fn (r &Repo) genenerate() ? { } -// pkg_path returns path to the given package, prepended with the repo's path. +// Returns path to the given package, prepended with the repo's path. pub fn (r &Repo) pkg_path(pkg string) string { return os.join_path_single(r.pkg_dir, pkg) } -// exists checks whether a package file exists pub fn (r &Repo) exists(pkg string) bool { return os.exists(r.pkg_path(pkg)) } -// db_path returns the full path to the database file +// Returns the full path to the database file pub fn (r &Repo) db_path() string { return os.join_path_single(r.repo_dir, 'repo.tar.gz') } -// add_package adds a package to the repository pub fn (r &Repo) add_package(pkg_path string) ? { mut res := os.Result{} diff --git a/src/routes.v b/src/routes.v index 101e1c5..c7df286 100644 --- a/src/routes.v +++ b/src/routes.v @@ -7,7 +7,6 @@ import time 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) @@ -24,7 +23,6 @@ fn is_pkg_name(s string) bool { return s.contains('.pkg') } -// get_root handles a GET request for a file on the root ['/:filename'; get] fn (mut app App) get_root(filename string) web.Result { mut full_path := '' @@ -90,7 +88,6 @@ fn (mut app App) get_root(filename string) web.Result { // return app.text('Package added successfully.') // } -// add_package PUT a new package to the server ['/add'; put] pub fn (mut app App) add_package() web.Result { return app.text('') diff --git a/src/web/logging.v b/src/web/logging.v index fc697ff..66426f2 100644 --- a/src/web/logging.v +++ b/src/web/logging.v @@ -2,34 +2,28 @@ module web import log -// log reate a log message with the given level pub fn (mut ctx Context) log(msg &string, level log.Level) { lock ctx.logger { ctx.logger.send_output(msg, level) } } -// lfatal create a log message with the fatal level pub fn (mut ctx Context) lfatal(msg &string) { ctx.log(msg, log.Level.fatal) } -// lerror create a log message with the error level pub fn (mut ctx Context) lerror(msg &string) { ctx.log(msg, log.Level.error) } -// lwarn create a log message with the warn level pub fn (mut ctx Context) lwarn(msg &string) { ctx.log(msg, log.Level.warn) } -// linfo create a log message with the info level pub fn (mut ctx Context) linfo(msg &string) { ctx.log(msg, log.Level.info) } -// ldebug create a log message with the debug level pub fn (mut ctx Context) ldebug(msg &string) { ctx.log(msg, log.Level.debug) } diff --git a/src/web/web.v b/src/web/web.v index 2db8f18..404bd08 100644 --- a/src/web/web.v +++ b/src/web/web.v @@ -187,14 +187,14 @@ struct Route { } // Defining this method is optional. -// init_server is called at server start. +// This method called at server start. // You can use it for initializing globals. pub fn (ctx Context) init_server() { eprintln('init_server() has been deprecated, please init your web app in `fn main()`') } // Defining this method is optional. -// before_request is called before every request (aka middleware). +// This method called before every request (aka middleware). // Probably you can use it for check user session cookie or add header. pub fn (ctx Context) before_request() {} @@ -206,7 +206,7 @@ pub struct Cookie { http_only bool } -// send_response_to_client sends a response to the client +// web intern function [manualfree] pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bool { if ctx.done { @@ -230,33 +230,33 @@ pub fn (mut ctx Context) send_response_to_client(mimetype string, res string) bo return true } -// html HTTP_OK with s as payload with content-type `text/html` +// Response HTTP_OK with s as payload with content-type `text/html` pub fn (mut ctx Context) html(s string) Result { ctx.send_response_to_client('text/html', s) return Result{} } -// text HTTP_OK with s as payload with content-type `text/plain` +// Response HTTP_OK with s as payload with content-type `text/plain` pub fn (mut ctx Context) text(s string) Result { ctx.send_response_to_client('text/plain', s) return Result{} } -// json HTTP_OK with json_s as payload with content-type `application/json` +// Response HTTP_OK with json_s as payload with content-type `application/json` pub fn (mut ctx Context) json(j T) Result { json_s := json.encode(j) ctx.send_response_to_client('application/json', json_s) return Result{} } -// json_pretty Response HTTP_OK with a pretty-printed JSON result +// Response HTTP_OK with a pretty-printed JSON result pub fn (mut ctx Context) json_pretty(j T) Result { json_s := json.encode_pretty(j) ctx.send_response_to_client('application/json', json_s) return Result{} } -// file Response HTTP_OK with file as payload +// Response HTTP_OK with file as payload // This function manually implements responses because it needs to stream the file contents pub fn (mut ctx Context) file(f_path string) Result { if ctx.done { @@ -329,13 +329,13 @@ pub fn (mut ctx Context) file(f_path string) Result { return Result{} } -// ok Response HTTP_OK with s as payload +// Response HTTP_OK with s as payload pub fn (mut ctx Context) ok(s string) Result { ctx.send_response_to_client(ctx.content_type, s) return Result{} } -// server_error Response a server error +// Response a server error pub fn (mut ctx Context) server_error(ecode int) Result { $if debug { eprintln('> ctx.server_error ecode: $ecode') @@ -347,7 +347,7 @@ pub fn (mut ctx Context) server_error(ecode int) Result { return Result{} } -// redirect Redirect to an url +// Redirect to an url pub fn (mut ctx Context) redirect(url string) Result { if ctx.done { return Result{} @@ -360,7 +360,7 @@ pub fn (mut ctx Context) redirect(url string) Result { return Result{} } -// not_found Send an not_found response +// Send an not_found response pub fn (mut ctx Context) not_found() Result { if ctx.done { return Result{} @@ -370,7 +370,7 @@ pub fn (mut ctx Context) not_found() Result { return Result{} } -// set_cookie Sets a cookie +// Sets a cookie pub fn (mut ctx Context) set_cookie(cookie Cookie) { mut cookie_data := []string{} mut secure := if cookie.secure { 'Secure;' } else { '' } @@ -383,17 +383,17 @@ pub fn (mut ctx Context) set_cookie(cookie Cookie) { ctx.add_header('Set-Cookie', '$cookie.name=$cookie.value; $data') } -// set_content_type Sets the response content type +// Sets the response content type pub fn (mut ctx Context) set_content_type(typ string) { ctx.content_type = typ } -// set_cookie_with_expire_date Sets a cookie with a `expire_data` +// Sets a cookie with a `expire_data` pub fn (mut ctx Context) set_cookie_with_expire_date(key string, val string, expire_date time.Time) { ctx.add_header('Set-Cookie', '$key=$val; Secure; HttpOnly; expires=$expire_date.utc_string()') } -// get_cookie Gets a cookie by a key +// Gets a cookie by a key pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor mut cookie_header := ctx.get_header('cookie') if cookie_header == '' { @@ -413,7 +413,7 @@ pub fn (ctx &Context) get_cookie(key string) ?string { // TODO refactor return error('Cookie not found') } -// set_status Sets the response status +// Sets the response status pub fn (mut ctx Context) set_status(code int, desc string) { if code < 100 || code > 599 { ctx.status = '500 Internal Server Error' @@ -422,12 +422,12 @@ pub fn (mut ctx Context) set_status(code int, desc string) { } } -// add_header Adds an header to the response with key and val +// Adds an header to the response with key and val pub fn (mut ctx Context) add_header(key string, val string) { ctx.header.add_custom(key, val) or {} } -// get_header Returns the header data from the key +// Returns the header data from the key pub fn (ctx &Context) get_header(key string) string { return ctx.req.header.get_custom(key) or { '' } } @@ -436,7 +436,7 @@ interface DbInterface { db voidptr } -// run runs the app +// run_app [manualfree] pub fn run(global_app &T, port int) { mut l := net.listen_tcp(.ip6, ':$port') or { panic('failed to listen $err.code $err') } @@ -478,7 +478,6 @@ pub fn run(global_app &T, port int) { } } -// handle_conn handles a connection [manualfree] fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { conn.set_read_timeout(30 * time.second) @@ -616,7 +615,6 @@ fn handle_conn(mut conn net.TcpConn, mut app T, routes map[string]Route) { conn.write(web.http_404.bytes()) or {} } -// route_matches returns wether a route matches fn route_matches(url_words []string, route_words []string) ?[]string { // URL path should be at least as long as the route path // except for the catchall route (`/:path...`) @@ -659,7 +657,7 @@ fn route_matches(url_words []string, route_words []string) ?[]string { return params } -// serve_if_static checks if request is for a static file and serves it +// check if request is for a static file and serves it // returns true if we served a static file, false otherwise [manualfree] fn serve_if_static(mut app T, url urllib.URL) bool { @@ -678,7 +676,6 @@ fn serve_if_static(mut app T, url urllib.URL) bool { return true } -// scan_static_directory makes a static route for each file in a directory fn (mut ctx Context) scan_static_directory(directory_path string, mount_path string) { files := os.ls(directory_path) or { panic(err) } if files.len > 0 { @@ -698,7 +695,7 @@ fn (mut ctx Context) scan_static_directory(directory_path string, mount_path str } } -// handle_static Handles a directory static +// Handles a directory static // If `root` is set the mount path for the dir will be in '/' pub fn (mut ctx Context) handle_static(directory_path string, root bool) bool { if ctx.done || !os.exists(directory_path) { @@ -727,7 +724,7 @@ pub fn (mut ctx Context) mount_static_folder_at(directory_path string, mount_pat return true } -// serve_static Serves a file static +// Serves a file static // `url` is the access path on the site, `file_path` is the real path to the file, `mime_type` is the file type pub fn (mut ctx Context) serve_static(url string, file_path string) { ctx.static_files[url] = file_path @@ -736,7 +733,7 @@ pub fn (mut ctx Context) serve_static(url string, file_path string) { ctx.static_mime_types[url] = web.mime_types[ext] } -// ip Returns the ip address from the current user +// Returns the ip address from the current user pub fn (ctx &Context) ip() string { mut ip := ctx.req.header.get(.x_forwarded_for) or { '' } if ip == '' { @@ -752,23 +749,22 @@ pub fn (ctx &Context) ip() string { return ip } -// error Set s to the form error +// Set s to the form error pub fn (mut ctx Context) error(s string) { println('web error: $s') ctx.form_error = s } -// not_found Returns an empty result +// Returns an empty result pub fn not_found() Result { return Result{} } -// send_string fn send_string(mut conn net.TcpConn, s string) ? { conn.write(s.bytes()) ? } -// filter Do not delete. +// Do not delete. // It used by `vlib/v/gen/c/str_intp.v:130` for string interpolation inside web templates // TODO: move it to template render fn filter(s string) string {