11use std:: borrow:: Cow ;
2- #[ cfg( not( windows) ) ]
3- use std:: fs;
42use std:: io:: Write as _;
5- #[ cfg( not( windows) ) ]
6- use std:: os:: unix:: fs:: PermissionsExt as _;
7- use std:: path:: { Path , PathBuf } ;
3+ use std:: path:: Path ;
84
95use anyhow:: { anyhow, bail, Context as _, Result } ;
106use clap:: { Arg , ArgAction , ArgMatches , Command } ;
117use indicatif:: ProgressStyle ;
12- use itertools:: Itertools as _;
138use log:: { debug, info, warn} ;
149use sha1_smol:: Digest ;
1510use symbolic:: common:: ByteView ;
16- use walkdir:: WalkDir ;
1711use zip:: write:: SimpleFileOptions ;
1812use zip:: { DateTime , ZipWriter } ;
1913
@@ -28,7 +22,7 @@ use crate::utils::fs::TempFile;
2822use crate :: utils:: mobile_app:: {
2923 handle_asset_catalogs, ipa_to_xcarchive, is_apple_app, is_ipa_file,
3024} ;
31- use crate :: utils:: mobile_app:: { is_aab_file, is_apk_file, is_zip_file} ;
25+ use crate :: utils:: mobile_app:: { is_aab_file, is_apk_file, is_zip_file, normalize_directory } ;
3226use crate :: utils:: progress:: ProgressBar ;
3327use crate :: utils:: vcs;
3428
@@ -271,22 +265,6 @@ fn normalize_file(path: &Path, bytes: &[u8]) -> Result<TempFile> {
271265 Ok ( temp_file)
272266}
273267
274- fn sort_entries ( path : & Path ) -> Result < std:: vec:: IntoIter < ( PathBuf , PathBuf ) > > {
275- Ok ( WalkDir :: new ( path)
276- . follow_links ( true )
277- . into_iter ( )
278- . filter_map ( Result :: ok)
279- . filter ( |entry| entry. path ( ) . is_file ( ) )
280- . map ( |entry| {
281- let entry_path = entry. into_path ( ) ;
282- let relative_path = entry_path. strip_prefix ( path) ?. to_owned ( ) ;
283- Ok ( ( entry_path, relative_path) )
284- } )
285- . collect :: < Result < Vec < _ > > > ( ) ?
286- . into_iter ( )
287- . sorted_by ( |( _, a) , ( _, b) | a. cmp ( b) ) )
288- }
289-
290268fn handle_directory ( path : & Path ) -> Result < TempFile > {
291269 let temp_dir = TempDir :: create ( ) ?;
292270 #[ cfg( all( target_os = "macos" , target_arch = "aarch64" ) ) ]
@@ -296,78 +274,6 @@ fn handle_directory(path: &Path) -> Result<TempFile> {
296274 normalize_directory ( path, temp_dir. path ( ) )
297275}
298276
299- // For XCArchive directories, we'll zip the entire directory
300- fn normalize_directory ( path : & Path , parsed_assets_path : & Path ) -> Result < TempFile > {
301- debug ! ( "Creating normalized zip for directory: {}" , path. display( ) ) ;
302-
303- let temp_file = TempFile :: create ( ) ?;
304- let mut zip = ZipWriter :: new ( temp_file. open ( ) ?) ;
305-
306- let mut file_count = 0 ;
307- let directory_name = path. file_name ( ) . expect ( "Failed to get basename" ) ;
308-
309- // Collect and sort entries for deterministic ordering
310- // This is important to ensure stable sha1 checksums for the zip file as
311- // an optimization is used to avoid re-uploading the same chunks if they're already on the server.
312- let entries = sort_entries ( path) ?;
313-
314- // Need to set the last modified time to a fixed value to ensure consistent checksums
315- // This is important as an optimization to avoid re-uploading the same chunks if they're already on the server
316- // but the last modified time being different will cause checksums to be different.
317- let options = SimpleFileOptions :: default ( )
318- . compression_method ( zip:: CompressionMethod :: Stored )
319- . last_modified_time ( DateTime :: default ( ) ) ;
320-
321- for ( entry_path, relative_path) in entries {
322- let zip_path = format ! (
323- "{}/{}" ,
324- directory_name. to_string_lossy( ) ,
325- relative_path. to_string_lossy( )
326- ) ;
327- debug ! ( "Adding file to zip: {}" , zip_path) ;
328-
329- #[ cfg( not( windows) ) ]
330- // On Unix, we need to preserve the file permissions.
331- let options = options. unix_permissions ( fs:: metadata ( & entry_path) ?. permissions ( ) . mode ( ) ) ;
332-
333- zip. start_file ( zip_path, options) ?;
334- let file_byteview = ByteView :: open ( & entry_path) ?;
335- zip. write_all ( file_byteview. as_slice ( ) ) ?;
336- file_count += 1 ;
337- }
338-
339- // Add parsed assets to the zip in a "ParsedAssets" directory
340- if parsed_assets_path. exists ( ) {
341- debug ! (
342- "Adding parsed assets from: {}" ,
343- parsed_assets_path. display( )
344- ) ;
345-
346- let parsed_assets_entries = sort_entries ( parsed_assets_path) ?;
347-
348- for ( entry_path, relative_path) in parsed_assets_entries {
349- let zip_path = format ! (
350- "{}/ParsedAssets/{}" ,
351- directory_name. to_string_lossy( ) ,
352- relative_path. to_string_lossy( )
353- ) ;
354- debug ! ( "Adding parsed asset to zip: {}" , zip_path) ;
355-
356- zip. start_file ( zip_path, options) ?;
357- let file_byteview = ByteView :: open ( & entry_path) ?;
358- zip. write_all ( file_byteview. as_slice ( ) ) ?;
359- file_count += 1 ;
360- }
361- }
362-
363- zip. finish ( ) ?;
364- debug ! (
365- "Successfully created normalized zip for directory with {} files" ,
366- file_count
367- ) ;
368- Ok ( temp_file)
369- }
370-
371277fn upload_file (
372278 api : & AuthenticatedApi ,
373279 bytes : & [ u8 ] ,
0 commit comments