summaryrefslogblamecommitdiff
path: root/src/files.rs
blob: 636c934141d5a403aa5c7e8c808e20f3077559b5 (plain) (tree)
1
2
3
4
5
6
7
8
9
                            
                                                           
                    
          
             
                         
                                                    
              
  
                         

            
                                                      

                                                        
                               
                      
                                                                       
  
 
            






                                  
                                                     
 
                                
 
                                                                                                                                    
 

                                     
                                                                                                           


                                                   
                                                                            

                                            
                                                           
 
                         
                                                                  
                                           









                                          
                   

     
                                                                   

          

 
            






                                        
                                                                    


                                                      
                                                                             
 
                           


                                       
                                                                       
                                
                                                    
            


                                                                        
                                     

                                                                          

                                                                     








                                

                                                                             

                                                                      
                                                                            
                                                                   
 
                                        

     



                                                 



          
            





                           
                                                



                                                                          
                                                                     


                                        
                              
                                       
 
                      
                                                

                                        

                                
                                                
                           

                                
 





                                                                     




          
            
                       
                 
                        


                      
                                                        
                                              


                            
                                                                                 



          
            
                                                              
                                             
 




                       
            
                                                                   
                                                            
                                                  

                                

                                                                    
              



                                                                      
     
 




                                                            
      
 

                           
 
            
                                                                             
                                                               
                                                  
                                






                                                                                          
                                                                    



                                                             

         

                
 
            

                                               


                                              
     

          
use futures_util::StreamExt;
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use reqwest::Client;
use std::{
    cmp::min,
    collections::HashMap,
    fs::{copy, read_dir, remove_file, rename, File},
    io::Write,
};
use tokio::task::JoinSet;

use crate::{
    cache::{copy_cached_version, get_cached_versions},
    config::Cfg,
    db::{mods_get_info, userlist_add_disabled_versions},
    error::{EType, MLErr, MLE},
    modrinth::Version,
    List, PROGRESS_CHARS, STYLE_BAR_BYTE, STYLE_BAR_POS, STYLE_SPINNER,
};

/// # Errors
pub async fn download_versions(
    list: List,
    config: Cfg,
    versions: Vec<Version>,
    progress: &MultiProgress,
    progress_before: &ProgressBar,
) -> MLE<()> {
    let cached = get_cached_versions(&config.cache)?;

    let mut js = JoinSet::new();

    let style_spinner = ProgressStyle::with_template(STYLE_SPINNER).map_err(|_| MLErr::new(EType::LibIndicatif, "template error"))?;

    let all = progress.insert_before(
        progress_before,
        ProgressBar::new(versions.len().try_into().map_err(|_| MLErr::new(EType::Other, "ListStackLen"))?),
    );
    all.set_style(
        ProgressStyle::with_template(STYLE_BAR_POS)
            .map_err(|_| MLErr::new(EType::LibIndicatif, "template error"))?
            .progress_chars(PROGRESS_CHARS),
    );
    all.set_message(format!("✓Downloading {}", list.id));

    for ver in versions {
        let p = progress.insert_before(&all, ProgressBar::new(1));
        p.set_style(style_spinner.clone());
        js.spawn(download_version(
            config.clone(),
            list.clone(),
            ver,
            cached.clone(),
            p,
        ));
    }

    while js.join_next().await.is_some() {
        all.inc(1);
    }

    all.finish_with_message(format!("✓Downloading {}", list.id));

    Ok(())
}

/// # Errors
async fn download_version(
    config: Cfg,
    list: List,
    version: Version,
    mut cached: HashMap<String, String>,
    progress: ProgressBar,
) -> MLE<()> {
    let project_info = mods_get_info(&config, &version.project_id)?;

    let dl_path = String::from(&list.download_folder);

    progress.set_message(format!("{} - {}", project_info.title, version.id));

    let mut cache_msg = "";
    //Check cache if already downloaded
    let c = cached.remove(&version.id);
    if c.is_some() {
        progress.set_message(format!("Get {} from cache", version.id));
        cache_msg = " (cached)";
        copy_cached_version(&c.unwrap(), &dl_path)?;
    } else {
        let files = version.files;
        let file = match files.clone().into_iter().find(|f| f.primary) {
            Some(f) => f,
            None => files[0].clone(),
        };
        let mut splitname: Vec<&str> = file.filename.split('.').collect();
        let Ok(extension) = splitname.pop().ok_or("") else {
            return Err(MLErr::new(EType::Other, "NO_FILE_EXTENSION"))
        };
        let filename = format!(
            "{}.mr.{}.{}.{}",
            splitname.join("."),
            version.project_id,
            version.id,
            extension
        );

        download_file(&file.url, &list.download_folder, &filename, &progress)
            .await?;

        progress.set_message(format!("Copy {} to cache", version.id));
        let dl_path_file = format!("{}/{}", list.download_folder, filename);
        let cache_path = format!("{}/{}", &config.cache, filename);

        copy(dl_path_file, cache_path)?;
    }

    progress.finish_with_message(format!(
        "✓{} - {}{}",
        project_info.title, version.id, cache_msg
    ));

    Ok(())
}

/// # Errors
async fn download_file(
    url: &str,
    path: &str,
    name: &str,
    progress: &ProgressBar,
) -> MLE<()> {
    let dl_path_file = format!("{path}/{name}");
    let res = Client::new().get(url).send().await?;

    let size = res.content_length().expect("Couldn't get content length");

    let style_bar_byte = ProgressStyle::with_template(STYLE_BAR_BYTE)
        .unwrap()
        .progress_chars(PROGRESS_CHARS);

    progress.set_length(size);
    progress.set_style(style_bar_byte);

    // download chunks
    let mut file = File::create(&dl_path_file)?;
    let mut stream = res.bytes_stream();

    let mut downloaded: u64 = 0;

    while let Some(item) = stream.next().await {
        // progress.inc(1);
        let chunk = item?;
        file.write_all(&chunk)?;

        // Progress bar
        let new = min(downloaded + (chunk.len() as u64), size);
        downloaded = new;
        progress.set_position(new);

        // std::thread::sleep(std::time::Duration::from_millis(100));
    }

    Ok(())
}

/// # Errors
pub fn disable_version(
    config: &Cfg,
    current_list: &List,
    versionid: String,
    mod_id: String,
) -> MLE<()> {
    let file = get_file_path(current_list, &versionid)?;
    let disabled = format!("{file}.disabled");

    rename(file, disabled)?;

    userlist_add_disabled_versions(config, &current_list.id, versionid, mod_id)?;

    Ok(())
}

/// # Errors
pub fn delete_version(list: &List, version: &str) -> MLE<()> {
    let file = get_file_path(list, version)?;

    remove_file(file)?;

    Ok(())
}

/// # Errors
pub fn get_file_path(list: &List, versionid: &str) -> MLE<String> {
    let mut names: HashMap<String, String> = HashMap::new();
    for file in read_dir(&list.download_folder)? {
        let path = file?.path();
        if path.is_file() {
            let Ok(pathstr) = path.to_str().ok_or("") else {
                return Err(MLErr::new(EType::Other, "INVALID_PATH"))
            };
            let namesplit: Vec<&str> = pathstr.split('.').collect();
            let ver_id = namesplit[namesplit.len() - 2];
            names.insert(String::from(ver_id), String::from(pathstr));
        }
    }

    let Ok(filename) = names.get(versionid).ok_or("") else {
        return Err(MLErr::new(
            EType::ArgumentError,
            "VERSION_NOT_FOUND_IN_FILES",
        ))
    };

    Ok(filename.to_owned())
}

/// # Errors
pub fn get_downloaded_versions(list: &List) -> MLE<HashMap<String, String>> {
    let mut versions: HashMap<String, String> = HashMap::new();
    for file in read_dir(&list.download_folder)? {
        let path = file?.path();
        if path.is_file()
            && path
                .extension()
                .ok_or(MLErr::new(EType::IoError, "extension"))?
                == "jar"
        {
            let pathstr = path.to_str().ok_or(MLErr::new(EType::IoError, "path_to_str"))?;
            let namesplit: Vec<&str> = pathstr.split('.').collect();
            versions.insert(
                String::from(namesplit[namesplit.len() - 3]),
                String::from(namesplit[namesplit.len() - 2]),
            );
        }
    }
    Ok(versions)
}

/// # Errors
pub fn clean_list_dir(list: &List) -> MLE<()> {
    let dl_path = &list.download_folder;
    for entry in std::fs::read_dir(dl_path)? {
        let entry = entry?;
        std::fs::remove_file(entry.path())?;
    }
    Ok(())
}