leetcode_api/dao/
save_info.rs1use std::{
2 fmt::Write as _,
3 ops::Not,
4 path::{Path, PathBuf},
5};
6
7use lcode_config::global::G_USER_CONFIG;
8use miette::{IntoDiagnostic, Result};
9use tokio::{
10 fs::{create_dir_all, File, OpenOptions},
11 io::{AsyncReadExt, AsyncWriteExt},
12};
13use tracing::{instrument, trace};
14
15use crate::{
16 entities::*,
17 leetcode::{question::qs_detail::Question, IdSlug},
18 render::Render,
19};
20
21#[derive(Clone)]
24#[derive(Debug)]
25#[derive(Default)]
26#[derive(PartialEq, Eq)]
27pub struct FileInfo {
28 pub code_path: PathBuf,
29 pub test_case_path: PathBuf,
30 pub content_path: PathBuf,
31}
32
33impl FileInfo {
34 async fn rest_file<A: AsRef<Path> + Send>(path: A) -> Result<File> {
35 OpenOptions::new()
36 .create(true)
37 .truncate(true)
38 .write(true)
39 .open(path)
40 .await
41 .into_diagnostic()
42 }
43 async fn append_file<A: AsRef<Path> + Send>(path: A) -> Result<File> {
44 OpenOptions::new()
45 .create(true)
46 .append(true)
47 .open(path)
48 .await
49 .into_diagnostic()
50 }
51
52 pub async fn append_test_case(&self, case: &str) -> Result<()> {
54 if case.is_empty() {
55 return Ok(());
56 }
57
58 let mut f = Self::append_file(&self.test_case_path).await?;
59
60 f.write_all(b"\n")
61 .await
62 .into_diagnostic()?;
63 f.write_all(case.as_bytes())
64 .await
65 .into_diagnostic()?;
66
67 Ok(())
68 }
69
70 pub async fn reset_test_case(&self, case: &str) -> Result<()> {
71 if case.is_empty() {
72 return Ok(());
73 }
74
75 let mut f = Self::rest_file(&self.test_case_path).await?;
76 f.write_all(case.as_bytes())
77 .await
78 .into_diagnostic()?;
79
80 Ok(())
81 }
82}
83
84impl FileInfo {
85 #[instrument]
87 pub async fn build(pb: &index::Model) -> Result<Self> {
88 let mut cache_path = G_USER_CONFIG.config.code_dir.clone();
89
90 let sub_dir = if G_USER_CONFIG
92 .config
93 .dir_with_frontend_id
94 {
95 format!("{}_{}", pb.frontend_question_id, pb.question_title_slug)
96 }
97 else {
98 format!("{}_{}", pb.question_id, pb.question_title_slug)
99 };
100 cache_path.push(sub_dir);
101
102 create_dir_all(&cache_path)
103 .await
104 .into_diagnostic()?;
105
106 let mut code_path = cache_path.clone();
107 let code_file_name = format!("{}{}", pb.question_id, G_USER_CONFIG.get_suffix());
108 code_path.push(code_file_name);
109 trace!("code path: {:?}", code_path);
110
111 let mut test_case_path = cache_path.clone();
112 let test_file_name = format!("{}_test_case.txt", pb.question_id);
113 test_case_path.push(test_file_name);
114 trace!("test case path: {:?}", test_case_path);
115
116 let mut content_path = cache_path;
117 let temp = if G_USER_CONFIG.config.translate {
118 "cn"
119 }
120 else {
121 "en"
122 };
123 let detail_file_name = format!("{}_detail_{}.md", pb.question_id, temp);
124 content_path.push(detail_file_name);
125 trace!("content case path: {:?}", content_path);
126 Ok(Self {
127 code_path,
128 test_case_path,
129 content_path,
130 })
131 }
132
133 pub async fn write_to_file(&self, detail: &Question) -> Result<()> {
135 let content = detail.to_md_str(true);
136
137 let (r1, r2) = tokio::join!(
138 Self::write_file(&self.test_case_path, &detail.example_testcases),
139 Self::write_file(&self.content_path, &content)
140 );
141 r1?;
142 r2?;
143
144 if let Some(snippets) = &detail.code_snippets {
145 for snippet in snippets {
146 if snippet.lang_slug == G_USER_CONFIG.config.lang {
147 let (start, end, mut inject_start, inject_end) = G_USER_CONFIG.get_lang_info();
148
149 if !inject_start.is_empty() {
150 inject_start += "\n";
151 }
152 let code_str = format!(
153 "{}{}\n{}\n{}\n{}",
154 inject_start, start, snippet.code, end, inject_end
155 );
156 Self::write_file(&self.code_path, &code_str).await?;
157 }
158 }
159 }
160
161 if !self.code_path.exists() {
163 let temp = if detail.is_paid_only {
164 "this question is paid only".to_owned()
165 }
166 else {
167 let mut temp = format!(
168 "this question not support {} \n\nsupport below:\n",
169 G_USER_CONFIG.config.lang
170 );
171 if let Some(snippets) = &detail.code_snippets {
172 for snippet in snippets {
173 writeln!(&mut temp, "{}", snippet.lang_slug).into_diagnostic()?;
174 }
175 }
176 temp
177 };
178
179 Self::write_file(&self.code_path, &temp).await?;
180 }
181
182 Ok(())
183 }
184 pub async fn get_user_code(&self, idslug: &IdSlug) -> Result<(String, String)> {
185 let (code_file, test_case_file) = tokio::join!(
186 File::open(&self.code_path),
187 File::open(&self.test_case_path)
188 );
189
190 let (mut code_file, mut test_case_file) = (
191 code_file.map_err(|err| {
192 miette::miette!(
193 "Error: {}. There is no code file, maybe you changed the name, please get \
194 **{}** question detail again",
195 err,
196 idslug
197 )
198 })?,
199 test_case_file.map_err(|err| {
200 miette::miette!(
201 "Error: {}. There is no test case file, maybe you changed the name, please \
202 remove relate file and get **{}** question detail again, or manual create a \
203 same name blank file",
204 err,
205 idslug
206 )
207 })?,
208 );
209
210 let mut code = String::new();
211 let mut test_case = String::new();
212
213 let (code_res, test_case_res) = tokio::join!(
214 code_file.read_to_string(&mut code),
215 test_case_file.read_to_string(&mut test_case)
216 );
217 code_res.into_diagnostic()?;
218 test_case_res.into_diagnostic()?;
219
220 Ok((code, test_case))
221 }
222
223 async fn write_file(path: &PathBuf, val: &str) -> Result<()> {
225 if path.exists().not() {
226 create_dir_all(
227 &path
228 .parent()
229 .expect("get path parent failed"),
230 )
231 .await
232 .into_diagnostic()
233 .expect("create_dir_all failed");
234
235 let mut f = Self::rest_file(&path).await?;
236 f.write_all(val.as_bytes())
237 .await
238 .into_diagnostic()?;
239
240 f.sync_all().await.into_diagnostic()?;
241 }
242 Ok(())
243 }
244}