cpclib_xfertool/
lib.rs

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        )/* To implement when needed
71        .arg(
72            clap::with_name("destination")
73            .help("Destination folder.")
74            .required(false)
75        )*/
76    )
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    // Retreivethe hostname from the args or from the environment
116    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        // Simple file sending
146        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)?; // .expect("Unable to launch file on CPC.");
175    }
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}