Compare commits

...

No commits in common. "v0.1.0-alpha2" and "main" have entirely different histories.

56 changed files with 1159 additions and 337 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
**/dist/
**/target
**/Cargo.lock
*.pkg

25
.idea/jsonSchemas.xml Normal file
View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JsonSchemaMappingsProjectConfiguration">
<state>
<map>
<entry key="Woodpecker pipeline config">
<value>
<SchemaInfo>
<option name="name" value="Woodpecker pipeline config" />
<option name="relativePathToSchema" value="https://raw.githubusercontent.com/woodpecker-ci/woodpecker/master/pipeline/schema/schema.json" />
<option name="applicationDefined" value="true" />
<option name="patterns">
<list>
<Item>
<option name="path" value=".woodpecker.yml" />
</Item>
</list>
</option>
</SchemaInfo>
</value>
</entry>
</map>
</state>
</component>
</project>

View file

@ -1,8 +1,47 @@
# Packager
[![CI Status](https://ci.ixvd.net/api/badges/2/status.svg)](https://ci.ixvd.net/repos/2)
> "A package manager and builder but like rust."
> -- Gandhi (2050)
> ***not even close to done*** :)
> ***Almost to a point you can use it*** :)
Packager is a simple yet powerful package manager
## Install a package
pkgr supports fetching packages with a:
- path
- url (http/https)
- package locator
```shell
# example with path
pkgr install ./foxy/snek.pkg
# example with url
pkgr install https://example.com/doggo.pkg
# example with package locator
pkgr install foo:stable,bar
```
## Composing a package
Right now, pkgr does not have a compose/package command. (it's in the works dw!)
You can create a package with `pkg.py`!
```shell
# Usage: pkg.py <package_toml_path> <output_path> [<directories_to_include> ...]
# example:
python pkg.py manifest.toml example.pkg root/ source/ scripts/
# this will create a package with an archive that look like this:
# [header+manifest]
# ...
# [archive:
# root/
# source/
# scripts/
# ]
```
It's not perfect, but it'll get the job done :)

View file

@ -1,31 +0,0 @@
import os
import tarfile
with open("./package.toml", mode='r') as mani:
data = mani.read()
with open("./sample.pkg", mode='wb') as pkg:
print("building header...")
pkg.write(bytes([0x01, (len(data) >> 8) & 0xFF, len(data) & 0xFF]))
print("writing manifest into pkg...")
pkg.write(data.encode("utf-8"))
with tarfile.TarFile("/tmp/pkgtar", 'w') as pkgtar:
print("tarring ./pkg...")
os.chdir("pkg")
for root, dirs, files in os.walk("."):
for file in files:
print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True)
pkgtar.add(os.path.join(root, file))
os.chdir("..")
for root, dirs, files in os.walk("pkgr"):
for file in files:
print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True)
pkgtar.add(os.path.join(root, file))
print("\33[2K", end="\r", flush=True)
with open("/tmp/pkgtar", 'rb') as pkgtar:
print("appending pkgtar to pkg...")
pkg.write(pkgtar.read())
print("deleting /tmp/pkgtar...")
os.unlink("/tmp/pkgtar")
print("closing write stream")
pkg.close()

View file

@ -14,3 +14,4 @@ regex = "1.9.1"
reqwest = { version = "0.11.18", features = ["blocking"] }
uuid = { version = "1.4.0", features = ["serde", "v4"] }
tar = "0.4.39"
libc = "0.2.80"

10
bootpkg/package.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "bootpkg" #*
description = "A tool to strap pkgs" #*
version = 1
tags = []
type = "application"
arch = "x86_64"
[bin]
root = "/root"

View file

