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
138
139
140
141
142
143
//! Save bad network\'s ass.
pub mod models;
mod parser;
mod sql;
mod schemas;

use self::models::*;
use self::schemas::problems::dsl::*;
use self::sql::*;
use crate::{cfg, err::Error, plugins::LeetCode};
use diesel::prelude::*;
use serde_json::Value;
use reqwest::Error as ReqwestError;


/// sqlite connection
pub fn conn(p: String) -> SqliteConnection {
    SqliteConnection::establish(&p)
        .unwrap_or_else(|_| panic!("Error connecting to {:?}", p))
}

/// req if data not download
pub struct Cache(pub LeetCode);

impl Cache {
    /// ref to sqliteconnection
    fn conn(&self) -> SqliteConnection {
        conn(self.0.conf.storage.cache())
    }
    
    /// Clean cache
    pub fn clean(&self) -> Result<(), Error> {
        let res = std::fs::remove_file(&self.0.conf.storage.cache());
        if res.is_err() {
            let err = res.err().unwrap();
            error!("{:?}", Error::CacheError(format!("clean local cache failed -> {}", &err)));
            return Err(Error::CacheError(format!("clean local cache failed -> {}", &err)));
        }
        
        Ok(())
    }

    /// ref to download probems
    pub fn update(self) -> Result<(), Error> {
        let c = conn((&self.0.conf.storage.cache()).to_owned());
        let res = self.download_problems();
        if res.is_err() {
            return Err(res.err().unwrap());
        }
        
        for i in res.unwrap().into_iter() {
            let target = problems.filter(id.eq(i.id));
            let u_res = diesel::update(target).set(i.to_owned()).execute(&c);
            if u_res.is_err() {
                let err = u_res.err().unwrap();
                error!("{:?}", Error::CacheError(format!("Update cache failed -> {}", &err)));
                return Err(Error::CacheError(format!("Update cache failed -> {}", &err)));
            }
        }
        
        Ok(())
    }
    
    /// Download leetcode problems to db
    pub fn download_problems(self) -> Result<Vec<Problem>, Error> {
        info!("Downloading leetcode problems...");
        let mut ps: Vec<Problem> = vec![];

        for i in &self.0.conf.sys.categories.to_owned() {
            let res = self.0.clone().get_category_problems(&i);

            if res.is_err() {
                return Err(res.err().unwrap());
            }

            let json: Result<Value, ReqwestError> = res.unwrap().json();
            if json.is_err() {
                error!("{:?}", Error::DownloadError(format!("category {}", &i)));
                return Err(Error::DownloadError(format!("category {}", &i)));
            }

            // get "stat_status_pairs" from respnonse
            let res = parser::parse_problem(&mut ps, json.unwrap());
            if res.is_err() {
                error!("{:?}", Error::DownloadError(format!("category {}", &i)));
                return Err(Error::DownloadError(format!("category {}", &i)));
            }
        }

        // store problems into database
        let j = serde_json::to_string(&ps);
        if j.is_err() {
            error!("{:?}", Error::ParseError("data from cache"));
            return Err(Error::ParseError("data from cache"));
        }

        let count = self.get_problems().unwrap().len();
        if count == 0 {
            ps.sort_by(|a, b| b.id.partial_cmp(&a.id).unwrap());
            let res = diesel::insert_into(problems).values(&ps).execute(&self.conn());
            if res.is_err() {
                let err = res.err().unwrap();
                error!("{:?}", Error::CacheError(format!("Save to cache failed -> {}", &err)));
                return Err(Error::CacheError(format!("Save to cache failed -> {}", &err)));
            }
        }

        Ok(ps)
    }

    /// Get problems from cache
    ///
    /// if cache doesn't exist, request a new copy
    ///
    /// [TODO]:
    ///  1. make downloading async
    pub fn get_problems(&self) -> Result<Vec<Problem>, Error> {
        let res = problems.load::<Problem>(&self.conn());
        if res.is_err() {
            let err = res.err().unwrap();
            warn!("Select problems from cache failed -> {:?} -> try downloading", &err);
            return Err(Error::CacheError(
                format!("Select problems from cache failed -> {:?} -> try downloading", &err)
            ));
        }
        
        Ok(res.unwrap())
    }

    /// New cache
    pub fn new() -> Result<Self, Error> {
        let conf = cfg::locate();
        let c = conn(conf.storage.cache());
        let r = diesel::sql_query(CREATE_PROBLEMS_IF_NOT_EXISTS).execute(&c);
        if r.is_err() {
            let err = r.err().unwrap();
            error!("{:?}", Error::CacheError(format!("Create local cache failed -> {}", &err)));
            return Err(Error::CacheError(format!("Create local cache failed -> {}", &err)));
        }
        
        Ok(Cache(LeetCode::new()))
    }
}