1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use crate::errors::*;
use crate::api::Client;
use crate::config::Config;
use crate::engine;
use crate::paths;
use std::collections::HashSet;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use std::thread;

// 1 week
const UPDATE_INTERVAL: u64 = 3600 * 24 * 7;


#[derive(Debug, Default, Serialize, Deserialize)]
pub struct AutoUpdater {
    #[serde(default)]
    registry: Option<u64>,
    #[serde(default)]
    last_update: u64,
    #[serde(default)]
    outdated: HashSet<String>,
}

impl AutoUpdater {
    #[inline]
    fn path() -> Result<PathBuf> {
        let path = paths::sn0int_dir()?;
        Ok(path.join("autoupdate.json"))
    }

    #[inline]
    fn now() -> u64 {
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("Time went backwards")
            .as_secs()
    }

    #[inline]
    pub fn outdated(&self) -> usize {
        self.outdated.len()
    }

    #[inline]
    pub fn updated(&mut self, module: &str) {
        self.outdated.remove(module);
    }

    #[inline]
    pub fn is_outdated(&self, module: &str) -> bool {
        self.outdated.contains(module)
    }

    pub fn load() -> Result<AutoUpdater> {
        let config = fs::read(AutoUpdater::path()?)
            .unwrap_or_else(|_| b"{}".to_vec());

        let config = serde_json::from_slice(&config)
            .unwrap_or_else(|_| AutoUpdater::default());

        Ok(config)
    }

    pub fn save(&self) -> Result<()> {
        let config = serde_json::to_string(&self)?;
        fs::write(AutoUpdater::path()?, &config)?;
        Ok(())
    }

    pub fn check_background(mut self, config: &Config, modules: Vec<&engine::Module>) {
        if config.core.no_autoupdate {
            debug!("Auto update has been disabled, skipping");
            return;
        }

        if self.last_update + UPDATE_INTERVAL >= AutoUpdater::now() {
            debug!("Auto update timer hasn't expired yet");
            return;
        }

        let modules = modules.into_iter()
            .map(|x| x.clone())
            .collect();

        debug!("Checking for outdated modules in the background");
        match Client::new(config) {
            Ok(client) => {
                thread::spawn(move || {
                    if let Err(err) = self.check_updates(client, modules) {
                        error!("AutoUpdater failed: {}", err);
                    } else {
                        debug!("AutoUpdater finished");
                    }
                });
            },
            Err(err) => error!("Failed to create client: {}", err),
        }
    }

    pub fn check_updates(&mut self, client: Client, modules: Vec<engine::Module>) -> Result<()> {
        let latest = client.latest_release()?;

        if latest.time != self.registry {
            let mut outdated = HashSet::new();

            for module in modules {
                if module.is_private() {
                    debug!("{} is a private module, skipping", module.canonical());
                    continue;
                }

                let installed = module.version();
                if let Ok(infos) = client.query_module(&module.id()) {
                    debug!("Latest version: {:?}", infos);

                    if let Some(latest) = infos.latest {
                        if installed != latest {
                            let canonical = module.canonical();
                            debug!("Outdated: {}: {:?} -> {:?}", canonical, installed, latest);
                            outdated.insert(canonical);
                        }
                    }
                }
            }

            self.outdated = outdated;
        }

        self.registry = latest.time;
        self.last_update = AutoUpdater::now();
        self.save()?;

        Ok(())
    }
}