Skip to main content

a2kit/lang/
disk_server.rs

1//! General disk image access optimized for language servers
2//! 
3//! This is primarily an interface, the heavy lifting is done in `img` and `fs` modules.
4
5use crate::commands::{ItemType,CommandError};
6use crate::fs::DiskFS;
7use crate::img::tracks::DiskFormat;
8use crate::{STDRESULT,DYNERR};
9
10pub struct DiskServer {
11    path_to_img: String,
12    disk: Option<Box<dyn DiskFS>>
13}
14
15pub struct SimpleFileImage {
16    pub file_system: String,
17    pub fs_type: Vec<u8>,
18    pub load_addr: usize,
19    pub data: Vec<u8>
20}
21
22pub enum SelectionResult {
23    Directory(Vec<String>),
24    FileData(SimpleFileImage)
25}
26
27impl DiskServer {
28    pub fn new() -> Self {
29        Self {
30            path_to_img: "".to_string(),
31            disk: None
32        }
33    }
34    /// Buffer a file system object including its underlying storage.
35    /// Any previously mounted disk image is dropped.
36    /// The white list can be used to restrict the file systems that are accepted.
37    pub fn mount(&mut self,path_to_img: &str,maybe_white_list: &Option<Vec<String>>,maybe_fmt: Option<&DiskFormat>) -> STDRESULT {
38        match crate::create_fs_from_file(path_to_img,maybe_fmt) {
39            Ok(mut disk) => {
40                let stat = disk.stat()?;
41                match maybe_white_list {
42                    Some(white_list) => {
43                        if white_list.contains(&stat.fs_name) {
44                            self.disk = Some(disk);
45                            self.path_to_img = path_to_img.to_string();
46                            Ok(())
47                        } else {
48                            Err(Box::new(CommandError::UnsupportedFormat))
49                        }
50                    },
51                    None => {
52                        self.disk = Some(disk);
53                        self.path_to_img = path_to_img.to_string();
54                        Ok(())
55                    }
56                }
57            },
58            Err(e) => Err(e)
59        }
60    }
61    fn evaluate_selection(&mut self,path: &str,maybe_white_list: Option<Vec<String>>) -> Result<SelectionResult,DYNERR> {
62        if let Some(disk) = self.disk.as_mut() {
63            if let Ok(full_cat) = disk.catalog_to_vec(path) {
64                if maybe_white_list.is_none() {
65                    return Ok(SelectionResult::Directory(full_cat));
66                }
67                let mut filtered_cat = Vec::new();
68                let mut white_list = maybe_white_list.to_owned().unwrap();
69                if !white_list.contains(&"DIR".to_string()) {
70                    white_list.push("DIR".to_string());
71                }
72                for row in full_cat {
73                    for typ in &white_list {
74                        if row.starts_with(&typ.to_uppercase()) {
75                            filtered_cat.push(row);
76                            break;
77                        }
78                    }
79                }
80                return Ok(SelectionResult::Directory(filtered_cat));
81            }
82            return match disk.get(path) {
83                Ok(fimg) => Ok(SelectionResult::FileData(SimpleFileImage {
84                    file_system: fimg.file_system.clone(),
85                    fs_type: fimg.fs_type.clone(),
86                    load_addr: fimg.get_load_address(),
87                    data: match fimg.unpack() {
88                        Ok(result) => match result {
89                            crate::fs::UnpackedData::Binary(dat) => dat,
90                            crate::fs::UnpackedData::Text(txt) => txt.as_bytes().to_vec(),
91                            _ => return Err(Box::new(CommandError::UnsupportedFormat))
92                        },
93                        Err(e) => return Err(e)
94                    }
95                })),
96                Err(e) => Err(e)
97            }
98        }
99        return Err(Box::new(CommandError::InvalidCommand));
100    }
101    /// Extract path and white list from args and return an enumeration representing the outcome.
102    /// If the selection is a directory, return its listing, if a file return a "simplified file image".
103    /// Directory listings are generated using the `catalog_to_vec` trait method, so that the white list
104    /// should use textual types consistent with that format.
105    /// If white list is json falsey show everything, if white list is an empty array show only directories. 
106    pub fn handle_selection(&mut self,args: &Vec<serde_json::Value>) -> Result<SelectionResult,DYNERR> {
107        if args.len()!=2 {
108            return Err(Box::new(CommandError::UnknownFormat));
109        }
110        let maybe_img_path = serde_json::from_value::<String>(args[0].clone());
111        let maybe_white_list = serde_json::from_value::<Vec<String>>(args[1].clone());
112        match (maybe_img_path,maybe_white_list) {
113            (Ok(path),Ok(white_list)) => self.evaluate_selection(&path, Some(white_list)),
114            (Ok(path),Err(_)) => self.evaluate_selection(&path,None),
115            _ => Err(Box::new(CommandError::UnknownFormat))
116        }
117    }
118    /// Write any sequential data (BASIC tokens, text, binary) and commit to real disk.
119    /// N.b. the path that was used at mount time is assumed valid.
120    pub fn write(&mut self,path: &str,dat: &[u8],typ: ItemType) -> STDRESULT {
121        match self.disk.as_mut() { Some(disk) => {
122            let mut fimg = disk.new_fimg(None, true, path)?;
123            match typ {
124                ItemType::IntegerTokens | ItemType::ApplesoftTokens => fimg.pack_tok(dat,typ,None)?,
125                ItemType::MerlinTokens => fimg.pack_raw(dat)?,
126                ItemType::Text => fimg.pack_raw(dat)?,
127                _ => return Err(Box::new(CommandError::UnsupportedFormat))
128            };
129            disk.put(&fimg)?;
130            crate::save_img(disk, &self.path_to_img)?;
131            Ok(())
132        } _ => {
133            Err(Box::new(CommandError::InvalidCommand))
134        }}
135    }
136    /// Delete a file or directory.
137    /// There is no overwriting in a2kit, but the client will often want to do so.
138    /// So the workaround, as usual, is delete first.
139    pub fn delete(&mut self,path: &str) -> STDRESULT {
140        match self.disk.as_mut() { Some(disk) => {
141            disk.delete(path)
142        } _ => {
143            Err(Box::new(CommandError::InvalidCommand))
144        }}
145    }
146}