z_mirror/fmm
git clone https://git.raiguard.me/z_mirror/fmm
9f375e8
rust-ice
1use std:: cmp:: min; 2use std:: fs::{ self , File }; 3use std:: io::{ Read , Write }; 4 5use anyhow::{ anyhow, Result }; 6use console:: style; 7use indicatif::{ ProgressBar , ProgressStyle }; 8use reqwest:: blocking:: Client ; 9use reqwest:: StatusCode ; 10use semver:: Version ; 11use serde:: Deserialize ; 12use sha1::{ Digest , Sha1 }; 13use thiserror:: Error ; 14 15use crate :: config:: Config ; 16use crate :: directory:: Directory ; 17use crate :: get_mod_version; 18use crate :: types::{ ModEntry , ModIdent }; 19 20pub fn download_mod ( 21mod_ident : & ModIdent , 22directory : & mut Directory , 23config : & Config , 24client : & Client , 25) ->Result < bool > { 26Ok ( match download_mod_internal ( mod_ident, config, client) { 27Ok ( data) =>{ 28 directory. add ( data); 29true 30} 31Err ( err) =>{ 32eprintln! ( 33"{} Could not download {}: {}" , 34style ( "Error:" ). red (). bold (), 35 mod_ident. name , 36 err 37); 38false 39} 40}) 41} 42 43fn download_mod_internal ( 44mod_ident : & ModIdent , 45config : & Config , 46client : & Client , 47) ->Result <( String , ModEntry )> { 48// Get authentication token and username 49let portal_auth = config 50. portal_auth 51. as_ref () 52. ok_or ( DownloadModErr :: CredentialsNotFound ) ?; 53 54// println!("{} {}", style("Fetching").cyan().bold(), mod_ident.name); 55 56// Download mod information 57let mod_info: ModPortalResult = client 58. get ( format! ( 59"https://mods.factorio.com/api/mods/{}" , 60 mod_ident. name 61)) 62. send () ? 63. json () 64. map_err ( |_|DownloadModErr :: ModNotFound ) ?; 65 66// Get the corresponding release 67let release = 68get_mod_version ( & mod_info. releases , mod_ident). ok_or ( DownloadModErr :: ModNotFound ) ?; 69 70// Download the mod 71let mut res =match client 72. get ( format! ( "https://mods.factorio.com{}" , release. download_url ,)) 73. query ( & [ 74( "username" , & portal_auth. username ), 75( "token" , & portal_auth. token ), 76]) 77. send () ? 78. error_for_status () 79{ 80Ok ( res) => res, 81Err ( err) if err. status (). unwrap () ==StatusCode :: FORBIDDEN =>{ 82return Err ( anyhow! ( DownloadModErr :: InvalidCredentials )) 83} 84Err ( err) =>return Err ( anyhow! ( err)), 85}; 86 87let total_size = res 88. content_length () 89. ok_or ( DownloadModErr :: NoContentLength ) ?; 90 91let pb =ProgressBar :: new ( total_size); 92 pb. set_style ( 93ProgressStyle :: default_bar () 94. template ( "{msg} [{elapsed_precise:.blue}] [{bar:.green}] {bytes} / {total_bytes} ({bytes_per_sec}, {eta})" ) 95. progress_chars ( "=> " ), 96); 97 98 pb. set_message ( format! ( 99"{} {} v{}" , 100style ( "Downloading" ). cyan (). bold (), 101 mod_ident. name , 102 release. version 103)); 104 105let mut path = config. mods_dir . clone (); 106 path. push ( format! ( 107"{}_DOWNLOAD" , 108 release 109. file_name 110. strip_suffix ( ".zip" ) 111. unwrap_or ( & release. file_name ) 112)); 113let mut file =File :: create ( & path) ?; 114 115let mut downloaded: u64 =0 ; 116 117let mut buf =vec! [ 0 ; 8_096 ]; 118let mut hasher =Sha1 :: new (); 119 120while downloaded < total_size{ 121if let Some ( bytes) =match res. read ( & mut buf) { 122Ok ( bytes) =>Some ( bytes), 123Err ( err) if matches! ( err. kind (), std:: io:: ErrorKind :: Interrupted ) =>None , 124Err ( err) =>return Err ( anyhow! ( err)), 125} { 126 file. write_all ( & buf[ 0 ..bytes]) ?; 127 hasher. update ( & buf[ 0 ..bytes]); 128// Update progress bar 129 downloaded =min ( downloaded +( bytesas u64 ), total_size); 130 pb. set_position ( downloaded); 131} 132} 133 134// Verify download 135if hasher. finalize ()[ ..] != hex:: decode ( & release. sha1 ) ?{ 136return Err ( anyhow! ( DownloadModErr :: DamagedDownload )); 137} 138 139// Rename file 140let mut proper_path = config. mods_dir . clone (); 141 proper_path. push ( & release. file_name ); 142 fs:: rename ( & path, & proper_path) ?; 143 144// Finish up 145 pb. finish_and_clear (); 146println! ( 147"{} {} v{}" , 148style ( "Downloaded" ). cyan (). bold (), 149 mod_ident. name , 150 release. version 151); 152 153Ok (( 154 mod_info. name , 155ModEntry { 156entry : fs:: read_dir ( & config. mods_dir ) ? 157. filter_map ( |entry| entry. ok ()) 158. find ( |entry|{ 159 entry 160. file_name () 161. to_str () 162. map ( |file_name| file_name == release. file_name ) 163. is_some () 164}) 165. unwrap (), 166version : release. version . clone (), 167}, 168)) 169} 170 171# [ derive ( Debug , Error )] 172enum DownloadModErr { 173# [ error ( "Could not find mod portal credentials" )] 174CredentialsNotFound , 175# [ error ( "Damaged download" )] 176DamagedDownload , 177# [ error ( "Invalid mod portal credentials" )] 178InvalidCredentials , 179# [ error ( "Mod was not found on the portal" )] 180ModNotFound , 181# [ error ( "Could not get content length" )] 182NoContentLength , 183} 184 185# [ derive ( Debug , Deserialize )] 186struct ModPortalResult { 187name : String , 188releases : Vec < ModPortalRelease >, 189} 190 191# [ derive ( Debug , Deserialize )] 192struct ModPortalRelease { 193download_url : String , 194file_name : String , 195sha1 : String , 196version : Version , 197} 198 199impl crate :: HasVersion for ModPortalRelease { 200fn get_version ( & self ) ->& Version { 201& self . version 202} 203}