aboutsummaryrefslogtreecommitdiffstats
path: root/src/utils.rs
blob: 5c3b2db55c88084e9f9b949edfb5d0a51365e8e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use std::collections::HashSet;
use std::env;
use std::fs::{self, DirBuilder, File};
use std::io::{BufReader, Read, Write};
use std::path::PathBuf;

use crate::errors::*;
use dirs;
use reqwest;
use rss::Channel;

pub const UNABLE_TO_PARSE_REGEX: &str = "unable to parse regex";
pub const UNABLE_TO_OPEN_FILE: &str = "unable to open file";
pub const UNABLE_TO_CREATE_FILE: &str = "unable to create file";
pub const UNABLE_TO_READ_FILE: &str = "unable to read file";
pub const UNABLE_TO_WRITE_FILE: &str = "unable to write file";
pub const UNABLE_TO_READ_FILE_TO_STRING: &str = "unable to read file to string";
pub const UNABLE_TO_READ_DIRECTORY: &str = "unable to read directory";
pub const UNABLE_TO_READ_ENTRY: &str = "unable to read entry";
pub const UNABLE_TO_CREATE_DIRECTORY: &str = "unable to create directory";
pub const UNABLE_TO_READ_RESPONSE_TO_END: &str = "unable to read response to end";
pub const UNABLE_TO_GET_HTTP_RESPONSE: &str = "unable to get http response";
pub const UNABLE_TO_CONVERT_TO_STR: &str = "unable to convert to &str";
pub const UNABLE_TO_REMOVE_FILE: &str = "unable to remove file";
pub const UNABLE_TO_CREATE_CHANNEL_FROM_RESPONSE: &str =
    "unable to create channel from http response";
pub const UNABLE_TO_CREATE_CHANNEL_FROM_FILE: &str = "unable to create channel from xml file";
pub const UNABLE_TO_RETRIEVE_PODCAST_BY_TITLE: &str = "unable to retrieve podcast by title";

pub fn trim_extension(filename: &str) -> Option<String> {
    let name = String::from(filename);
    let index = name.rfind('.')?;
    Some(String::from(&name[0..index]))
}

pub fn find_extension(input: &str) -> Option<&str> {
    let tmp = String::from(input);
    if tmp.ends_with(".mp3") {
        Some(".mp3")
    } else if tmp.ends_with(".m4a") {
        Some(".m4a")
    } else if tmp.ends_with(".wav") {
        Some(".wav")
    } else if tmp.ends_with(".ogg") {
        Some(".ogg")
    } else if tmp.ends_with(".opus") {
        Some(".opus")
    } else {
        None
    }
}

pub fn get_podcast_dir() -> Result<PathBuf> {
    match env::var_os("PODCAST") {
        Some(val) => Ok(PathBuf::from(val)),
        None => {
            let mut path = dirs::home_dir().chain_err(|| "Couldn't find your home directory")?;
            path.push("Podcasts");
            Ok(path)
        }
    }
}

pub fn create_dir_if_not_exist(path: &PathBuf) -> Result<()> {
    DirBuilder::new()
        .recursive(true)
        .create(&path)
        .chain_err(|| UNABLE_TO_CREATE_DIRECTORY)?;
        Ok(())
}

pub fn create_directories() -> Result<()> {
    let mut path = get_podcast_dir()?;
    path.push(".rss");
    create_dir_if_not_exist(&path)
}

pub fn delete(title: &str) -> Result<()> {
        let mut path = get_xml_dir()?;
        let mut filename = String::from(title);
        filename.push_str(".xml");
        path.push(filename);
        fs::remove_file(path).chain_err(|| UNABLE_TO_REMOVE_FILE)
    }

    pub fn delete_all() -> Result<()> {
        fs::remove_dir_all(get_xml_dir()?).chain_err(|| UNABLE_TO_READ_DIRECTORY)
    }

