-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmod.rs
More file actions
231 lines (190 loc) · 7.58 KB
/
mod.rs
File metadata and controls
231 lines (190 loc) · 7.58 KB
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
222
223
224
225
226
227
228
229
230
231
pub mod cache;
pub mod error;
pub mod index;
pub mod install;
pub mod resolver;
use std::borrow::Borrow;
use std::fs::File;
use std::io::{ErrorKind, Read, Seek};
use std::path::{Path, PathBuf};
use colored::Colorize;
use futures::prelude::*;
use serde::{Deserialize, Serialize};
use serde_with::{self, serde_as, DisplayFromStr};
use tokio::fs;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use self::index::PackageIndex;
use crate::error::{Error, IoError, IoResultToTcli};
use crate::ts::package_manifest::PackageManifestV1;
use crate::ts::package_reference::PackageReference;
use crate::ts::{self, CLIENT};
use crate::ui::reporter::ProgressBarTrait;
use crate::TCLI_HOME;
#[derive(Serialize, Deserialize, Debug)]
pub struct PackageMetadata {
#[serde(flatten)]
manifest: PackageManifestV1,
reference: String,
icon: PathBuf,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum PackageSource {
Remote(String),
Local(PathBuf),
Cache(PathBuf),
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Package {
pub source: PackageSource,
#[serde_as(as = "DisplayFromStr")]
pub identifier: PackageReference,
#[serde(with = "crate::ts::package_reference::ser::string_array")]
pub dependencies: Vec<PackageReference>,
}
impl Package {
/// Attempt to resolve the package from the local cache or remote.
/// This does not download the package, it just finds its "source".
pub async fn from_any(ident: impl Borrow<PackageReference>) -> Result<Self, Error> {
if cache::get_cache_location(ident.borrow()).exists() {
return Package::from_cache(ident).await;
}
Package::from_repo(ident).await
}
/// Load package metadata by parsing its cached manifest.
///
/// This function will fall back to Package::from_repo if the manifest is invalid.
pub async fn from_cache(ident: impl Borrow<PackageReference>) -> Result<Self, Error> {
let ident = ident.borrow();
let path = cache::get_cache_location(ident);
let manifest_path = path.join("manifest.json");
let mut manifest_str = String::new();
fs::File::open(&manifest_path)
.await
.map_fs_error(&manifest_path)
.unwrap()
.read_to_string(&mut manifest_str)
.await
.unwrap();
// Remove UTF-8 BOM: https://github.com/serde-rs/serde/issues/1753
let manifest_str = manifest_str.trim_start_matches('\u{feff}');
match serde_json::from_str::<PackageManifestV1>(manifest_str) {
Ok(manifest) => Ok(Package {
identifier: ident.clone(),
source: PackageSource::Cache(path.to_path_buf()),
dependencies: manifest.dependencies,
}),
Err(_) => {
println!(
"{} package \"{}\" has a malformed manifest, grabbing info from repo instead",
"[!]".bright_yellow(),
ident,
);
let mut package = Package::from_repo(ident).await?;
package.source = PackageSource::Cache(path);
Ok(package)
}
}
}
/// Load package metadata by querying the repository.
pub async fn from_repo(ident: impl Borrow<PackageReference>) -> Result<Self, Error> {
let ident = ident.borrow();
let index = PackageIndex::open(&TCLI_HOME).await?;
let package = index.lock().unwrap().get_package(ident).unwrap();
Ok(Package {
identifier: ident.clone(),
source: PackageSource::Remote(ts::v1::package::download_for_package(ident)),
dependencies: package.dependencies,
})
}
/// Load package metadata from an arbitrary path, extracting it into the cache if it passes
// manifest validation.
pub async fn from_path(ident: PackageReference, path: &Path) -> Result<Self, Error> {
// let package = Package::from_repo(ident).await?;
add_to_cache(&ident, File::open(path).map_fs_error(path)?)?;
let package = Package::from_cache(ident).await?;
Ok(Package {
identifier: package.identifier,
source: PackageSource::Local(path.to_path_buf()),
dependencies: package.dependencies,
})
}
/// Resolve the package into a discrete path, returning None if it does not exist locally.
pub async fn get_path(&self) -> Option<PathBuf> {
match &self.source {
PackageSource::Local(path) => {
add_to_cache(&self.identifier, File::open(path).map_fs_error(path).ok()?).ok()
}
PackageSource::Cache(path) => Some(path.clone()),
PackageSource::Remote(_) => None,
}
}
/// Get the metadata associated with this package. This will return None
/// the package does not exist locally.
pub async fn get_metadata(&self) -> Result<Option<PackageMetadata>, Error> {
let Some(package_dir) = self.get_path().await else {
return Ok(None);
};
let manifest = {
let str = fs::read_to_string(package_dir.join("manifest.json")).await?;
serde_json::from_str::<PackageManifestV1>(&str)?
};
let icon = package_dir.join("icon.png");
let reference = package_dir
.file_name()
.unwrap()
.to_string_lossy()
.to_string();
Ok(Some(PackageMetadata {
manifest,
reference,
icon,
}))
}
pub async fn download(&self, reporter: &dyn ProgressBarTrait) -> Result<PathBuf, Error> {
let PackageSource::Remote(package_source) = &self.source else {
panic!("Invalid use, this is a local package.")
};
let output_path = cache::get_cache_location(&self.identifier);
if output_path.is_dir() {
reporter.finish();
return Ok(output_path);
}
let download_result = CLIENT.get(package_source).send().await.unwrap();
let download_size = download_result.content_length().unwrap();
let progress_message = format!(
"{}-{} ({})",
self.identifier.namespace.bold(),
self.identifier.name.bold(),
self.identifier.version.to_string().truecolor(90, 90, 90)
);
reporter.set_length(download_size);
reporter.set_message(format!("Downloading {progress_message}..."));
let mut download_stream = download_result.bytes_stream();
let mut temp_file = cache::get_temp_zip_file(&self.identifier).await?;
let zip_file = temp_file.file_mut();
while let Some(chunk) = download_stream.next().await {
let chunk = chunk.unwrap();
zip_file.write_all(&chunk).await.unwrap();
reporter.inc(chunk.len() as u64);
}
reporter.set_message(format!("Extracting {progress_message}..."));
let cache_path = add_to_cache(&self.identifier, temp_file.into_std().await.file())?;
// reporter.finish();
Ok(cache_path)
}
}
fn add_to_cache(package: &PackageReference, zipfile: impl Read + Seek) -> Result<PathBuf, Error> {
let output_path = cache::get_cache_location(package);
match std::fs::remove_dir_all(&output_path) {
Ok(_) => (),
Err(e) if e.kind() == ErrorKind::NotFound => (),
Err(e) => Err(e).map_fs_error(&output_path)?,
};
std::fs::create_dir_all(&output_path).map_fs_error(&output_path)?;
zip::read::ZipArchive::new(zipfile)
.map_err(IoError::ZipError)?
.extract(&output_path)
.map_err(IoError::ZipError)?;
Ok(output_path)
}