diff --git a/Cargo.toml b/Cargo.toml index 9398ff4..d1c1cdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,5 +11,6 @@ description = "A fast and efficient aur helper." clap = "2.34.0" regex = "1.5.4" runas = "0.2.1" +rusqlite = "0.26.3" reqwest = { version = "0.11.7", default-features = false, features = [ "blocking", "json", "default-tls" ] } serde = { version = "1.0.90", default-features = false, features = [ "derive", "serde_derive" ] } \ No newline at end of file diff --git a/README.md b/README.md index ae91791..21cecc4 100644 --- a/README.md +++ b/README.md @@ -15,18 +15,13 @@ Made for Crystal, compatible with any Arch-based Linux distribution.

![](screenshot.png) ## Basic usage -| Action | FreeBSD pkg-style alias | Pacman-style flag(s) | -| ------ | ------ | ------ | -| Install a package | ame ins | ame -S | -| Remove a package | ame rm | ame -R | -| Remove a package with its dependencies | ame purge | ame -Rs | -| Update repository | ame upd | ame -Sy | -| Upgrade a package | ame upg | ame -Syu | -| Search for a package in general | ame sea | ame -Ss | -| Search for a package in the official arch repos | ame repsea | ame -Sr | -| Search for a package in aur | ame aursea | ame -Sa | - -You can also use any pacman flag! +| Action | FreeBSD pkg-style alias | Pacman-style flag(s) | +|----------------------|-------------------------|----------------------| +| Install a package | ame ins/install | ame -S | +| Remove a package | ame rm/remove | ame -R/-Rs | +| Upgrade a package | ame upg/upgrade | ame -Syu | +| Search for a package | ame sea | ame -Ss | + ## How to build: (Install cargo) @@ -47,5 +42,4 @@ Clean all build directories: echo "AME_UWU=YES" >> ~/.zshrc # for zsh echo "AME_UWU=YES" >> ~/.bashrc # for bash set -Ux AME_UWU YES # for fish -``` -self explanatory +``` \ No newline at end of file diff --git a/src/database/add.rs b/src/database/add.rs index f3a4676..00920db 100644 --- a/src/database/add.rs +++ b/src/database/add.rs @@ -1,7 +1,23 @@ +use crate::internal::rpc::Package; use crate::Options; +use rusqlite::Connection; +use std::env; +use std::path::Path; -#[allow(dead_code)] -#[allow(unused_variables)] -pub fn add(a: String, options: Options) { - // TODO: the actual database code lmao +pub fn add(pkg: Package, options: Options) { + let conn = Connection::open(Path::new(&format!( + "{}/.local/share/ame/db.sqlite", + env::var("HOME").unwrap() + ))) + .expect("Couldn't connect to database."); + + if options.verbosity >= 1 { + eprintln!("Adding package {} to database", pkg.name); + } + + conn.execute("INSERT OR REPLACE INTO packages (name, version, description, depends, make_depends) VALUES (?1, ?2, ?3, ?4, ?5)", + [&pkg.name, &pkg.version, &pkg.description.unwrap_or_else(|| "No description found.".parse().unwrap()), &pkg.depends.join(" "), &pkg.make_depends.join(" ")] + ).unwrap_or_else(|e| { + panic!("Failed adding package {} to the database: {}", pkg.name, e); + }); } diff --git a/src/database/initialise.rs b/src/database/initialise.rs new file mode 100644 index 0000000..73a0e79 --- /dev/null +++ b/src/database/initialise.rs @@ -0,0 +1,35 @@ +use crate::Options; +use rusqlite::Connection; +use std::env; +use std::path::Path; + +pub fn init(options: Options) { + let path = format!("{}/.local/share/ame/db.sqlite", env::var("HOME").unwrap()); + let dbpath = Path::new(&path); + let verbosity = options.verbosity; + + if verbosity >= 1 { + eprintln!("Creating database at {}", &path); + } + + let conn = + Connection::open(dbpath).expect("Couldn't create database at ~/.local/share/ame/db.sqlite"); + + if verbosity >= 1 { + eprintln!("Populating database with table") + } + + conn.execute( + "CREATE TABLE packages ( + name TEXT PRIMARY KEY NOT NULL, + version TEXT NOT NULL, + description TEXT, + depends BLOB, + make_depends BLOB + )", + [], + ) + .unwrap_or_else(|e| { + panic!("Couldn't initialise database: {}", e); + }); +} diff --git a/src/database/mod.rs b/src/database/mod.rs index 8923ff6..f37d309 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,8 +1,23 @@ +use crate::internal::rpc::Package; use crate::Options; mod add; +mod initialise; +mod query; +mod remove; -#[allow(dead_code)] -pub fn add(a: String, options: Options) { +pub fn add(a: Package, options: Options) { add::add(a, options); } + +pub fn remove(a: &str, options: Options) { + remove::remove(a, options); +} + +pub fn init(options: Options) { + initialise::init(options); +} + +pub fn query(a: &str, options: Options) -> Vec { + query::query(a, options) +} diff --git a/src/database/query.rs b/src/database/query.rs new file mode 100644 index 0000000..b27416a --- /dev/null +++ b/src/database/query.rs @@ -0,0 +1,64 @@ +use crate::internal::rpc::Package; +use crate::Options; +use rusqlite::Connection; +use std::env; +use std::path::Path; + +pub fn query(a: &str, options: Options) -> Vec { + let verbosity = options.verbosity; + + if verbosity >= 1 { + eprintln!("Connecting to database"); + } + + let conn = Connection::open(Path::new(&format!( + "{}/.local/share/ame/db.sqlite", + env::var("HOME").unwrap() + ))) + .expect("Couldn't connect to database."); + + if verbosity >= 1 { + eprintln!("Querying database for input") + } + + let mut rs = conn + .prepare("SELECT name, version, description, depends, make_depends FROM packages WHERE name LIKE \"?\"") + .unwrap(); + let packages_iter = rs + .query_map([a], |row| { + Ok(Package { + name: row.get(0).unwrap(), + version: row.get(1).unwrap(), + description: row.get(2).unwrap(), + depends: row + .get::(3) + .unwrap() + .split(' ') + .map(|s| s.to_string()) + .collect::>(), + make_depends: row + .get::(4) + .unwrap() + .split(' ') + .map(|s| s.to_string()) + .collect::>(), + }) + }) + .expect("Couldn't query database for packages."); + + if verbosity >= 1 { + eprintln!("Retrieved results"); + } + + let mut results: Vec = vec![]; + + for package in packages_iter { + results.push(package.unwrap()); + } + + if verbosity >= 1 { + eprintln!("Collected results") + } + + results +} diff --git a/src/database/remove.rs b/src/database/remove.rs new file mode 100644 index 0000000..44c8a00 --- /dev/null +++ b/src/database/remove.rs @@ -0,0 +1,28 @@ +use crate::Options; +use rusqlite::Connection; +use std::env; +use std::path::Path; + +pub fn remove(pkg: &str, options: Options) { + let conn = Connection::open(Path::new(&format!( + "{}/.local/share/ame/db.sqlite", + env::var("HOME").unwrap() + ))) + .expect("Couldn't connect to database."); + + let verbosity = options.verbosity; + + if verbosity >= 1 { + eprintln!("Removing package {} from database", pkg); + } + + conn.execute( + "DELETE FROM packages + WHERE EXISTS + (SELECT * + FROM packages + WHERE name = ?);", + [pkg], + ) + .expect("Couldn't delete package from database."); +} diff --git a/src/internal/initialise.rs b/src/internal/initialise.rs index 3620092..8e6d355 100644 --- a/src/internal/initialise.rs +++ b/src/internal/initialise.rs @@ -20,6 +20,10 @@ pub fn init(options: Options) { } } + if !Path::new(&format!("{}/.local/share/ame/db.sqlite", homedir)).exists() { + crate::database::init(options); + } + if !Path::new(&format!("{}/.cache/ame/", homedir)).exists() { let r = std::fs::create_dir_all(format!("{}/.cache/ame", homedir)); match r { diff --git a/src/internal/rpc.rs b/src/internal/rpc.rs index 9972e76..01e1aa9 100644 --- a/src/internal/rpc.rs +++ b/src/internal/rpc.rs @@ -8,11 +8,11 @@ pub struct Package { pub version: String, #[serde(rename = "Description")] pub description: Option, - #[serde(default)] #[serde(rename = "Depends")] - pub depends: Vec, #[serde(default)] + pub depends: Vec, #[serde(rename = "MakeDepends")] + #[serde(default)] pub make_depends: Vec, } @@ -22,26 +22,32 @@ pub struct SearchResults { pub results: Vec, } +#[derive(Clone)] pub struct InfoResults { pub found: bool, - pub package: Option + pub package: Option, } +pub const URL: &str = "https://aur.archlinux.org/"; + pub fn rpcinfo(pkg: String) -> InfoResults { let res = reqwest::blocking::get(&format!( "https://aur.archlinux.org/rpc/?v=5&type=info&arg={}", pkg - )).unwrap().json::().unwrap(); + )) + .unwrap() + .json::() + .unwrap(); if res.results.is_empty() { InfoResults { found: false, - package: None + package: None, } } else { InfoResults { found: true, - package: Some(res.results[0].clone()) + package: Some(res.results[0].clone()), } } } @@ -50,5 +56,8 @@ pub fn rpcsearch(pkg: String) -> SearchResults { reqwest::blocking::get(&format!( "https://aur.archlinux.org/rpc/?v=5&type=search&arg={}", pkg - )).unwrap().json().unwrap() + )) + .unwrap() + .json() + .unwrap() } diff --git a/src/internal/sort.rs b/src/internal/sort.rs index d8050d8..a1fb26d 100644 --- a/src/internal/sort.rs +++ b/src/internal/sort.rs @@ -25,7 +25,6 @@ pub fn sort(input: &[String], options: Options) -> structs::Sorted { } for b in a { - #[cfg(unix)] let rs = Command::new("pacman") .arg("-Ss") .arg(format!("^{}$", &b)) @@ -33,16 +32,6 @@ pub fn sort(input: &[String], options: Options) -> structs::Sorted { .status() .expect("Something has gone wrong."); - #[cfg(windows)] - let rs = Command::new("pwsh") - .arg("-nop") - .arg("-c") - .arg("exit") - .arg("1") - .stdout(Stdio::null()) - .status() - .expect("Something has gone wrong."); - if rpc::rpcinfo(b.to_string()).found { if verbosity >= 1 { eprintln!("{} found in AUR.", b); diff --git a/src/internal/structs.rs b/src/internal/structs.rs index 39c2a3e..7fd8007 100644 --- a/src/internal/structs.rs +++ b/src/internal/structs.rs @@ -19,15 +19,5 @@ impl Sorted { pub struct Options { pub verbosity: i32, pub noconfirm: bool, -} - -impl Options { - #[allow(dead_code)] - pub fn new(verbosity: i32, noconfirm: bool) -> Self { - let a: Options = Options { - verbosity, - noconfirm, - }; - a - } + pub asdeps: bool, } diff --git a/src/main.rs b/src/main.rs index 936496d..baa9e8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,6 +72,11 @@ fn main() { .index(1), ), ) + .subcommand( + SubCommand::with_name("upgrade") + .about("Upgrades locally installed packages to their latest versions") + .aliases(&["-Syu", "upg"]), + ) .settings(&[ AppSettings::GlobalVersion, AppSettings::VersionlessSubcommands, @@ -85,6 +90,7 @@ fn main() { let options = Options { verbosity, noconfirm, + asdeps: false, }; init(options); @@ -102,8 +108,12 @@ fn main() { if let true = matches.is_present("install") { let sorted = sort(&packages, options); - operations::install(sorted.repo, options); - operations::aur_install(sorted.aur, options); + if !sorted.repo.is_empty() { + operations::install(sorted.repo, options); + } + if !sorted.aur.is_empty() { + operations::aur_install(sorted.aur, options); + } if !sorted.nf.is_empty() { eprintln!( "Couldn't find packages: {} in repos or the AUR.", @@ -118,6 +128,11 @@ fn main() { exit(0); } + if let true = matches.is_present("upgrade") { + operations::upgrade(options); + exit(0); + } + if let true = matches.is_present("search") { if matches .subcommand_matches("search") diff --git a/src/operations/aur_install.rs b/src/operations/aur_install.rs index 4f82c10..5ede818 100644 --- a/src/operations/aur_install.rs +++ b/src/operations/aur_install.rs @@ -1,7 +1,16 @@ +use crate::internal::rpc::rpcinfo; use crate::Options; +use std::env; +use std::env::set_current_dir; +use std::fs::remove_dir_all; +use std::path::Path; +use std::process::Command; pub fn aur_install(a: Vec, options: Options) { + let url = crate::internal::rpc::URL; + let cachedir = format!("{}/.cache/ame/", env::var("HOME").unwrap()); let verbosity = options.verbosity; + let noconfirm = options.noconfirm; match verbosity { 0 => {} 1 => { @@ -10,9 +19,102 @@ pub fn aur_install(a: Vec, options: Options) { } _ => { eprintln!("Installing from AUR:"); - for b in a { + for b in &a { eprintln!("{:?}", b); } } } + + for package in a { + let rpcres = rpcinfo(package); + + if !rpcres.found { + break; + } + + let pkg = &rpcres.package.as_ref().unwrap().name; + + if verbosity >= 1 { + eprintln!("Cloning {} into cachedir", pkg); + } + + // cloning + set_current_dir(Path::new(&cachedir)).unwrap(); + Command::new("git") + .arg("clone") + .arg(format!("{}/{}", url, pkg)) + .status() + .expect("Something has gone wrong."); + + if verbosity >= 1 { + eprintln!( + "Cloned {} into cachedir, moving on to resolving dependencies", + pkg + ); + eprintln!( + "Raw dependencies for package {} are:\n{:?}", + pkg, + rpcres.package.as_ref().unwrap().depends.join(", ") + ) + } + + // dep sorting + let sorted = crate::internal::sort(&rpcres.package.as_ref().unwrap().depends, options); + + if verbosity >= 1 { + eprintln!("Sorted depndencies for {} are:\n{:?}", pkg, &sorted) + } + + let newopts = Options { + verbosity, + noconfirm, + asdeps: true, + }; + + if !sorted.nf.is_empty() { + panic!( + "Could not find dependencies {} for package {}. Aborting", + sorted.nf.join(", "), + pkg + ); + } + + if !noconfirm { + let editor = env::var("EDITOR").unwrap_or_else(|_| "nano".parse().unwrap()); + + Command::new(editor) + .arg(format!("{}/PKGBUILD", pkg)) + .spawn() + .unwrap() + .wait() + .unwrap(); + } + + // dep installing + crate::operations::install(sorted.repo, newopts); + crate::operations::aur_install(sorted.aur, newopts); + + let mut makepkg_args = vec!["-rsic", "--needed"]; + if options.asdeps { + makepkg_args.push("--asdeps") + } + if options.noconfirm { + makepkg_args.push("--noconfirm") + } + + // package building and installing + set_current_dir(format!("{}/{}", cachedir, pkg)).unwrap(); + Command::new("makepkg") + .args(&makepkg_args) + .status() + .expect("Something has gone wrong."); + + if makepkg_args.contains(&"--asdeps") { + set_current_dir(&cachedir).unwrap(); + remove_dir_all(format!("{}/{}", cachedir, pkg)).unwrap(); + } + + // pushes package to database + crate::database::add(rpcres.package.unwrap(), options); + } } diff --git a/src/operations/install.rs b/src/operations/install.rs index 115b8a3..45796d7 100644 --- a/src/operations/install.rs +++ b/src/operations/install.rs @@ -5,6 +5,9 @@ pub fn install(mut a: Vec, options: Options) { if options.noconfirm { a.push("--noconfirm".to_string()); } + if options.asdeps { + a.push("--asdeps".to_string()); + } let verbosity = options.verbosity; match verbosity { 0 => {} @@ -22,6 +25,7 @@ pub fn install(mut a: Vec, options: Options) { let r = runas::Command::new("pacman") .arg("-S") + .arg("--needed") .args(&a) .status() .expect("Something has gone wrong."); diff --git a/src/operations/mod.rs b/src/operations/mod.rs index 7021e82..cb6f241 100644 --- a/src/operations/mod.rs +++ b/src/operations/mod.rs @@ -4,6 +4,7 @@ mod aur_install; mod install; mod search; mod uninstall; +mod upgrade; pub fn install(a: Vec, options: Options) { install::install(a, options); @@ -24,3 +25,7 @@ pub fn aur_install(a: Vec, options: Options) { pub fn aur_search(a: &str, options: Options) { search::aur_search(a, options); } + +pub fn upgrade(options: Options) { + upgrade::upgrade(options); +} diff --git a/src/operations/uninstall.rs b/src/operations/uninstall.rs index 7fa4864..f95a49e 100644 --- a/src/operations/uninstall.rs +++ b/src/operations/uninstall.rs @@ -1,4 +1,6 @@ use crate::Options; +use std::path::Path; +use std::{env, fs}; pub fn uninstall(mut a: Vec, options: Options) { let b = a.clone(); @@ -31,4 +33,19 @@ pub fn uninstall(mut a: Vec, options: Options) { eprintln!("Uninstalling packages: {:?} exited with code {}.", &b, x) } } + + for b in a { + crate::database::remove(&b, options); + if Path::new(&format!("{}/.cache/ame/{}", env::var("HOME").unwrap(), b)).exists() { + if verbosity >= 1 { + eprintln!("Old cache directory found, deleting") + } + fs::remove_dir_all(Path::new(&format!( + "{}/.cache/ame/{}", + env::var("HOME").unwrap(), + b + ))) + .unwrap(); + } + } } diff --git a/src/operations/upgrade.rs b/src/operations/upgrade.rs new file mode 100644 index 0000000..c287157 --- /dev/null +++ b/src/operations/upgrade.rs @@ -0,0 +1,40 @@ +use crate::internal::rpc::rpcinfo; +use crate::operations::aur_install::aur_install; +use crate::Options; +use runas::Command; + +pub fn upgrade(options: Options) { + let verbosity = options.verbosity; + let noconfirm = options.noconfirm; + + let mut pacman_args = vec!["-Syu"]; + if noconfirm { + pacman_args.push("--noconfirm"); + } + + if verbosity >= 1 { + eprintln!("Upgrading repo packages") + } + + Command::new("pacman") + .args(&pacman_args) + .status() + .expect("Something has gone wrong."); + + if verbosity >= 1 { + eprintln!("Upgrading AUR packages") + } + + let res = crate::database::query("\"%\"", options); + + let mut aur_upgrades = vec![]; + for r in res { + let re = r.clone(); + let ver = rpcinfo(r.name); + if ver.package.unwrap().version != r.version { + aur_upgrades.push(re.name); + } + } + + aur_install(aur_upgrades, options); +}