z_mirror/fmm
git clone https://git.raiguard.me/z_mirror/fmm
3e7423d
refactor
1use anyhow:: Result ; 2use std:: cmp:: Ordering ; 3use std:: collections:: HashMap ; 4use std:: ffi:: OsString ; 5use std:: fs; 6use std:: fs::{ DirEntry , File }; 7use std:: io:: Read ; 8use std:: path::{ Path , PathBuf }; 9 10use semver::{ Version , VersionReq }; 11use zip:: ZipArchive ; 12 13use crate :: dependency:: ModDependencyType ; 14use crate :: types:: * ; 15 16pub struct Directory { 17pub mods : HashMap < String , Vec < ModVersion >>, 18pub mod_list : Vec < ModListJsonMod >, 19pub mod_list_path : PathBuf , 20} 21 22impl Directory { 23pub fn new ( dir : & Path ) ->Result < Self > { 24// Get all mods in the directory 25let mod_entries = fs:: read_dir ( & dir) ? 26. filter_map ( |entry|{ 27let entry = entry. ok () ?; 28 29if let Some (( mod_name, version)) =parse_file_name ( & entry. file_name ()) { 30Some (( 31 mod_name, 32ModVersion { 33 entry, 34 version, 35info_json : None , 36}, 37)) 38} else { 39let info_json =read_info_json ( & entry) ?; 40Some (( 41 info_json. name . clone (), 42ModVersion { 43 entry, 44version : info_json. version . clone (), 45info_json : Some ( info_json), 46}, 47)) 48} 49}) 50. fold ( HashMap :: new (), |mut directory_mods, ( mod_name, version) |{ 51let versions = directory_mods. entry ( mod_name). or_insert_with ( Vec :: new); 52 53let index = versions 54. binary_search ( & version) 55. unwrap_or_else ( |index| index); 56 versions. insert ( index, version); 57 58 directory_mods 59}); 60 61// Parse mod-list.json 62let mut mlj_path = dir. to_owned (); 63 mlj_path. push ( "mod-list.json" ); 64let enabled_versions = fs:: read_to_string ( & mlj_path) ?; 65let mod_list_json: ModListJson = serde_json:: from_str ( & enabled_versions) ?; 66 67Ok ( Self { 68mods : mod_entries, 69mod_list : mod_list_json. mods , 70mod_list_path : mlj_path. clone (), 71}) 72} 73 74pub fn get_mut ( & mut self , mod_ident : & ModIdent ) ->Option < & mut ModVersion > { 75self . mods . get_mut ( & mod_ident. name ). and_then ( |mod_versions|{ 76if let Some ( version_req) =& mod_ident. version_req { 77 mod_versions 78. iter_mut () 79. rev () 80. find ( |version| version_req. matches ( & version. version )) 81} else { 82 mod_versions. last_mut () 83} 84}) 85} 86 87pub fn disable ( & mut self , mod_ident : & ModIdent ) { 88if mod_ident. name =="base" ||self . mods . contains_key ( & mod_ident. name ) { 89let mod_state =self 90. mod_list 91. iter_mut () 92. find ( |mod_state| mod_ident. name == mod_state. name ); 93 94println! ( "Disabled {}" , & mod_ident); 95 96if let Some ( mod_state) = mod_state{ 97 mod_state. enabled =false ; 98 mod_state. version =None ; 99} 100} else { 101println! ( "Could not find {}" , & mod_ident); 102} 103} 104 105pub fn disable_all ( & mut self ) { 106println! ( "Disabled all mods" ); 107for mod_datain self 108. mod_list 109. iter_mut () 110. filter ( |mod_state| mod_state. name !="base" ) 111{ 112 mod_data. enabled =false ; 113 mod_data. version =None ; 114} 115} 116 117pub fn enable ( & mut self , mod_ident : & ModIdent ) { 118let mod_entry =self . mods . get ( & mod_ident. name ). and_then ( |mod_versions|{ 119if let Some ( version_req) =& mod_ident. version_req { 120 mod_versions 121. iter () 122. rev () 123. find ( |version| version_req. matches ( & version. version )) 124} else { 125 mod_versions. last () 126} 127}); 128 129if let Some ( mod_entry) = mod_entry{ 130let mod_state =self 131. mod_list 132. iter_mut () 133. find ( |mod_state| mod_ident. name == mod_state. name ); 134 135let enabled = mod_state. is_some () && mod_state. as_ref (). unwrap (). enabled ; 136 137if !enabled{ 138println! ( "Enabled {} v{}" , mod_ident. name , mod_entry. version ); 139 140let version = mod_ident 141. version_req 142. as_ref () 143. map ( |_| mod_entry. version . clone ()); 144 145if let Some ( mod_state) = mod_state{ 146 mod_state. enabled =true ; 147 mod_state. version = version; 148} else { 149self . mod_list . push ( ModListJsonMod { 150name : mod_ident. name . to_string (), 151enabled : true , 152 version, 153}); 154} 155} 156} else { 157println! ( "Could not find or read {}" , & mod_ident); 158} 159} 160 161pub fn enable_all ( & mut self ) { 162println! ( "Enabled latest versions of all mods" ); 163for mod_datain self . mod_list . iter_mut () { 164 mod_data. enabled =true ; 165 mod_data. version =None ; 166} 167} 168 169pub fn remove ( & mut self , mod_ident : & ModIdent ) { 170let version_req = mod_ident 171. version_req 172. as_ref () 173. cloned () 174. unwrap_or_else ( VersionReq :: any); 175if let Some ( mod_versions) =self . mods . get ( & mod_ident. name ) { 176 mod_versions 177. iter () 178. filter ( |version| version_req. matches ( & version. version )) 179. for_each ( |version|{ 180let result = version. entry . metadata (). and_then ( |metadata|{ 181if metadata. is_dir () { 182 fs:: remove_dir_all ( version. entry . path ()) 183} else { 184 fs:: remove_file ( version. entry . path ()) 185} 186}); 187if result. is_ok () { 188println! ( "Removed {} v{}" , & mod_ident. name , version. version ); 189} else { 190println! ( "Could not remove {} v{}" , & mod_ident. name , version. version ); 191} 192}); 193self . mods . remove ( & mod_ident. name ); 194} 195 196if let Some (( index, _)) =self 197. mod_list 198. iter () 199. enumerate () 200. find ( |( _, mod_state) | mod_ident. name == mod_state. name ) 201{ 202self . mod_list . remove ( index); 203} 204} 205} 206 207pub struct ModVersion { 208pub entry : DirEntry , 209pub version : Version , 210pub info_json : Option < InfoJson >, 211} 212 213impl ModVersion { 214pub fn get_info_json ( & mut self ) ->Option < & InfoJson > { 215if self . info_json . is_none () { 216let info_json =read_info_json ( & self . entry ); 217self . info_json = info_json; 218} 219self . info_json . as_ref () 220} 221} 222 223impl PartialOrd for ModVersion { 224fn partial_cmp ( & self , other : & Self ) ->Option < Ordering > { 225self . version . partial_cmp ( & other. version ) 226} 227} 228 229impl Ord for ModVersion { 230fn cmp ( & self , other : & Self ) ->Ordering { 231self . version . cmp ( & other. version ) 232} 233} 234 235impl PartialEq for ModVersion { 236fn eq ( & self , other : & Self ) ->bool { 237self . version == other. version 238} 239} 240 241impl Eq for ModVersion {} 242 243fn parse_file_name ( file_name : & OsString ) ->Option <( String , Version )> { 244let ( name, version) = file_name 245. to_str () ? 246. trim_end_matches ( ".zip" ) 247. rsplit_once ( "_" ) ?; 248 249if let Ok ( version) =Version :: parse ( version) { 250Some (( name. to_string (), version)) 251} else { 252None 253} 254} 255 256// TODO: Use errors instead of an option 257fn read_info_json ( entry : & DirEntry ) ->Option < InfoJson > { 258let contents =match ModEntryStructure :: parse ( entry) ?{ 259ModEntryStructure :: Directory |ModEntryStructure :: Symlink =>{ 260let mut path = entry. path (); 261 path. push ( "info.json" ); 262 fs:: read_to_string ( path). ok () ? 263} 264ModEntryStructure :: Zip =>{ 265let mut archive =ZipArchive :: new ( File :: open ( entry. path ()). ok () ?). ok () ?; 266let filename = archive 267. file_names () 268. find ( |name| name. contains ( "info.json" )) 269. map ( ToString :: to_string) ?; 270let mut file = archive. by_name ( & filename). ok () ?; 271let mut contents =String :: new (); 272 file. read_to_string ( & mut contents). ok () ?; 273 contents 274} 275}; 276 277 serde_json:: from_str ::< InfoJson >( & contents). ok () 278}