leetcode_cli/cache/
mod.rs

1//! Save bad network\'s ass.
2pub mod models;
3pub mod parser;
4pub mod schemas;
5mod sql;
6use self::models::*;
7use self::schemas::{problems::dsl::*, tags::dsl::*};
8use self::sql::*;
9use crate::helper::test_cases_path;
10use crate::{config::Config, err::Error, plugins::LeetCode};
11use anyhow::anyhow;
12use colored::Colorize;
13use diesel::prelude::*;
14use reqwest::Response;
15use serde::de::DeserializeOwned;
16use serde_json::Value;
17use std::collections::HashMap;
18
19/// sqlite connection
20pub fn conn(p: String) -> SqliteConnection {
21    SqliteConnection::establish(&p).unwrap_or_else(|_| panic!("Error connecting to {:?}", p))
22}
23
24/// Condition submit or test
25#[derive(Clone, Debug, Default)]
26pub enum Run {
27    Test,
28    #[default]
29    Submit,
30}
31
32/// Requests if data not download
33#[derive(Clone)]
34pub struct Cache(pub LeetCode);
35
36impl Cache {
37    /// Ref to sqlite connection
38    fn conn(&self) -> Result<SqliteConnection, Error> {
39        Ok(conn(self.0.conf.storage.cache()?))
40    }
41
42    /// Clean cache
43    pub fn clean(&self) -> Result<(), Error> {
44        Ok(std::fs::remove_file(self.0.conf.storage.cache()?)?)
45    }
46
47    /// ref to download problems
48    pub async fn update(self) -> Result<(), Error> {
49        self.download_problems().await?;
50        Ok(())
51    }
52
53    pub fn update_after_ac(self, rid: i32) -> Result<(), Error> {
54        let mut c = conn(self.0.conf.storage.cache()?);
55        let target = problems.filter(id.eq(rid));
56        diesel::update(target)
57            .set(status.eq("ac"))
58            .execute(&mut c)?;
59        Ok(())
60    }
61
62    async fn get_user_info(&self) -> Result<(String, bool), Error> {
63        let user = parser::user(self.clone().0.get_user_info().await?.json().await?);
64        match user {
65            None => Err(Error::NoneError),
66            Some(None) => Err(Error::CookieError),
67            Some(Some((s, b))) => Ok((s, b)),
68        }
69    }
70
71    async fn is_session_bad(&self) -> bool {
72        // i.e. self.get_user_info().contains_err(Error::CookieError)
73        matches!(self.get_user_info().await, Err(Error::CookieError))
74    }
75
76    async fn resp_to_json<T: DeserializeOwned>(&self, resp: Response) -> Result<T, Error> {
77        let maybe_json: Result<T, _> = resp.json().await;
78        if maybe_json.is_err() && self.is_session_bad().await {
79            Err(Error::CookieError)
80        } else {
81            Ok(maybe_json?)
82        }
83    }
84
85    /// Download leetcode problems to db
86    pub async fn download_problems(self) -> Result<Vec<Problem>, Error> {
87        info!("Fetching leetcode problems...");
88        let mut ps: Vec<Problem> = vec![];
89
90        for i in &self.0.conf.sys.categories.to_owned() {
91            let json = self
92                .0
93                .clone()
94                .get_category_problems(i)
95                .await?
96                .json() // does not require LEETCODE_SESSION
97                .await?;
98            parser::problem(&mut ps, json).ok_or(Error::NoneError)?;
99        }
100
101        diesel::replace_into(problems)
102            .values(&ps)
103            .execute(&mut self.conn()?)?;
104
105        Ok(ps)
106    }
107
108    /// Get problem
109    pub fn get_problem(&self, rfid: i32) -> Result<Problem, Error> {
110        let p: Problem = problems.filter(fid.eq(rfid)).first(&mut self.conn()?)?;
111        if p.category != "algorithms" {
112            return Err(anyhow!("No support for database and shell questions yet").into());
113        }
114
115        Ok(p)
116    }
117
118    /// Get problem from name
119    pub fn get_problem_id_from_name(&self, problem_name: &String) -> Result<i32, Error> {
120        let p: Problem = problems
121            .filter(name.eq(problem_name))
122            .first(&mut self.conn()?)?;
123        if p.category != "algorithms" {
124            return Err(anyhow!("No support for database and shell questions yet").into());
125        }
126        Ok(p.fid)
127    }
128
129    /// Get daily problem
130    pub async fn get_daily_problem_id(&self) -> Result<i32, Error> {
131        parser::daily(
132            self.clone()
133                .0
134                .get_question_daily()
135                .await?
136                .json() // does not require LEETCODE_SESSION
137                .await?,
138        )
139        .ok_or(Error::NoneError)
140    }
141
142    /// Get problems from cache
143    pub fn get_problems(&self) -> Result<Vec<Problem>, Error> {
144        Ok(problems.load::<Problem>(&mut self.conn()?)?)
145    }
146
147    /// Get question
148    #[allow(clippy::useless_let_if_seq)]
149    pub async fn get_question(&self, rfid: i32) -> Result<Question, Error> {
150        let target: Problem = problems.filter(fid.eq(rfid)).first(&mut self.conn()?)?;
151
152        let ids = match target.level {
153            1 => target.fid.to_string().green(),
154            2 => target.fid.to_string().yellow(),
155            3 => target.fid.to_string().red(),
156            _ => target.fid.to_string().dimmed(),
157        };
158
159        println!(
160            "\n[{}] {} {}\n\n",
161            &ids,
162            &target.name.bold().underline(),
163            "is on the run...".dimmed()
164        );
165
166        if target.category != "algorithms" {
167            return Err(anyhow!("No support for database and shell questions yet").into());
168        }
169
170        let mut rdesc = Question::default();
171        if !target.desc.is_empty() {
172            rdesc = serde_json::from_str(&target.desc)?;
173        } else {
174            let json: Value = self
175                .0
176                .clone()
177                .get_question_detail(&target.slug)
178                .await?
179                .json()
180                .await?;
181            debug!("{:#?}", &json);
182            match parser::desc(&mut rdesc, json) {
183                None => return Err(Error::NoneError),
184                Some(false) => {
185                    return if self.is_session_bad().await {
186                        Err(Error::CookieError)
187                    } else {
188                        Err(Error::PremiumError)
189                    };
190                }
191                Some(true) => (),
192            }
193
194            // update the question
195            let sdesc = serde_json::to_string(&rdesc)?;
196            diesel::update(&target)
197                .set(desc.eq(sdesc))
198                .execute(&mut self.conn()?)?;
199        }
200
201        Ok(rdesc)
202    }
203
204    pub async fn get_tagged_questions(self, rslug: &str) -> Result<Vec<String>, Error> {
205        trace!("Geting {} questions...", &rslug);
206        let ids: Vec<String>;
207        let rtag = tags
208            .filter(tag.eq(rslug.to_string()))
209            .first::<Tag>(&mut self.conn()?);
210        if let Ok(t) = rtag {
211            trace!("Got {} questions from local cache...", &rslug);
212            ids = serde_json::from_str(&t.refs)?;
213        } else {
214            ids = parser::tags(
215                self.clone()
216                    .0
217                    .get_question_ids_by_tag(rslug)
218                    .await?
219                    .json()
220                    .await?,
221            )
222            .ok_or(Error::NoneError)?;
223            let t = Tag {
224                r#tag: rslug.to_string(),
225                r#refs: serde_json::to_string(&ids)?,
226            };
227
228            diesel::insert_into(tags)
229                .values(&t)
230                .execute(&mut self.conn()?)?;
231        }
232
233        Ok(ids)
234    }
235
236    pub fn get_tags(&self) -> Result<Vec<Tag>, Error> {
237        Ok(tags.load::<Tag>(&mut self.conn()?)?)
238    }
239
240    /// run_code data
241    async fn pre_run_code(
242        &self,
243        run: Run,
244        rfid: i32,
245        test_case: Option<String>,
246    ) -> Result<(HashMap<&'static str, String>, [String; 2]), Error> {
247        trace!("pre-run code...");
248        use crate::helper::code_path;
249        use std::fs::File;
250        use std::io::Read;
251
252        let mut p = self.get_problem(rfid)?;
253        if p.desc.is_empty() {
254            trace!("Problem description does not exist, pull desc and exec again...");
255            self.get_question(rfid).await?;
256            p = self.get_problem(rfid)?;
257        }
258
259        let d: Question = serde_json::from_str(&p.desc)?;
260        let conf = &self.0.conf;
261        let mut json: HashMap<&'static str, String> = HashMap::new();
262        let mut code: String = "".to_string();
263
264        let maybe_file_testcases: Option<String> = test_cases_path(&p)
265            .map(|file_name| {
266                let mut tests = "".to_string();
267                File::open(file_name)
268                    .and_then(|mut file_descriptor| file_descriptor.read_to_string(&mut tests))
269                    .map(|_| Some(tests))
270                    .unwrap_or(None)
271            })
272            .unwrap_or(None);
273
274        let maybe_all_testcases: Option<String> = if d.all_cases.is_empty() {
275            None
276        } else {
277            Some(d.all_cases.to_string())
278        };
279
280        // Takes test cases using following priority
281        // 1. cli parameter
282        // 2. if test cases file exist, use the file test cases(user can edit it)
283        // 3. test cases from problem desc all test cases
284        // 4. sample test case from the task
285        let test_case = test_case
286            .or(maybe_file_testcases)
287            .or(maybe_all_testcases)
288            .unwrap_or(d.case);
289
290        File::open(code_path(&p, None)?)?.read_to_string(&mut code)?;
291
292        let code = if conf.code.edit_code_marker {
293            let begin = code.find(&conf.code.start_marker);
294            let end = code.find(&conf.code.end_marker);
295            if let (Some(l), Some(r)) = (begin, end) {
296                code.get((l + conf.code.start_marker.len())..r)
297                    .map(|s| s.to_string())
298                    .unwrap_or_else(|| code)
299            } else {
300                code
301            }
302        } else {
303            code
304        };
305
306        json.insert("lang", conf.code.lang.to_string());
307        json.insert("question_id", p.id.to_string());
308        json.insert("typed_code", code);
309
310        // pass manually data
311        json.insert("name", p.name.to_string());
312        json.insert("data_input", test_case);
313
314        let url = match run {
315            Run::Test => conf.sys.urls.test(&p.slug),
316            Run::Submit => {
317                json.insert("judge_type", "large".to_string());
318                conf.sys.urls.submit(&p.slug)
319            }
320        };
321
322        Ok((json, [url, conf.sys.urls.problem(&p.slug)]))
323    }
324
325    /// TODO: The real delay
326    async fn recur_verify(&self, rid: String) -> Result<VerifyResult, Error> {
327        use std::time::Duration;
328
329        trace!("Run verify recursion...");
330        std::thread::sleep(Duration::from_micros(3000));
331
332        let json: VerifyResult = self
333            .resp_to_json(self.clone().0.verify_result(rid.clone()).await?)
334            .await?;
335
336        Ok(json)
337    }
338
339    /// Exec problem filter —— Test or Submit
340    pub async fn exec_problem(
341        &self,
342        rfid: i32,
343        run: Run,
344        test_case: Option<String>,
345    ) -> Result<VerifyResult, Error> {
346        trace!("Exec problem filter —— Test or Submit");
347        let (json, [url, refer]) = self.pre_run_code(run.clone(), rfid, test_case).await?;
348        trace!("Pre-run code result {:#?}, {}, {}", json, url, refer);
349
350        let text = self
351            .0
352            .clone()
353            .run_code(json.clone(), url.clone(), refer.clone())
354            .await?
355            .text()
356            .await?;
357
358        let run_res: RunCode = serde_json::from_str(&text).map_err(|e| {
359            anyhow!(
360                "Failed to decode run code result, could be caused by cookie expiration, \
361                 csrf token mismatch, or network issue:\n {e}, raw response:\n {text}\n, \
362                 please report this issue at https://github.com/clearloop/leetcode-cli/issues/new"
363            )
364        })?;
365
366        trace!("Run code result {:#?}", run_res);
367
368        // Check if leetcode accepted the Run request
369        if match run {
370            Run::Test => run_res.interpret_id.is_empty(),
371            Run::Submit => run_res.submission_id == 0,
372        } {
373            return Err(Error::CookieError);
374        }
375
376        let mut res: VerifyResult = VerifyResult::default();
377        while res.state != "SUCCESS" {
378            res = match run {
379                Run::Test => self.recur_verify(run_res.interpret_id.clone()).await?,
380                Run::Submit => self.recur_verify(run_res.submission_id.to_string()).await?,
381            };
382        }
383        trace!("Recur verify result {:#?}", res);
384
385        res.name = json.get("name").ok_or(Error::NoneError)?.to_string();
386        res.data_input = json.get("data_input").ok_or(Error::NoneError)?.to_string();
387        res.result_type = run;
388        Ok(res)
389    }
390
391    /// New cache
392    pub fn new() -> Result<Self, Error> {
393        let conf = Config::locate()?;
394        let mut c = conn(conf.storage.cache()?);
395        diesel::sql_query(CREATE_PROBLEMS_IF_NOT_EXISTS).execute(&mut c)?;
396        diesel::sql_query(CREATE_TAGS_IF_NOT_EXISTS).execute(&mut c)?;
397
398        Ok(Cache(LeetCode::new()?))
399    }
400}