try parse manifest
This commit is contained in:
@ -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();
|
||||
|
||||
|
@ -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<Self, Self::Error> {
|
||||
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<Self, Self::Error> {
|
||||
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<Vec<RepoRelease>> {
|
||||
@ -115,16 +77,33 @@ impl Repo for GithubRepo {
|
||||
))
|
||||
.build()?;
|
||||
|
||||
let rsp: Vec<GithubRelease> = 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<GithubRelease> = 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)
|
||||
}
|
||||
}
|
||||
|
@ -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<Box<dyn Repo>> for &Manifest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Download an artifact and create a [RepoArtifact]
|
||||
async fn load_artifact_url(url: &str) -> Result<RepoArtifact> {
|
||||
|
||||
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<RepoArtifact> {
|
||||
match path
|
||||
.extension()
|
||||
.ok_or(anyhow!("missing file extension"))?
|
||||
.to_str()
|
||||
.unwrap()
|
||||
{
|
||||
"apk" => load_apk_artifact(path).await,
|
||||
_ => bail!("unknown file extension"),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
async fn load_apk_artifact(path: &Path) -> Result<RepoArtifact> {
|
||||
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 },
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user