1use crate::color::ColorScheme;
2use crate::error::{ErrorKind, Result};
3use async_std::{fs, prelude::*};
4use dirs;
5use failure::ResultExt;
6use futures::future;
7use std::path::PathBuf;
8use surf::RequestBuilder;
9
10pub struct Provider {
12 user_name: String,
13 repo_name: String,
14 list_path: String,
15 extension: String,
16}
17
18impl Provider {
19 pub fn iterm() -> Self {
21 Provider::new(
22 "mbadolato",
23 "iTerm2-Color-Schemes",
24 "schemes",
25 ".itermcolors",
26 )
27 }
28
29 pub fn gogh() -> Self {
31 Provider::new("Gogh-Co", "Gogh", "themes", ".sh")
32 }
33
34 fn new(user_name: &str, repo_name: &str, list_path: &str, extension: &str) -> Self {
36 Provider {
37 user_name: user_name.to_string(),
38 repo_name: repo_name.to_string(),
39 list_path: list_path.to_string(),
40 extension: extension.to_string(),
41 }
42 }
43
44 pub async fn get(&self, name: &str) -> Result<ColorScheme> {
46 let req = surf::get(&self.individual_url(name));
47 let body = http_get(req).await?;
48 self.parse_color_scheme(&body)
49 }
50
51 pub async fn list(self) -> Result<Vec<(String, ColorScheme)>> {
55 match self.read_color_schemes().await {
56 Ok(color_schemes) => {
57 if color_schemes.len() > 0 {
58 return Ok(color_schemes);
59 }
60 }
61 _ => {}
62 }
63
64 self.download_all().await?;
66 self.read_color_schemes().await
67 }
68
69 pub async fn download_all(&self) -> Result<()> {
71 let repo_dir = self.repo_dir()?;
72
73 eprintln!(
74 "Downloading color schemes into {}",
75 repo_dir.to_str().unwrap()
76 );
77
78 fs::create_dir_all(&repo_dir)
80 .await
81 .context(ErrorKind::CreateDirAll)?;
82
83 let list_req = surf::get(&self.list_url());
84 let list_body = http_get(list_req).await?;
85 let items = json::parse(&list_body).context(ErrorKind::ParseJson)?;
86
87 let mut futures = Vec::new();
89 for item in items.members() {
90 let filename = item["name"].as_str().unwrap();
91
92 if filename.starts_with('_') || !filename.ends_with(&self.extension) {
94 continue;
95 }
96
97 let name = filename.replace(&self.extension, "");
98 let req = surf::get(&self.individual_url(&name));
99 futures.push(self.download_color_scheme(req, name));
100
101 if futures.len() > 10 {
111 future::try_join_all(futures).await?;
112 futures = Vec::new();
113 }
114 }
115
116 Ok(())
117 }
118
119 async fn read_color_schemes(&self) -> Result<Vec<(String, ColorScheme)>> {
121 let mut entries = fs::read_dir(self.repo_dir()?)
122 .await
123 .context(ErrorKind::ReadDir)?;
124
125 let mut futures = Vec::new();
127 while let Some(entry) = entries.next().await {
128 let dir_entry = entry.context(ErrorKind::ReadDirEntry)?;
129 let filename = dir_entry.file_name().into_string().unwrap();
130
131 let name = filename.replace(&self.extension, "").to_string();
132 futures.push(self.read_color_scheme(name));
133 }
134
135 let color_schemes = future::try_join_all(futures).await?;
136
137 Ok(color_schemes)
138 }
139
140 async fn read_color_scheme(&self, name: String) -> Result<(String, ColorScheme)> {
142 let file_path = self.individual_path(&name)?;
143
144 let body = fs::read_to_string(file_path)
145 .await
146 .context(ErrorKind::ReadFile)?;
147 let color_scheme = self.parse_color_scheme(&body)?;
148
149 Ok((name, color_scheme))
150 }
151
152 async fn download_color_scheme(&self, req: RequestBuilder, name: String) -> Result<()> {
154 let body = http_get(req).await?;
155 fs::write(self.individual_path(&name)?, body)
156 .await
157 .context(ErrorKind::WriteFile)?;
158 Ok(())
159 }
160
161 fn repo_dir(&self) -> Result<PathBuf> {
163 let mut repo_dir = dirs::cache_dir().ok_or(ErrorKind::NoCacheDir)?;
164 repo_dir.push("colortty");
165 repo_dir.push("repositories");
166 repo_dir.push(&self.user_name);
167 repo_dir.push(&self.repo_name);
168 Ok(repo_dir)
169 }
170
171 fn individual_path(&self, name: &str) -> Result<PathBuf> {
173 let mut file_path = self.repo_dir()?;
174 file_path.push(name);
175 file_path.set_extension(&self.extension[1..]);
176 Ok(file_path)
177 }
178
179 fn individual_url(&self, name: &str) -> String {
181 format!(
182 "https://raw.githubusercontent.com/{}/{}/master/{}/{}{}",
183 self.user_name, self.repo_name, self.list_path, name, self.extension
184 )
185 }
186
187 fn list_url(&self) -> String {
189 format!(
190 "https://api.github.com/repos/{}/{}/contents/{}",
191 self.user_name, self.repo_name, self.list_path
192 )
193 }
194
195 fn parse_color_scheme(&self, body: &str) -> Result<ColorScheme> {
197 if self.extension == ".itermcolors" {
199 ColorScheme::from_iterm(&body)
200 } else {
201 ColorScheme::from_gogh(&body)
202 }
203 }
204}
205
206async fn http_get(req: RequestBuilder) -> Result<String> {
210 let mut res = req.header("User-Agent", "colortty").await.map_err(|e| {
211 println!("HTTP request error: {}", e);
212 ErrorKind::HttpGet
213 })?;
214
215 if !res.status().is_success() {
216 println!("HTTP status code: {}", res.status());
217 return Err(ErrorKind::HttpGet.into());
218 }
219
220 let body = res.body_string().await.map_err(|_| ErrorKind::HttpGet)?;
222 Ok(body)
223}