@ -18,9 +18,20 @@ pub struct Args {
impl From<Vec<String>> for Args {
fn from(value: Vec<String>) -> Self {
if value.len() == 0 {
return Args {
command: Command::from(String::default()),
args: vec![],
}
}
Args {
command: Command::from(value[0].to_owned()),
args: value[1..].to_owned(),
args: if value.len() > 1 {
value[1..].to_owned()
} else {
vec![]
},
}
}
}

View file

@ -15,6 +15,14 @@ mod args;
mod prelude;
fn main() {
#[cfg(not(debug_assertions))]
{
if unsafe { libc::getuid() } != 0 {
println!("bootpkg must be run as root.");
std::process::exit(1);
}
}
let args = Args::from(env::args().collect::<Vec<String>>()[1..].to_owned());
match args.command {
Command::Strap => {

View file

@ -30,12 +30,12 @@ pub fn mani_from_str(s: &str) -> Manifest<Option<PKGR>> {
fs: mani.fs,
bin: mani.bin,
build: mani.build,
pkgr: bmani.pkgr,
ext: bmani.pkgr,
}
}
pub fn run_bootstrap(mani: Manifest<Option<PKGR>>) -> bool {
if let Some(pkgr) = mani.pkgr {
if let Some(pkgr) = mani.ext {
if let Some(bootstrap) = pkgr.bootstrap {
fn run_command<S: Into<String>>(s: S) -> i32 {
std::process::Command::new("sh")

39
build Executable file
View file

@ -0,0 +1,39 @@
#!/bin/bash
build_pkgr() {
cd pkgr
mkdir -p dist/root/{usr/bin,etc/pkgr}
echo -e "You can't use pkgr to update pkgr because the file will be in use while updating.\nuse bootpkg" > dist/root/etc/pkgr.d/YOU-CAN-NOT-USE-PKGR-TO-UPDATE-PKGR.txt
# for bin
cargo build -r
cp target/release/pkgr dist/root/usr/bin/pkgr
# for build
mkdir -p dist/pkgr
cp -r src/ Cargo.toml dist/pkgr
cp -r ../manifest dist/manifest
cp -r ../pkgfile dist/pkgfile
cd dist
python ../../pkg.py ../package.toml pkgr.pkg "*"
cd ../..
}
build_bootpkg() {
cd bootpkg
mkdir -p dist/root/usr/bin
cargo build -r
cp target/release/bootpkg dist/root/usr/bin/bootpkg
cd dist
python ../../pkg.py ../package.toml bootpkg.pkg "*"
cd ../..
}
set -e
build_bootpkg
build_pkgr

View file

@ -9,7 +9,9 @@ pkgr is the main tool for the packager.
Because this tool is so feature rich, it has its own [README](./pkgr/README.md).
## bootpkg
bootpkg is a tool primariliy used to bootstrap the packager.
`bootpkg` is a simple pkgr variant.
It doesn't contain anything fancy and is only used to install and update pkgr.
Using it for anything other than pkgr is not recommended.
### Usage

5
docs/manifest.md Normal file
View file

@ -0,0 +1,5 @@
# Manifest
---
The manifest spec is located in `/package.example.toml` as an example.

View file

@ -1,24 +0,0 @@
# PKGFILE
This file is essentially a tar with the manifest in the header.
## Format
The format is as follows:
```
[PKGFILE version (1 byte)][manifest length x 256 (1 byte)][manifest length (1 byte)]
[manifest (manifest length bytes)]
[archive]
```
The file is Big Endian.
## Unpacking
To unpack a PKGFILE, you can use the following command:
```bash
pkgr unpack <pkgfile> <output>
```
## Packing
You can write your own packer, or use the following command:
```bash
pkgr pack <manifest path> <archive path> <output file>
```

25
docs/pkgfiles/current.md Normal file
View file

@ -0,0 +1,25 @@
# PKGFILE
version 1
---
PKGFiles are an all-in-one solution to install a package.
## Overhead
Because pkgfiles include everything, many times you're downloading more than what is needed.
This may seem stupid but pkgfiles are *not* meant to stay on your system.
## Format
The format is as follows:
```
[PKGFILE version (1 byte)][manifest length x 256 (1 byte)][manifest length (1 byte)]
[manifest (manifest length bytes)]
[archive]
```
### Endianness
The file is Big Endian.

22
docs/pkgfiles/planned.md Normal file
View file

@ -0,0 +1,22 @@
# Planned PKGFILE features.
---
### Compression
> Planned for next PKGFILE version.
Currently, compression is not supported, since PKGFILE is an all-in-one solution this is very unpleasant.
When compression will be implemented, there is a big possibility that the manifest won't be compressed.
This is done to allow servers to easily parse and inspect manifests.
The compression will likely be versatile in the way that the format will be stated in the header and the pkgr
implementation can work it out by itself.
There will be a default compression format, likely gzip.
### Compact Manifest
> Not planned yet.
Right now, the manifest is an utf-8 blob sandwiched between the header and the archive.
This also requires a fixed max size for the manifest which is unfavourable.
In the future, the manifest may turn into a versatile binary blob with no size limitations.

View file

@ -5,5 +5,15 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Bin {
pub root: String,
#[serde(default)]
pub checksums: HashMap<String, String>,
}
impl Default for Bin {
fn default() -> Self {
Bin {
root: String::default(),
checksums: HashMap::default(),
}
}
}

View file

@ -8,3 +8,13 @@ pub struct Build {
pub install_script: String,
pub dependencies: HashMap<String, String>,
}
impl Default for Build {
fn default() -> Self {
Build {
dependencies: HashMap::default(),
build_script: String::default(),
install_script: String::default()
}
}
}

View file

@ -2,9 +2,9 @@ use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PKGR {}
pub struct Extension {}
impl Display for PKGR {
impl Display for Extension {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "")
}

View file

@ -6,17 +6,17 @@ pub mod bin;
pub mod build;
pub mod fs;
pub mod package;
pub mod pkgr;
pub mod ext;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Manifest<P: Clone = Option<pkgr::PKGR>> {
pub struct Manifest<E: Clone = Option<ext::Extension>> {
pub package: package::Package,
pub dependencies: HashMap<String, String>,
pub fs: fs::FS,
pub bin: Option<bin::Bin>,
pub build: Option<build::Build>,
pub pkgr: P,
pub ext: E,
}
impl<P: Clone> Manifest<P> {
@ -50,7 +50,7 @@ impl Default for Manifest {
fs: fs::FS::default(),
bin: None,
build: None,
pkgr: None,
ext: None,
}
}
}

View file

@ -12,6 +12,16 @@ pub enum PackageType {
Meta,
}
impl ToString for PackageType {
fn to_string(&self) -> String {
match &self {
PackageType::Application => "application",
PackageType::Library => "library",
PackageType::Meta => "meta"
}.to_string()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Package {

View file

@ -84,16 +84,16 @@ install_script = "scripts/install" # relative to pkg
[build.dependencies]
base = "latest,stable" # selected by default
## pkgr.*
## ext.*
# packager is the official client but you may use other clients supporting the "pkgr v1 spec".
# other clients may offer extra functionality this must be put under "pkgr.*"
[pkgr]
# other clients may offer extra functionality this must be put under "ext.*"
[ext]
## pkgr.bootstrap
## ext.bootstrap
# This section is used for bootpkg. An edition of packager that bootstraps the full version.
# This exists so that packager is easy to install on anything!
# and only 1 release channel for pkgr
[pkgr.bootstrap]
[ext.bootstrap]
## any non-zero = installed
check_installed_commands = [
"sh scripts/check_installed"

View file

@ -1,69 +0,0 @@
[package]
name = "packager" #*
description = "A package installation tool" #*
version = 1 # this can automatically be incremented when publishing by running `pkgr publish -i ...`
tags = [ "prod", "pkgr-spec-1" ]
type = "application"
arch = "x86_64" # this is automatically filled by `pkgr publish ...`
## dependencies
# you may use the following syntax
# "<version/tag>" or "<version/tag>,<version/tag>,..."
[dependencies]
## bin
# Used for systems that don't want to build pkgs.
[bin] # binary files root
## bin.root
# ** RELATIVE TO PACKAGE ROOT **
# bin.root specifies the root of the installed tree.
# anything in here will be overlayed on top of the system.
root = "/root" #*
## bin.checksums
# ** KEYS are relative to BIN.ROOT **
# ** VALUES are relative to PKG ROOT **
# checksums is used to perform checksums before install
# values may be paths or a uri. You may include variables.
# supported variables:
# - @name
# - @version
# - @display_version
[bin.checksums]
"/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version"
## build
# Scripts will be copied to a custom root.
# After the pacakge is built, the install script is ran.
# After the install script the build directory will be deleted and an CTREE (changed tree) will be generated.
# Then the CTREE will be used to copy the files over.
[build]
build_script = "scripts/build" # relative to pkg
install_script = "scripts/install" # relative to pkg
[build.dependencies]
base = "latest,stable" # selected by default
## pkgr.*
# packager is the official client but you may use other clients supporting the "pkgr v1 spec".
# other clients may offer extra functionality this must be put under "pkgr.*"
[pkgr]
## pkgr.bootstrap
# This section is used for bootpkg. An edition of packager that bootstraps the full version.
# This exists so that packager is easy to install on anything!
# and only 1 release channel for pkgr
[pkgr.bootstrap]
## any non-zero = installed
check_installed_commands = [
"sh scripts/check_installed"
]
# any non-zero = fail
commands = [
"sh scripts/bootstrap/download_latest @version /tmp/pkgr.pkg",
"sh scripts/bootstrap/dirty_install /tmp/pkgr.pkg",
"sh scripts/check_installed"
]

79
pkg.py Normal file
View file

@ -0,0 +1,79 @@
import glob
import os
import sys
import tarfile
def build_package(package_toml_path, output_path, directories_to_include):
data = read_package_toml(package_toml_path)
header = build_header(data)
pkg_path = output_path
with open(pkg_path, mode='wb') as pkg:
pkg.write(header)
write_manifest(pkg, data)
build_tarball(pkg, directories_to_include)
return pkg_path
def read_package_toml(package_toml_path):
with open(package_toml_path, mode='r') as mani:
return mani.read()
def build_header(data):
header = bytes([0x01, (len(data) >> 8) & 0xFF, len(data) & 0xFF])
return header
def write_manifest(pkg, data):
pkg.write(data.encode("utf-8"))
def build_tarball(pkg, directories_to_include):
with tarfile.TarFile("/tmp/pkgtar", 'w') as pkgtar:
for directory in directories_to_include:
add_files_to_tar(pkgtar, directory, True)
append_tar_to_pkg(pkg)
cleanup_tmp_files()
def add_files_to_tar(pkgtar, directory, skip_target=False):
for root, dirs, files in os.walk(directory):
for file in files:
if skip_target and "target" in root:
continue
print(f"\33[2Kadd: {os.path.join(root, file)}", end="\r", flush=True)
# print()
pkgtar.add(os.path.join(root, file))
print("\33[2K", end="\r", flush=True)
def append_tar_to_pkg(pkg):
with open("/tmp/pkgtar", 'rb') as pkgtar:
pkg.write(pkgtar.read())
def cleanup_tmp_files():
print("deleting /tmp/pkgtar...")
os.unlink("/tmp/pkgtar")
if __name__ == '__main__':
if len(sys.argv) < 3:
print("Usage: pkg.py <package_toml_path> <output_path> [<directories_to_include> ...]")
sys.exit(1)
package_toml_path = sys.argv[1]
output_path = sys.argv[2]
directories_to_include = glob.glob(sys.argv[3]) if len(sys.argv) == 4 else sys.argv[3:]
try:
pkg_path = build_package(package_toml_path, output_path, directories_to_include)
print(f"Package created: {pkg_path}")
except FileNotFoundError:
print("Error: File not found.")
except Exception as e:
print(f"Error occurred: {e}")

View file

@ -1,3 +0,0 @@
#!/bin/sh
printf ""

View file

@ -1,9 +0,0 @@
#!/bin/sh
fetch_latest_version() {
printf ""
}
download_file() {
printf ""
}

View file

@ -1,3 +0,0 @@
#!/bin/sh
printf ""

View file

@ -1,4 +1,9 @@
#[derive(Debug, Clone)]
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::io;
use std::path::Path;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PKGFile {
pub manifest: String,
pub data: Vec<u8>,
@ -23,28 +28,56 @@ impl Default for PKGFile {
}
}
impl TryFrom<Vec<u8>> for PKGFile {
type Error = ();
#[derive(Debug)]
pub enum PKGFileError {
IOError(io::Error),
ParsingError(String)
}
fn try_from(value: Vec<u8>) -> Result<Self, ()> {
impl Display for PKGFileError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
PKGFileError::IOError(e) => write!(f, "IOError: {}", e),
PKGFileError::ParsingError(s) => write!(f, "ParsingError: {}", s)
}
}
}
impl Error for PKGFileError {}
impl<'a> TryFrom<&'a Path> for PKGFile {
type Error = PKGFileError;
fn try_from(path: &'a Path) -> Result<Self, Self::Error> {
let d = match std::fs::read(path) {
Ok(d) => d,
Err(e) => return Err(PKGFileError::IOError(e))
};
PKGFile::try_from(d)
}
}
impl TryFrom<Vec<u8>> for PKGFile {
type Error = PKGFileError;
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
match value[0] {
1 => {
let header: Vec<u32> = value[..3].iter().map(|v| u32::from(*v)).collect();
let manifest_size: u32 = (header[1] << 8) | header[2];
if manifest_size > value.len() as u32 {
return Err(());
return Err(PKGFileError::ParsingError("Invalid header length".into()));
}
Ok(PKGFile {
manifest: match String::from_utf8(
value[3..(manifest_size as usize + 3)].to_vec(),
) {
Ok(s) => s,
_ => return Err(()),
_ => return Err(PKGFileError::ParsingError("Could not parse manifest".into())),
},
data: value[(manifest_size as usize + 3)..].to_vec(),
})
}
_ => Err(()),
_ => Err(PKGFileError::ParsingError("Unknown pkgfile version".into())),
}
}
}

View file

@ -23,3 +23,7 @@ serde = { version = "1.0.171", features = ["derive"] }
libc = "0.2.80"
reqwest = { version = "0.11.18", features = ["blocking"] }
tar = "0.4.39"
humantime = "2.1.0"
expanduser = "1.2.2"
url = { version = "2.4.0", features = ["serde"] }
dns-lookup = "2.0.3"

32
pkgr/package.toml Normal file
View file

@ -0,0 +1,32 @@
[package]
name = "packager"
description = "A package installation tool"
version = 1
tags = [ "prod", "pkgr-spec-1" ]
type = "application"
arch = "x86_64"
[dependencies]
[bin]
root = "/root"
[bin.checksums]
"/usr/bin/pkgr" = "https://ixvd.net/checksums/packager/@version"
[build]
build_script = "scripts/build"
install_script = "scripts/install"
[build.dependencies]
[ext]
[ext.bootstrap]
check_installed_commands = [
"file /usr/bin/pkgr"
]
commands = [
"cp root/usr/bin/pkgr /usr/bin/pkgr"
]

View file

@ -0,0 +1,4 @@
[repo.main]
name = "Main"
url = "tcp://pkgs.ixvd.net:1050"

7
pkgr/skel/etc/pkgr.toml Normal file
View file

@ -0,0 +1,7 @@
build_by_default = false
tmp_dir = "/tmp/pkgr"
[storage]
repo_file = "/etc/pkgr.d/repos.toml"
data_dir = "/var/lib/pkgr/packages"
index_dir = "/var/lib/pkgr/indexes"

View file

@ -0,0 +1,20 @@
[index.main]
last_update = "1610219288"
[index.main.packager]
versions = [1, 2, 3]
tags = ["tag"]
## rust
# IndexedPackage
# - name -> packager
# - origin_repo -> main
# - versions -> [1,2,3]
# - tags -> ["tag"]
# - get_package() -> Package
## rust
# Repo
# - name -> main
# - last_update -> 1610219288
# - update_repo() -> io::Result<()>
# - searchIndex(p: PackageIdentifier) -> IndexedPackage

0
pkgr/src/api/client.rs Normal file
View file

63
pkgr/src/api/mod.rs Normal file
View file

@ -0,0 +1,63 @@
use std::collections::HashMap;
use serde;
use serde::{Deserialize, Serialize};
pub mod client;
#[derive(Serialize, Deserialize)]
pub enum Query {
#[serde(rename = "pull")]
Pull {
name: String,
version: Option<u128>,
tags: Vec<String>
},
#[serde(rename = "push")]
Push {
// todo: review me pls
_data: Vec<u8>
},
#[serde(rename = "index")]
Index {
request_update: bool,
fetch: bool
}
}
#[derive(Serialize, Deserialize)]
pub struct Request {
version: u32,
id: String,
token: Option<String>,
query: HashMap<String, Query>
}
#[derive(Serialize, Deserialize)]
pub struct DataErrorDetails {
actor: String,
detailed_cause: String,
recovery_options: Vec<String>,
}
#[derive(Serialize, Deserialize)]
pub struct DataError {
name: String,
cause: Option<String>,
details: Option<DataErrorDetails>
}
#[derive(Serialize, Deserialize)]
pub enum Data {
Pull {
_data: Option<Vec<u8>>,
}
}
#[derive(Serialize, Deserialize)]
pub struct Response {
version: u32,
id: String,
reply_to: String,
errors: HashMap<String, DataError>,
data: HashMap<String, Data>
}

View file

@ -1,12 +1,17 @@
use crate::package::identifier::PackageIdentifier;
use std::process::exit;
use crate::package::Package;
use clap::{Parser, Subcommand};
use colored::Colorize;
use log::{debug, error, info, trace, warn};
use std::process::exit;
use crate::CONFIG;
use manifest::package::PackageType;
use crate::package::identifier::PackageIdentifier;
use crate::package::Package;
use crate::package::queue::PackageQueue;
use crate::process::Process;
use crate::types::fetch::TryFetch;
use crate::util::prompts::prompt_bool;
#[derive(Parser, Debug)]
#[clap(name = "pkgr", version)]
@ -21,6 +26,8 @@ pub enum Command {
Install {
#[arg(short, long, default_value_t = false)]
build: bool,
#[arg(short, long, default_value_t = false)]
ask: bool,
package_identifier: PackageIdentifier,
},
/// Remove a package from the system
@ -38,7 +45,11 @@ pub enum Command {
},
/// Update packages on the system
Update,
#[command(hide = true)]
/// Get info about a package
Info {
package_identifier: Option<PackageIdentifier>
},
#[cfg(debug_assertions)]
Debug,
#[command(hide = true)]
None,
@ -49,6 +60,7 @@ impl Command {
match self {
Command::Install {
build,
ask,
package_identifier,
} => {
warn!("Installer does not run in isolation.");
@ -59,8 +71,7 @@ impl Command {
.unwrap();
info!("Parsing package...");
trace!("Fetching package: {}", package_identifier);
let mut pkg = Package::fetch(package_identifier.clone()).unwrap();
let mut pkg = Package::try_fetch(package_identifier.clone()).unwrap();
debug!("manifest size: {}kb", pkg.pkgfile.manifest.len() / 1024);
debug!("files size: {}kb", pkg.pkgfile.data.len() / 1024);
@ -70,14 +81,19 @@ impl Command {
exit(1);
}
trace!("Starting install...");
match pkg.install(CONFIG.with(|c| if !*build { c.build_by_default } else { *build })) {
Ok(_) => (),
Err(e) => {
error!("Install failed: {}", e.to_string());
exit(1);
let mut queue = PackageQueue::new();
queue.add_package(pkg, *build);
trace!("Installing queue...");
{
if *ask {
info!("Install following packages?");
info!(target: "item", "{}", queue);
if !prompt_bool("Continue?", false) {
return;
}
}
}
queue.install(*build);
let end = std::time::Instant::now();
let _unix_end = std::time::SystemTime::now()
@ -87,7 +103,7 @@ impl Command {
let duration = end.duration_since(start);
info!("Install complete.");
info!("Install took {}ms.", duration.as_nanos() as f64 / 1000000.0);
info!("Install took {}.", humantime::format_duration(duration));
}
Command::Remove {
package_identifier,
@ -106,6 +122,74 @@ impl Command {
Command::Update => {
error!("Update is not yet implemented.");
}
Command::Info {
package_identifier
} => {
if let Some(p) = package_identifier {
if let Ok(mut pkg) = Package::try_fetch(p.clone()) {
trace!("{}", pkg.pkgfile.manifest);
info!(target: "item", "Identifier: {:?}", pkg.identifier);
info!(target: "item", "");
let mani = pkg.manifest();
info!(target: "item", "Package name: {}", mani.package.name);
info!(target: "item", "Package description: {}", mani.package.description);
info!(target: "item", "Package version: {}", mani.package.version);
info!(target: "item", "Package tags: {}", mani.package.tags.join(", "));
info!(target: "item", "Package type: {}", mani.package.package_type.to_string());
info!(target: "item", "");
info!(target: "item", "Supported install types: {}", {
let mut types = vec![];
if let Some(_) = mani.bin { types.push("bin") }
if let Some(_) = mani.build { types.push("build") }
if let PackageType::Meta = mani.package.package_type { types.push("meta") }
types.join(", ")
});
info!(target: "item", "");
info!(target: "item", "Dependencies: {}", {
let deps = pkg.dependencies();
if deps.len() == 0 {
String::from("None")
} else {
deps
.iter()
.map(|p| {
p.identifier.to_string()
})
.collect::<Vec<String>>()
.join(", ")
}
});
info!(target: "item", "Build Dependencies: {}", {
let deps = pkg.build_dependencies();
if deps.len() == 0 {
String::from("None")
} else {
deps
.iter()
.map(|p| {
p.identifier.to_string()
})
.collect::<Vec<String>>()
.join(", ")
}
});
} else {
error!("Could not find {p}");
}
} else {
info!("Welcome to pkgr!\n\
{}\n\
To get help please run \"{} -h\"", env!("CARGO_PKG_DESCRIPTION"), std::env::args().nth(0).unwrap());
info!("");
info!("version: {}", env!("CARGO_PKG_VERSION"));
info!("authors: {}", env!("CARGO_PKG_AUTHORS"));
info!("");
info!("If you can't seem to figure something out, use environment variable: PKGR_LOG_LEVEL=debug");
trace!("{}", "The trace log level should really only be used by PKGR devs.".red());
}
}
#[cfg(debug_assertions)]
Command::Debug => {
trace!("Trace message.\nWith newline.");
debug!("Debug message.\nWith newline.");
@ -123,10 +207,6 @@ impl Command {
.unwrap();
}
info!("");
info!("PKGR VERSION: {}", env!("CARGO_PKG_VERSION"));
info!("PKGR AUTHORS: {}", env!("CARGO_PKG_AUTHORS"));
info!("PKGR DESCRIPTION: {}", env!("CARGO_PKG_DESCRIPTION"));
info!("");
info!(
"PKGR_LOG_LEVEL: {}",

View file

@ -1,4 +1,8 @@
use serde::{Deserialize, Serialize};
use storage::Storage;
mod storage;
pub mod repos;
#[derive(Debug, Serialize, Deserialize)]
pub struct Config {
@ -6,13 +10,16 @@ pub struct Config {
pub build_by_default: bool,
#[serde(default)]
pub tmp_dir: Option<String>,
#[serde(default)]
pub storage: Storage
}
impl Default for Config {
fn default() -> Config {
Config {
build_by_default: false,
tmp_dir: Some(String::from("/tmp/pkgr"))
tmp_dir: Some(String::from("/tmp/pkgr")),
storage: Storage::default()
}
}
}

37
pkgr/src/config/repos.rs Normal file
View file

@ -0,0 +1,37 @@
use std::collections::HashMap;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Serialize, Deserialize)]
pub struct Repo {
#[serde(default)]
pub name: String,
pub uri: Url
}
impl Default for Repo {
fn default() -> Self {
Repo {
name: String::from("Repo"),
uri: Url::parse("tcp://0.0.0.0:0000").unwrap()
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct RepoFile {
pub repos: HashMap<String, Repo>
}
impl RepoFile {
pub fn from_path(path: &str) -> Result<RepoFile, String> {
match std::fs::read_to_string(path) {
Ok(s) => match toml::from_str(&s) {
Ok(c) => Ok(c),
Err(e) => Err(format!("failed to parse config: {}", e)),
},
Err(e) => Err(format!("failed to read config: {}", e)),
}
}
}

View file

@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Storage {
/// Where the repositories are defined.
#[serde(default)]
pub repo_file: String,
/// Where to store pkgs data
#[serde(default)]
pub data_dir: String,
/// Where to store repo indexes
#[serde(default)]
pub index_dir: String,
}
impl Default for Storage {
fn default() -> Self {
Storage {
repo_file: String::from("/etc/pkgr.d/repos.toml"),
data_dir: String::from("/var/lib/pkgr/packages"),
index_dir: String::from("/var/lib/pkgr/indexes"),
}
}
}

View file

@ -1,18 +1,19 @@
use std::env;
use colored::Colorize;
use fern::Dispatch;
use log::{Record, SetLoggerError};
use std::env;
fn format_regular<S: Into<String>>(log: S, record: &Record) -> String {
let log = log.into();
let line_prefix = |line: String, extend: bool| {
let prefix = if extend {
match record.level() {
log::Level::Trace => " :".bright_blue(),
log::Level::Debug => " :".green(),
log::Level::Info => " :".blue(),
log::Level::Warn => " :".yellow(),
log::Level::Error => " :".red(),
log::Level::Trace => " ]".bright_blue(),
log::Level::Debug => " ?".green(),
log::Level::Info => " >".blue(),
log::Level::Warn => " #".yellow(),
log::Level::Error => " !".red(),
}.to_string()
} else {
match record.level() {
@ -43,14 +44,12 @@ pub fn setup_logger() -> Result<(), SetLoggerError> {
Dispatch::new()
.format(|out, message, record| {
match record.metadata().target() {
"command:stdout" => {
out.finish(format_args!("{} {}", ">>".cyan(), message.to_string()));
return;
}
"command:stderr" => {
out.finish(format_args!("{} {}", ">>".red(), message.to_string()));
return;
}
// command output logging
"command:stdout" => out.finish(format_args!("{} {}", ">>".cyan(), message.to_string())),
"command:stderr" => out.finish(format_args!("{} {}", ">>".red(), message.to_string())),
// this target means, it's an item and not a log.
"item" => out.finish(format_args!("{} {}", "*".blue(), message.to_string())),
// default logging
_ => out.finish(format_args!("{}", format_regular(message.to_string(), record))),
}
})

View file

@ -1,16 +1,27 @@
use crate::commands::Cli;
use clap::Parser;
use log::trace;
use crate::commands::Cli;
/// pkgr's commands.
mod commands;
/// Logging implementations for pkgr.
mod logging;
/// Package and helpers.
mod package;
/// Repo and helpers
mod repo;
/// Process wrapper with logging wrapper.
mod process;
/// tmpfs wrapper.
mod tmpfs;
/// pkgr's optional config.
mod config;
/// custom types used by pkgr
mod types;
/// utils
mod util;
mod api;
thread_local! {
static CONFIG: config::Config = config::Config::from_path("/etc/pkgr.toml")
@ -18,7 +29,8 @@ thread_local! {
}
fn main() {
logging::setup_logger().expect("Unable to setup logger.");
logging::setup_logger()
.expect("unable to set logger.");
#[cfg(not(debug_assertions))]
{

View file

@ -1,55 +0,0 @@
use crate::package::identifier::PackageLocator;
use pkgfile::PKGFile;
use reqwest::blocking::get;
use std::error::Error;
use std::fmt::Display;
use std::io::Read;
#[derive(Debug)]
pub enum FetchError {
HTTPError(reqwest::Error),
IOError(std::io::Error),
ParseError,
}
impl Display for FetchError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
FetchError::HTTPError(e) => write!(f, "HTTP Error: {}", e),
FetchError::IOError(e) => write!(f, "IO Error: {}", e),
FetchError::ParseError => write!(f, "Parse Error"),
}
}
}
impl Error for FetchError {}
pub fn fetch_by_path<S: Into<String>>(path: S) -> Result<PKGFile, FetchError> {
std::fs::read(path.into())
.map_err(|e| FetchError::IOError(e))
.and_then(|bytes| PKGFile::try_from(bytes).map_err(|_| FetchError::ParseError))
}
pub fn fetch_by_uri<S: Into<String>>(uri: S) -> Result<PKGFile, FetchError> {
// get file contents as bytes
let mut bytes = Vec::new();
match get(uri.into()) {
Ok(response) => {
match response.take(1024 * 1024).read_to_end(&mut bytes) {
Ok(_) => (),
Err(e) => return Err(FetchError::IOError(e)),
};
}
Err(e) => return Err(FetchError::HTTPError(e)),
};
// parse bytes as PKGFile
match PKGFile::try_from(bytes) {
Ok(pkgfile) => Ok(pkgfile),
Err(_e) => Err(FetchError::ParseError),
}
}
pub fn fetch_by_package_locator(_package_locator: PackageLocator) -> Result<PKGFile, FetchError> {
// TODO: search index for package locator
Ok(PKGFile::default())
}

View file

@ -1,7 +1,9 @@
use std::error::Error;
use std::fmt::Formatter;
use std::str::FromStr;
use regex::Regex;
use serde::Serializer;
#[derive(Debug, Clone)]
pub enum PackageIdentifierError {
@ -22,11 +24,29 @@ impl std::fmt::Display for PackageIdentifierError {
impl Error for PackageIdentifierError {}
#[derive(Debug, Clone)]
#[derive(Clone, Eq, PartialEq)]
pub enum PackageIdentifier {
PackageLocator(PackageLocator),
URI(String),
Path(String),
Path(String)
}
impl std::fmt::Debug for PackageIdentifier {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
match self {
PackageIdentifier::PackageLocator(pl) => write!(f, "PackageLocator({:#?})", pl),
PackageIdentifier::URI(uri) => write!(f, "URI({:?})", uri),
PackageIdentifier::Path(path) => write!(f, "Path({:?})", path),
}
} else {
match self {
PackageIdentifier::PackageLocator(pl) => write!(f, "PL: {:?}", pl),
PackageIdentifier::URI(uri) => write!(f, "URI: {:?}", uri),
PackageIdentifier::Path(path) => write!(f, "Path: {:?}", path),
}
}
}
}
impl std::fmt::Display for PackageIdentifier {
@ -39,7 +59,7 @@ impl std::fmt::Display for PackageIdentifier {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct PackageLocator {
pub name: String,
pub version: Option<u32>,
@ -140,8 +160,9 @@ impl FromStr for PackageLocator {
}
}
impl From<(String, String)> for PackageLocator {
fn from((name, locate_str): (String, String)) -> Self {
impl TryFrom<(String, String)> for PackageLocator {
type Error = PackageIdentifierError;
fn try_from((name, locate_str): (String, String)) -> Result<PackageLocator, Self::Error> {
// name = "pkg"
// locate_str = "1.0.0:tag1,tag2" or "1.0.0" or "tag1,tag2"
let mut version = None;
@ -165,10 +186,10 @@ impl From<(String, String)> for PackageLocator {
);
}
PackageLocator {
Ok(PackageLocator {
name,
version,
tags,
}
})
}
}

View file

@ -1,21 +1,26 @@
use std::fmt::Display;
use std::io;
#[derive(Debug)]
pub enum BinError {
UnpackError(String),
IOError(io::Error),
Cancelled,
}
impl Display for BinError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BinError::UnpackError(e) => write!(f, "Unpack error: {}", e),
BinError::IOError(e) => write!(f, "IO error: {}", e),
BinError::Cancelled => write!(f, "Cancelled by user"),
}
}
}
#[derive(Debug)]
pub enum BuildError {
InvalidManifest,
InvalidManifest(String),
}
impl Display for BuildError {
@ -30,17 +35,15 @@ impl Display for BuildError {
pub enum InstallError {
BuildError(BuildError),
BinError(BinError),
InvalidManifest,
Generic,
InvalidManifest(String),
}
impl ToString for InstallError {
fn to_string(&self) -> String {
match self {
InstallError::BuildError(e) => format!("Build error: \n{}", e),
InstallError::BinError(e) => format!("Bin error: \n{}", e),
InstallError::InvalidManifest => "Invalid manifest".to_string(),
InstallError::Generic => "Install error".to_string(),
InstallError::BuildError(e) => format!("{}", e),
InstallError::BinError(e) => format!("{}", e),
InstallError::InvalidManifest(s) => format!("{}", s),
}
}
}

View file

@ -1,13 +1,17 @@
use crate::tmpfs::TempDir;
use std::io;
use std::fs::remove_dir_all;
use std::path::Path;
use log::{debug, trace};
use errors::{BinError, BuildError, InstallError};
use log::{debug, error, info, trace};
use manifest::Manifest;
use pkgfile::PKGFile;
use std::process::exit;
use crate::CONFIG;
use crate::package::identifier::{PackageIdentifier, PackageLocator};
use crate::package::Package;
use crate::tmpfs::TempDir;
use crate::types::fetch::TryFetch;
use crate::util::fs::copy_recursively;
pub mod errors;
@ -37,11 +41,14 @@ impl PackageInstaller {
if !self.pkgfile.has_data() {
return Err(BinError::UnpackError("package has no data".to_string()));
}
if std::path::Path::new(&path).exists() {
return Err(BinError::UnpackError(format!(
"path already exists: {}",
path
)));
let path = std::path::Path::new(&path);
if path.exists() {
trace!("cache already exists..");
debug!("removing cache dir...");
match remove_dir_all(path) {
Ok(_) => debug!("removed cache directory"),
Err(e) => return Err(BinError::UnpackError(format!("unable to remove directory ({}): {}", path.to_str().unwrap_or("unknown"), e.to_string())))
}
}
tar::Archive::new(self.pkgfile.data.as_slice())
.unpack(&path)
@ -52,39 +59,32 @@ impl PackageInstaller {
let mut tmpdir = TempDir::default();
tmpdir.push(&self.manifest.package.name);
trace!("extracting package into: {}", tmpdir.to_string());
match self.extract_to(tmpdir.to_string()) {
Ok(_) => {}
Err(e) => return Err(e),
if let Err(e) = self.extract_to(tmpdir.to_string()) {
return Err(e);
}
debug!("extracted package in: {}", tmpdir.to_string());
match self.apply_overlay() {
Ok(_) => Ok(()),
Err(e) => Err(BinError::IOError(e))
}
}
fn apply_overlay(&self) -> Result<(), io::Error> {
let mut tmpdir = TempDir::default();
tmpdir.push(&self.manifest.package.name);
tmpdir.push(&self.manifest.bin.clone().unwrap().root);
if let Err(e) = copy_recursively(&tmpdir.path(), Path::new("/")) {
return Err(e);
}
Ok(())
}
fn build(&self) -> Result<(), BuildError> {
if let None = self.manifest.build.clone() {
return Err(BuildError::InvalidManifest);
return Err(BuildError::InvalidManifest(String::from("No build manifest")));
}
let build_manifest = self.manifest.build.clone().unwrap();
// TODO: Check dependencies
for pkg_tuple in build_manifest.dependencies {
let mut pkg = Package::fetch(
PackageIdentifier::PackageLocator(
PackageLocator::from(pkg_tuple)
)
).expect("no pkg");
if !pkg.is_installed() {
match pkg.install(CONFIG.with(|c| {
c.build_by_default
})) {
Ok(_) => { info!("Installed dependency: \"{}\"", pkg.manifest().package.name) },
Err(_) => {
error!("Could not install dependency: \"{}\"", pkg.identifier);
exit(1);
}
}
}
}
Ok(())
}
@ -95,7 +95,7 @@ impl PackageInstaller {
if let None = self.manifest.bin {
self.install_type = InstallType::Build;
if let None = self.manifest.build {
return Err(InstallError::InvalidManifest);
return Err(InstallError::InvalidManifest(String::from("no bin or build manifest")));
}
}
}
@ -103,17 +103,23 @@ impl PackageInstaller {
if let None = self.manifest.build {
self.install_type = InstallType::Bin;
if let None = self.manifest.bin {
return Err(InstallError::InvalidManifest);
return Err(InstallError::InvalidManifest(String::from("no build or bin manifest")));
}
}
}
}
match self.install_type {
let r = match self.install_type {
InstallType::Bin => self.bin()
.map_err(|e| InstallError::BinError(e)),
InstallType::Build => self.build()
.map_err(|e| InstallError::BuildError(e)),
}
};
if let Err(e) = r {
return Err(e);
}
Ok(())
}
}

View file

@ -1,19 +1,31 @@
use log::{info, trace};
use std::fmt::Display;
use std::path::Path;
use std::process::exit;
use log::{debug, error, info, trace};
use reqwest::blocking::get;
use manifest::build::Build;
use pkgfile::PKGFile;
use crate::package::identifier::{PackageIdentifier, PackageLocator};
use crate::types::fetch::TryFetch;
pub mod fetch;
pub mod identifier;
pub mod installer;
pub mod queue;
#[derive(Eq, PartialEq)]
pub struct Package {
pub identifier: identifier::PackageIdentifier,
pub pkgfile: pkgfile::PKGFile,
pub identifier: PackageIdentifier,
pub pkgfile: PKGFile,
is_installed: bool,
is_indexed: bool,
}
impl Package {
/// Create a new package from a package identifier and a package file.
pub fn new(identifier: identifier::PackageIdentifier, pkgfile: pkgfile::PKGFile) -> Package {
pub fn new(identifier: PackageIdentifier, pkgfile: PKGFile) -> Package {
Package {
identifier,
pkgfile,
@ -22,34 +34,60 @@ impl Package {
}
}
/// Fetch a package from a package identifier.
pub fn fetch(
package_identifier: identifier::PackageIdentifier,
) -> Result<Package, fetch::FetchError> {
match &package_identifier {
identifier::PackageIdentifier::Path(path) => {
trace!("fetching package from path: {}", path);
let pkgfile = fetch::fetch_by_path(path).unwrap();
Ok(Package::new(package_identifier, pkgfile))
}
identifier::PackageIdentifier::URI(url) => {
trace!("fetching package from uri: {}", url);
let pkgfile = fetch::fetch_by_uri(url).unwrap();
Ok(Package::new(package_identifier, pkgfile))
}
identifier::PackageIdentifier::PackageLocator(locator) => {
trace!("fetching package from locator: {}", locator);
let pkgfile = fetch::fetch_by_package_locator(locator.clone()).unwrap();
Ok(Package::new(package_identifier, pkgfile))
}
}
}
/// Get the package manifest.
pub fn manifest(&self) -> manifest::Manifest {
manifest::Manifest::try_from(self.pkgfile.manifest.clone()).unwrap()
}
/// Get package dependencies
pub fn dependencies(&self) -> Vec<Package> {
let mut dependencies = vec![];
for dependency in self.manifest().dependencies {
let pkglocate = if let Ok(pl) = PackageLocator::try_from(dependency.clone()) {
trace!("parsed pl successfully...");
pl
} else {
error!("Could not parse package locator: {:?} in dependencies.", &dependency);
exit(1);
};
let pkg = match Package::try_fetch(PackageIdentifier::PackageLocator(pkglocate)) {
Ok(p) => p,
Err(e) => {
error!("Could not fetch dependency: {}", e);
exit(1);
}
};
dependencies.push(pkg);
}
dependencies
}
pub fn build_dependencies(&self) -> Vec<Package> {
let mut dependencies = vec![];
for dependency in self
.manifest()
.build
.unwrap_or(Build::default())
.dependencies {
let pkglocate = if let Ok(pl) = PackageLocator::try_from(dependency.clone()) {
trace!("parsed pl successfully...");
pl
} else {
error!("Could not parse package locator: {:?} in dependencies.", &dependency);
exit(1);
};
let pkg = match Package::try_fetch(PackageIdentifier::PackageLocator(pkglocate)) {
Ok(p) => p,
Err(e) => {
error!("Could not fetch dependency: {}", e);
exit(1);
}
};
dependencies.push(pkg);
}
dependencies
}
/// Install the package.
pub fn install(&mut self, build: bool) -> Result<(), installer::errors::InstallError> {
let manifest = self.manifest();
@ -96,3 +134,57 @@ impl Package {
unimplemented!();
}
}
#[derive(Debug)]
pub enum FetchError {
HTTPError(reqwest::Error),
IOError(std::io::Error),
ParseError,
}
impl Display for FetchError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
FetchError::HTTPError(e) => write!(f, "HTTP Error: {}", e),
FetchError::IOError(e) => write!(f, "IO Error: {}", e),
FetchError::ParseError => write!(f, "Parse Error"),
}
}
}
impl TryFetch<PackageIdentifier> for Package {
type Error = FetchError;
/// Fetch a package from a package identifier.
fn try_fetch(query: PackageIdentifier) -> Result<Package, Self::Error> {
trace!("Fetching: {query:#?}");
let pkgfile = match &query {
PackageIdentifier::Path(s) => match PKGFile::try_from(Path::new(&s)) {
Ok(p) => Ok(p),
Err(e) => Err(FetchError::ParseError)
},
PackageIdentifier::URI(s) => {
let mut bytes = Vec::new();
debug!("sending GET request...");
match get::<String>(s.into()) {
Ok(response) => {
debug!("Got response!");
if let Ok(b) = response.bytes() {
bytes.extend(b);
}
}
Err(e) => return Err(FetchError::HTTPError(e)),
};
// parse bytes as PKGFile
match PKGFile::try_from(bytes) {
Ok(pkgfile) => Ok(pkgfile),
Err(_e) => Err(FetchError::ParseError),
}
}
PackageIdentifier::PackageLocator(l) => unimplemented!()
};
pkgfile
.map(|p| Package::new(query, p))
}
}

51
pkgr/src/package/queue.rs Normal file
View file

@ -0,0 +1,51 @@
use std::fmt::{Display, Formatter};
use log::trace;
use crate::package::Package;
pub struct PackageQueue {
packages: Vec<Package>,
}
impl PackageQueue {
pub fn new() -> Self {
PackageQueue {
packages: vec![]
}
}
pub fn add_package(&mut self, package: Package, build: bool) {
let dependencies = package.dependencies();
for dependency in dependencies {
trace!("Checking package: {}", &dependency.identifier);
if self.packages.contains(&dependency) {
continue;
}
trace!("Adding package: {}", &dependency.identifier);
self.packages.push(dependency);
}
if !self.packages.contains(&package) {
self.packages.push(package);
}
}
pub fn install(&mut self, build: bool) {
self.packages
.iter_mut()
.for_each(|pkg| {
pkg.install(build).expect("TODO: panic message");
});
}
}
impl Display for PackageQueue {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for pkg in &self.packages {
if let Err(e) = write!(f, "{}", pkg.identifier) {
return Err(e);
}
}
write!(f, "")
}
}

View file

@ -1,7 +1,7 @@
use std::io::{BufRead, BufReader};
use std::process::Command;
use log::{info, trace};
use log::{info, trace};
pub struct Process {
pub command: Vec<String>,

View file

@ -0,0 +1,18 @@
use std::io;
use std::path::Path;
use serde::{Deserialize, Serialize};
use url::Url;
use pkgfile::PKGFile;
use crate::api::Query;
use crate::CONFIG;
use crate::package::Package;
use crate::repo::index::RepoIndex;
/// This struct solely exists for indexing and has no real functionality.
#[derive(Serialize, Deserialize)]
pub struct IndexPackage {
name: String,
versions: Vec<u32>,
tags: Vec<String>,
uri: Url
}

View file

@ -0,0 +1,58 @@
use std::collections::HashMap;
use std::io;
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::api::Request;
use crate::CONFIG;
use crate::repo::index::index_package::IndexPackage;
use crate::repo::Repo;
use crate::types::fetch::{Fetch, TryFetch};
use crate::util::create_uuid;
pub mod index_package;
#[derive(Serialize, Deserialize)]
pub struct RepoIndex {
origin_repo: String,
packages: HashMap<String, IndexPackage>
}
impl RepoIndex {
// /// Fetch existing index or create a new one.
// pub fn from_repo(repo: Repo) -> Self {
//
// }
//
// /// Create new index.
// pub fn create_with_repo(repo: Repo) -> Self {
// }
/// Get repo
pub fn get_repo(&self) -> io::Result<Repo> {
Ok(Repo::from_name(&self.origin_repo)?)
}
pub fn from_path(path: &str) -> Result<RepoIndex, String> {
match std::fs::read_to_string(path) {
Ok(s) => match toml::from_str(&s) {
Ok(c) => Ok(c),
Err(e) => Err(format!("failed to parse config: {}", e)),
},
Err(e) => Err(format!("failed to read config: {}", e)),
}
}
}
impl TryFetch<Repo> for RepoIndex {
type Error = ();
/// Fetch
fn try_fetch(query: Repo) -> Result<Self, Self::Error> {
let path = CONFIG.with(|c| c.storage.index_dir.clone()) + query.uri.as_str() + ".toml";
if Path::new(path.as_str()).exists() {
RepoIndex::from_path(path.as_str())
.map_err(|_| ())
} else {
Err(())
}
}
}

42
pkgr/src/repo/mod.rs Normal file
View file

@ -0,0 +1,42 @@
use std::io;
use std::io::{Error, ErrorKind};
use std::path::Path;
use url::Url;
use crate::CONFIG;
use crate::config::repos::RepoFile;
/// Indexed repos
pub mod index;
pub struct Repo {
name: String,
uri: Url,
}
impl Repo {
pub fn from_name(name: &String) -> io::Result<Repo> {
let r = RepoFile::from_path(CONFIG.with(|c| c.storage.repo_file.clone()).as_str())
.map_err(|e| Error::new(ErrorKind::Other, e))?;
let r = r
.repos
.get(name)
.ok_or(Error::new(ErrorKind::InvalidData, "Could not get repo"))?;
Ok(Repo {
name: r.name.clone(),
uri: r.uri.clone()
})
}
pub fn get_name(&self) -> String {
self.name.clone()
}
pub fn get_uri(&self) -> Url {
self.uri.clone()
}
/// Fetch indexed repo
pub fn get_index(&self) {
}
}

View file

@ -1,20 +1,32 @@
use std::path::PathBuf;
use crate::CONFIG;
#[derive(Clone)]
pub struct TempDir {
path: PathBuf,
}
impl TempDir {
pub fn new(path: PathBuf) -> TempDir {
let pbs: String = path.to_str().unwrap().into();
let path = expanduser::expanduser(&pbs).unwrap();
if !path.exists() {
std::fs::create_dir_all(&path).unwrap();
}
TempDir { path }
}
pub fn path(&self) -> PathBuf {
self.path.clone()
}
pub fn push<S: Into<String>>(&mut self, path: S) {
self.path.push(path.into());
let mut path_str = path.into();
if path_str.starts_with('/') {
path_str = path_str[1..].to_string();
}
self.path.push(path_str);
}
}

10
pkgr/src/types/fetch.rs Normal file
View file

@ -0,0 +1,10 @@
/// Get a result from an external source
pub trait Fetch<Q, R = Self> {
fn fetch(query: Q) -> R;
}
/// Try to get a result from an external source
pub trait TryFetch<Q, R = Self> {
type Error;
fn try_fetch(query: Q) -> Result<R, Self::Error>;
}

1
pkgr/src/types/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod fetch;

41
pkgr/src/util/fs.rs Normal file
View file

@ -0,0 +1,41 @@
use std::{fs, io};
use std::fs::DirEntry;
use std::path::Path;
use log::trace;
pub fn visit_dirs(dir: &Path, cb: &dyn Fn(&DirEntry)) -> std::io::Result<()> {
if dir.is_dir() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
visit_dirs(&path, cb)?;
} else {
cb(&entry);
}
}
}
Ok(())
}
pub fn copy_recursively(source: &Path, target: &Path) -> io::Result<()> {
if source.is_file() {
trace!("source: {:?}, target: {:?}", source, target);
fs::copy(source, target)?;
} else if source.is_dir() {
if !target.exists() {
fs::create_dir(target)?;
}
for entry in fs::read_dir(source)? {
let entry = entry?;
let file_name = entry.file_name();
let source_path = entry.path();
let target_path = target.join(&file_name);
copy_recursively(&source_path, &target_path)?;
}
}
Ok(())
}

9
pkgr/src/util/mod.rs Normal file
View file

@ -0,0 +1,9 @@
pub mod prompts;
/// Helpers for fs
pub mod fs;
/// Create a UUID
pub fn create_uuid() -> String {
// TODO
String::from("rand")
}

33
pkgr/src/util/prompts.rs Normal file
View file

@ -0,0 +1,33 @@
use std::io::Write;
use log::trace;
pub fn is_noninteractive() -> bool {
if let Ok(v) = std::env::var("PKGR_NON_INTERACTIVE") {
trace!("PKGR_NON_INTERACTIVE={}", v);
match v.as_str() {
"1" => true,
"true" => true,
_ => false
}
} else { false }
}
pub fn prompt_bool<S: Into<String>>(prompt: S, default: bool) -> bool {
if is_noninteractive() { return default; }
print!("{} [{}]: ", prompt.into(), if default { "Y/n" } else { "y/N" });
match std::io::stdout().flush() {
Ok(_) => (),
_ => println!()
};
let mut input = String::new();
std::io::stdin().read_line(&mut input).expect("Failed to read input.");
let answer = input.trim().to_lowercase();
if answer.is_empty() {
default
} else {
answer == "y" || answer == "yes"
}
}