1use std::{
2 path::{Path, PathBuf},
3 sync::mpsc::Sender,
4};
5
6use crate::*;
7use crate::bundle::Manifest;
8
9pub trait UpdateSource: Send + Sync {
13 fn get_release_feed(&self, channel: &str, app: &bundle::Manifest, staged_user_id: &str) -> Result<VelopackAssetFeed, Error>;
16 fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error>;
18 fn clone_boxed(&self) -> Box<dyn UpdateSource>;
20}
21
22impl Clone for Box<dyn UpdateSource> {
23 fn clone(&self) -> Self {
24 self.clone_boxed()
25 }
26}
27
28#[derive(Clone)]
30pub struct NoneSource {}
31
32impl UpdateSource for NoneSource {
33 fn get_release_feed(&self, _channel: &str, _app: &Manifest, _staged_user_id: &str) -> Result<VelopackAssetFeed, Error> {
34 Err(Error::Generic("None source does not checking release feed".to_owned()))
35 }
36 fn download_release_entry(&self, _asset: &VelopackAsset, _local_file: &str, _progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
37 Err(Error::Generic("None source does not support downloads".to_owned()))
38 }
39 fn clone_boxed(&self) -> Box<dyn UpdateSource> {
40 Box::new(self.clone())
41 }
42}
43
44#[derive(Clone)]
45pub struct AutoSource {
48 source: Box<dyn UpdateSource>,
49}
50
51impl AutoSource {
52 pub fn new(input: &str) -> AutoSource {
54 let source: Box<dyn UpdateSource> = if Self::is_http_url(input) {
55 Box::new(HttpSource::new(input))
56 } else {
57 Box::new(FileSource::new(input))
58 };
59 AutoSource { source }
60 }
61
62 fn is_http_url(url: &str) -> bool {
63 match url::Url::parse(url) {
64 Ok(url) => url.scheme().eq_ignore_ascii_case("http") || url.scheme().eq_ignore_ascii_case("https"),
65 _ => false,
66 }
67 }
68}
69
70impl UpdateSource for AutoSource {
71 fn get_release_feed(&self, channel: &str, app: &bundle::Manifest, staged_user_id: &str) -> Result<VelopackAssetFeed, Error> {
72 self.source.get_release_feed(channel, app, staged_user_id)
73 }
74
75 fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
76 self.source.download_release_entry(asset, local_file, progress_sender)
77 }
78
79 fn clone_boxed(&self) -> Box<dyn UpdateSource> {
80 self.source.clone_boxed()
81 }
82}
83
84#[derive(Clone)]
85pub struct HttpSource {
89 url: String,
90}
91
92impl HttpSource {
93 pub fn new<S: AsRef<str>>(url: S) -> HttpSource {
95 HttpSource { url: url.as_ref().to_owned() }
96 }
97}
98
99impl UpdateSource for HttpSource {
100 fn get_release_feed(&self, channel: &str, app: &bundle::Manifest, staged_user_id: &str) -> Result<VelopackAssetFeed, Error> {
101 let releases_name = format!("releases.{}.json", channel);
102
103 let path = self.url.trim_end_matches('/').to_owned() + "/";
104 let url = url::Url::parse(&path)?;
105 let mut releases_url = url.join(&releases_name)?;
106 releases_url.set_query(Some(format!("localVersion={}&id={}&stagingId={}", app.version, app.id, staged_user_id).as_str()));
107
108 info!("Downloading releases for channel {} from: {}", channel, releases_url.to_string());
109 let json = download::download_url_as_string(releases_url.as_str())?;
110 let feed: VelopackAssetFeed = serde_json::from_str(&json)?;
111 Ok(feed)
112 }
113
114 fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
115 let path = self.url.trim_end_matches('/').to_owned() + "/";
116 let url = url::Url::parse(&path)?;
117 let asset_url = url.join(&asset.FileName)?;
118
119 info!("About to download from URL '{}' to file '{}'", asset_url, local_file);
120 download::download_url_to_file(asset_url.as_str(), local_file, move |p| {
121 if let Some(progress_sender) = &progress_sender {
122 let _ = progress_sender.send(p);
123 }
124 })?;
125 Ok(())
126 }
127
128 fn clone_boxed(&self) -> Box<dyn UpdateSource> {
129 Box::new(self.clone())
130 }
131}
132
133#[derive(Clone)]
134pub struct FileSource {
137 path: PathBuf,
138}
139
140impl FileSource {
141 pub fn new<P: AsRef<Path>>(path: P) -> FileSource {
143 let path = path.as_ref();
144 FileSource { path: PathBuf::from(path) }
145 }
146}
147
148impl UpdateSource for FileSource {
149 fn get_release_feed(&self, channel: &str, _: &bundle::Manifest, _staged_user_id: &str) -> Result<VelopackAssetFeed, Error> {
150 let releases_name = format!("releases.{}.json", channel);
151 let releases_path = self.path.join(&releases_name);
152
153 info!("Reading releases from file: {}", releases_path.display());
154 let json = std::fs::read_to_string(releases_path)?;
155 let feed: VelopackAssetFeed = serde_json::from_str(&json)?;
156 Ok(feed)
157 }
158
159 fn download_release_entry(&self, asset: &VelopackAsset, local_file: &str, progress_sender: Option<Sender<i16>>) -> Result<(), Error> {
160 let asset_path = self.path.join(&asset.FileName);
161 info!("About to copy from file '{}' to file '{}'", asset_path.display(), local_file);
162 if let Some(progress_sender) = &progress_sender {
163 let _ = progress_sender.send(50);
164 }
165 std::fs::copy(asset_path, local_file)?;
166 if let Some(progress_sender) = &progress_sender {
167 let _ = progress_sender.send(100);
168 }
169 Ok(())
170 }
171
172 fn clone_boxed(&self) -> Box<dyn UpdateSource> {
173 Box::new(self.clone())
174 }
175}