1#[cfg(feature = "interactive")]
2pub mod interact;
3pub mod parser;
4
5use cpclib_common::camino::Utf8PathBuf;
6use cpclib_common::{clap, utf8pathbuf_value_parser};
7use cpclib_xfer::{CpcXfer, send_and_run_file};
8#[cfg(feature = "watch")]
9use notify::{RecommendedWatcher, RecursiveMode, Watcher};
10
11use crate::clap::{ArgAction, Command};
12
13pub mod built_info {
14 include!(concat!(env!("OUT_DIR"), "/built.rs"));
15}
16
17pub fn build_args_parser() -> clap::Command {
18 let y_command = Command::new("-y")
19 .about("Upload a file on the M4 in the /tmp folder and launch it. V3 snapshots are automatically downgraded to V2 version");
20
21 let y_command = if cfg!(feature = "watch") {
22 y_command.arg(
23 clap::Arg::new("WATCH")
24 .help("Watch the file and resend it on the M4 if modified (so xfer does not end when started with this option).")
25 .short('w')
26 .long("watch")
27 .action(ArgAction::SetTrue)
28 )
29 }
30 else {
31 y_command
32 };
33
34 let y_command = y_command.arg(
35 clap::Arg::new("fname")
36 .help("Filename to send and execute. Can be an executable (Amsdos header expected) or a snapshot V2")
37 .value_parser(
38 |p: &str| {utf8pathbuf_value_parser(true)(p)}
39 )
40 .required(true)
41 );
42
43 let cmd = clap::Command::new("CPC xfer to M4")
44 .author("Krusty/Benediction")
45 .version(built_info::PKG_VERSION)
46 .about("RUST version of the communication tool between a PC and a CPC through the CPC Wifi card")
47 .arg(
48 clap::Arg::new("CPCADDR")
49 .help("Specify the address of the M4. This argument is optional. If not set up, the content of the environment variable CPCIP is used.")
50 .required(false)
51 )
52 .subcommand(
53 Command::new("-r")
54 .about("Reboot M4.")
55 )
56 .subcommand(
57 Command::new("-s")
58 .about("Reboot CPC.")
59 )
60 .subcommand(
61 Command::new("-p")
62 .about("Upload the given file in the current folder or the provided one")
63 .arg(
64 clap::Arg::new("fname")
65 .help("Filename to send to the CPC")
66 .value_parser(
67 |p: &str| {utf8pathbuf_value_parser(true)(p)}
68 )
69 .required(true)
70 ))
77 .subcommand(
78 y_command
79 )
80 .subcommand(
81 Command::new("-x")
82 .about("Execute a file on the cpc (executable or snapshot)")
83 .arg(
84 clap::Arg::new("fname")
85 .help("Filename to execute on the CPC")
86 )
87 )
88 .subcommand(
89 Command::new("--ls")
90 .about("Display contents of the M4")
91 )
92 .subcommand(
93 Command::new("--pwd")
94 .about("Display the current working directory selected on the M4")
95 )
96 .subcommand(
97 Command::new("--cd")
98 .about("Change of current directory in the M4.")
99 .arg(
100 clap::Arg::new("directory")
101 .help("Directory to move on. Must exists")
102 .required(true)
103 )
104 );
105
106 if cfg!(feature = "interactive") {
107 cmd.subcommand(Command::new("--interactive").about("Start an interactive session"))
108 }
109 else {
110 cmd
111 }
112}
113
114pub fn process(matches: &clap::ArgMatches) -> anyhow::Result<()> {
115 let hostname: String = match matches.get_one::<String>("CPCADDR") {
117 Some(cpcaddr) => cpcaddr.to_string(),
118 None => {
119 match std::env::var("CPCIP") {
120 Ok(cpcaddr) => cpcaddr,
121 Err(_) => {
122 return Err(anyhow::Error::msg(
123 "You should provide the CPCADDR argument or set the CPCIP environmeent variable"
124 ));
125 }
126 }
127 },
128 };
129
130 let xfer = CpcXfer::new(hostname);
131
132 if let Some("-r") = matches.subcommand_name() {
133 xfer.reset_m4()?;
134 }
135 else if let Some("-s") = matches.subcommand_name() {
136 xfer.reset_cpc()?;
137 }
138 else if let Some(p_opt) = matches.subcommand_matches("-p") {
139 let fname: &Utf8PathBuf = p_opt.get_one("fname").unwrap();
140 send_and_run_file(&xfer, fname, false);
141 }
142 else if let Some(y_opt) = matches.subcommand_matches("-y") {
143 let fname: &Utf8PathBuf = y_opt.get_one("fname").unwrap();
144
145 send_and_run_file(&xfer, fname, true);
147
148 #[cfg(feature = "watch")]
149 if y_opt.get_flag("WATCH") {
150 println!(
151 "I will not stop and redo the operation when detecting a modification of the file"
152 );
153 let (tx, rx) = std::sync::mpsc::channel();
154 let mut watcher = RecommendedWatcher::new(
155 move |res| tx.send(res).unwrap(),
156 notify::Config::default()
157 )?;
158
159 watcher.watch(std::path::Path::new(&fname), RecursiveMode::NonRecursive)?;
160
161 for res in rx {
162 if let Ok(notify::event::Event {
163 kind: notify::event::EventKind::Modify(_) | notify::event::EventKind::Create(_),
164 ..
165 }) = res
166 {
167 send_and_run_file(&xfer, fname, true);
168 }
169 }
170 }
171 }
172 else if let Some(x_opt) = matches.subcommand_matches("-x") {
173 let fname = x_opt.get_one::<String>("fname").unwrap();
174 xfer.run(fname)?; }
176 else if let Some(_ls_opt) = matches.subcommand_matches("--ls") {
177 let content = xfer.current_folder_content()?;
178 for file in content.files() {
179 println!("{file:?}");
180 }
181 }
182 else if let Some(_pwd_opt) = matches.subcommand_matches("--pwd") {
183 let cwd = xfer.current_working_directory()?;
184 println!("{cwd}");
185 }
186 else if let Some(cd_opt) = matches.subcommand_matches("--cd") {
187 xfer.cd(cd_opt.get_one::<String>("directory").unwrap())
188 .expect("Unable to move in the requested folder.");
189 }
190 else if let Some(_interactive_opt) = matches.subcommand_matches("--interactive") {
191 #[cfg(feature = "interactive")]
192 {
193 println!("Benediction welcomes you to the interactive mode for M4.");
194 interact::XferInteractor::start(&xfer);
195 }
196 }
197
198 Ok(())
199}