2022-06-27 15:53:25 -07:00
//! Theseus directory information
2025-05-02 04:51:17 -05:00
use crate ::LoadingBarType ;
2024-08-16 23:20:11 -07:00
use crate ::event ::emit ::{ emit_loading , init_loading } ;
2026-02-02 08:10:23 -05:00
use crate ::state ::LAUNCHER_STATE ;
2024-08-16 23:20:11 -07:00
use crate ::state ::{ JavaVersion , Profile , Settings } ;
2024-07-24 11:03:19 -07:00
use crate ::util ::fetch ::IoSemaphore ;
2024-08-16 23:20:11 -07:00
use dashmap ::DashSet ;
2024-07-24 11:03:19 -07:00
use std ::path ::{ Path , PathBuf } ;
2024-08-16 23:20:11 -07:00
use std ::sync ::Arc ;
2024-07-24 11:03:19 -07:00
use tokio ::fs ;
2022-06-27 15:53:25 -07:00
2023-07-21 20:16:07 -07:00
pub const CACHES_FOLDER_NAME : & str = " caches " ;
pub const LAUNCHER_LOGS_FOLDER_NAME : & str = " launcher_logs " ;
2024-01-05 11:00:48 -08:00
pub const PROFILES_FOLDER_NAME : & str = " profiles " ;
pub const METADATA_FOLDER_NAME : & str = " meta " ;
2023-07-21 20:16:07 -07:00
2022-06-27 15:53:25 -07:00
#[ derive(Debug) ]
pub struct DirectoryInfo {
2024-08-16 23:20:11 -07:00
pub settings_dir : PathBuf , // Base settings directory- app database
2024-07-24 11:03:19 -07:00
pub config_dir : PathBuf , // Base config directory- instances, minecraft downloads, etc. Changeable as a setting.
2026-02-02 08:10:23 -05:00
pub app_identifier : String ,
2022-06-27 15:53:25 -07:00
}
impl DirectoryInfo {
2026-02-02 08:10:23 -05:00
pub fn global_handle_if_ready ( ) -> Option < & 'static Self > {
LAUNCHER_STATE . get ( ) . map ( | x | & x . directories )
}
pub fn get_initial_settings_dir ( & self ) -> Option < PathBuf > {
Self ::initial_settings_dir_path ( & self . app_identifier )
}
2023-07-21 20:16:07 -07:00
// Get the settings directory
// init() is not needed for this function
2026-02-02 08:10:23 -05:00
pub fn initial_settings_dir_path ( app_identifier : & str ) -> Option < PathBuf > {
2023-07-21 20:16:07 -07:00
Self ::env_path ( " THESEUS_CONFIG_DIR " )
2026-02-02 08:10:23 -05:00
. or_else ( | | Some ( dirs ::data_dir ( ) ? . join ( app_identifier ) ) )
2023-07-21 20:16:07 -07:00
}
2022-06-27 15:53:25 -07:00
/// Get all paths needed for Theseus to operate properly
2022-07-15 15:39:38 +00:00
#[ tracing::instrument ]
2026-02-02 08:10:23 -05:00
pub async fn init (
config_dir : Option < String > ,
app_identifier : & str ,
) -> crate ::Result < Self > {
let settings_dir = Self ::initial_settings_dir_path ( app_identifier )
. ok_or ( crate ::ErrorKind ::FSError (
2023-07-21 20:16:07 -07:00
" Could not find valid settings dir " . to_string ( ) ,
2026-02-02 08:10:23 -05:00
) ) ? ;
2022-06-27 15:53:25 -07:00
2024-07-24 11:03:19 -07:00
fs ::create_dir_all ( & settings_dir ) . await . map_err ( | err | {
2022-07-15 15:39:38 +00:00
crate ::ErrorKind ::FSError ( format! (
2022-06-27 15:53:25 -07:00
" Error creating Theseus config directory: {err} "
) )
} ) ? ;
2025-06-14 02:10:12 +02:00
let config_dir =
config_dir . map_or_else ( | | settings_dir . clone ( ) , PathBuf ::from ) ;
2023-07-21 20:16:07 -07:00
2022-06-27 15:53:25 -07:00
Ok ( Self {
2023-07-21 20:16:07 -07:00
settings_dir ,
2024-07-24 11:03:19 -07:00
config_dir ,
2026-02-02 08:10:23 -05:00
app_identifier : app_identifier . to_owned ( ) ,
2022-06-27 15:53:25 -07:00
} )
}
/// Get the Minecraft instance metadata directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn metadata_dir ( & self ) -> PathBuf {
self . config_dir . join ( METADATA_FOLDER_NAME )
2022-06-27 15:53:25 -07:00
}
2023-05-16 15:30:04 -07:00
/// Get the Minecraft java versions metadata directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn java_versions_dir ( & self ) -> PathBuf {
self . metadata_dir ( ) . join ( " java_versions " )
2023-05-16 15:30:04 -07:00
}
2022-06-27 15:53:25 -07:00
/// Get the Minecraft versions metadata directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn versions_dir ( & self ) -> PathBuf {
self . metadata_dir ( ) . join ( " versions " )
2022-06-27 15:53:25 -07:00
}
/// Get the metadata directory for a given version
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn version_dir ( & self , version : & str ) -> PathBuf {
self . versions_dir ( ) . join ( version )
2022-06-27 15:53:25 -07:00
}
/// Get the Minecraft libraries metadata directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn libraries_dir ( & self ) -> PathBuf {
self . metadata_dir ( ) . join ( " libraries " )
2022-06-27 15:53:25 -07:00
}
/// Get the Minecraft assets metadata directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn assets_dir ( & self ) -> PathBuf {
self . metadata_dir ( ) . join ( " assets " )
2022-06-27 15:53:25 -07:00
}
/// Get the assets index directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn assets_index_dir ( & self ) -> PathBuf {
self . assets_dir ( ) . join ( " indexes " )
2022-06-27 15:53:25 -07:00
}
/// Get the assets objects directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn objects_dir ( & self ) -> PathBuf {
self . assets_dir ( ) . join ( " objects " )
2022-06-27 15:53:25 -07:00
}
/// Get the directory for a specific object
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn object_dir ( & self , hash : & str ) -> PathBuf {
self . objects_dir ( ) . join ( & hash [ .. 2 ] ) . join ( hash )
2022-06-27 15:53:25 -07:00
}
2025-03-20 15:42:37 -05:00
/// Get the Minecraft log config's directory
#[ inline ]
pub fn log_configs_dir ( & self ) -> PathBuf {
self . metadata_dir ( ) . join ( " log_configs " )
}
2022-06-27 15:53:25 -07:00
/// Get the Minecraft legacy assets metadata directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn legacy_assets_dir ( & self ) -> PathBuf {
self . metadata_dir ( ) . join ( " resources " )
2022-06-27 15:53:25 -07:00
}
/// Get the Minecraft legacy assets metadata directory
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn natives_dir ( & self ) -> PathBuf {
self . metadata_dir ( ) . join ( " natives " )
2022-06-27 15:53:25 -07:00
}
/// Get the natives directory for a version of Minecraft
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn version_natives_dir ( & self , version : & str ) -> PathBuf {
self . natives_dir ( ) . join ( version )
2022-06-27 15:53:25 -07:00
}
/// Get the directory containing instance icons
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn icon_dir ( & self ) -> PathBuf {
self . config_dir . join ( " icons " )
2022-06-27 15:53:25 -07:00
}
2023-03-31 11:00:43 -07:00
/// Get the profiles directory for created profiles
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn profiles_dir ( & self ) -> PathBuf {
self . config_dir . join ( PROFILES_FOLDER_NAME )
2023-03-31 11:00:43 -07:00
}
2023-05-03 09:02:34 -07:00
/// Gets the logs dir for a given profile
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn profile_logs_dir ( & self , profile_path : & str ) -> PathBuf {
self . profiles_dir ( ) . join ( profile_path ) . join ( " logs " )
2023-05-03 09:02:34 -07:00
}
2024-07-09 15:17:38 -07:00
2024-05-09 13:29:19 -04:00
/// Gets the crash reports dir for a given profile
#[ inline ]
2024-07-24 11:03:19 -07:00
pub fn crash_reports_dir ( & self , profile_path : & str ) -> PathBuf {
self . profiles_dir ( ) . join ( profile_path ) . join ( " crash-reports " )
2024-05-09 13:29:19 -04:00
}
2023-05-03 09:02:34 -07:00
2023-06-30 08:11:32 -07:00
#[ inline ]
2026-02-02 08:10:23 -05:00
pub fn launcher_logs_dir ( & self ) -> Option < PathBuf > {
self . get_initial_settings_dir ( )
. map ( | d | d . join ( LAUNCHER_LOGS_FOLDER_NAME ) )
}
#[ inline ]
pub fn launcher_logs_dir_path ( app_identifier : & str ) -> Option < PathBuf > {
Self ::initial_settings_dir_path ( app_identifier )
2023-07-21 20:16:07 -07:00
. map ( | d | d . join ( LAUNCHER_LOGS_FOLDER_NAME ) )
2023-06-30 08:11:32 -07:00
}
2023-03-29 15:34:46 -07:00
/// Get the cache directory for Theseus
#[ inline ]
pub fn caches_dir ( & self ) -> PathBuf {
2024-08-16 23:20:11 -07:00
self . config_dir . join ( CACHES_FOLDER_NAME )
2023-03-29 15:34:46 -07:00
}
2022-06-27 15:53:25 -07:00
/// Get path from environment variable
#[ inline ]
fn env_path ( name : & str ) -> Option < PathBuf > {
std ::env ::var_os ( name ) . map ( PathBuf ::from )
}
2024-07-24 11:03:19 -07:00
2024-08-16 23:20:11 -07:00
#[ tracing::instrument(skip(settings, exec, io_semaphore)) ]
2024-07-24 11:03:19 -07:00
pub async fn move_launcher_directory < ' a , E > (
settings : & mut Settings ,
exec : E ,
io_semaphore : & IoSemaphore ,
2026-02-02 08:10:23 -05:00
app_identifier : & str ,
2024-07-24 11:03:19 -07:00
) -> crate ::Result < ( ) >
where
E : sqlx ::Executor < ' a , Database = sqlx ::Sqlite > + Copy ,
{
2026-02-02 08:10:23 -05:00
let app_dir = DirectoryInfo ::initial_settings_dir_path ( app_identifier )
. ok_or ( crate ::ErrorKind ::FSError (
2024-08-16 23:20:11 -07:00
" Could not find valid config dir " . to_string ( ) ,
2026-02-02 08:10:23 -05:00
) ) ? ;
2024-08-16 23:20:11 -07:00
2024-07-24 11:03:19 -07:00
if let Some ( ref prev_custom_dir ) = settings . prev_custom_dir {
let prev_dir = PathBuf ::from ( prev_custom_dir ) ;
let move_dir = settings
. custom_dir
. as_ref ( )
2025-06-14 02:10:12 +02:00
. map_or_else ( | | app_dir . clone ( ) , PathBuf ::from ) ;
2024-07-24 11:03:19 -07:00
2026-05-03 14:18:31 +02:00
async fn is_dir_writable (
2024-07-24 11:03:19 -07:00
new_config_dir : & Path ,
) -> crate ::Result < bool > {
let temp_path = new_config_dir . join ( " .tmp " ) ;
match fs ::write ( temp_path . clone ( ) , " test " ) . await {
Ok ( _ ) = > {
fs ::remove_file ( temp_path ) . await ? ;
Ok ( true )
}
Err ( e ) = > {
tracing ::error! (
" Error writing to new config dir: {} " ,
e
) ;
Ok ( false )
}
}
}
2024-08-16 23:20:11 -07:00
fn get_disk_usage ( path : & Path ) -> crate ::Result < Option < u64 > > {
let path = crate ::util ::io ::canonicalize ( path ) ? ;
2024-07-24 11:03:19 -07:00
2024-08-16 23:20:11 -07:00
let disks = sysinfo ::Disks ::new_with_refreshed_list ( ) ;
2025-06-14 02:10:12 +02:00
for disk in & disks {
2024-08-16 23:20:11 -07:00
if path . starts_with ( disk . mount_point ( ) ) {
return Ok ( Some ( disk . available_space ( ) ) ) ;
}
2024-07-24 11:03:19 -07:00
}
2024-08-16 23:20:11 -07:00
Ok ( None )
2024-07-24 11:03:19 -07:00
}
let new_dir = move_dir . to_string_lossy ( ) . to_string ( ) ;
if prev_dir ! = move_dir {
2024-08-16 23:20:11 -07:00
let loader_bar_id = init_loading (
LoadingBarType ::DirectoryMove {
old : prev_dir . clone ( ) ,
new : move_dir . clone ( ) ,
} ,
100.0 ,
" Moving launcher directory " ,
)
. await ? ;
2026-05-03 14:18:31 +02:00
if ! is_dir_writable ( & move_dir ) . await ? {
return Err ( crate ::ErrorKind ::DirectoryMoveError ( format! ( " Cannot move directory to {} : directory is not writable " , move_dir . display ( ) ) ) . into ( ) ) ;
2024-08-16 23:20:11 -07:00
}
const MOVE_DIRS : & [ & str ] = & [
CACHES_FOLDER_NAME ,
PROFILES_FOLDER_NAME ,
METADATA_FOLDER_NAME ,
] ;
2024-07-24 11:03:19 -07:00
2024-08-16 23:20:11 -07:00
struct MovePath {
old : PathBuf ,
new : PathBuf ,
size : u64 ,
2024-07-24 11:03:19 -07:00
}
2024-08-16 23:20:11 -07:00
async fn add_paths (
source : & Path ,
destination : & Path ,
paths : & mut Vec < MovePath > ,
total_size : & mut u64 ,
) -> crate ::Result < ( ) > {
if ! source . exists ( ) {
crate ::util ::io ::create_dir_all ( source ) . await ? ;
}
2024-07-24 11:03:19 -07:00
2024-08-16 23:20:11 -07:00
if ! destination . exists ( ) {
crate ::util ::io ::create_dir_all ( destination ) . await ? ;
}
for entry_path in
crate ::pack ::import ::get_all_subfiles ( source , false )
. await ?
{
let relative_path = entry_path . strip_prefix ( source ) ? ;
let new_path = destination . join ( relative_path ) ;
let path_size =
entry_path . metadata ( ) . map ( | x | x . len ( ) ) . unwrap_or ( 0 ) ;
* total_size + = path_size ;
paths . push ( MovePath {
old : entry_path ,
new : new_path ,
size : path_size ,
} ) ;
}
Ok ( ( ) )
}
let mut paths : Vec < MovePath > = vec! [ ] ;
let mut total_size = 0 ;
for dir in MOVE_DIRS {
add_paths (
& prev_dir . join ( dir ) ,
& move_dir . join ( dir ) ,
& mut paths ,
& mut total_size ,
)
. await ? ;
emit_loading (
& loader_bar_id ,
10.0 / ( MOVE_DIRS . len ( ) as f64 ) ,
None ,
2024-08-30 10:42:58 -07:00
) ? ;
2024-08-16 23:20:11 -07:00
}
let paths_len = paths . len ( ) ;
2025-02-24 21:00:17 -06:00
if crate ::util ::io ::is_same_disk ( & prev_dir , & move_dir )
. unwrap_or ( false )
{
2024-08-16 23:20:11 -07:00
let success_idxs = Arc ::new ( DashSet ::new ( ) ) ;
let loader_bar_id = Arc ::new ( & loader_bar_id ) ;
let res =
futures ::future ::try_join_all ( paths . iter ( ) . enumerate ( ) . map ( | ( idx , x ) | {
let loader_bar_id = loader_bar_id . clone ( ) ;
let success_idxs = success_idxs . clone ( ) ;
async move {
let _permit = io_semaphore . 0. acquire ( ) . await ? ;
if let Some ( parent ) = x . new . parent ( ) {
crate ::util ::io ::create_dir_all ( parent ) . await . map_err ( | e | {
crate ::Error ::from ( crate ::ErrorKind ::DirectoryMoveError (
format! (
" Failed to create directory {}: {} " ,
parent . display ( ) ,
e
)
) )
} ) ? ;
}
2025-02-24 21:00:17 -06:00
crate ::util ::io ::rename_or_move (
2024-08-16 23:20:11 -07:00
& x . old ,
& x . new ,
)
. await
. map_err ( | e | {
crate ::Error ::from ( crate ::ErrorKind ::DirectoryMoveError (
format! (
2026-04-17 15:00:08 +01:00
" Failed to move directory from {} to {}: {e:?} " ,
2024-08-16 23:20:11 -07:00
x . old . display ( ) ,
x . new . display ( ) ,
) ,
) )
} ) ? ;
let _ = emit_loading (
& loader_bar_id ,
90.0 / paths_len as f64 ,
None ,
2024-08-30 10:42:58 -07:00
) ;
2024-08-16 23:20:11 -07:00
success_idxs . insert ( idx ) ;
Ok ::< ( ) , crate ::Error > ( ( ) )
}
} ) )
. await ;
if let Err ( e ) = res {
for idx in success_idxs . iter ( ) {
let path = & paths [ * idx . key ( ) ] ;
let res =
tokio ::fs ::rename ( & path . new , & path . old ) . await ;
if let Err ( e ) = res {
tracing ::warn! (
" Failed to rollback directory {}: {} " ,
path . new . display ( ) ,
e
) ;
}
}
return Err ( e ) ;
}
} else {
2025-08-08 15:50:44 -07:00
if let Some ( disk_usage ) = get_disk_usage ( & move_dir ) ?
& & total_size > disk_usage
{
return Err ( crate ::ErrorKind ::DirectoryMoveError ( format! ( " Not enough space to move directory to {} : only {} bytes available " , app_dir . display ( ) , disk_usage ) ) . into ( ) ) ;
2024-08-16 23:20:11 -07:00
}
let loader_bar_id = Arc ::new ( & loader_bar_id ) ;
futures ::future ::try_join_all ( paths . iter ( ) . map ( | x | {
let loader_bar_id = loader_bar_id . clone ( ) ;
async move {
crate ::util ::fetch ::copy (
& x . old ,
& x . new ,
io_semaphore ,
)
. await . map_err ( | e | { crate ::Error ::from (
2026-04-17 15:00:08 +01:00
crate ::ErrorKind ::DirectoryMoveError ( format! ( " Failed to move directory from {} to {} : {e:?} " , x . old . display ( ) , x . new . display ( ) ) ) )
2024-08-16 23:20:11 -07:00
} ) ? ;
let _ = emit_loading (
& loader_bar_id ,
( ( x . size as f64 ) / ( total_size as f64 ) ) * 60.0 ,
None ,
2024-08-30 10:42:58 -07:00
) ;
2024-08-16 23:20:11 -07:00
Ok ::< ( ) , crate ::Error > ( ( ) )
}
} ) )
. await ? ;
futures ::future ::join_all ( paths . iter ( ) . map ( | x | {
let loader_bar_id = loader_bar_id . clone ( ) ;
async move {
let res = async {
let _permit = io_semaphore . 0. acquire ( ) . await ? ;
crate ::util ::io ::remove_file ( & x . old ) . await ? ;
emit_loading (
& loader_bar_id ,
30.0 / paths_len as f64 ,
None ,
2024-08-30 10:42:58 -07:00
) ? ;
2024-08-16 23:20:11 -07:00
Ok ::< ( ) , crate ::Error > ( ( ) )
} ;
if let Err ( e ) = res . await {
tracing ::warn! (
" Failed to remove old file {}: {} " ,
x . old . display ( ) ,
e
) ;
}
}
} ) )
. await ;
}
2024-07-24 11:03:19 -07:00
let java_versions = JavaVersion ::get_all ( exec ) . await ? ;
for ( _ , mut java_version ) in java_versions {
java_version . path = java_version . path . replace (
prev_custom_dir ,
new_dir . trim_end_matches ( '/' ) . trim_end_matches ( '\\' ) ,
) ;
2024-08-16 23:20:11 -07:00
java_version . upsert ( exec ) . await ?
}
let profiles = Profile ::get_all ( exec ) . await ? ;
for mut profile in profiles {
profile . icon_path = profile . icon_path . map ( | x | {
x . replace (
prev_custom_dir ,
new_dir
. trim_end_matches ( '/' )
. trim_end_matches ( '\\' ) ,
)
} ) ;
profile . java_path = profile . java_path . map ( | x | {
x . replace (
prev_custom_dir ,
new_dir
. trim_end_matches ( '/' )
. trim_end_matches ( '\\' ) ,
)
} ) ;
profile . upsert ( exec ) . await ? ;
2024-07-24 11:03:19 -07:00
}
}
2024-08-16 23:20:11 -07:00
settings . custom_dir = Some ( new_dir ) ;
}
2024-07-24 11:03:19 -07:00
2024-08-16 23:20:11 -07:00
settings . prev_custom_dir . clone_from ( & settings . custom_dir ) ;
if settings . custom_dir . is_none ( ) {
settings . custom_dir = Some ( app_dir . to_string_lossy ( ) . to_string ( ) ) ;
2024-07-24 11:03:19 -07:00
}
2024-08-16 23:20:11 -07:00
settings . update ( exec ) . await ? ;
2024-07-24 11:03:19 -07:00
Ok ( ( ) )
}
2022-06-27 15:53:25 -07:00
}