1use std::path::PathBuf;
2use std::sync::Arc;
3
4use crate::error::{ADError, ADResult};
5use crate::ndarray::{NDArray, NDDataType, NDDimension};
6
7use super::file_base::{NDFileMode, NDFileWriter, NDPluginFileBase};
8use super::runtime::{
9 ParamChangeResult, ParamChangeValue, ParamUpdate, PluginParamSnapshot, ProcessResult,
10};
11
12#[derive(Default)]
14pub struct FileParamIndices {
15 pub file_path: Option<usize>,
16 pub file_name: Option<usize>,
17 pub file_number: Option<usize>,
18 pub file_template: Option<usize>,
19 pub auto_increment: Option<usize>,
20 pub write_file: Option<usize>,
21 pub read_file: Option<usize>,
22 pub write_mode: Option<usize>,
23 pub num_capture: Option<usize>,
24 pub capture: Option<usize>,
25 pub auto_save: Option<usize>,
26 pub create_dir: Option<usize>,
27 pub file_path_exists: Option<usize>,
28 pub write_status: Option<usize>,
29 pub write_message: Option<usize>,
30 pub full_file_name: Option<usize>,
31 pub file_temp_suffix: Option<usize>,
32 pub num_captured: Option<usize>,
33 pub lazy_open: Option<usize>,
34 pub delete_driver_file: Option<usize>,
35 pub free_capture: Option<usize>,
36}
37
38pub struct FilePluginController<W: NDFileWriter> {
45 pub file_base: NDPluginFileBase,
46 pub writer: W,
47 pub params: FileParamIndices,
48 pub auto_save: bool,
49 pub capture_active: bool,
50 pub lazy_open: bool,
51 pub delete_driver_file: bool,
52 pub latest_array: Option<Arc<NDArray>>,
53}
54
55impl<W: NDFileWriter> FilePluginController<W> {
56 pub fn new(writer: W) -> Self {
57 Self {
58 file_base: NDPluginFileBase::new(),
59 writer,
60 params: FileParamIndices::default(),
61 auto_save: false,
62 capture_active: false,
63 lazy_open: false,
64 delete_driver_file: false,
65 latest_array: None,
66 }
67 }
68
69 pub fn register_params(
71 &mut self,
72 base: &mut asyn_rs::port::PortDriverBase,
73 ) -> asyn_rs::error::AsynResult<()> {
74 self.params.file_path = base.find_param("FILE_PATH");
75 self.params.file_name = base.find_param("FILE_NAME");
76 self.params.file_number = base.find_param("FILE_NUMBER");
77 self.params.file_template = base.find_param("FILE_TEMPLATE");
78 self.params.auto_increment = base.find_param("AUTO_INCREMENT");
79 self.params.write_file = base.find_param("WRITE_FILE");
80 self.params.read_file = base.find_param("READ_FILE");
81 self.params.write_mode = base.find_param("WRITE_MODE");
82 self.params.num_capture = base.find_param("NUM_CAPTURE");
83 self.params.capture = base.find_param("CAPTURE");
84 self.params.auto_save = base.find_param("AUTO_SAVE");
85 self.params.create_dir = base.find_param("CREATE_DIR");
86 self.params.file_path_exists = base.find_param("FILE_PATH_EXISTS");
87 self.params.write_status = base.find_param("WRITE_STATUS");
88 self.params.write_message = base.find_param("WRITE_MESSAGE");
89 self.params.full_file_name = base.find_param("FULL_FILE_NAME");
90 self.params.file_temp_suffix = base.find_param("FILE_TEMP_SUFFIX");
91 self.params.num_captured = base.find_param("NUM_CAPTURED");
92 self.params.lazy_open = base.find_param("FILE_LAZY_OPEN");
93 self.params.delete_driver_file = base.find_param("DELETE_DRIVER_FILE");
94 self.params.free_capture = base.find_param("FREE_CAPTURE");
95 Ok(())
96 }
97
98 pub fn process_array(&mut self, array: &NDArray) -> ProcessResult {
100 let mut proc_result = ProcessResult::empty();
101 let array = Arc::new(array.clone());
102 self.latest_array = Some(array.clone());
103
104 let result = match self.file_base.mode() {
105 NDFileMode::Single => {
106 if self.auto_save {
107 self.write_single(array)
108 } else {
109 Ok(())
110 }
111 }
112 NDFileMode::Capture => {
113 if self.capture_active {
114 self.file_base.capture_array(array);
115 self.push_num_captured_update(&mut proc_result.param_updates);
116 if self.file_base.num_captured() >= self.file_base.num_capture_target() {
117 if self.auto_save {
118 if let Err(err) = self.file_base.flush_capture(&mut self.writer) {
119 Err(err)
120 } else {
121 self.push_full_file_name_update(&mut proc_result.param_updates);
122 self.push_num_captured_update(&mut proc_result.param_updates);
123 self.capture_active = false;
124 Ok(())
125 }
126 } else {
127 self.capture_active = false;
128 Ok(())
129 }
130 } else {
131 Ok(())
132 }
133 } else {
134 Ok(())
135 }
136 }
137 NDFileMode::Stream => {
138 if self.capture_active {
139 let r = self.file_base.process_array(array, &mut self.writer);
140 let target = self.file_base.num_capture_target();
141 if r.is_ok() && target > 0 && self.file_base.num_captured() >= target {
142 if let Err(e) = self.file_base.close_stream(&mut self.writer) {
143 return ProcessResult::sink(self.error_updates(
144 false,
145 false,
146 e.to_string(),
147 ));
148 }
149 self.capture_active = false;
150 self.push_full_file_name_update(&mut proc_result.param_updates);
151 self.push_num_captured_update(&mut proc_result.param_updates);
152 }
153 r
154 } else {
155 Ok(())
156 }
157 }
158 };
159
160 if result.is_ok() {
161 proc_result.param_updates.extend(self.success_updates());
162 if self.file_base.mode() == NDFileMode::Single && self.auto_save {
163 self.push_full_file_name_update(&mut proc_result.param_updates);
164 }
165 if self.file_base.mode() == NDFileMode::Stream && self.capture_active {
166 self.push_full_file_name_update(&mut proc_result.param_updates);
167 }
168 } else if let Err(err) = result {
169 proc_result.param_updates = self.error_updates(false, false, err.to_string());
170 }
171 proc_result
172 }
173
174 pub fn on_param_change(
176 &mut self,
177 reason: usize,
178 params: &PluginParamSnapshot,
179 ) -> ParamChangeResult {
180 let mut updates = Vec::new();
181
182 if Some(reason) == self.params.file_path {
183 if let ParamChangeValue::Octet(s) = ¶ms.value {
184 let normalized = normalize_file_path(s);
185 self.file_base.file_path = normalized.clone();
186 let exists =
187 std::path::Path::new(normalized.trim_end_matches(std::path::MAIN_SEPARATOR))
188 .is_dir();
189 if let Some(idx) = self.params.file_path_exists {
190 updates.push(ParamUpdate::Int32 {
191 reason: idx,
192 addr: 0,
193 value: if exists { 1 } else { 0 },
194 });
195 }
196 }
197 } else if Some(reason) == self.params.file_name {
198 if let ParamChangeValue::Octet(s) = ¶ms.value {
199 self.file_base.file_name = s.clone();
200 }
201 } else if Some(reason) == self.params.file_number {
202 self.file_base.file_number = params.value.as_i32();
203 } else if Some(reason) == self.params.file_template {
204 if let ParamChangeValue::Octet(s) = ¶ms.value {
205 self.file_base.file_template = s.clone();
206 }
207 } else if Some(reason) == self.params.auto_increment {
208 self.file_base.auto_increment = params.value.as_i32() != 0;
209 } else if Some(reason) == self.params.auto_save {
210 self.auto_save = params.value.as_i32() != 0;
211 } else if Some(reason) == self.params.write_mode {
212 self.file_base
213 .set_mode(NDFileMode::from_i32(params.value.as_i32()));
214 } else if Some(reason) == self.params.num_capture {
215 self.file_base
216 .set_num_capture(params.value.as_i32().max(1) as usize);
217 } else if Some(reason) == self.params.create_dir {
218 self.file_base.create_dir = params.value.as_i32();
219 } else if Some(reason) == self.params.file_temp_suffix {
220 if let ParamChangeValue::Octet(s) = ¶ms.value {
221 self.file_base.temp_suffix = s.clone();
222 }
223 } else if Some(reason) == self.params.write_file {
224 if params.value.as_i32() != 0 {
225 let result = match self.file_base.mode() {
226 NDFileMode::Single => {
227 if let Some(array) = self.latest_array.clone() {
228 self.write_single(array)
229 } else {
230 Err(ADError::UnsupportedConversion(
231 "no array available for write".into(),
232 ))
233 }
234 }
235 NDFileMode::Capture => self.file_base.flush_capture(&mut self.writer),
236 NDFileMode::Stream => {
237 if let Some(array) = self.latest_array.clone() {
238 self.file_base.process_array(array, &mut self.writer)
239 } else {
240 Err(ADError::UnsupportedConversion(
241 "no array available for write".into(),
242 ))
243 }
244 }
245 };
246 match result {
247 Ok(()) => {
248 updates.extend(self.success_updates());
249 self.push_num_captured_update(&mut updates);
250 self.push_full_file_name_update(&mut updates);
251 }
252 Err(err) => {
253 return ParamChangeResult::updates(self.error_updates(
254 false,
255 true,
256 err.to_string(),
257 ));
258 }
259 }
260 }
261 } else if Some(reason) == self.params.read_file {
262 if params.value.as_i32() != 0 {
263 let result = (|| -> ADResult<Arc<NDArray>> {
264 let path = PathBuf::from(self.file_base.create_file_name());
265 self.writer.open_file(
266 &path,
267 NDFileMode::Single,
268 &NDArray::new(vec![NDDimension::new(1)], NDDataType::UInt8),
269 )?;
270 let array = Arc::new(self.writer.read_file()?);
271 self.writer.close_file()?;
272 self.latest_array = Some(array.clone());
273 Ok(array)
274 })();
275 match result {
276 Ok(array) => {
277 updates.extend(self.success_updates());
278 self.push_full_file_name_update(&mut updates);
279 return ParamChangeResult::combined(vec![array], updates);
280 }
281 Err(err) => {
282 return ParamChangeResult::updates(self.error_updates(
283 true,
284 false,
285 err.to_string(),
286 ));
287 }
288 }
289 }
290 } else if Some(reason) == self.params.lazy_open {
291 self.lazy_open = params.value.as_i32() != 0;
292 } else if Some(reason) == self.params.delete_driver_file {
293 self.delete_driver_file = params.value.as_i32() != 0;
294 } else if Some(reason) == self.params.free_capture {
295 if params.value.as_i32() != 0 {
296 self.file_base.clear_capture();
297 self.push_num_captured_update(&mut updates);
298 }
299 } else if Some(reason) == self.params.capture {
300 if params.value.as_i32() != 0 {
301 match self.file_base.mode() {
302 NDFileMode::Single => {
303 self.capture_active = false;
304 return ParamChangeResult::updates(self.error_updates(
305 false,
306 false,
307 "ERROR: capture not supported in Single mode".into(),
308 ));
309 }
310 NDFileMode::Capture => {
311 self.file_base.clear_capture();
312 self.capture_active = true;
313 self.push_num_captured_update(&mut updates);
314 }
315 NDFileMode::Stream => {
316 self.capture_active = true;
317 self.push_num_captured_update(&mut updates);
318 }
319 }
320 } else {
321 if self.file_base.mode() == NDFileMode::Stream {
322 if let Err(err) = self.file_base.close_stream(&mut self.writer) {
323 return ParamChangeResult::updates(self.error_updates(
324 false,
325 false,
326 err.to_string(),
327 ));
328 }
329 }
330 self.capture_active = false;
331 }
332 }
333
334 ParamChangeResult::updates(updates)
335 }
336
337 fn write_single(&mut self, array: Arc<NDArray>) -> ADResult<()> {
340 self.file_base.ensure_directory()?;
341 self.file_base.process_array(array, &mut self.writer)
342 }
343
344 fn success_updates(&self) -> Vec<ParamUpdate> {
345 let mut updates = Vec::new();
346 if let Some(idx) = self.params.file_number {
347 updates.push(ParamUpdate::Int32 {
348 reason: idx,
349 addr: 0,
350 value: self.file_base.file_number,
351 });
352 }
353 if let Some(idx) = self.params.write_status {
354 updates.push(ParamUpdate::Int32 {
355 reason: idx,
356 addr: 0,
357 value: 0,
358 });
359 }
360 if let Some(idx) = self.params.write_message {
361 updates.push(ParamUpdate::Octet {
362 reason: idx,
363 addr: 0,
364 value: String::new(),
365 });
366 }
367 if let Some(idx) = self.params.write_file {
368 updates.push(ParamUpdate::Int32 {
369 reason: idx,
370 addr: 0,
371 value: 0,
372 });
373 }
374 if let Some(idx) = self.params.capture {
375 updates.push(ParamUpdate::Int32 {
376 reason: idx,
377 addr: 0,
378 value: if self.capture_active { 1 } else { 0 },
379 });
380 }
381 if let Some(idx) = self.params.read_file {
382 updates.push(ParamUpdate::Int32 {
383 reason: idx,
384 addr: 0,
385 value: 0,
386 });
387 }
388 updates
389 }
390
391 fn push_num_captured_update(&self, updates: &mut Vec<ParamUpdate>) {
392 if let Some(idx) = self.params.num_captured {
393 updates.push(ParamUpdate::Int32 {
394 reason: idx,
395 addr: 0,
396 value: self.file_base.num_captured() as i32,
397 });
398 }
399 }
400
401 fn push_full_file_name_update(&self, updates: &mut Vec<ParamUpdate>) {
402 if let Some(idx) = self.params.full_file_name {
403 updates.push(ParamUpdate::Octet {
404 reason: idx,
405 addr: 0,
406 value: self.file_base.last_written_name().to_string(),
407 });
408 }
409 }
410
411 fn error_updates(
412 &self,
413 read_reason: bool,
414 write_reason: bool,
415 message: String,
416 ) -> Vec<ParamUpdate> {
417 let mut updates = Vec::new();
418 if write_reason {
419 if let Some(idx) = self.params.write_file {
420 updates.push(ParamUpdate::Int32 {
421 reason: idx,
422 addr: 0,
423 value: 0,
424 });
425 }
426 }
427 if read_reason {
428 if let Some(idx) = self.params.read_file {
429 updates.push(ParamUpdate::Int32 {
430 reason: idx,
431 addr: 0,
432 value: 0,
433 });
434 }
435 }
436 if let Some(idx) = self.params.write_status {
437 updates.push(ParamUpdate::Int32 {
438 reason: idx,
439 addr: 0,
440 value: 1,
441 });
442 }
443 if let Some(idx) = self.params.write_message {
444 updates.push(ParamUpdate::Octet {
445 reason: idx,
446 addr: 0,
447 value: message,
448 });
449 }
450 updates
451 }
452}
453
454fn normalize_file_path(path: &str) -> String {
455 if path.is_empty() || path.ends_with(std::path::MAIN_SEPARATOR) {
456 path.to_string()
457 } else {
458 format!("{path}{}", std::path::MAIN_SEPARATOR)
459 }
460}