mirror of
https://github.com/TeFiLeDo/listload.git
synced 2024-11-23 20:56:17 +01:00
implement downloading
This commit is contained in:
parent
d10022fec8
commit
55c60fbb84
@ -12,6 +12,7 @@ clap = { version = "4.3.8", features = ["cargo", "derive", "env", "wrap_help"] }
|
|||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
downloader = { version = "0.2.7", default-features = false, features = ["rustls-tls"] }
|
downloader = { version = "0.2.7", default-features = false, features = ["rustls-tls"] }
|
||||||
indoc = "2.0.1"
|
indoc = "2.0.1"
|
||||||
|
rand = "0.8.5"
|
||||||
regex = "1.8.4"
|
regex = "1.8.4"
|
||||||
serde = { version = "1.0.164", features = ["derive"] }
|
serde = { version = "1.0.164", features = ["derive"] }
|
||||||
serde_json = "1.0.99"
|
serde_json = "1.0.99"
|
||||||
|
81
src/main.rs
81
src/main.rs
@ -1,6 +1,6 @@
|
|||||||
use std::{path::PathBuf, sync::OnceLock};
|
use std::{collections::HashMap, fs, path::PathBuf, sync::OnceLock};
|
||||||
|
|
||||||
use anyhow::{ensure, Context};
|
use anyhow::Context;
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use directories::{ProjectDirs, UserDirs};
|
use directories::{ProjectDirs, UserDirs};
|
||||||
@ -29,10 +29,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
.set(user_dirs)
|
.set(user_dirs)
|
||||||
.ok()
|
.ok()
|
||||||
.context("failed to initialize user directories")?;
|
.context("failed to initialize user directories")?;
|
||||||
PROJ_DIRS
|
let proj_dirs = PROJ_DIRS.get_or_init(|| proj_dirs);
|
||||||
.set(proj_dirs)
|
|
||||||
.ok()
|
|
||||||
.context("failed to initialize program directories")?;
|
|
||||||
|
|
||||||
if let CMD::License = cli.command {
|
if let CMD::License = cli.command {
|
||||||
println!("{}", include_str!("../LICENSE"));
|
println!("{}", include_str!("../LICENSE"));
|
||||||
@ -41,7 +38,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
// prepare for operation
|
// prepare for operation
|
||||||
let cfg = Config::read_from_default_file().context("failed to load config")?;
|
let cfg = Config::read_from_default_file().context("failed to load config")?;
|
||||||
let downloader = cfg.downloader().context("failed to create downloader")?;
|
let mut downloader = cfg.downloader().context("failed to create downloader")?;
|
||||||
let mut persistent =
|
let mut persistent =
|
||||||
PersistentState::read_from_default_file().context("failed to load persistent state")?;
|
PersistentState::read_from_default_file().context("failed to load persistent state")?;
|
||||||
|
|
||||||
@ -52,6 +49,71 @@ fn main() -> anyhow::Result<()> {
|
|||||||
CMD::PersistentState => {
|
CMD::PersistentState => {
|
||||||
println!("{persistent}");
|
println!("{persistent}");
|
||||||
}
|
}
|
||||||
|
CMD::Download { name } => {
|
||||||
|
let mut cache = proj_dirs.cache_dir().to_path_buf();
|
||||||
|
cache.push(&format!("{:0>16x}", rand::random::<u64>()));
|
||||||
|
fs::create_dir_all(&cache).context("failed to create cache dir")?;
|
||||||
|
|
||||||
|
let name = name
|
||||||
|
.as_ref()
|
||||||
|
.map(|n| n.as_str())
|
||||||
|
.or(persistent.list())
|
||||||
|
.context("no list specified or selected")?;
|
||||||
|
let list = TargetList::load(&name).context("failed to load list")?;
|
||||||
|
let mut downloads = list.downloads();
|
||||||
|
|
||||||
|
let mut mapping = HashMap::with_capacity(downloads.len());
|
||||||
|
let mut counter = 0;
|
||||||
|
for d in &mut downloads {
|
||||||
|
let mut cache_path = cache.clone();
|
||||||
|
cache_path.push(format!("{counter:0>16x}"));
|
||||||
|
let prev = std::mem::replace(&mut d.file_name, cache_path.clone());
|
||||||
|
mapping.insert(cache_path, prev);
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = downloader
|
||||||
|
.download(&downloads)
|
||||||
|
.context("all downloads failed")?;
|
||||||
|
|
||||||
|
for res in results {
|
||||||
|
if res.is_err() {
|
||||||
|
eprintln!("{:?}", res.context("download_failed").unwrap_err());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = res.unwrap();
|
||||||
|
let res = mapping
|
||||||
|
.remove(&res.file_name)
|
||||||
|
.context("target file name missing")
|
||||||
|
.map(|target| {
|
||||||
|
if target.is_absolute() {
|
||||||
|
target
|
||||||
|
} else {
|
||||||
|
let mut path = cfg.base_directory.clone();
|
||||||
|
path.push(target);
|
||||||
|
path
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(|target| {
|
||||||
|
fs::rename(&res.file_name, target).context("failed to move cached result")
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
eprintln!("{:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (leftover, _) in mapping {
|
||||||
|
if let Err(err) =
|
||||||
|
fs::remove_file(leftover).context("failed to delete leftover cache file")
|
||||||
|
{
|
||||||
|
eprintln!("{err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::remove_dir(cache).context("failed to delete cache directory")?;
|
||||||
|
}
|
||||||
CMD::List { cmd } => match cmd {
|
CMD::List { cmd } => match cmd {
|
||||||
ListCommand::Create {
|
ListCommand::Create {
|
||||||
name,
|
name,
|
||||||
@ -132,6 +194,11 @@ enum CMD {
|
|||||||
License,
|
License,
|
||||||
/// Print the current persistent state.
|
/// Print the current persistent state.
|
||||||
PersistentState,
|
PersistentState,
|
||||||
|
/// Download a target list.
|
||||||
|
Download {
|
||||||
|
/// The name of the target list. Defaults to the selected list.
|
||||||
|
name: Option<String>,
|
||||||
|
},
|
||||||
/// Target list operations.
|
/// Target list operations.
|
||||||
List {
|
List {
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
|
@ -9,7 +9,7 @@ use anyhow::{bail, ensure, Context};
|
|||||||
use indoc::writedoc;
|
use indoc::writedoc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{target_list::TargetList, PROJ_DIRS};
|
use crate::PROJ_DIRS;
|
||||||
|
|
||||||
const PERSISTENT_FILE: &str = "persistent_state.json";
|
const PERSISTENT_FILE: &str = "persistent_state.json";
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ impl Display for Target {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Download> for Target {
|
impl Into<Download> for &Target {
|
||||||
fn into(self) -> Download {
|
fn into(self) -> Download {
|
||||||
match self.urls.len() {
|
match self.urls.len() {
|
||||||
0 => panic!("target without url"),
|
0 => panic!("target without url"),
|
||||||
|
@ -4,7 +4,8 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{ensure, Context, Result};
|
use anyhow::{ensure, Context};
|
||||||
|
use downloader::Download;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -28,6 +29,10 @@ impl TargetList {
|
|||||||
pub fn len_targets(&self) -> usize {
|
pub fn len_targets(&self) -> usize {
|
||||||
self.inner.targets.len()
|
self.inner.targets.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn downloads(&self) -> Vec<Download> {
|
||||||
|
self.inner.targets.iter().map(|t| t.into()).collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TargetList {
|
impl TargetList {
|
||||||
@ -54,8 +59,6 @@ impl TargetList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(name: &str, comment: Option<&str>) -> anyhow::Result<Self> {
|
pub fn new(name: &str, comment: Option<&str>) -> anyhow::Result<Self> {
|
||||||
let dirs = PROJ_DIRS.get().expect("directories not initialized");
|
|
||||||
|
|
||||||
let dir = Self::list_dir();
|
let dir = Self::list_dir();
|
||||||
ensure!(
|
ensure!(
|
||||||
dir.is_dir() || !dir.exists(),
|
dir.is_dir() || !dir.exists(),
|
||||||
|
Loading…
Reference in New Issue
Block a user