Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ jobs:
- uses: actions/checkout@v3
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build --verbose
- run: cargo test --verbose
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dev-dependencies]
mockall = "0.13"

[dependencies]
actix-governor = "0.5"
actix-web = "4.8"
Expand Down
4 changes: 3 additions & 1 deletion src/app_data.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use cached::TimedCache;
use octocrab::Octocrab;
use tokio::sync::Mutex;

use crate::fetcher::Fetcher;
use crate::fetcher::HttpChecksumFetcher;
use crate::routes::version::CachedReleased;

pub struct AppData {
pub cache: Mutex<TimedCache<&'static str, CachedReleased>>,
pub fetcher: Fetcher,
pub fetcher: Fetcher<Octocrab, HttpChecksumFetcher>,
}
45 changes: 45 additions & 0 deletions src/fetcher/checksum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#[cfg(test)]
use mockall::automock;

use crate::errors::{InternalError, Result};
use crate::game_data::Asset;

#[cfg_attr(test, automock)]
pub trait ChecksumFetcher {
async fn resolve_asset(&self, asset: &Asset) -> Result<String>;
}

pub struct HttpChecksumFetcher(reqwest::Client);

impl HttpChecksumFetcher {
pub fn new() -> Self {
Self(reqwest::Client::new())
}

fn parse_response(&self, asset_name: &str, response: &str) -> Result<String> {
let parts: Vec<_> = response.split_whitespace().collect();
if parts.len() != 2 {
return Err(InternalError::InvalidSha256(parts.len()));
}

let (sha256, filename) = (parts[0], parts[1]);
match !filename.starts_with('*') || &filename[1..] != asset_name {
false => Ok(sha256.to_string()),
true => Err(InternalError::WrongChecksum),
}
}
}

impl ChecksumFetcher for HttpChecksumFetcher {
async fn resolve_asset(&self, asset: &Asset) -> Result<String> {
let response = self
.0
.get(format!("{}.sha256", asset.download_url))
.send()
.await?
.text()
.await?;

self.parse_response(asset.name.as_str(), response.as_str())
}
}
86 changes: 30 additions & 56 deletions src/fetcher.rs → src/fetcher/mod.rs
Original file line number Diff line number Diff line change
@@ -1,50 +1,55 @@
pub use checksum::{ChecksumFetcher, HttpChecksumFetcher};
use futures::future::join_all;
use octocrab::models::repos;
use octocrab::repos::RepoHandler;
use octocrab::{Octocrab, OctocrabBuilder};
pub use repo::RepoFetcher;
use semver::Version;

use crate::config::ApiConfig;
use crate::errors::{InternalError, Result};
use crate::game_data::{Asset, Assets, GameRelease, Repo};

pub struct Fetcher {
octocrab: Octocrab,
mod checksum;
mod repo;
#[cfg(test)]
mod tests;

pub struct Fetcher<F: RepoFetcher, C: ChecksumFetcher> {
game_repo: Repo,
updater_repo: Repo,

checksum_fetcher: ChecksumFetcher,
repo_fetcher: F,
checksum_fetcher: C,
}

struct ChecksumFetcher(reqwest::Client);

impl Fetcher {
impl Fetcher<Octocrab, HttpChecksumFetcher> {
pub fn from_config(config: &ApiConfig) -> Result<Self> {
let mut octocrab = OctocrabBuilder::default();
if let Some(github_pat) = &config.github_pat {
octocrab = octocrab.personal_token(github_pat.unsecure().to_string());
}

Ok(Self {
octocrab: octocrab.build()?,
game_repo: Repo::new(&config.repo_owner, &config.game_repository),
updater_repo: Repo::new(&config.repo_owner, &config.updater_repository),

checksum_fetcher: ChecksumFetcher::new(),
})
Ok(Self::new(
Repo::new(&config.repo_owner, &config.game_repository),
Repo::new(&config.repo_owner, &config.updater_repository),
octocrab.build()?,
HttpChecksumFetcher::new(),
))
}
}

fn on_repo(&self, repo: &Repo) -> RepoHandler<'_> {
self.octocrab.repos(repo.owner(), repo.repository())
impl<F: RepoFetcher, C: ChecksumFetcher> Fetcher<F, C> {
pub fn new(game_repo: Repo, updater_repo: Repo, repo_fetcher: F, checksum_fetcher: C) -> Self {
Self {
game_repo,
updater_repo,
repo_fetcher,
checksum_fetcher,
}
}

pub async fn get_latest_game_release(&self) -> Result<GameRelease> {
let releases = self
.on_repo(&self.game_repo)
.releases()
.list()
.send()
.await?;
let releases = self.repo_fetcher.get_releases(&self.game_repo).await?;

let mut versions_released = releases
.into_iter()
Expand Down Expand Up @@ -103,9 +108,8 @@ impl Fetcher {

pub async fn get_latest_updater_release(&self) -> Result<Assets> {
let last_release = self
.on_repo(&self.updater_repo)
.releases()
.get_latest()
.repo_fetcher
.get_last_release(&self.updater_repo)
.await?;

let version = Version::parse(&last_release.tag_name)?;
Expand Down Expand Up @@ -151,44 +155,14 @@ impl Fetcher {
let checksums = join_all(
assets
.iter()
.map(|(_, asset)| self.checksum_fetcher.resolve(asset)),
.map(|(_, asset)| self.checksum_fetcher.resolve_asset(asset)),
)
.await;

assets.into_iter().zip(checksums)
}
}

impl ChecksumFetcher {
fn new() -> Self {
Self(reqwest::Client::new())
}

async fn resolve(&self, asset: &Asset) -> Result<String> {
let response = self
.0
.get(format!("{}.sha256", asset.download_url))
.send()
.await?
.text()
.await?;
self.parse_response(asset.name.as_str(), response.as_str())
}

fn parse_response(&self, asset_name: &str, response: &str) -> Result<String> {
let parts: Vec<_> = response.split_whitespace().collect();
if parts.len() != 2 {
return Err(InternalError::InvalidSha256(parts.len()));
}

let (sha256, filename) = (parts[0], parts[1]);
match !filename.starts_with('*') || &filename[1..] != asset_name {
false => Ok(sha256.to_string()),
true => Err(InternalError::WrongChecksum),
}
}
}

fn remove_game_suffix(asset_name: &str) -> &str {
let platform = asset_name
.find('.')
Expand Down
37 changes: 37 additions & 0 deletions src/fetcher/repo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#[cfg(test)]
use mockall::automock;
use octocrab::models::repos::Release;
use octocrab::Octocrab;

use crate::errors::{InternalError, Result};
use crate::game_data::Repo;

#[cfg_attr(test, automock(type Pager = Vec<Release>;))]
pub trait RepoFetcher {
type Pager: IntoIterator<Item = Release>;

async fn get_releases(&self, repo: &Repo) -> Result<Self::Pager>;
async fn get_last_release(&self, repo: &Repo) -> Result<Release>;
}

impl RepoFetcher for Octocrab {
type Pager = std::vec::IntoIter<Release>;

async fn get_releases(&self, repo: &Repo) -> Result<<Self as RepoFetcher>::Pager> {
Ok(self
.repos(repo.owner(), repo.repository())
.releases()
.list()
.send()
.await?
.into_iter())
}

async fn get_last_release(&self, repo: &Repo) -> Result<Release> {
self.repos(repo.owner(), repo.repository())
.releases()
.get_latest()
.await
.map_err(|err| InternalError::External(Box::new(err)))
}
}
Loading