1use std::error::Error;
2use std::path::Path;
3
4use crate::dictionary_builder::DictionaryBuilder;
5
6pub struct FetchParams {
7 pub file_name: &'static str,
9
10 pub input_dir: &'static str,
12
13 pub output_dir: &'static str,
15
16 pub dummy_input: &'static str,
18
19 pub download_url: &'static str,
21}
22
23#[cfg(not(target_os = "windows"))]
24fn empty_directory(dir: &Path) -> Result<(), Box<dyn Error>> {
25 if dir.is_dir() {
26 for entry in std::fs::read_dir(dir)? {
27 let entry = entry?;
28 let path = entry.path();
29 if path.is_dir() {
30 std::fs::remove_dir_all(&path)?;
31 } else {
32 std::fs::remove_file(&path)?;
33 }
34 }
35 }
36 Ok(())
37}
38
39#[cfg(target_os = "windows")]
40fn copy_dir_all(src: &Path, dst: &Path) -> Result<(), Box<dyn Error>> {
41 if !dst.exists() {
42 std::fs::create_dir(dst)?;
43 }
44
45 for entry in std::fs::read_dir(src)? {
46 let entry = entry?;
47 let entry_path = entry.path();
48 let dst_path = dst.join(entry.file_name());
49
50 if entry_path.is_dir() {
51 copy_dir_all(&entry_path, &dst_path)?;
52 } else {
53 std::fs::copy(&entry_path, &dst_path)?;
54 }
55 }
56 Ok(())
57}
58
59pub fn fetch(params: FetchParams, builder: impl DictionaryBuilder) -> Result<(), Box<dyn Error>> {
61 use std::env;
62 use std::fs::{create_dir, rename, File};
63 use std::io::{self, Cursor, Read, Write};
64 use std::path::{Path, PathBuf};
65
66 use encoding::all::UTF_8;
67 use encoding::{EncoderTrap, Encoding};
68 use flate2::read::GzDecoder;
69 use tar::Archive;
70
71 println!("cargo:rerun-if-changed=build.rs");
72 println!("cargo:rerun-if-changed=Cargo.toml");
73
74 let (build_dir, is_cache) = if let Some(lindera_cache_dir) = env::var_os("LINDERA_CACHE") {
80 (
81 PathBuf::from(lindera_cache_dir).join(env::var_os("CARGO_PKG_VERSION").unwrap()),
82 true,
83 )
84 } else {
85 (
86 PathBuf::from(env::var_os("OUT_DIR").unwrap()), false,
88 )
89 };
90
91 println!("cargo::rustc-env=LINDERA_WORKDIR={}", build_dir.display());
93
94 std::fs::create_dir_all(&build_dir)?;
95
96 let input_dir = build_dir.join(params.input_dir);
97
98 let output_dir = build_dir.join(params.output_dir);
99
100 if is_cache && output_dir.is_dir() {
102 return Ok(());
103 }
104
105 if std::env::var("DOCS_RS").is_ok() {
106 create_dir(&input_dir)?;
108
109 let mut dummy_char_def = File::create(input_dir.join("char.def"))?;
111 dummy_char_def.write_all(b"DEFAULT 0 1 0\n")?;
112
113 let mut dummy_dict_csv = File::create(input_dir.join("dummy_dict.csv"))?;
115 dummy_dict_csv.write_all(
116 &UTF_8
117 .encode(params.dummy_input, EncoderTrap::Ignore)
118 .unwrap(),
119 )?;
120
121 File::create(input_dir.join("unk.def"))?;
123 let mut dummy_matrix_def = File::create(input_dir.join("matrix.def"))?;
124 dummy_matrix_def.write_all(b"0 1 0\n")?;
125 } else {
126 let source_path_for_build = &build_dir.join(params.file_name);
128
129 let tmp_path = Path::new(&build_dir).join(params.file_name.to_owned() + ".download");
131
132 let resp = ureq::get(params.download_url).call()?;
134 let mut dest = File::create(&tmp_path)?;
135
136 io::copy(&mut resp.into_reader(), &mut dest)?;
137 dest.flush()?;
138
139 rename(tmp_path.clone(), source_path_for_build).expect("Failed to rename temporary file");
140
141 let tmp_extract_path =
143 Path::new(&build_dir).join(format!("tmp-archive-{}", params.input_dir));
144 let tmp_extracted_path = tmp_extract_path.join(params.input_dir);
145 let _ = std::fs::remove_dir_all(&tmp_extract_path);
146 std::fs::create_dir_all(&tmp_extract_path)?;
147
148 let mut tar_gz = File::open(source_path_for_build)?;
149 let mut buffer = Vec::new();
150 tar_gz.read_to_end(&mut buffer)?;
151 let cursor = Cursor::new(buffer);
152 let decoder = GzDecoder::new(cursor);
153 let mut archive = Archive::new(decoder);
154 archive.unpack(&tmp_extract_path)?;
155
156 #[cfg(target_os = "windows")]
157 {
158 if input_dir.exists() {
161 std::fs::remove_dir_all(&input_dir).expect("Failed to remove input directory");
163
164 std::fs::create_dir_all(&input_dir).expect("Failed to create input directory");
166 }
167
168 copy_dir_all(&tmp_extracted_path, &input_dir)
170 .expect("Failed to copy files from temporary directory to input directory");
171
172 std::fs::remove_dir_all(&tmp_extracted_path)
174 .expect("Failed to remove temporary directory");
175 }
176 #[cfg(not(target_os = "windows"))]
177 {
178 empty_directory(&input_dir).expect("Failed to empty input directory");
180 rename(tmp_extracted_path, &input_dir).expect("Failed to rename archive directory");
181 }
182
183 let _ = std::fs::remove_dir_all(&tmp_extract_path);
184 drop(dest);
185 let _ = std::fs::remove_file(source_path_for_build);
186 }
187
188 let tmp_path = build_dir.join(format!("tmp-output-{}", params.output_dir));
189 let _ = std::fs::remove_dir_all(&tmp_path);
190
191 builder.build_dictionary(&input_dir, &tmp_path)?;
192
193 #[cfg(target_os = "windows")]
194 {
195 if output_dir.exists() {
197 std::fs::remove_dir_all(&output_dir).expect("Failed to remove output directory");
199
200 std::fs::create_dir_all(&output_dir).expect("Failed to create output directory");
202 }
203
204 copy_dir_all(&tmp_path, &output_dir).expect("Failed to copy output directory");
206
207 std::fs::remove_dir_all(&tmp_path).expect("Failed to copy output directory");
209 }
210
211 #[cfg(not(target_os = "windows"))]
212 {
213 empty_directory(&output_dir).expect("Failed to empty output directory");
215
216 rename(tmp_path, &output_dir).expect("Failed to rename output directory");
218 }
219
220 let _ = std::fs::remove_dir_all(&input_dir);
221
222 Ok(())
223}