use std::{collections::HashMap, path::Path};
use anyhow::{bail, Context};
use mcvm_plugin::hooks::{
AddInstanceTransferFormats, ExportInstance, ExportInstanceArg, ImportInstance,
ImportInstanceArg, InstanceTransferFeatureSupport, InstanceTransferFormat,
InstanceTransferFormatDirection,
};
use mcvm_shared::id::InstanceID;
use mcvm_shared::lang::translate::TranslationKey;
use mcvm_shared::output::{MCVMOutput, MessageContents, MessageLevel};
use mcvm_shared::translate;
use crate::config::builder::InstanceBuilder;
use crate::config::instance::InstanceConfig;
use crate::io::lock::Lockfile;
use crate::{io::paths::Paths, plugin::PluginManager};
use super::Instance;
impl Instance {
pub fn export(
&mut self,
format: &str,
result_path: &Path,
formats: &Formats,
plugins: &PluginManager,
lock: &Lockfile,
paths: &Paths,
o: &mut impl MCVMOutput,
) -> anyhow::Result<()> {
let format = formats
.formats
.get(format)
.context("Transfer format does not exist")?;
let export_info = format
.info
.export
.as_ref()
.context("This format or the plugin providing it does not support exporting")?;
output_support_warnings(export_info, o);
if !lock.has_instance_done_first_update(&self.id) {
bail!("Instance has not done it's first update and is not ready for transfer");
}
self.ensure_dirs(paths)
.context("Failed to ensure instance directories")?;
o.display(
MessageContents::StartProcess(translate!(
o,
StartExporting,
"instance" = &self.id,
"format" = &format.info.id,
"plugin" = &format.plugin
)),
MessageLevel::Important,
);
let arg = ExportInstanceArg {
id: self.id.to_string(),
format: format.info.id.clone(),
name: self.config.name.clone(),
side: Some(self.get_side()),
game_dir: self.dirs.get().game_dir.to_string_lossy().to_string(),
result_path: result_path.to_string_lossy().to_string(),
minecraft_version: Some(self.config.version.clone().to_serialized()),
client_type: Some(self.config.modifications.client_type().clone()),
server_type: Some(self.config.modifications.server_type().clone()),
};
let result = plugins
.call_hook_on_plugin(ExportInstance, &format.plugin, &arg, paths, o)
.context("Failed to export instance using plugin")?;
if let Some(result) = result {
result.result(o)?;
o.display(
MessageContents::Success(o.translate(TranslationKey::FinishExporting).into()),
MessageLevel::Important,
);
} else {
o.display(
MessageContents::Error(o.translate(TranslationKey::ExportPluginNoResult).into()),
MessageLevel::Debug,
);
}
Ok(())
}
pub fn import(
id: &str,
format: &str,
source_path: &Path,
formats: &Formats,
plugins: &PluginManager,
paths: &Paths,
o: &mut impl MCVMOutput,
) -> anyhow::Result<InstanceConfig> {
let format = formats
.formats
.get(format)
.context("Transfer format does not exist")?;
let import_info = format
.info
.import
.as_ref()
.context("This format or the plugin providing it does not support importing")?;
output_support_warnings(import_info, o);
o.display(
MessageContents::StartProcess(translate!(
o,
StartImporting,
"instance" = id,
"format" = &format.info.id,
"plugin" = &format.plugin
)),
MessageLevel::Important,
);
let target_dir = paths.project.data_dir().join("instances").join(id);
std::fs::create_dir_all(&target_dir)
.context("Failed to create directory for new instance")?;
let arg = ImportInstanceArg {
format: format.info.id.clone(),
id: id.to_string(),
source_path: source_path.to_string_lossy().to_string(),
result_path: target_dir.to_string_lossy().to_string(),
};
let result = plugins
.call_hook_on_plugin(ImportInstance, &format.plugin, &arg, paths, o)
.context("Failed to import instance using plugin")?;
let Some(result) = result else {
o.display(
MessageContents::Error(o.translate(TranslationKey::ImportPluginNoResult).into()),
MessageLevel::Debug,
);
bail!("Import plugin did not return a result");
};
let result = result.result(o)?;
o.display(
MessageContents::Success(o.translate(TranslationKey::FinishImporting).into()),
MessageLevel::Important,
);
let side = result
.side
.context("Import result is missing the instance side")?;
let mut builder = InstanceBuilder::new(InstanceID::from(id), side);
if let Some(version) = result.version {
builder.version(version);
}
if let Some(name) = result.name {
builder.name(name);
}
if let Some(client_type) = result.client_type {
builder.client_type(client_type);
}
if let Some(server_type) = result.server_type {
builder.server_type(server_type);
}
Ok(builder.build_config())
}
}
pub fn load_formats(
plugins: &PluginManager,
paths: &Paths,
o: &mut impl MCVMOutput,
) -> anyhow::Result<Formats> {
let results = plugins
.call_hook(AddInstanceTransferFormats, &(), paths, o)
.context("Failed to get transfer formats from plugins")?;
let mut formats = HashMap::with_capacity(results.len());
for result in results {
let plugin_id = result.get_id().to_owned();
let result = result.result(o)?;
for result in result {
formats.insert(
result.id.clone(),
Format {
plugin: plugin_id.clone(),
info: result,
},
);
}
}
Ok(Formats { formats })
}
pub struct Formats {
formats: HashMap<String, Format>,
}
impl Formats {
pub fn iter_format_names(&self) -> impl Iterator<Item = &String> {
self.formats.keys()
}
}
pub struct Format {
plugin: String,
info: InstanceTransferFormat,
}
fn output_support_warnings(info: &InstanceTransferFormatDirection, o: &mut impl MCVMOutput) {
for (support, name) in [
(
info.launch_settings,
TranslationKey::TransferLaunchSettingsFeature,
),
(info.modloader, TranslationKey::TransferModloaderFeature),
(info.mods, TranslationKey::TransferModsFeature),
] {
let feat = o.translate(name);
match support {
InstanceTransferFeatureSupport::Supported => {}
InstanceTransferFeatureSupport::FormatUnsupported => o.display(
MessageContents::Warning(translate!(
o,
TransferFeatureUnsupportedByFormat,
"feat" = feat
)),
MessageLevel::Important,
),
InstanceTransferFeatureSupport::PluginUnsupported => o.display(
MessageContents::Warning(translate!(
o,
TransferFeatureUnsupportedByPlugin,
"feat" = feat
)),
MessageLevel::Important,
),
}
}
}