use crate::config::Config;
use anyhow::{Context, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use semver::Version;
use std::fs;
const VERSION: &str = env!("CARGO_PKG_VERSION");
static GODOT_PATH_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"/godot-(.*?)/").unwrap());
pub fn migrate() -> Result<()> {
let new_version = Version::parse(VERSION)?;
if !Config::default().data_dir.exists() {
write_data_format_version(&new_version)?;
return Ok(());
}
struct Migration {
to_version: Version,
migrate_fn: fn() -> Result<()>,
}
let migrations: Vec<Migration> = vec![
Migration {
to_version: Version::parse("0.2.0")?,
migrate_fn: v0_1_6_to_v0_2_0::migrate,
},
];
let old_version = get_data_format_version();
if old_version == Some(new_version.clone()) {
return Ok(());
}
tracing::info!(
"Migrating gdenv data directory to latest format. {} -> {}",
old_version
.clone()
.map(|v| v.to_string())
.unwrap_or("?".to_string()),
new_version
);
for migration in &migrations {
if old_version
.as_ref()
.is_none_or(|v| v < &migration.to_version)
{
(migration.migrate_fn)()?;
}
}
write_data_format_version(&new_version)?;
tracing::info!("Migration successful!");
Ok(())
}
fn write_data_format_version(version: &Version) -> Result<()> {
let config = Config::default();
let data_format_version_file = config.data_dir_format_version_file;
fs::create_dir_all(data_format_version_file.parent().context(format!(
"Failed to create parent directory for data format version file: {}",
&data_format_version_file.to_string_lossy()
))?)?;
fs::write(&data_format_version_file, version.to_string()).context(format!(
"Could not write the data format version file: {}",
&data_format_version_file.to_str().unwrap_or("?")
))
}
fn get_data_format_version() -> Option<Version> {
let config = Config::default();
let data_format_version_file = config.data_dir_format_version_file;
if data_format_version_file.exists() {
fs::read_to_string(data_format_version_file)
.ok()?
.parse()
.ok()
} else {
None
}
}
mod v0_1_6_to_v0_2_0 {
use crate::config::Config;
use crate::godot::godot_installation_name;
use crate::godot_version::GodotVersion;
use crate::installer;
use crate::migrate::GODOT_PATH_REGEX;
use anyhow::Result;
use std::fs;
pub fn migrate() -> Result<()> {
migrate_installations_dir()?;
migrate_symlinks()
}
pub fn migrate_installations_dir() -> Result<()> {
let config = Config::default();
let installations_dir = &config.installations_dir;
if !installations_dir.exists() {
return Ok(());
}
for entry_result in fs::read_dir(installations_dir)? {
let entry = entry_result?;
if !entry.file_type()?.is_dir() {
continue;
}
let dir_name = entry.file_name();
let Some(dir_name) = dir_name.to_str() else {
continue;
};
if let Some(expected_dir_name) = try_get_new_name(dir_name) {
let src = installations_dir.join(dir_name);
let dst = installations_dir.join(&expected_dir_name);
if !dst.exists() {
fs::rename(&src, &dst)?;
}
}
}
Ok(())
}
fn try_get_new_name(name: &str) -> Option<String> {
let version_part = name.strip_prefix("godot-")?;
let (version_str, is_dotnet) = if let Some(v) = version_part.strip_suffix("-dotnet") {
(v, true)
} else {
(version_part, false)
};
let version = GodotVersion::new(version_str, is_dotnet).ok()?;
let expected_name = godot_installation_name(&version);
if name != expected_name {
Some(expected_name)
} else {
None
}
}
fn migrate_symlinks() -> Result<()> {
let config = Config::default();
let bin_dir = &config.bin_dir;
let godot_symlink = bin_dir.join("godot");
let Ok(target) = fs::read_link(&godot_symlink) else {
return Ok(());
};
if let Some(target_str) = target.to_str()
&& let Some(version_str) = extract_godot_version(target_str)
{
let version = GodotVersion::new(&version_str, false)?;
installer::set_active_version(&config, &version)?;
}
Ok(())
}
fn extract_godot_version(target: &str) -> Option<String> {
GODOT_PATH_REGEX
.captures(target)
.and_then(|caps| caps.get(1).map(|m| m.as_str().to_string()))
}
}