Finalized watcher
This commit is contained in:
92
src/app.rs
92
src/app.rs
@ -1,10 +1,19 @@
|
||||
use std::fs;
|
||||
use crate::config::Config;
|
||||
use crate::util::restart_service;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
use std::sync::mpsc;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use crate::config::Config;
|
||||
use std::{fs, sync::mpsc::Receiver};
|
||||
|
||||
pub fn move_file_to_dir(watch_path_string: &String, filename: &String, move_path_string: &String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use crate::config::{CONFIG_PATH, ConfigErr, create_default_config, load_config};
|
||||
|
||||
pub fn move_file_to_dir(
|
||||
watch_path_string: &String,
|
||||
filename: &String,
|
||||
move_path_string: &String,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let move_path = Path::new(move_path_string);
|
||||
let watch_path = Path::new(watch_path_string);
|
||||
let file_path = watch_path.join(Path::new(filename));
|
||||
@ -27,17 +36,35 @@ pub fn move_file_to_dir(watch_path_string: &String, filename: &String, move_path
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn restart_server() {
|
||||
|
||||
pub fn restart_server(service_name: String) {
|
||||
restart_service(service_name);
|
||||
}
|
||||
|
||||
pub fn watch_files(config: Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||
pub fn watch_files(
|
||||
config: Config,
|
||||
shutdown_rx: Receiver<()>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
loop {
|
||||
// Poll shutdown event.
|
||||
match shutdown_rx.recv_timeout(Duration::from_secs(1)) {
|
||||
// Break the loop either upon stop or channel disconnect
|
||||
Ok(_) | Err(mpsc::RecvTimeoutError::Disconnected) => {
|
||||
info!("Received shutdown event. Shutting down...");
|
||||
break
|
||||
},
|
||||
|
||||
// Continue work if no events were received within the timeout
|
||||
Err(mpsc::RecvTimeoutError::Timeout) => (),
|
||||
};
|
||||
|
||||
let mut has_found = false;
|
||||
for file in fs::read_dir(Path::new(&config.watch_path))? {
|
||||
if file.is_ok() {
|
||||
let filename = file.unwrap().file_name().into_string().unwrap();
|
||||
if filename.starts_with(&config.watch_file_prefix) {
|
||||
let file_obj = file.unwrap();
|
||||
let filename = file_obj.file_name().into_string().unwrap();
|
||||
if filename.starts_with(&config.watch_file_prefix)
|
||||
&& file_obj.metadata().unwrap().len() == config.watch_file_size
|
||||
{
|
||||
info!("Found file: {}. Moving file.", filename);
|
||||
move_file_to_dir(&config.watch_path, &filename, &config.dir)?;
|
||||
has_found = true;
|
||||
@ -46,10 +73,53 @@ pub fn watch_files(config: Config) -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
if has_found {
|
||||
info!("Restarting server");
|
||||
restart_server();
|
||||
info!("Restarting target service");
|
||||
restart_server(config.service_name.clone());
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(config.period as u64));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run(shutdown_rx: Receiver<()>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Step 1: Get or create config file
|
||||
let config_res = load_config(CONFIG_PATH);
|
||||
|
||||
if let Err(err) = &config_res {
|
||||
match err {
|
||||
ConfigErr::NotExist => {
|
||||
error!("Failed to find existing config file. Create one.");
|
||||
match create_default_config(CONFIG_PATH) {
|
||||
Ok(_) => (),
|
||||
Err(_) => error!("Failed to create config file."),
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
ConfigErr::WriteError(e) => {
|
||||
error!("Failed to write config file.");
|
||||
error!("Message: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ConfigErr::ParsingError(e) => {
|
||||
error!("Failed to parse config file");
|
||||
error!("Message: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ConfigErr::Unknown(e) => {
|
||||
error!("Unknown error");
|
||||
error!("Message: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
watch_files(config_res.unwrap(), shutdown_rx)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use std::env;
|
||||
use std::fs::{read_to_string, File};
|
||||
use std::io::Write;
|
||||
use serde::*;
|
||||
@ -30,10 +31,23 @@ pub enum ConfigErr {
|
||||
NotExist,
|
||||
ParsingError(String),
|
||||
WriteError(String),
|
||||
Unknown(String)
|
||||
}
|
||||
|
||||
pub fn load_config(config_file: &str) -> Result<Config, ConfigErr> {
|
||||
let toml_contents = read_to_string(config_file);
|
||||
let exe_path_res = env::current_exe();
|
||||
|
||||
if let Err(e) = &exe_path_res {
|
||||
error!("Failed to get current working directory.");
|
||||
return Err(ConfigErr::Unknown(String::from(format!("{}", e))));
|
||||
}
|
||||
|
||||
|
||||
//let exe_path = exe_path_res.unwrap();
|
||||
let binding = exe_path_res.unwrap();
|
||||
let exe_dir = binding.parent().unwrap();
|
||||
let config_path = exe_dir.join(config_file);
|
||||
let toml_contents = read_to_string(config_path);
|
||||
|
||||
if let Ok(toml_contents) = toml_contents {
|
||||
let config = toml::from_str::<Config>(&toml_contents);
|
||||
|
||||
170
src/main.rs
170
src/main.rs
@ -1,52 +1,150 @@
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::fs::File;
|
||||
use simplelog::*;
|
||||
|
||||
use crate::app::watch_files;
|
||||
use crate::config::{create_default_config, load_config, ConfigErr, CONFIG_PATH};
|
||||
|
||||
mod config;
|
||||
mod app;
|
||||
mod config;
|
||||
mod util;
|
||||
|
||||
use simplelog::*;
|
||||
use std::fs::File;
|
||||
|
||||
const LOG_FILE: &str = "sv_file_watcher.log";
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
CombinedLogger::init(
|
||||
vec![
|
||||
TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto),
|
||||
WriteLogger::new(LevelFilter::Info, Config::default(), File::create(LOG_FILE).unwrap()),
|
||||
]
|
||||
).unwrap();
|
||||
#[cfg(windows)]
|
||||
fn main() -> windows_service::Result<()> {
|
||||
let current_exe = std::env::current_exe().expect("Failed to get current working directory");
|
||||
let parent = current_exe.parent();
|
||||
let cwd = parent.expect("Failed to get parent directory");
|
||||
|
||||
// Step 1: Get or create config file
|
||||
let mut config_res = load_config(CONFIG_PATH);
|
||||
CombinedLogger::init(vec![
|
||||
TermLogger::new(
|
||||
LevelFilter::Info,
|
||||
simplelog::Config::default(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
),
|
||||
WriteLogger::new(
|
||||
LevelFilter::Info,
|
||||
simplelog::Config::default(),
|
||||
File::create(cwd.join(LOG_FILE)).unwrap(),
|
||||
),
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
if let Err(err) = &config_res {
|
||||
match err {
|
||||
ConfigErr::NotExist => {
|
||||
error!("Failed to find existing config file. Create one.");
|
||||
config_res = create_default_config(CONFIG_PATH);
|
||||
},
|
||||
info!("Initialized logger");
|
||||
|
||||
ConfigErr::WriteError(e) => {
|
||||
error!("Failed to write config file.");
|
||||
error!("Message: {e}");
|
||||
return Ok(());
|
||||
},
|
||||
ping_service::run()
|
||||
}
|
||||
|
||||
ConfigErr::ParsingError(e) => {
|
||||
error!("Failed to parse config file");
|
||||
error!("Message: {e}");
|
||||
return Ok(());
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
fn main() {
|
||||
panic!("This program is only intended to run on Windows.");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod ping_service {
|
||||
use crate::app::run as run_watch;
|
||||
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
sync::mpsc,
|
||||
time::Duration,
|
||||
};
|
||||
use windows_service::{
|
||||
Result, define_windows_service,
|
||||
service::{
|
||||
ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus,
|
||||
ServiceType,
|
||||
},
|
||||
service_control_handler::{self, ServiceControlHandlerResult},
|
||||
service_dispatcher,
|
||||
};
|
||||
|
||||
const SERVICE_NAME: &str = "SV: SXP Service Watcher";
|
||||
const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
|
||||
|
||||
pub fn run() -> Result<()> {
|
||||
// Register generated `ffi_service_main` with the system and start the service, blocking
|
||||
// this thread until the service is stopped.
|
||||
service_dispatcher::start(SERVICE_NAME, ffi_service_main)
|
||||
}
|
||||
|
||||
// Generate the windows service boilerplate.
|
||||
// The boilerplate contains the low-level service entry function (ffi_service_main) that parses
|
||||
// incoming service arguments into Vec<OsString> and passes them to user defined service
|
||||
// entry (my_service_main).
|
||||
define_windows_service!(ffi_service_main, my_service_main);
|
||||
|
||||
// Service entry function which is called on background thread by the system with service
|
||||
// parameters. There is no stdout or stderr at this point so make sure to configure the log
|
||||
// output to file if needed.
|
||||
pub fn my_service_main(_arguments: Vec<OsString>) {
|
||||
if let Err(e) = run_service() {
|
||||
// Handle the error, by logging or something.
|
||||
error!("Service failed: {}", e)
|
||||
}
|
||||
}
|
||||
|
||||
let config = config_res.unwrap();
|
||||
pub fn run_service() -> Result<()> {
|
||||
// Create a channel to be able to poll a stop event from the service worker loop.
|
||||
let (shutdown_tx, shutdown_rx) = mpsc::channel();
|
||||
|
||||
watch_files(config)?;
|
||||
// Define system service event handler that will be receiving service events.
|
||||
let event_handler = move |control_event| -> ServiceControlHandlerResult {
|
||||
match control_event {
|
||||
// Notifies a service to report its current status information to the service
|
||||
// control manager. Always return NoError even if not implemented.
|
||||
ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||
|
||||
Ok(())
|
||||
// Handle stop
|
||||
ServiceControl::Stop => {
|
||||
shutdown_tx.send(()).unwrap();
|
||||
ServiceControlHandlerResult::NoError
|
||||
}
|
||||
|
||||
// treat the UserEvent as a stop request
|
||||
ServiceControl::UserEvent(code) => {
|
||||
if code.to_raw() == 130 {
|
||||
shutdown_tx.send(()).unwrap();
|
||||
}
|
||||
ServiceControlHandlerResult::NoError
|
||||
}
|
||||
|
||||
_ => ServiceControlHandlerResult::NotImplemented,
|
||||
}
|
||||
};
|
||||
|
||||
// Register system service event handler.
|
||||
// The returned status handle should be used to report service status changes to the system.
|
||||
let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?;
|
||||
|
||||
// Tell the system that service is running
|
||||
status_handle.set_service_status(ServiceStatus {
|
||||
service_type: SERVICE_TYPE,
|
||||
current_state: ServiceState::Running,
|
||||
controls_accepted: ServiceControlAccept::STOP,
|
||||
exit_code: ServiceExitCode::Win32(0),
|
||||
checkpoint: 0,
|
||||
wait_hint: Duration::default(),
|
||||
process_id: None,
|
||||
})?;
|
||||
|
||||
if let Err(e) = run_watch(shutdown_rx) {
|
||||
error!("Service crashed. Error: {}", e);
|
||||
|
||||
}
|
||||
|
||||
// Tell the system that service has stopped.
|
||||
status_handle.set_service_status(ServiceStatus {
|
||||
service_type: SERVICE_TYPE,
|
||||
current_state: ServiceState::Stopped,
|
||||
controls_accepted: ServiceControlAccept::empty(),
|
||||
exit_code: ServiceExitCode::Win32(0),
|
||||
checkpoint: 0,
|
||||
wait_hint: Duration::default(),
|
||||
process_id: None,
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
130
src/util.rs
130
src/util.rs
@ -0,0 +1,130 @@
|
||||
use std::{ptr::null_mut, time::Duration};
|
||||
use winapi::{
|
||||
shared::minwindef::DWORD,
|
||||
um::{
|
||||
errhandlingapi::GetLastError,
|
||||
winsvc::{
|
||||
CloseServiceHandle, ControlService, OpenSCManagerW, OpenServiceW, QueryServiceStatus, StartServiceW, SC_HANDLE__, SC_MANAGER_CONNECT, SERVICE_CONTROL_STOP, SERVICE_QUERY_STATUS, SERVICE_START, SERVICE_STOP
|
||||
},
|
||||
},
|
||||
};
|
||||
use widestring::U16CString;
|
||||
|
||||
const SERVICE_STATUS_RUNNING: u32 = 0x00000004;
|
||||
const SERVICE_STATUS_STOPPED: u32 = 0x00000001;
|
||||
|
||||
fn open_sc_manager(desired_access: DWORD) -> Result<*mut SC_HANDLE__, DWORD> {
|
||||
let sc_manager_handle = unsafe { OpenSCManagerW(null_mut(), null_mut(), desired_access) };
|
||||
if sc_manager_handle.is_null() {
|
||||
Err(unsafe { GetLastError() })
|
||||
} else {
|
||||
Ok(sc_manager_handle)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn open_service(
|
||||
sc_manager_handle: *mut SC_HANDLE__,
|
||||
service_name: &str,
|
||||
desired_access: DWORD,
|
||||
) -> Result<*mut SC_HANDLE__, DWORD> {
|
||||
let service_name_wstr = U16CString::from_str(service_name).unwrap();
|
||||
let service_handle = unsafe {
|
||||
OpenServiceW(
|
||||
sc_manager_handle,
|
||||
service_name_wstr.as_ptr(),
|
||||
desired_access,
|
||||
)
|
||||
};
|
||||
if service_handle.is_null() {
|
||||
Err(unsafe { GetLastError() })
|
||||
} else {
|
||||
Ok(service_handle)
|
||||
}
|
||||
}
|
||||
|
||||
fn start_service(service_handle: *mut SC_HANDLE__) -> Result<(), DWORD> {
|
||||
let result = unsafe { StartServiceW(service_handle, 0, null_mut()) };
|
||||
if result == 0 {
|
||||
Err(unsafe { GetLastError() })
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_service(service_handle: *mut SC_HANDLE__) -> Result<(), DWORD> {
|
||||
let mut service_status = unsafe { std::mem::zeroed() };
|
||||
let result = unsafe {
|
||||
ControlService(
|
||||
service_handle,
|
||||
SERVICE_CONTROL_STOP,
|
||||
&mut service_status,
|
||||
)
|
||||
};
|
||||
if result == 0 {
|
||||
Err(unsafe { GetLastError() })
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn query_service_status(service_handle: *mut SC_HANDLE__) -> Result<DWORD, DWORD> {
|
||||
let mut service_status = unsafe { std::mem::zeroed() };
|
||||
let result = unsafe { QueryServiceStatus(service_handle, &mut service_status) };
|
||||
if result == 0 {
|
||||
Err(unsafe { GetLastError() })
|
||||
} else {
|
||||
Ok(service_status.dwCurrentState)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restart_service(service_name: String) {
|
||||
let sc_manager_handle = open_sc_manager(SC_MANAGER_CONNECT).unwrap();
|
||||
|
||||
let service_handle = open_service(
|
||||
sc_manager_handle,
|
||||
service_name.as_str(),
|
||||
SERVICE_QUERY_STATUS | SERVICE_START | SERVICE_STOP,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let service_status_res = query_service_status(service_handle);
|
||||
|
||||
if let Err(e) = service_status_res {
|
||||
error!("Failed to query service status. Error code: {e}");
|
||||
return;
|
||||
}
|
||||
|
||||
let service_status = service_status_res.unwrap();
|
||||
|
||||
if service_status == SERVICE_STATUS_RUNNING {
|
||||
// Stop the service
|
||||
match stop_service(service_handle) {
|
||||
Ok(_) => info!("Service stopped successfully."),
|
||||
Err(error) => info!("Failed to stop service. Error: {}", error),
|
||||
}
|
||||
|
||||
info!("Waiting for service being stopped successfully");
|
||||
std::thread::sleep(Duration::from_secs(10));
|
||||
|
||||
// Start the service
|
||||
match start_service(service_handle) {
|
||||
Ok(_) => info!("Service started successfully."),
|
||||
Err(error) => info!("Failed to start service. Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
if service_status == SERVICE_STATUS_STOPPED {
|
||||
// Start the service
|
||||
match start_service(service_handle) {
|
||||
Ok(_) => info!("Service started successfully."),
|
||||
Err(error) => info!("Failed to start service. Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
// Close the service and SCM handles
|
||||
unsafe {
|
||||
CloseServiceHandle(service_handle);
|
||||
CloseServiceHandle(sc_manager_handle);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user