Skip to content

Commit 79f4c9c

Browse files
authored
feat: Add decompress flag to sourcemaps and files upload (#1277)
* feat: Add decompress flag to sourcemaps and files upload
1 parent 638c365 commit 79f4c9c

File tree

7 files changed

+100
-143
lines changed

7 files changed

+100
-143
lines changed

src/api.rs

Lines changed: 18 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::collections::{HashMap, HashSet};
99
use std::ffi::OsStr;
1010
use std::fs::{create_dir_all, File};
1111
use std::io::{self, Read, Write};
12-
use std::path::{Path, PathBuf};
12+
use std::path::Path;
1313
use std::rc::Rc;
1414
use std::str::FromStr;
1515
use std::sync::Arc;
@@ -20,10 +20,8 @@ use backoff::backoff::Backoff;
2020
use brotli2::write::BrotliEncoder;
2121
use chrono::{DateTime, Duration, FixedOffset, Utc};
2222
use clap::ArgMatches;
23-
use console::style;
2423
use flate2::write::GzEncoder;
2524
use if_chain::if_chain;
26-
use indicatif::ProgressStyle;
2725
use lazy_static::lazy_static;
2826
use log::{debug, info, warn};
2927
use parking_lot::{Mutex, RwLock};
@@ -41,6 +39,7 @@ use uuid::Uuid;
4139
use crate::config::{Auth, Config};
4240
use crate::constants::{ARCH, EXT, PLATFORM, RELEASE_REGISTRY_LATEST_URL, VERSION};
4341
use crate::utils::android::AndroidManifest;
42+
use crate::utils::file_upload::UploadContext;
4443
use crate::utils::http::{self, is_absolute_url, parse_link_header};
4544
use crate::utils::progress::ProgressBar;
4645
use crate::utils::retry::{get_default_backoff, DurationAsMilliseconds};
@@ -51,13 +50,6 @@ use crate::utils::xcode::InfoPlist;
5150
const QUERY_ENCODE_SET: AsciiSet = CONTROLS.add(b' ').add(b'"').add(b'#').add(b'<').add(b'>');
5251
const DEFAULT_ENCODE_SET: AsciiSet = QUERY_ENCODE_SET.add(b'`').add(b'?').add(b'{').add(b'}');
5352

54-
/// Represents file contents temporarily
55-
#[derive(Clone, Debug)]
56-
pub enum FileContents<'a> {
57-
FromPath(&'a Path),
58-
FromBytes(&'a [u8]),
59-
}
60-
6153
/// Wrapper that escapes arguments for URL path segments.
6254
pub struct PathArg<A: fmt::Display>(A);
6355

@@ -669,48 +661,39 @@ impl Api {
669661

670662
/// Uploads a new release file. The file is loaded directly from the file
671663
/// system and uploaded as `name`.
672-
// TODO: Simplify this function interface
673-
#[allow(clippy::too_many_arguments)]
674664
pub fn upload_release_file(
675665
&self,
676-
org: &str,
677-
project: Option<&str>,
678-
version: &str,
679-
contents: &FileContents,
666+
context: &UploadContext,
667+
contents: &[u8],
680668
name: &str,
681-
dist: Option<&str>,
682669
headers: Option<&[(String, String)]>,
683670
progress_bar_mode: ProgressBarMode,
684671
) -> ApiResult<Option<Artifact>> {
685-
let path = if let Some(project) = project {
672+
let path = if let Some(project) = context.project {
686673
format!(
687674
"/projects/{}/{}/releases/{}/files/",
688-
PathArg(org),
675+
PathArg(context.org),
689676
PathArg(project),
690-
PathArg(version)
677+
PathArg(context.release)
691678
)
692679
} else {
693680
format!(
694681
"/organizations/{}/releases/{}/files/",
695-
PathArg(org),
696-
PathArg(version)
682+
PathArg(context.org),
683+
PathArg(context.release)
697684
)
698685
};
699686
let mut form = curl::easy::Form::new();
700-
match contents {
701-
FileContents::FromPath(path) => {
702-
form.part("file").file(path).add()?;
703-
}
704-
FileContents::FromBytes(bytes) => {
705-
let filename = Path::new(name)
706-
.file_name()
707-
.and_then(OsStr::to_str)
708-
.unwrap_or("unknown.bin");
709-
form.part("file").buffer(filename, bytes.to_vec()).add()?;
710-
}
711-
}
687+
688+
let filename = Path::new(name)
689+
.file_name()
690+
.and_then(OsStr::to_str)
691+
.unwrap_or("unknown.bin");
692+
form.part("file")
693+
.buffer(filename, contents.to_vec())
694+
.add()?;
712695
form.part("name").contents(name.as_bytes()).add()?;
713-
if let Some(dist) = dist {
696+
if let Some(dist) = context.dist {
714697
form.part("dist").contents(dist.as_bytes()).add()?;
715698
}
716699

@@ -742,90 +725,6 @@ impl Api {
742725
}
743726
}
744727

745-
pub fn upload_release_files(
746-
&self,
747-
org: &str,
748-
project: Option<&str>,
749-
version: &str,
750-
sources: HashSet<(String, PathBuf)>,
751-
dist: Option<&str>,
752-
headers: Option<&[(String, String)]>,
753-
) -> Result<(), ApiError> {
754-
if sources.is_empty() {
755-
return Ok(());
756-
}
757-
758-
let mut successful_req = vec![];
759-
let mut failed_req = vec![];
760-
761-
println!(
762-
"{} Uploading {} files for release {}",
763-
style(">").dim(),
764-
style(sources.len()).cyan(),
765-
style(version).cyan()
766-
);
767-
768-
let pb = ProgressBar::new(sources.len());
769-
pb.set_style(ProgressStyle::default_bar().template(&format!(
770-
"{} {{msg}}\n{{wide_bar}} {{pos}}/{{len}}",
771-
style(">").cyan()
772-
)));
773-
774-
for (url, path) in sources {
775-
pb.set_message(path.to_str().unwrap());
776-
777-
let upload_result = self.upload_release_file(
778-
org,
779-
project,
780-
version,
781-
&FileContents::FromPath(&path),
782-
&url.to_owned(),
783-
dist,
784-
headers,
785-
ProgressBarMode::Disabled,
786-
);
787-
788-
match upload_result {
789-
Ok(Some(artifact)) => {
790-
successful_req.push((path, artifact));
791-
}
792-
Ok(None) => {
793-
failed_req.push((path, url, String::from("File already present")));
794-
}
795-
Err(err) => {
796-
failed_req.push((path, url, err.to_string()));
797-
}
798-
};
799-
800-
pb.inc(1);
801-
}
802-
803-
pb.finish_and_clear();
804-
805-
for (path, url, reason) in failed_req.iter() {
806-
println!(
807-
" Failed to upload: {} as {} ({})",
808-
&path.display(),
809-
&url,
810-
reason
811-
);
812-
}
813-
814-
for (path, artifact) in successful_req.iter() {
815-
println!(
816-
" Successfully uploaded: {} as {} ({}) ({} bytes)",
817-
&path.display(),
818-
artifact.name,
819-
artifact.sha1,
820-
artifact.size
821-
);
822-
}
823-
824-
println!("{} Done uploading.", style(">").dim());
825-
826-
Ok(())
827-
}
828-
829728
/// Creates a new release.
830729
pub fn new_release(&self, org: &str, release: &NewRelease) -> ApiResult<ReleaseInfo> {
831730
// for single project releases use the legacy endpoint that is project bound.

src/commands/files/upload.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
use std::ffi::OsStr;
2+
use std::fs;
3+
use std::io::Read;
24
use std::path::Path;
35

46
use anyhow::{bail, format_err, Result};
57
use clap::{Arg, ArgMatches, Command};
8+
use log::warn;
69
use symbolic::debuginfo::sourcebundle::SourceFileType;
710

8-
use crate::api::{Api, FileContents, ProgressBarMode};
11+
use crate::api::{Api, ProgressBarMode};
912
use crate::config::Config;
1013
use crate::utils::file_search::ReleaseFileSearch;
1114
use crate::utils::file_upload::{ReleaseFile, ReleaseFileUpload, UploadContext};
12-
use crate::utils::fs::path_as_url;
15+
use crate::utils::fs::{decompress_gzip_content, is_gzip_compressed, path_as_url};
1316

1417
pub fn make_command(command: Command) -> Command {
1518
command
@@ -34,6 +37,11 @@ pub fn make_command(command: Command) -> Command {
3437
.value_name("DISTRIBUTION")
3538
.help("Optional distribution identifier for this file."),
3639
)
40+
.arg(
41+
Arg::new("decompress")
42+
.long("decompress")
43+
.help("Enable files gzip decompression prior to upload."),
44+
)
3745
.arg(
3846
Arg::new("wait")
3947
.long("wait")
@@ -110,8 +118,16 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
110118
headers.push((key.trim().to_string(), value.trim().to_string()));
111119
}
112120
};
113-
let path = Path::new(matches.value_of("path").unwrap());
114121

122+
let context = &UploadContext {
123+
org: &org,
124+
project: project.as_deref(),
125+
release: &release,
126+
dist,
127+
wait: matches.is_present("wait"),
128+
};
129+
130+
let path = Path::new(matches.value_of("path").unwrap());
115131
// Batch files upload
116132
if path.is_dir() {
117133
let ignore_file = matches.value_of("ignore_file").unwrap_or("");
@@ -128,6 +144,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
128144
.ignore_file(ignore_file)
129145
.ignores(ignores)
130146
.extensions(extensions)
147+
.decompress(matches.is_present("decompress"))
131148
.collect_files()?;
132149

133150
let url_suffix = matches.value_of("url_suffix").unwrap_or("");
@@ -157,15 +174,7 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
157174
})
158175
.collect();
159176

160-
let ctx = &UploadContext {
161-
org: &org,
162-
project: project.as_deref(),
163-
release: &release,
164-
dist,
165-
wait: matches.is_present("wait"),
166-
};
167-
168-
ReleaseFileUpload::new(ctx).files(&files).upload()
177+
ReleaseFileUpload::new(context).files(&files).upload()
169178
}
170179
// Single file upload
171180
else {
@@ -177,13 +186,21 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
177186
.ok_or_else(|| format_err!("No filename provided."))?,
178187
};
179188

189+
let mut f = fs::File::open(path)?;
190+
let mut contents = Vec::new();
191+
f.read_to_end(&mut contents)?;
192+
193+
if matches.is_present("decompress") && is_gzip_compressed(&contents) {
194+
contents = decompress_gzip_content(&contents).unwrap_or_else(|_| {
195+
warn!("Could not decompress: {}", name);
196+
contents
197+
});
198+
}
199+
180200
if let Some(artifact) = api.upload_release_file(
181-
&org,
182-
project.as_deref(),
183-
&release,
184-
&FileContents::FromPath(path),
201+
context,
202+
&contents,
185203
name,
186-
dist,
187204
Some(&headers[..]),
188205
ProgressBarMode::Request,
189206
)? {

src/commands/sourcemaps/upload.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ pub fn make_command(command: Command) -> Command {
5151
.long("validate")
5252
.help("Enable basic sourcemap validation."),
5353
)
54+
.arg(
55+
Arg::new("decompress")
56+
.long("decompress")
57+
.help("Enable files gzip decompression prior to upload."),
58+
)
5459
.arg(
5560
Arg::new("wait")
5661
.long("wait")
@@ -258,6 +263,7 @@ fn process_sources_from_paths(
258263
};
259264

260265
let mut search = ReleaseFileSearch::new(path.to_path_buf());
266+
search.decompress(matches.is_present("decompress"));
261267

262268
if check_ignore {
263269
search

src/utils/file_search.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ use console::style;
88
use ignore::overrides::OverrideBuilder;
99
use ignore::types::TypesBuilder;
1010
use ignore::WalkBuilder;
11-
use log::info;
11+
use log::{info, warn};
1212

1313
use crate::utils::progress::{ProgressBar, ProgressStyle};
1414

15+
use super::fs::{decompress_gzip_content, is_gzip_compressed};
16+
1517
pub struct ReleaseFileSearch {
1618
path: PathBuf,
1719
extensions: BTreeSet<String>,
1820
ignores: BTreeSet<String>,
1921
ignore_file: Option<String>,
22+
decompress: bool,
2023
}
2124

2225
#[derive(Eq, PartialEq, Hash)]
@@ -33,9 +36,15 @@ impl ReleaseFileSearch {
3336
extensions: BTreeSet::new(),
3437
ignore_file: None,
3538
ignores: BTreeSet::new(),
39+
decompress: false,
3640
}
3741
}
3842

43+
pub fn decompress(&mut self, decompress: bool) -> &mut Self {
44+
self.decompress = decompress;
45+
self
46+
}
47+
3948
pub fn extension<E>(&mut self, extension: E) -> &mut Self
4049
where
4150
E: Into<String>,
@@ -86,9 +95,12 @@ impl ReleaseFileSearch {
8695
}
8796

8897
pub fn collect_file(path: PathBuf) -> Result<ReleaseFileMatch> {
98+
// NOTE: `collect_file` currently do not handle gzip decompression,
99+
// as its mostly used for 3rd tools like xcode, appcenter or gradle.
89100
let mut f = fs::File::open(path.clone())?;
90101
let mut contents = Vec::new();
91102
f.read_to_end(&mut contents)?;
103+
92104
Ok(ReleaseFileMatch {
93105
base_path: path.clone(),
94106
path,
@@ -155,6 +167,13 @@ impl ReleaseFileSearch {
155167
let mut contents = Vec::new();
156168
f.read_to_end(&mut contents)?;
157169

170+
if self.decompress && is_gzip_compressed(&contents) {
171+
contents = decompress_gzip_content(&contents).unwrap_or_else(|_| {
172+
warn!("Could not decompress: {}", file.path().display());
173+
contents
174+
});
175+
}
176+
158177
let file_match = ReleaseFileMatch {
159178
base_path: self.path.clone(),
160179
path: file.path().to_path_buf(),

0 commit comments

Comments
 (0)