1pub 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
19pub fn conn(p: String) -> SqliteConnection {
21 SqliteConnection::establish(&p).unwrap_or_else(|_| panic!("Error connecting to {:?}", p))
22}
23
24#[derive(Clone, Debug, Default)]
26pub enum Run {
27 Test,
28 #[default]
29 Submit,
30}
31
32#[derive(Clone)]
34pub struct Cache(pub LeetCode);
35
36impl Cache {
37 fn conn(&self) -> Result<SqliteConnection, Error> {
39 Ok(conn(self.0.conf.storage.cache()?))
40 }
41
42 pub fn clean(&self) -> Result<(), Error> {
44 Ok(std::fs::remove_file(self.0.conf.storage.cache()?)?)
45 }
46
47 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 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 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() .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 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 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 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() .await?,
138 )
139 .ok_or(Error::NoneError)
140 }
141
142 pub fn get_problems(&self) -> Result<Vec<Problem>, Error> {
144 Ok(problems.load::<Problem>(&mut self.conn()?)?)
145 }
146
147 #[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 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 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 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 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 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 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 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 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}