use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use anyhow::Context;
use mcvm_core::auth_crate::mc::ClientId;
use mcvm_core::config::BrandingProperties;
use mcvm_core::user::UserManager;
use mcvm_core::util::versions::MinecraftVersion;
use mcvm_core::version::InstalledVersion;
use mcvm_core::MCVMCore;
use mcvm_plugin::hooks::{AddVersions, HandleAuth, HandleAuthArg};
use mcvm_shared::later::Later;
use mcvm_shared::output::MCVMOutput;
use mcvm_shared::output::NoOp;
use mcvm_shared::versions::VersionInfo;
use mcvm_shared::UpdateDepth;
use reqwest::Client;
use crate::io::paths::Paths;
use crate::plugin::PluginManager;
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum UpdateRequirement {
ClientLoggingConfig,
}
#[derive(Debug)]
pub struct UpdateSettings {
pub depth: UpdateDepth,
pub offline_auth: bool,
}
pub struct UpdateManager {
pub settings: UpdateSettings,
requirements: HashSet<UpdateRequirement>,
files: HashSet<PathBuf>,
mc_version: Later<MinecraftVersion>,
ms_client_id: Option<ClientId>,
pub core: Later<MCVMCore>,
pub version_info: Later<VersionInfo>,
}
impl UpdateManager {
pub fn new(depth: UpdateDepth) -> Self {
let settings = UpdateSettings {
depth,
offline_auth: false,
};
Self {
settings,
requirements: HashSet::new(),
core: Later::Empty,
ms_client_id: None,
files: HashSet::new(),
version_info: Later::Empty,
mc_version: Later::Empty,
}
}
pub fn offline_auth(&mut self) {
self.settings.offline_auth = true;
}
pub fn set_client_id(&mut self, id: ClientId) {
self.ms_client_id = Some(id);
}
pub fn add_requirement(&mut self, req: UpdateRequirement) {
self.requirements.insert(req);
}
pub fn add_requirements(&mut self, reqs: HashSet<UpdateRequirement>) {
self.requirements.extend(reqs);
}
pub fn has_requirement(&self, req: UpdateRequirement) -> bool {
self.requirements.contains(&req)
}
pub fn add_files(&mut self, files: HashSet<PathBuf>) {
self.files.extend(files);
}
pub fn add_result(&mut self, result: UpdateMethodResult) {
self.add_files(result.files_updated);
}
pub fn should_update_file(&self, file: &Path) -> bool {
if self.settings.depth == UpdateDepth::Force {
!self.files.contains(file) || !file.exists()
} else {
!file.exists()
}
}
pub fn set_version(&mut self, version: &MinecraftVersion) {
self.mc_version.fill(version.clone());
self.version_info.clear();
}
pub async fn fulfill_requirements(
&mut self,
users: &UserManager,
plugins: &PluginManager,
paths: &Paths,
client: &Client,
o: &mut impl MCVMOutput,
) -> anyhow::Result<()> {
self.setup_core(client, users, plugins, paths, o)
.await
.context("Failed to setup core")?;
if self.mc_version.is_empty() {
return Ok(());
}
let version = self
.get_core_version(o)
.await
.context("Failed to get version")?;
let version_info = version.get_version_info();
self.version_info.fill(version_info);
Ok(())
}
async fn setup_core(
&mut self,
client: &Client,
users: &UserManager,
plugins: &PluginManager,
paths: &Paths,
o: &mut impl MCVMOutput,
) -> anyhow::Result<()> {
if self.core.is_full() {
return Ok(());
}
let mut core_config = mcvm_core::ConfigBuilder::new()
.update_depth(self.settings.depth)
.branding(BrandingProperties::new(
"mcvm".into(),
crate::VERSION.into(),
));
if let Some(client_id) = &self.ms_client_id {
core_config = core_config.ms_client_id(client_id.clone());
}
let core_config = core_config.build();
let mut core = MCVMCore::with_config(core_config).context("Failed to initialize core")?;
core.get_users().steal_users(users);
core.get_users().set_offline(self.settings.offline_auth);
{
let plugins = plugins.clone();
let paths = paths.clone();
core.get_users()
.set_custom_auth_function(Arc::new(move |user_id, user_type| {
let arg = HandleAuthArg {
user_id: user_id.to_string(),
user_type: user_type.to_string(),
};
let results = plugins
.call_hook(HandleAuth, &arg, &paths, &mut NoOp)
.context("Failed to call handle auth hook")?;
for result in results {
let result = result.result(&mut NoOp)?;
if result.handled {
return Ok(result.profile);
}
}
Ok(None)
}));
}
core.set_client(client.clone());
let results = plugins
.call_hook(AddVersions, &(), paths, o)
.context("Failed to call add_versions hook")?;
for result in results {
let result = result.result(o)?;
core.add_additional_versions(result);
}
self.core.fill(core);
Ok(())
}
pub async fn get_core_version(
&mut self,
o: &mut impl MCVMOutput,
) -> anyhow::Result<InstalledVersion> {
let version = self
.core
.get_mut()
.get_version(self.mc_version.get_mut(), o)
.await
.context("Failed to get core version")?;
Ok(version)
}
}
#[derive(Default)]
pub struct UpdateMethodResult {
pub files_updated: HashSet<PathBuf>,
}
impl UpdateMethodResult {
pub fn new() -> Self {
Self::default()
}
pub fn from_path(path: PathBuf) -> Self {
let mut out = Self::new();
out.files_updated.insert(path);
out
}
pub fn merge(&mut self, other: Self) {
self.files_updated.extend(other.files_updated);
}
}