@@ -11,15 +11,14 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
1111use indicatif:: ProgressStyle ;
1212use itertools:: Itertools as _;
1313use log:: { debug, info, warn} ;
14- use sha1_smol:: Digest ;
1514use symbolic:: common:: ByteView ;
1615use zip:: write:: SimpleFileOptions ;
1716use zip:: { DateTime , ZipWriter } ;
1817
1918use crate :: api:: { Api , AuthenticatedApi , ChunkUploadCapability } ;
2019use crate :: config:: Config ;
2120use crate :: utils:: args:: ArgExt as _;
22- use crate :: utils:: chunks:: { upload_chunks, Chunk , ASSEMBLE_POLL_INTERVAL } ;
21+ use crate :: utils:: chunks:: { upload_chunks, Chunk } ;
2322use crate :: utils:: fs:: get_sha1_checksums;
2423#[ cfg( all( target_os = "macos" , target_arch = "aarch64" ) ) ]
2524use crate :: utils:: fs:: TempDir ;
@@ -129,9 +128,10 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
129128
130129 let config = Config :: current ( ) ;
131130 let ( org, project) = config. get_org_and_project ( matches) ?;
131+ let base_url = config. get_base_url ( ) ?;
132132
133- let mut uploaded_paths = vec ! [ ] ;
134- let mut errored_paths = vec ! [ ] ;
133+ let mut uploaded_paths_and_ids = vec ! [ ] ;
134+ let mut errored_paths_and_reasons = vec ! [ ] ;
135135 for ( path, zip) in normalized_zips {
136136 info ! ( "Uploading file: {}" , path. display( ) ) ;
137137 let bytes = ByteView :: open ( zip. path ( ) ) ?;
@@ -143,41 +143,50 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
143143 sha. as_deref ( ) ,
144144 build_configuration,
145145 ) {
146- Ok ( _ ) => {
146+ Ok ( artifact_id ) => {
147147 info ! ( "Successfully uploaded file: {}" , path. display( ) ) ;
148- uploaded_paths . push ( path. to_path_buf ( ) ) ;
148+ uploaded_paths_and_ids . push ( ( path. to_path_buf ( ) , artifact_id ) ) ;
149149 }
150150 Err ( e) => {
151151 debug ! ( "Failed to upload file at path {}: {}" , path. display( ) , e) ;
152- errored_paths . push ( path. to_path_buf ( ) ) ;
152+ errored_paths_and_reasons . push ( ( path. to_path_buf ( ) , e ) ) ;
153153 }
154154 }
155155 }
156156
157- if !errored_paths . is_empty ( ) {
157+ if !errored_paths_and_reasons . is_empty ( ) {
158158 warn ! (
159159 "Failed to upload {} file{}:" ,
160- errored_paths. len( ) ,
161- if errored_paths. len( ) == 1 { "" } else { "s" }
160+ errored_paths_and_reasons. len( ) ,
161+ if errored_paths_and_reasons. len( ) == 1 {
162+ ""
163+ } else {
164+ "s"
165+ }
162166 ) ;
163- for path in errored_paths {
164- warn ! ( " - {}" , path. display( ) ) ;
165- }
166- }
167-
168- println ! (
169- "Successfully uploaded {} file{} to Sentry" ,
170- uploaded_paths. len( ) ,
171- if uploaded_paths. len( ) == 1 { "" } else { "s" }
172- ) ;
173- if uploaded_paths. len ( ) < 3 {
174- for path in & uploaded_paths {
175- println ! ( " - {}" , path. display( ) ) ;
167+ for ( path, reason) in errored_paths_and_reasons {
168+ warn ! ( " - {} ({})" , path. display( ) , reason) ;
176169 }
177170 }
178171
179- if uploaded_paths . is_empty ( ) {
172+ if uploaded_paths_and_ids . is_empty ( ) {
180173 bail ! ( "Failed to upload any files" ) ;
174+ } else {
175+ println ! (
176+ "Successfully uploaded {} file{} to Sentry" ,
177+ uploaded_paths_and_ids. len( ) ,
178+ if uploaded_paths_and_ids. len( ) == 1 {
179+ ""
180+ } else {
181+ "s"
182+ }
183+ ) ;
184+ if uploaded_paths_and_ids. len ( ) < 3 {
185+ for ( path, artifact_id) in & uploaded_paths_and_ids {
186+ let url = format ! ( "{base_url}/{org}/preprod/{project}/{artifact_id}" ) ;
187+ println ! ( " - {} {url}" , path. display( ) ) ;
188+ }
189+ }
181190 }
182191 Ok ( ( ) )
183192}
@@ -337,14 +346,15 @@ fn normalize_directory(path: &Path) -> Result<TempFile> {
337346 Ok ( temp_file)
338347}
339348
349+ /// Returns artifact id if upload was successful.
340350fn upload_file (
341351 api : & AuthenticatedApi ,
342352 bytes : & [ u8 ] ,
343353 org : & str ,
344354 project : & str ,
345355 sha : Option < & str > ,
346356 build_configuration : Option < & str > ,
347- ) -> Result < ( ) > {
357+ ) -> Result < String > {
348358 const SELF_HOSTED_ERROR_HINT : & str = "If you are using a self-hosted Sentry server, \
349359 update to the latest version of Sentry to use the mobile-app upload command.";
350360
@@ -371,7 +381,7 @@ fn upload_file(
371381 }
372382
373383 let progress_style =
374- ProgressStyle :: default_spinner ( ) . template ( "{spinner} Optimizing bundle for upload..." ) ;
384+ ProgressStyle :: default_spinner ( ) . template ( "{spinner} Preparing for upload..." ) ;
375385 let pb = ProgressBar :: new_spinner ( ) ;
376386 pb. enable_steady_tick ( 100 ) ;
377387 pb. set_style ( progress_style) ;
@@ -384,76 +394,46 @@ fn upload_file(
384394 . map ( |( data, checksum) | Chunk ( ( * checksum, data) ) )
385395 . collect :: < Vec < _ > > ( ) ;
386396
387- pb. finish_with_duration ( "Finishing upload" ) ;
388-
389- let response =
390- api. assemble_mobile_app ( org, project, checksum, & checksums, sha, build_configuration) ?;
391- chunks. retain ( |Chunk ( ( digest, _) ) | response. missing_chunks . contains ( digest) ) ;
392-
393- if !chunks. is_empty ( ) {
394- let upload_progress_style = ProgressStyle :: default_bar ( ) . template (
395- "{prefix:.dim} Uploading files...\
396- \n {wide_bar} {bytes}/{total_bytes} ({eta})",
397- ) ;
398- upload_chunks ( & chunks, & chunk_upload_options, upload_progress_style) ?;
399- } else {
400- println ! ( "Nothing to upload, all files are on the server" ) ;
401- }
402-
403- poll_assemble (
404- api,
405- checksum,
406- & checksums,
407- org,
408- project,
409- sha,
410- build_configuration,
411- ) ?;
412- Ok ( ( ) )
413- }
414-
415- fn poll_assemble (
416- api : & AuthenticatedApi ,
417- checksum : Digest ,
418- chunks : & [ Digest ] ,
419- org : & str ,
420- project : & str ,
421- sha : Option < & str > ,
422- build_configuration : Option < & str > ,
423- ) -> Result < ( ) > {
424- debug ! ( "Polling assemble for checksum: {}" , checksum) ;
425-
426- let progress_style = ProgressStyle :: default_spinner ( ) . template ( "{spinner} Processing files..." ) ;
427- let pb = ProgressBar :: new_spinner ( ) ;
428-
429- pb. enable_steady_tick ( 100 ) ;
430- pb. set_style ( progress_style) ;
431-
432- let response = loop {
397+ pb. finish_with_duration ( "Preparing for upload" ) ;
398+
399+ // In the normal case we go through this loop exactly twice:
400+ // 1. state=not_found
401+ // server tells us the we need to send every chunk and we do so
402+ // 2. artifact_id set so we're done (likely state=created)
403+ //
404+ // In the case where all the chunks are already on the server we go
405+ // through only once:
406+ // 1. state=ok, artifact_id set
407+ //
408+ // In the case where something went wrong (which could be on either
409+ // iteration of the loop) we get:
410+ // n. state=err, artifact_id unset
411+ let result = loop {
433412 let response =
434- api. assemble_mobile_app ( org, project, checksum, chunks, sha, build_configuration) ?;
413+ api. assemble_mobile_app ( org, project, checksum, & checksums, sha, build_configuration) ?;
414+ chunks. retain ( |Chunk ( ( digest, _) ) | response. missing_chunks . contains ( digest) ) ;
415+
416+ if !chunks. is_empty ( ) {
417+ let upload_progress_style = ProgressStyle :: default_bar ( ) . template (
418+ "{prefix:.dim} Uploading files...\
419+ \n {wide_bar} {bytes}/{total_bytes} ({eta})",
420+ ) ;
421+ upload_chunks ( & chunks, & chunk_upload_options, upload_progress_style) ?;
422+ }
435423
436- if response. state . is_finished ( ) {
437- break response;
424+ // is_err() is not_found or error and is_finished() is ok or error, so if both the the
425+ // state is error
426+ if response. state . is_finished ( ) && response. state . is_err ( ) {
427+ let message = response. detail . as_deref ( ) . unwrap_or ( "unknown error" ) ;
428+ bail ! ( "Failed to process uploaded files: {}" , message) ;
438429 }
439430
440- std:: thread:: sleep ( ASSEMBLE_POLL_INTERVAL ) ;
431+ if let Some ( artifact_id) = response. artifact_id {
432+ break Ok ( artifact_id) ;
433+ }
441434 } ;
442435
443- pb. finish_with_duration ( "Processing" ) ;
444-
445- if response. state . is_err ( ) {
446- let message = response. detail . as_deref ( ) . unwrap_or ( "unknown error" ) ;
447- bail ! ( "Failed to process uploaded files: {}" , message) ;
448- }
449-
450- if response. state . is_pending ( ) {
451- info ! ( "File upload complete (processing pending on server)" ) ;
452- } else {
453- info ! ( "File processing complete" ) ;
454- }
455-
456- Ok ( ( ) )
436+ result
457437}
458438
459439#[ cfg( not( windows) ) ]
0 commit comments