Skip to content

Commit c25b132

Browse files
committed
PR feedback
1 parent 747a231 commit c25b132

File tree

5 files changed

+102
-122
lines changed

5 files changed

+102
-122
lines changed

src/commands/mobile_app/upload.rs

Lines changed: 2 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
use std::borrow::Cow;
22
#[cfg(not(windows))]
3-
use std::fs;
43
use std::io::Write as _;
54
#[cfg(not(windows))]
6-
use std::os::unix::fs::PermissionsExt as _;
7-
use std::path::{Path, PathBuf};
5+
use std::path::Path;
86

97
use anyhow::{anyhow, bail, Context as _, Result};
108
use clap::{Arg, ArgAction, ArgMatches, Command};
119
use indicatif::ProgressStyle;
12-
use itertools::Itertools as _;
1310
use log::{debug, info, warn};
1411
use sha1_smol::Digest;
1512
use symbolic::common::ByteView;
16-
use walkdir::WalkDir;
1713
use zip::write::SimpleFileOptions;
1814
use zip::{DateTime, ZipWriter};
1915

@@ -26,7 +22,7 @@ use crate::utils::fs::TempDir;
2622
use crate::utils::fs::TempFile;
2723
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
2824
use crate::utils::mobile_app::{
29-
handle_asset_catalogs, ipa_to_xcarchive, is_apple_app, is_ipa_file,
25+
handle_asset_catalogs, ipa_to_xcarchive, is_apple_app, is_ipa_file, normalize_directory,
3026
};
3127
use crate::utils::mobile_app::{is_aab_file, is_apk_file, is_zip_file};
3228
use crate::utils::progress::ProgressBar;
@@ -271,22 +267,6 @@ fn normalize_file(path: &Path, bytes: &[u8]) -> Result<TempFile> {
271267
Ok(temp_file)
272268
}
273269

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-
290270
fn handle_directory(path: &Path) -> Result<TempFile> {
291271
let temp_dir = TempDir::create()?;
292272
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
@@ -296,78 +276,6 @@ fn handle_directory(path: &Path) -> Result<TempFile> {
296276
normalize_directory(path, temp_dir.path())
297277
}
298278

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-
371279
fn upload_file(
372280
api: &AuthenticatedApi,
373281
bytes: &[u8],

src/utils/mobile_app/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
44
mod apple;
5+
mod normalize;
56
mod validation;
67

78
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
89
pub use self::apple::{handle_asset_catalogs, ipa_to_xcarchive};
10+
pub use self::normalize::normalize_directory;
911
pub use self::validation::{is_aab_file, is_apk_file, is_zip_file};
1012
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
1113
pub use self::validation::{is_apple_app, is_ipa_file};

src/utils/mobile_app/normalize.rs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
use std::fs::File;
2+
use std::io::Write as _;
3+
use std::os::unix::fs::PermissionsExt as _;
4+
use std::path::{Path, PathBuf};
5+
6+
use crate::utils::fs::TempFile;
7+
use anyhow::Result;
8+
use itertools::Itertools as _;
9+
use log::debug;
10+
use std::fs;
11+
use symbolic::common::ByteView;
12+
use walkdir::WalkDir;
13+
use zip::write::SimpleFileOptions;
14+
use zip::{DateTime, ZipWriter};
15+
16+
fn sort_entries(path: &Path) -> Result<impl Iterator<Item = (PathBuf, PathBuf)>> {
17+
Ok(WalkDir::new(path)
18+
.follow_links(true)
19+
.into_iter()
20+
.filter_map(Result::ok)
21+
.filter(|entry| entry.path().is_file())
22+
.map(|entry| {
23+
let entry_path = entry.into_path();
24+
let relative_path = entry_path.strip_prefix(path)?.to_owned();
25+
Ok((entry_path, relative_path))
26+
})
27+
.collect::<Result<Vec<_>>>()?
28+
.into_iter()
29+
.sorted_by(|(_, a), (_, b)| a.cmp(b)))
30+
}
31+
32+
fn add_entries_to_zip(
33+
zip: &mut ZipWriter<File>,
34+
entries: impl Iterator<Item = (PathBuf, PathBuf)>,
35+
directory_name: &str,
36+
) -> Result<i32> {
37+
let mut file_count = 0;
38+
39+
// Need to set the last modified time to a fixed value to ensure consistent checksums
40+
// This is important as an optimization to avoid re-uploading the same chunks if they're already on the server
41+
// but the last modified time being different will cause checksums to be different.
42+
let options = SimpleFileOptions::default()
43+
.compression_method(zip::CompressionMethod::Stored)
44+
.last_modified_time(DateTime::default());
45+
46+
for (entry_path, relative_path) in entries {
47+
#[cfg(not(windows))]
48+
// On Unix, we need to preserve the file permissions.
49+
let options = options.unix_permissions(fs::metadata(&entry_path)?.permissions().mode());
50+
51+
let zip_path = format!("{}/{}", directory_name, relative_path.to_string_lossy());
52+
53+
zip.start_file(zip_path, options)?;
54+
let file_byteview = ByteView::open(&entry_path)?;
55+
zip.write_all(file_byteview.as_slice())?;
56+
file_count += 1;
57+
}
58+
59+
Ok(file_count)
60+
}
61+
62+
// For XCArchive directories, we'll zip the entire directory
63+
pub fn normalize_directory(path: &Path, parsed_assets_path: &Path) -> Result<TempFile> {
64+
debug!("Creating normalized zip for directory: {}", path.display());
65+
66+
let temp_file = TempFile::create()?;
67+
let mut zip = ZipWriter::new(temp_file.open()?);
68+
69+
let directory_name = path.file_name().expect("Failed to get basename");
70+
71+
// Collect and sort entries for deterministic ordering
72+
// This is important to ensure stable sha1 checksums for the zip file as
73+
// an optimization is used to avoid re-uploading the same chunks if they're already on the server.
74+
let entries = sort_entries(path)?;
75+
let mut file_count = add_entries_to_zip(&mut zip, entries, &directory_name.to_string_lossy())?;
76+
77+
// Add parsed assets to the zip in a "ParsedAssets" directory
78+
if parsed_assets_path.exists() {
79+
debug!(
80+
"Adding parsed assets from: {}",
81+
parsed_assets_path.display()
82+
);
83+
84+
let parsed_assets_entries = sort_entries(parsed_assets_path)?;
85+
file_count += add_entries_to_zip(
86+
&mut zip,
87+
parsed_assets_entries,
88+
&format!("{}/ParsedAssets", directory_name.to_string_lossy()),
89+
)?;
90+
}
91+
92+
zip.finish()?;
93+
debug!(
94+
"Successfully created normalized zip for directory with {} files",
95+
file_count
96+
);
97+
Ok(temp_file)
98+
}

tests/integration/_cases/mobile_app/mobile_app-upload-xcarchive-all-uploaded.trycmd

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/integration/mobile_app/upload.rs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -92,25 +92,6 @@ fn command_mobile_app_upload_apk_all_uploaded() {
9292
.with_default_token();
9393
}
9494

95-
#[test]
96-
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
97-
fn command_mobile_app_upload_xcarchive_all_uploaded() {
98-
TestManager::new()
99-
.mock_endpoint(
100-
MockEndpointBuilder::new("GET", "/api/0/organizations/wat-org/chunk-upload/")
101-
.with_response_file("mobile_app/get-chunk-upload.json"),
102-
)
103-
.mock_endpoint(
104-
MockEndpointBuilder::new(
105-
"POST",
106-
"/api/0/projects/wat-org/wat-project/files/preprodartifacts/assemble/",
107-
)
108-
.with_response_body(r#"{"state":"ok","missingChunks":[]}"#),
109-
)
110-
.register_trycmd_test("mobile_app/mobile_app-upload-xcarchive-all-uploaded.trycmd")
111-
.with_default_token();
112-
}
113-
11495
/// This regex is used to extract the boundary from the content-type header.
11596
/// We need to match the boundary, since it changes with each request.
11697
/// The regex matches the format as specified in

0 commit comments

Comments
 (0)