Files
BlueArchiveToolkit/infrastructure/src/official_pull.rs
T

285 lines
9.7 KiB
Rust

//! Official JP resource pull planning.
use bat_adapters::official::inventory::{
YostarJpDownloadInventory, YostarJpPlatformDownloadInventory,
};
use bat_adapters::official::yostar_jp::{
verified_official_platforms, PatchPlatform, YostarJpResourceDiscoveryPlan, YostarJpResourceRoot,
};
use std::collections::HashSet;
/// Full official JP pull plan, covering discovery endpoints and concrete
/// resource downloads.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OfficialResourcePullPlan {
/// Seed URLs and selected root from server-info.
pub discovery: YostarJpResourceDiscoveryPlan,
/// Concrete file inventory extracted from the catalog bytes.
pub inventory: YostarJpPlatformDownloadInventory,
/// Platforms that will be pulled.
pub platforms: Vec<PatchPlatform>,
}
impl OfficialResourcePullPlan {
/// Creates a pull plan for the requested platforms.
pub fn new(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpDownloadInventory,
platforms: &[PatchPlatform],
) -> Self {
Self::new_platform_inventory(
discovery,
YostarJpPlatformDownloadInventory::from_shared_inventory(inventory, platforms),
platforms,
)
}
/// Creates a pull plan for the requested platforms using platform-specific
/// bundle/media catalogs.
pub fn new_platform_inventory(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpPlatformDownloadInventory,
platforms: &[PatchPlatform],
) -> Self {
Self {
discovery,
inventory,
platforms: unique_platforms(platforms),
}
}
/// Creates a pull plan for the verified official JP platforms.
pub fn verified(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpDownloadInventory,
) -> Self {
Self::new(discovery, inventory, &verified_official_platforms())
}
/// Creates a pull plan for the verified official JP platforms using
/// platform-specific bundle/media catalogs.
pub fn verified_platform_inventory(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpPlatformDownloadInventory,
) -> Self {
Self::new_platform_inventory(discovery, inventory, &verified_official_platforms())
}
/// Returns all discovery URLs in server-provided order.
pub fn discovery_urls(&self) -> Vec<String> {
self.discovery
.endpoints
.iter()
.map(|endpoint| endpoint.url.clone())
.collect()
}
/// Returns the validated official resource root used by the pull plan.
pub fn resource_root(&self) -> Result<YostarJpResourceRoot, String> {
YostarJpResourceRoot::from_addressables_root(&self.discovery.addressables_root)
}
/// Returns all concrete download URLs for the selected platforms.
pub fn content_urls(&self) -> Result<Vec<String>, String> {
let root = self.resource_root()?;
self.inventory
.direct_download_urls_for_platforms(&root, &self.platforms)
}
/// Returns discovery URLs followed by concrete content URLs.
pub fn all_urls(&self) -> Result<Vec<String>, String> {
let mut urls = self.discovery_urls();
let mut seen = urls.iter().cloned().collect::<HashSet<_>>();
for url in self.content_urls()? {
if seen.insert(url.clone()) {
urls.push(url);
}
}
Ok(urls)
}
}
/// Builds a pull plan for the verified official JP platforms.
pub fn build_official_pull_plan(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpDownloadInventory,
) -> OfficialResourcePullPlan {
OfficialResourcePullPlan::verified(discovery, inventory)
}
/// Builds a pull plan for the verified official JP platforms using
/// platform-specific bundle/media catalogs.
pub fn build_official_pull_plan_from_platform_inventory(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpPlatformDownloadInventory,
) -> OfficialResourcePullPlan {
OfficialResourcePullPlan::verified_platform_inventory(discovery, inventory)
}
/// Builds a pull plan for the requested platforms.
pub fn build_official_pull_plan_for_platforms(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpDownloadInventory,
platforms: &[PatchPlatform],
) -> OfficialResourcePullPlan {
OfficialResourcePullPlan::new(discovery, inventory, platforms)
}
/// Builds a pull plan for the requested platforms using platform-specific
/// bundle/media catalogs.
pub fn build_official_pull_plan_for_platform_inventory(
discovery: YostarJpResourceDiscoveryPlan,
inventory: YostarJpPlatformDownloadInventory,
platforms: &[PatchPlatform],
) -> OfficialResourcePullPlan {
OfficialResourcePullPlan::new_platform_inventory(discovery, inventory, platforms)
}
fn unique_platforms(platforms: &[PatchPlatform]) -> Vec<PatchPlatform> {
platforms
.iter()
.copied()
.fold(Vec::new(), |mut unique, platform| {
if !unique.contains(&platform) {
unique.push(platform);
}
unique
})
}
#[cfg(test)]
mod tests {
use super::*;
use bat_adapters::official::inventory::{
YostarJpDownloadInventory, YostarJpPlatformCatalogInventory,
YostarJpPlatformDownloadInventory,
};
use bat_adapters::official::yostar_jp::{
YostarJpResourceEndpoint, YostarJpResourceEndpointKind,
};
fn discovery_plan() -> YostarJpResourceDiscoveryPlan {
YostarJpResourceDiscoveryPlan {
connection_group_name: "Prod-Audit".to_string(),
app_version: "1.70.0".to_string(),
bundle_version: Some("s8tloc7lo3".to_string()),
addressables_root:
"https://prod-clientpatch.bluearchiveyostar.com/r93_token".to_string(),
endpoints: vec![
YostarJpResourceEndpoint {
kind: YostarJpResourceEndpointKind::TableCatalog,
platform: None,
url: "https://prod-clientpatch.bluearchiveyostar.com/r93_token/TableBundles/TableCatalog.bytes".to_string(),
},
YostarJpResourceEndpoint {
kind: YostarJpResourceEndpointKind::TableCatalogHash,
platform: None,
url: "https://prod-clientpatch.bluearchiveyostar.com/r93_token/TableBundles/TableCatalog.hash".to_string(),
},
],
}
}
fn inventory() -> YostarJpDownloadInventory {
YostarJpDownloadInventory::from_catalog_bytes(
b"FullPatch_000.zip",
b"ExcelDB.db ExcelDB.db",
b"JP_Airi.zip",
)
}
fn platform_inventory() -> YostarJpPlatformDownloadInventory {
YostarJpPlatformDownloadInventory::from_catalog_bytes(
b"ExcelDB.db ExcelDB.db",
vec![
YostarJpPlatformCatalogInventory::from_catalog_bytes(
PatchPlatform::Windows,
b"FullPatch_000.zip",
b"JP_Airi_Win.zip",
),
YostarJpPlatformCatalogInventory::from_catalog_bytes(
PatchPlatform::Android,
b"FullPatch_001.zip",
b"JP_Airi_Android.zip",
),
],
)
}
#[test]
fn builds_verified_pull_plan_with_all_urls() {
let plan = build_official_pull_plan(discovery_plan(), inventory());
assert_eq!(plan.platforms, verified_official_platforms());
assert_eq!(plan.discovery_urls().len(), 2);
let content_urls = plan.content_urls().unwrap();
assert!(content_urls
.iter()
.any(|url| url.contains("/Windows_PatchPack/FullPatch_000.zip")));
assert!(content_urls
.iter()
.any(|url| url.contains("/Android_PatchPack/FullPatch_000.zip")));
assert_eq!(content_urls.len(), 5);
let all_urls = plan.all_urls().unwrap();
assert_eq!(all_urls.len(), 7);
assert!(all_urls[0].ends_with("TableCatalog.bytes"));
assert!(all_urls
.iter()
.any(|url| url.ends_with("/MediaResources-Windows/JP_Airi.zip")));
assert_eq!(
all_urls
.iter()
.filter(|url| url.ends_with("/MediaResources/JP_Airi.zip"))
.count(),
1
);
}
#[test]
fn deduplicates_requested_platforms() {
let plan = build_official_pull_plan_for_platforms(
discovery_plan(),
inventory(),
&[PatchPlatform::Windows, PatchPlatform::Windows],
);
assert_eq!(plan.platforms, vec![PatchPlatform::Windows]);
}
#[test]
fn verified_pull_plan_is_windows_and_android_only() {
let plan = build_official_pull_plan(discovery_plan(), inventory());
assert_eq!(
plan.platforms,
vec![PatchPlatform::Windows, PatchPlatform::Android]
);
}
#[test]
fn builds_platform_inventory_pull_plan_without_cross_mixing_catalogs() {
let plan = build_official_pull_plan_from_platform_inventory(
discovery_plan(),
platform_inventory(),
);
let all_urls = plan.all_urls().unwrap();
assert_eq!(plan.platforms, verified_official_platforms());
assert_eq!(all_urls.len(), 7);
assert!(all_urls
.iter()
.any(|url| url.ends_with("/Windows_PatchPack/FullPatch_000.zip")));
assert!(all_urls
.iter()
.any(|url| url.ends_with("/Android_PatchPack/FullPatch_001.zip")));
assert!(!all_urls
.iter()
.any(|url| url.ends_with("/Android_PatchPack/FullPatch_000.zip")));
}
}