diff --git a/Cargo.lock b/Cargo.lock index 3f695bf..83e859c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,17 +27,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - [[package]] name = "ahash" version = "0.8.11" @@ -59,6 +48,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.18" @@ -123,7 +127,7 @@ checksum = "1cb37b529daddddf129612580831db21a538d1aa2798b367039e9316762c81a7" dependencies = [ "anyhow", "byteorder", - "quick-xml", + "quick-xml 0.26.0", "rasn", "rasn-pkix", "roxmltree", @@ -132,16 +136,7 @@ dependencies = [ "sha2", "tracing", "xcommon", - "zip 0.6.6", -] - -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" -dependencies = [ - "derive_arbitrary", + "zip", ] [[package]] @@ -156,6 +151,24 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-compression" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522" +dependencies = [ + "bzip2", + "deflate64", + "flate2", + "futures-core", + "futures-io", + "memchr", + "pin-project-lite", + "xz2", + "zstd", + "zstd-safe", +] + [[package]] name = "async-trait" version = "0.1.86" @@ -198,6 +211,22 @@ dependencies = [ "web-sys", ] +[[package]] +name = "async_zip" +version = "0.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" +dependencies = [ + "async-compression", + "chrono", + "crc32fast", + "futures-lite", + "pin-project", + "thiserror 1.0.69", + "tokio", + "tokio-util", +] + [[package]] name = "atomic-destructor" version = "0.3.0" @@ -386,9 +415,9 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.12+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9" dependencies = [ "cc", "libc", @@ -451,7 +480,10 @@ version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ + "android-tzdata", + "iana-time-zone", "num-traits", + "windows-targets", ] [[package]] @@ -575,12 +607,6 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - [[package]] name = "convert_case" version = "0.6.0" @@ -615,21 +641,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc32fast" version = "1.4.2" @@ -685,26 +696,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] - [[package]] name = "dialoguer" version = "0.11.0" @@ -919,6 +910,30 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -940,6 +955,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1198,6 +1214,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -1500,12 +1539,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - [[package]] name = "log" version = "0.4.25" @@ -1513,13 +1546,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] -name = "lzma-rs" -version = "0.3.0" +name = "lzma-sys" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" dependencies = [ - "byteorder", - "crc", + "cc", + "libc", + "pkg-config", ] [[package]] @@ -1568,6 +1602,7 @@ dependencies = [ "anyhow", "apk", "async-trait", + "async_zip", "clap", "config", "dialoguer", @@ -1575,11 +1610,12 @@ dependencies = [ "indicatif", "log", "nostr-sdk", + "quick-xml 0.37.2", "reqwest", "semver", "serde", + "sha2", "tokio", - "zip 2.2.2", ] [[package]] @@ -1712,12 +1748,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - [[package]] name = "num-integer" version = "0.1.46" @@ -1829,6 +1859,12 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "password-hash" version = "0.5.0" @@ -1925,6 +1961,26 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1995,12 +2051,6 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2029,6 +2079,16 @@ dependencies = [ "serde", ] +[[package]] +name = "quick-xml" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.38" @@ -2176,11 +2236,13 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tokio-util", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "windows-registry", ] @@ -2710,25 +2772,6 @@ dependencies = [ "syn 2.0.98", ] -[[package]] -name = "time" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" -dependencies = [ - "deranged", - "num-conv", - "powerfmt", - "serde", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - [[package]] name = "tiny-keccak" version = "2.0.2" @@ -2846,6 +2889,7 @@ checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", + "futures-io", "futures-sink", "pin-project-lite", "tokio", @@ -3173,6 +3217,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -3202,6 +3259,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-registry" version = "0.2.0" @@ -3368,7 +3434,7 @@ dependencies = [ "rasn-pkix", "rsa", "sha2", - "zip 0.6.6", + "zip", ] [[package]] @@ -3377,6 +3443,15 @@ version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + [[package]] name = "yaml-rust2" version = "0.9.0" @@ -3459,20 +3534,6 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.98", -] [[package]] name = "zerovec" @@ -3508,49 +3569,6 @@ dependencies = [ "flate2", ] -[[package]] -name = "zip" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45" -dependencies = [ - "aes", - "arbitrary", - "bzip2", - "constant_time_eq", - "crc32fast", - "crossbeam-utils", - "deflate64", - "displaydoc", - "flate2", - "hmac", - "indexmap", - "lzma-rs", - "memchr", - "pbkdf2", - "rand", - "sha1", - "thiserror 2.0.11", - "time", - "zeroize", - "zopfli", - "zstd", -] - -[[package]] -name = "zopfli" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" -dependencies = [ - "bumpalo", - "crc32fast", - "lockfree-object-pool", - "log", - "once_cell", - "simd-adler32", -] - [[package]] name = "zstd" version = "0.13.2" diff --git a/Cargo.toml b/Cargo.toml index ed62dbb..262c14f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,13 +16,15 @@ clap = { version = "4.5.28", features = ["derive"] } config = { version = "0.15.7", features = ["yaml"] } log = "0.4.25" nostr-sdk = "0.39.0" -reqwest = { version = "0.12.12", features = ["json"] } +reqwest = { version = "0.12.12", features = ["json", "stream"] } tokio = { version = "1.43.0", features = ["fs", "rt", "macros", "rt-multi-thread"] } serde = { version = "1.0.217", features = ["derive"] } async-trait = "0.1.86" apk = "0.4.0" -zip = "2.2.2" +async_zip = { version = "0.0.17", features = ["full", "tokio"] } semver = "1.0.25" indicatif = "0.17.11" dialoguer = "0.11.0" env_logger = "0.11.6" +sha2 = "0.10.8" +quick-xml = { version = "0.37.2", features = ["serialize"] } diff --git a/src/main.rs b/src/main.rs index 1904eab..de721d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,12 +20,11 @@ struct Args { #[tokio::main] async fn main() -> Result<()> { - env_logger::init(); - // Set default log level to info if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "info"); } + env_logger::init(); let args = Args::parse(); diff --git a/src/repo/github.rs b/src/repo/github.rs index 367cdbf..e995967 100644 --- a/src/repo/github.rs +++ b/src/repo/github.rs @@ -1,4 +1,6 @@ -use crate::repo::{Repo, RepoArtifact, RepoRelease, RepoResource}; +use crate::repo::{ + load_artifact, load_artifact_url, Repo, RepoArtifact, RepoRelease, RepoResource, +}; use anyhow::{anyhow, Result}; use log::{info, warn}; use nostr_sdk::Url; @@ -60,46 +62,6 @@ struct GithubReleaseArtifact { pub browser_download_url: String, } -impl TryFrom<&GithubRelease> for RepoRelease { - type Error = anyhow::Error; - - fn try_from(value: &GithubRelease) -> std::result::Result { - Ok(RepoRelease { - version: Version::parse(if value.tag_name.starts_with("v") { - &value.tag_name[1..] - } else { - &value.tag_name - })?, - artifacts: value - .assets - .iter() - .filter_map(|v| match RepoArtifact::try_from(v) { - Ok(art) => Some(art), - Err(e) => { - warn!("Failed to parse artifact {}: {}", &v.name, e); - None - } - }) - .collect(), - }) - } -} - -impl TryFrom<&GithubReleaseArtifact> for RepoArtifact { - type Error = anyhow::Error; - - fn try_from(value: &GithubReleaseArtifact) -> std::result::Result { - Ok(RepoArtifact { - name: value.name.clone(), - size: value.size, - content_type: value.content_type.clone(), - platform: Platform::IOS, - location: RepoResource::Remote(value.browser_download_url.clone()), - metadata: (), - }) - } -} - #[async_trait::async_trait] impl Repo for GithubRepo { async fn get_releases(&self) -> Result> { @@ -115,16 +77,33 @@ impl Repo for GithubRepo { )) .build()?; - let rsp: Vec = self.client.execute(req).await?.json().await?; - Ok(rsp - .into_iter() - .filter_map(|v| match RepoRelease::try_from(&v) { - Ok(r) => Some(r), - Err(e) => { - warn!("Failed to parse release: {} {}", v.tag_name, e); - None + let gh_release: Vec = self.client.execute(req).await?.json().await?; + + let mut releases = vec![]; + for release in gh_release { + let mut artifacts = vec![]; + for gh_artifact in release.assets { + match load_artifact_url(&gh_artifact.browser_download_url).await { + Ok(a) => artifacts.push(a), + Err(e) => warn!( + "Failed to load artifact {}: {}", + gh_artifact.browser_download_url, e + ), } - }) - .collect()) + } + if artifacts.is_empty() { + warn!("No artifacts found for {}", release.tag_name); + continue; + } + releases.push(RepoRelease { + version: Version::parse(if release.tag_name.starts_with("v") { + &release.tag_name[1..] + } else { + &release.tag_name + })?, + artifacts, + }); + } + Ok(releases) } } diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 8665e31..2bb240d 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,9 +1,20 @@ -use std::path::{Path, PathBuf}; use crate::manifest::Manifest; use crate::repo::github::GithubRepo; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; +use apk::AndroidManifest; +use async_zip::tokio::read::seek::ZipFileReader; +use async_zip::ZipFile; +use log::info; +use nostr_sdk::async_utility::futures_util::TryStreamExt; +use nostr_sdk::prelude::{hex, StreamExt}; +use reqwest::Url; use semver::Version; use serde::Deserialize; +use sha2::Digest; +use std::env::temp_dir; +use std::path::{Path, PathBuf}; +use tokio::fs::File; +use tokio::io::{AsyncWriteExt, BufReader}; mod github; @@ -14,16 +25,11 @@ pub struct RepoArtifact { pub location: RepoResource, pub content_type: String, pub platform: Platform, - pub metadata: ArtifactMetadata + pub metadata: ArtifactMetadata, } pub enum ArtifactMetadata { - APK { - version_code: u32, - min_sdk_version: u32, - target_sdk_version: u32, - sig_hash: String, - } + APK { manifest: AndroidManifest }, } pub enum Platform { @@ -79,10 +85,78 @@ impl TryInto> for &Manifest { } } +/// Download an artifact and create a [RepoArtifact] async fn load_artifact_url(url: &str) -> Result { - + info!("Downloading artifact {}", url); + let u = Url::parse(url)?; + let rsp = reqwest::get(u.clone()).await?; + let id = hex::encode(sha2::Sha256::digest(url.as_bytes())); + let mut tmp = temp_dir().join(id); + tmp.set_extension( + PathBuf::from(u.path()) + .extension() + .ok_or(anyhow!("Missing extension in URL"))? + .to_str() + .unwrap(), + ); + if !tmp.exists() { + let mut tmp_file = File::create(&tmp).await?; + let mut rsp_stream = rsp.bytes_stream(); + while let Some(data) = rsp_stream.next().await { + if let Ok(data) = data { + tmp_file.write_all(&data).await?; + } + } + } + load_artifact(&tmp).await } async fn load_artifact(path: &Path) -> Result { + match path + .extension() + .ok_or(anyhow!("missing file extension"))? + .to_str() + .unwrap() + { + "apk" => load_apk_artifact(path).await, + _ => bail!("unknown file extension"), + } +} -} \ No newline at end of file +async fn load_apk_artifact(path: &Path) -> Result { + let file = File::open(path).await?; + + let mut zip = ZipFileReader::with_tokio(BufReader::new(file)).await?; + + const ANDROID_MANIFEST: &'static str = "AndroidManifest.xml"; + + let idx = zip + .file() + .entries() + .iter() + .enumerate() + .find_map(|(i, entry)| { + if entry.filename().as_bytes() == ANDROID_MANIFEST.as_bytes() { + Some(i) + } else { + None + } + }) + .ok_or(anyhow!("missing AndroidManifest file"))?; + let mut manifest = zip.reader_with_entry(idx).await?; + let mut manifest_data = String::with_capacity(8192); + manifest.read_to_string_checked(&mut manifest_data).await?; + info!("Successfully loaded AndroidManifest: {}", &manifest_data); + let manifest: AndroidManifest = quick_xml::de::from_str(&manifest_data)?; + + Ok(RepoArtifact { + name: path.file_name().unwrap().to_str().unwrap().to_string(), + size: path.metadata()?.len(), + location: RepoResource::Local(path.to_path_buf()), + content_type: "application/apk".to_string(), + platform: Platform::Android { + arch: Architecture::ARMv8, + }, + metadata: ArtifactMetadata::APK { manifest }, + }) +}