f4-dir-per-channel #21
6 changed files with 62 additions and 16 deletions
|
@ -2,8 +2,10 @@ use crate::prelude::*;
|
||||||
|
|
||||||
use crate::network::{NetUrl, NetworkEnv};
|
use crate::network::{NetUrl, NetworkEnv};
|
||||||
|
|
||||||
pub fn find(site: &str, channel_name: &str, e: &NetworkEnv) -> Result<NetUrl> {
|
use super::ChannelName;
|
||||||
if let Some(channel_prefix) = channel_name.chars().next() {
|
|
||||||
|
pub fn find(site: &str, channel_name: &ChannelName, e: &NetworkEnv) -> Result<NetUrl> {
|
||||||
|
if let Some(channel_prefix) = channel_name.0.chars().next() {
|
||||||
if channel_prefix != '@' {
|
if channel_prefix != '@' {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"Channel Name must begin with an '@': {}",
|
"Channel Name must begin with an '@': {}",
|
||||||
|
@ -51,7 +53,7 @@ mod tests {
|
||||||
download_as_mp3: stub_network_download_as_mp3(),
|
download_as_mp3: stub_network_download_as_mp3(),
|
||||||
};
|
};
|
||||||
//when
|
//when
|
||||||
let result = find("site", "@channel", &network_env)?;
|
let result = find("site", &ChannelName::from("@channel"), &network_env)?;
|
||||||
//then
|
//then
|
||||||
assert_eq!(result, NetUrl::from("the-rss-url"));
|
assert_eq!(result, NetUrl::from("the-rss-url"));
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -66,7 +68,11 @@ mod tests {
|
||||||
download_as_mp3: stub_network_download_as_mp3(),
|
download_as_mp3: stub_network_download_as_mp3(),
|
||||||
};
|
};
|
||||||
//when
|
//when
|
||||||
let result = find("site", "invalid-channel-name", &network_env);
|
let result = find(
|
||||||
|
"site",
|
||||||
|
&ChannelName::from("invalid-channel-name"),
|
||||||
|
&network_env,
|
||||||
|
);
|
||||||
//then
|
//then
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -12,3 +12,16 @@ pub fn get(url: &NetUrl, e: &NetworkEnv) -> Result<Feed> {
|
||||||
let channel = Feed::read_from(&content[..]).context("Could not parse RSS feed")?;
|
let channel = Feed::read_from(&content[..]).context("Could not parse RSS feed")?;
|
||||||
Ok(channel)
|
Ok(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ChannelName(pub String);
|
||||||
|
impl ChannelName {
|
||||||
|
pub fn from(channel_name: &str) -> Self {
|
||||||
|
Self(channel_name.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for ChannelName {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use crate::prelude::*;
|
use crate::{feed::ChannelName, prelude::*};
|
||||||
|
|
||||||
use crate::file::FileEnv;
|
use crate::file::FileEnv;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader};
|
||||||
|
|
||||||
pub fn lines_from(file_name: &str, e: &FileEnv) -> Result<Vec<String>> {
|
pub fn lines_from(file_name: &str, e: &FileEnv) -> Result<Vec<ChannelName>> {
|
||||||
let file = (e.open)(file_name).context(format!("Opening file: {file_name}"))?;
|
let file = (e.open)(file_name).context(format!("Opening file: {file_name}"))?;
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
let mut lines = vec![];
|
let mut lines = vec![];
|
||||||
for line in reader.lines().flatten() {
|
for line in reader.lines().flatten() {
|
||||||
if line.starts_with('@') {
|
if line.starts_with('@') {
|
||||||
lines.push(line);
|
lines.push(ChannelName(line));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(lines)
|
Ok(lines)
|
||||||
|
@ -41,7 +41,13 @@ mod tests {
|
||||||
|
|
||||||
//then
|
//then
|
||||||
drop(dir);
|
drop(dir);
|
||||||
assert_eq!(result, ["@sub1", "@sub2", "@sub3"]);
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
["@sub1", "@sub2", "@sub3"]
|
||||||
|
.into_iter()
|
||||||
|
.map(ChannelName::from)
|
||||||
|
.collect::<Vec<ChannelName>>()
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +70,13 @@ mod tests {
|
||||||
|
|
||||||
//then
|
//then
|
||||||
drop(dir);
|
drop(dir);
|
||||||
assert_eq!(result, ["@sub1", "@sub2", "@sub3"]);
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
["@sub1", "@sub2", "@sub3"]
|
||||||
|
.into_iter()
|
||||||
|
.map(ChannelName::from)
|
||||||
|
.collect::<Vec<ChannelName>>()
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +99,13 @@ mod tests {
|
||||||
|
|
||||||
//then
|
//then
|
||||||
drop(dir);
|
drop(dir);
|
||||||
assert_eq!(result, ["@sub1", "@sub3"]);
|
assert_eq!(
|
||||||
|
result,
|
||||||
|
["@sub1", "@sub3"]
|
||||||
|
.into_iter()
|
||||||
|
.map(ChannelName::from)
|
||||||
|
.collect::<Vec<ChannelName>>()
|
||||||
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ pub fn run(site: &str, a: &Args, e: Env) -> Result<()> {
|
||||||
if let Some(link) = entry.links().first() {
|
if let Some(link) = entry.links().first() {
|
||||||
if !history::find(link, &a.history, &e.file).context("Finding history")? {
|
if !history::find(link, &a.history, &e.file).context("Finding history")? {
|
||||||
println!("Downloading {}: {}", &channel_name, entry.title().as_str());
|
println!("Downloading {}: {}", &channel_name, entry.title().as_str());
|
||||||
(e.network.download_as_mp3)(&NetUrl(link.href.clone()))
|
(e.network.download_as_mp3)(&NetUrl(link.href.clone()), &channel_name)
|
||||||
.context("Downloading as MP3")?;
|
.context("Downloading as MP3")?;
|
||||||
history::add(link, &a.history, &e.file).context("Adding to history")?;
|
history::add(link, &a.history, &e.file).context("Adding to history")?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::{feed::ChannelName, prelude::*};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct NetUrl(pub String);
|
pub struct NetUrl(pub String);
|
||||||
|
@ -19,7 +19,7 @@ pub type NetworkFetchAsTextFn = Box<dyn Fn(&NetUrl) -> Result<String>>;
|
||||||
|
|
||||||
pub type NetworkFetchAsBytesFn = Box<dyn Fn(&NetUrl) -> Result<bytes::Bytes>>;
|
pub type NetworkFetchAsBytesFn = Box<dyn Fn(&NetUrl) -> Result<bytes::Bytes>>;
|
||||||
|
|
||||||
pub type NetworkDownloadAsMp3Fn = Box<dyn Fn(&NetUrl) -> Result<()>>;
|
pub type NetworkDownloadAsMp3Fn = Box<dyn Fn(&NetUrl, &ChannelName) -> Result<()>>;
|
||||||
|
|
||||||
pub struct NetworkEnv {
|
pub struct NetworkEnv {
|
||||||
pub fetch_as_text: NetworkFetchAsTextFn,
|
pub fetch_as_text: NetworkFetchAsTextFn,
|
||||||
|
@ -41,12 +41,14 @@ impl Default for NetworkEnv {
|
||||||
.bytes()
|
.bytes()
|
||||||
.context(format!("Parsing bytes from body of response for {}", url))
|
.context(format!("Parsing bytes from body of response for {}", url))
|
||||||
}),
|
}),
|
||||||
download_as_mp3: Box::new(|url| {
|
download_as_mp3: Box::new(|url, channel_name| {
|
||||||
let cmd = "yt-dlp";
|
let cmd = "yt-dlp";
|
||||||
let output = Command::new(cmd)
|
let output = Command::new(cmd)
|
||||||
.arg("--extract-audio")
|
.arg("--extract-audio")
|
||||||
.arg("--audio-format")
|
.arg("--audio-format")
|
||||||
.arg("mp3")
|
.arg("mp3")
|
||||||
|
.arg("-p")
|
||||||
|
.arg(format!("~/Music/{}", channel_name))
|
||||||
.arg(&url.0)
|
.arg(&url.0)
|
||||||
.output()
|
.output()
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
|
|
|
@ -11,6 +11,7 @@ use anyhow::Context;
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
feed::ChannelName,
|
||||||
file::{FileAppendLineFn, FileOpenFn},
|
file::{FileAppendLineFn, FileOpenFn},
|
||||||
network::{NetUrl, NetworkDownloadAsMp3Fn, NetworkFetchAsBytesFn, NetworkFetchAsTextFn},
|
network::{NetUrl, NetworkDownloadAsMp3Fn, NetworkFetchAsBytesFn, NetworkFetchAsTextFn},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
@ -70,7 +71,7 @@ pub fn mock_file_open(real_paths: HashMap<String, String>) -> FileOpenFn {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mock_network_download_as_mp3(tx: Sender<NetUrl>) -> NetworkDownloadAsMp3Fn {
|
pub fn mock_network_download_as_mp3(tx: Sender<NetUrl>) -> NetworkDownloadAsMp3Fn {
|
||||||
Box::new(move |url: &NetUrl| {
|
Box::new(move |url: &NetUrl, _channel_name: &ChannelName| {
|
||||||
tx.send(url.clone())?;
|
tx.send(url.clone())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -84,5 +85,11 @@ pub fn stub_network_fetch_as_bytes() -> NetworkFetchAsBytesFn {
|
||||||
Box::new(|url: &NetUrl| Err(anyhow!("Not implemented: network_fetch_as_bytes: {}", url)))
|
Box::new(|url: &NetUrl| Err(anyhow!("Not implemented: network_fetch_as_bytes: {}", url)))
|
||||||
}
|
}
|
||||||
pub fn stub_network_download_as_mp3() -> NetworkDownloadAsMp3Fn {
|
pub fn stub_network_download_as_mp3() -> NetworkDownloadAsMp3Fn {
|
||||||
Box::new(|url: &NetUrl| Err(anyhow!("Not implemented: network_download_as_mp3: {}", url)))
|
Box::new(|url: &NetUrl, channel_name: &ChannelName| {
|
||||||
|
Err(anyhow!(
|
||||||
|
"Not implemented: network_download_as_mp3: ({}) {}",
|
||||||
|
channel_name,
|
||||||
|
url,
|
||||||
|
))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue