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

                               
                  
                     
  

                                    
                           

               


                           

                                          
  
 
                     


                                        
                   
                       
                              
                             
                       


             
                                                    




                                                          
                                           






                                                        
                  
                                          


          
                                                       
                                                             

                                            
 
                                                       
                                              
                                                       


                                                                 



                                     

                                                                                


              
                                                              
                                                                      
                                                             
                                                                                      

                             
                                                                                



                                    
          
 

                                 
                                                          


            
                                                                 
                                                                      
                                                                                

                            
                                    



                                          





                                                                     
                                                               

                                                                           
                    







                                                                                             
                                       

     








                                                                             

                                                                                 
              

     




                                                                              
     


                                               

                       


                         
                 




                                                                 





                                                                      
                                                                           

                                            
                                   


                                                     
                                          
                                                         
                                                            



              
 
use std::{
    fs::{create_dir_all, File},
    io::{Read, Write},
    path::PathBuf,
    time::SystemTime,
};

use serde::{Deserialize, Serialize};
use tracing::{debug, info};
use uuid::Uuid;

use crate::{
    config::Config,
    error::{Error, Result},
    packages::PackageList,
    pathinfo::PathInfo, send_notification,
};

pub type Id = String;

#[derive(Debug, Serialize, Deserialize)]
pub struct Backup {
    pub id: String,
    pub timestamp: u64,
    pub packages: PackageList,
    pub files: Vec<PathInfo>,
    pub device: String,
}

impl Backup {
    pub fn create(config: &Config) -> Result<Self> {
        let mut files: Vec<PathInfo> = Vec::new();
        for dir in &config.directories {
            files.push(PathInfo::from_path(config, dir)?);
        }
        Ok(Self {
            id: Uuid::new_v4().to_string(),
            timestamp: SystemTime::now()
                .duration_since(SystemTime::UNIX_EPOCH)?
                .as_secs(),
            packages: config
                .package_manager
                .to_package_manager()
                .get_installed()?,
            files,
            device: config.device.clone(),
        })
    }

    pub fn save(&self, config: &Config) -> Result<()> {
        info!("Save Backup {:?}", self.get_location(config));
        let loc = self.get_location(config);
        loc.append_to_root(config)?;

        let backup_root = loc.get_absolute_dir(config);
        create_dir_all(&backup_root).unwrap();
        let path = format!("{backup_root}/index.json");
        let mut f = File::create(path).unwrap();
        f.write_all(&serde_json::to_vec(self).unwrap()).unwrap();

        for path in &self.files {
            path.save(&backup_root)?;
        }

        send_notification("Backup created" , "", notify_rust::Urgency::Normal)?;

        Ok(())
    }

    pub fn get_last(config: &Config) -> Result<Option<Self>> {
        let backup_index_root = format!("{}/index.json", config.root);
        debug!(?backup_index_root, "backup index location:");
        let list: Vec<IndexEntry> = match Self::get_json_content(&backup_index_root) {
            Ok(list) => list,
            Err(err) => {
                if err.to_string() == "No such file or directory (os error 2)" {
                    return Ok(None);
                };
                return Err(err);
            }
        };

        Ok(Some(Self::from_index(
            config,
            &list.last().ok_or(Error::BackupNotFound)?.id,
        )?))
    }

    pub fn from_index(config: &Config, id: &Id) -> Result<Self> {
        let backup_index_root = format!("{}/index.json", config.root);
        let list: Vec<IndexEntry> = Self::get_json_content(&backup_index_root)?;
        let index_loc = list
            .iter()
            .find(|bl| &bl.id == id)
            .ok_or(Error::BackupNotFound)?
            .rel_location
            .clone();

        let path = format!("{}/{index_loc}/index.json", config.root);
        let index_file: Self = Self::get_json_content(&path)?;

        Ok(index_file)
    }

    pub fn get_location(&self, config: &Config) -> IndexEntry {
        let rel_location = format!("{}_{}", config.device, self.timestamp);

        IndexEntry {
            id: self.id.to_string(),
            rel_location,
        }
    }

    pub fn get_absolute_file_location(&self, config: &Config, rel_location: &str) -> String {
        let loc = self.get_location(config).get_absolute_dir(config);

        format!("{loc}/{rel_location}")
    }

    pub fn restore(&self, config: &Config) -> Result<()> {
        info!(?self.id, ?self.timestamp, "Restore Backup");

        let backup_root = self.get_location(config).get_absolute_dir(config);

        for path in &self.files {
            path.restore(config, &backup_root)?;
        }

        send_notification("Backup restored" , "", notify_rust::Urgency::Normal)?;

        Ok(())
    }

    fn get_json_content<T: for<'a> Deserialize<'a>>(path: &str) -> Result<T> {
        let mut file = File::open(path)?;
        let mut content = String::new();
        file.read_to_string(&mut content)?;
        Ok(serde_json::from_str(&content)?)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexEntry {
    id: Id,
    rel_location: String,
}

impl IndexEntry {
    pub fn get_absolute_dir(&self, config: &Config) -> String {
        format!("{}/{}", config.root, self.rel_location)
    }

    pub fn append_to_root(&self, config: &Config) -> Result<()> {
        let backup_index_root = format!("{}/index.json", config.root);
        let path = PathBuf::from(&backup_index_root);
        if path.exists() {
            let mut f = File::open(&path)?;
            let mut content = String::new();
            f.read_to_string(&mut content)?;
            let mut loc: Vec<IndexEntry> = serde_json::from_str(&content)?;

            let mut f = File::create(path)?;
            loc.push(self.clone());

            f.write_all(&serde_json::to_vec(&loc)?)?;
        } else {
            create_dir_all(&config.root)?;
            let mut f = File::create(backup_index_root)?;
            f.write_all(&serde_json::to_vec(&vec![self])?)?;
        };

        Ok(())
    }
}