pub fn already_downloaded(dir: &str) -> Result<HashSet<String>> {
    let mut result = HashSet::new();

    let mut path = get_podcast_dir()?;
    path.push(dir);

    let entries = fs::read_dir(path).chain_err(|| "unable to read directory")?;
    for entry in entries {
        let entry = entry.chain_err(|| "unable to read entry")?;
        match entry.file_name().into_string() {
            Ok(name) => {
                let index = name.find('.').chain_err(|| "unable to find string index")?;
                result.insert(String::from(&name[0..index]));
            }
            Err(_) => {
                eprintln!(
                    "OsString: {:?} couldn't be converted to String",
                    entry.file_name()
                );
            }
        }
    }
    Ok(result)
}

pub fn get_sub_file() -> Result<PathBuf> {
    let mut path = get_podcast_dir()?;
    path.push(".subscriptions.json");
    Ok(path)
}

pub fn get_xml_dir() -> Result<PathBuf> {
    let mut path = get_podcast_dir()?;
    path.push(".rss");
    Ok(path)
}

pub fn download_rss_feed(url: &str) -> Result<Channel> {
    let mut path = get_podcast_dir()?;
    path.push(".rss");
    create_dir_if_not_exist(&path)?;
    let mut resp = reqwest::get(url).chain_err(|| "unable to open url")?;
    let mut content: Vec<u8> = Vec::new();
    resp.read_to_end(&mut content)
        .chain_err(|| "unable to read http response to end")?;
    let channel = Channel::read_from(BufReader::new(&content[..]))
        .chain_err(|| "unable to create channel from xml http response")?;
    let mut filename = String::from(channel.title());
    filename.push_str(".xml");
    path.push(filename);
    let mut file = File::create(&path).chain_err(|| "unable to create file")?;
    file.write_all(&content)
        .chain_err(|| "unable to write file")?;
    Ok(channel)
}

pub fn parse_download_episodes(e_search: &str) -> Result<Vec<usize>> {
    let input = String::from(e_search);
    let mut ranges = Vec::<(usize, usize)>::new();
    let mut elements = Vec::<usize>::new();
    let comma_separated: Vec<&str> = input.split(',').collect();
    for elem in comma_separated {
        let temp = String::from(elem);
        if temp.contains('-') {
            let range: Vec<usize> = elem
                .split('-')
                .map(|i| i.parse::<usize>().chain_err(|| "unable to parse number"))
                .collect::<Result<Vec<usize>>>()
                .chain_err(|| "unable to collect ranges")?;
            ranges.push((range[0], range[1]));
        } else {
            elements.push(
                elem.parse::<usize>()
                    .chain_err(|| "unable to parse number")?,
            );
        }
    }

    for range in ranges {
        // Add 1 to upper range to include given episode in the download
        for num in range.0..=range.1 {
            elements.push(num);
        }
    }
    elements.dedup();
    Ok(elements)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_find_extension_mp3() {
        assert_eq!(find_extension("test.mp3"), Some(".mp3"))
    }

    #[test]
    fn test_find_extension_m4a() {
        assert_eq!(find_extension("test.m4a"), Some(".m4a"))
    }

    #[test]
    fn test_find_extension_wav() {
        assert_eq!(find_extension("test.wav"), Some(".wav"))
    }

    #[test]
    fn test_find_extension_ogg() {
        assert_eq!(find_extension("test.ogg"), Some(".ogg"))
    }

    #[test]
    fn test_find_extension_opus() {
        assert_eq!(find_extension("test.opus"), Some(".opus"))
    }

    #[test]
    fn test_find_extension_invalid() {
        assert_eq!(find_extension("test.taco"), None)
    }

    #[test]
    fn test_trim_extension() {
        assert_eq!(trim_extension("test.taco"), Some(String::from("test")))
    }

    #[test]
    fn test_trim_extension_invalid() {
        assert_eq!(trim_extension("test"), None)
    }
}