qsu

Check-in Differences
Login

Check-in Differences

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From qsu-0.0.3 To qsu-0.0.4

2023-11-03
09:10
Use Rocket 0.5.0-rc.4. check-in: 074300f463 user: jan tags: qsu-0.0.5, trunk
2023-10-29
14:14
Fix doc ref. check-in: fb71171304 user: jan tags: qsu-0.0.4, trunk
14:11
Prepare 0.0.4. check-in: b7eb7c0490 user: jan tags: trunk
2023-10-25
14:27
Remove CbOrigin. Use AppErr for argp errors. check-in: 2190365c01 user: jan tags: trunk
2023-10-23
12:29
Update index.md. check-in: 0d49c4abb4 user: jan tags: qsu-0.0.3, trunk
12:20
Release maintenance. check-in: ef926ac904 user: jan tags: trunk

Changes to Cargo.toml.

1
2
3
4
5
6
7
8
9
10
[package]
name = "qsu"
version = "0.0.3"
edition = "2021"
license = "0BSD"
categories = [ "asynchronous" ]
keywords = [ "service", "systemd", "winsvc" ]
repository = "https://repos.qrnch.tech/pub/qsu"
description = "Service subsystem wrapper."
rust-version = "1.56"


|







1
2
3
4
5
6
7
8
9
10
[package]
name = "qsu"
version = "0.0.4"
edition = "2021"
license = "0BSD"
categories = [ "asynchronous" ]
keywords = [ "service", "systemd", "winsvc" ]
repository = "https://repos.qrnch.tech/pub/qsu"
description = "Service subsystem wrapper."
rust-version = "1.56"
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
rt = []
tokio = ["rt", "tokio/macros", "tokio/rt-multi-thread", "tokio/signal"]
wait-for-debugger = ["dep:dbgtools-win"]

[dependencies]
async-trait = { version = "0.1.74" }
chrono = { version = "0.4.24" }
clap = { version = "4.4.6", optional = true, features = [
  "derive", "env", "string", "wrap_help"
] }
env_logger = { version = "0.10.0" }
futures = { version = "0.3.28" }
itertools = { version = "0.11.0", optional = true }
killswitch = { version = "0.4.2" }
log = { version = "0.4.20" }
parking_lot = { version = "0.12.1" }
rocket = { version = "0.5.0-rc.3", optional = true }
sidoc = { version = "0.1.0", optional = true }
tokio = { version = "1.33.0", features = ["sync"] }







|



|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
rt = []
tokio = ["rt", "tokio/macros", "tokio/rt-multi-thread", "tokio/signal"]
wait-for-debugger = ["dep:dbgtools-win"]

[dependencies]
async-trait = { version = "0.1.74" }
chrono = { version = "0.4.24" }
clap = { version = "4.4.7", optional = true, features = [
  "derive", "env", "string", "wrap_help"
] }
env_logger = { version = "0.10.0" }
futures = { version = "0.3.29" }
itertools = { version = "0.11.0", optional = true }
killswitch = { version = "0.4.2" }
log = { version = "0.4.20" }
parking_lot = { version = "0.12.1" }
rocket = { version = "0.5.0-rc.3", optional = true }
sidoc = { version = "0.1.0", optional = true }
tokio = { version = "1.33.0", features = ["sync"] }

Changes to examples/argp/mod.rs.

1
2
3
4


5


6
7
8
9
10
11
12
13














14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
use clap::ArgMatches;

use qsu::installer::RegSvc;



pub(crate) struct AppArgsProc {}



impl qsu::argp::ArgsProc for AppArgsProc {
  /// Process an `register-service` subcommand.
  fn proc_inst(
    &self,
    _sub_m: &ArgMatches,
    regsvc: RegSvc
  ) -> Result<RegSvc, qsu::Error> {














    // Use current working directory as the service's workdir
    let cwd = std::env::current_dir()?.to_str().unwrap().to_string();
    let regsvc = regsvc
      .workdir(cwd)
      .env("HOLY", "COW")
      .env("Private", "Public")
      .env("General", "Specific");

    // Add a callback that will increase log and trace levels by deafault.
    #[cfg(windows)]
    let regsvc = regsvc.regconf(|_svcname, params| {
      params.set_value("AppArgParser", &"SaysHello")?;

      Ok(())
    });

    Ok(regsvc)
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :


|

>
>
|
>
>




|


|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|

|
|
|
|

|
|

|
<



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

49
50
51
use clap::ArgMatches;

use qsu::{installer::RegSvc, rt::SrvAppRt, AppErr};

use crate::err::Error;

pub(crate) struct AppArgsProc {
  pub(crate) bldr: Box<dyn Fn() -> SrvAppRt>
}

impl qsu::argp::ArgsProc for AppArgsProc {
  /// Process an `register-service` subcommand.
  fn proc_inst(
    &mut self,
    _sub_m: &ArgMatches,
    regsvc: RegSvc
  ) -> Result<RegSvc, qsu::AppErr> {
    // This is split out into its own function because the orphan rule wouldn't
    // allow the application to implement a std::io::Error -> qsu::AppErr
    // conversion in one go, so we do it in two steps instead.
    // proc_inst_inner()'s '?' converts "all" errors into 'Error`.
    // The proc_inst() method's `?` converts from `Error` to `qsu::AppError`
    Ok(proc_inst_inner(regsvc)?)
  }

  fn build_apprt(&mut self) -> Result<SrvAppRt, AppErr> {
    Ok((self.bldr)())
  }
}

fn proc_inst_inner(regsvc: RegSvc) -> Result<RegSvc, Error> {
  // Use current working directory as the service's workdir
  let cwd = std::env::current_dir()?.to_str().unwrap().to_string();
  let regsvc = regsvc
    .workdir(cwd)
    .env("HOLY", "COW")
    .env("Private", "Public")
    .env("General", "Specific");

  // Add a callback that will increase log and trace levels by deafault.
  #[cfg(windows)]
  let regsvc = regsvc.regconf(|_svcname, params| {
    params.set_value("AppArgParser", &"SaysHello")?;

    Ok(())
  });

  Ok(regsvc)

}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to examples/err/mod.rs.

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45


46
47
48
49
50
51
52
impl From<qsu::Error> for Error {
  fn from(err: qsu::Error) -> Self {
    Error::Qsu(err.to_string())
  }
}

/*
/// Convenience converter used to pass an application-defined errors from the
/// qsu inner runtime back out from the qsu runtime.
impl From<Error> for qsu::Error {
  fn from(err: Error) -> qsu::Error {
    qsu::Error::app(err)
  }
}
*/



impl From<Error> for qsu::AppErr {
  fn from(err: Error) -> qsu::AppErr {
    qsu::AppErr::new(err)
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







|
|







>
>







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
impl From<qsu::Error> for Error {
  fn from(err: qsu::Error) -> Self {
    Error::Qsu(err.to_string())
  }
}

/*
/// Convenience converter used to pass application-defined errors from the
/// inner callback back out from the qsu runtime.
impl From<Error> for qsu::Error {
  fn from(err: Error) -> qsu::Error {
    qsu::Error::app(err)
  }
}
*/

/// Convenience converter for mapping application-specific errors to
/// `qsu::AppErr`.
impl From<Error> for qsu::AppErr {
  fn from(err: Error) -> qsu::AppErr {
    qsu::AppErr::new(err)
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to examples/hellosvc-rocket.rs.

92
93
94
95
96
97
98
99
100
101
102





103
104


105
106
107
108
109
110
111
112
113
114
115
116
  // In the future we'll be able to use Try to implement support for implicit
  // conversion to ProcRes from a Result using `?`, but for now use this hack.
  ProcRes::into(main2().into())
}

fn main2() -> Result<(), Error> {
  // Derive default service name from executable name.
  // (This causes a memory leak).
  let svcname = qsu::default_service_name()
    .expect("Unable to determine default service name");






  // Parse, and process, command line arguments.
  let mut argsproc = argp::AppArgsProc {};


  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc(|| {
    let handler = Box::new(MyService {});
    SrvAppRt::Rocket(handler)
  })?;

  Ok(())
}

#[get("/")]
fn index() -> &'static str {
  log::error!("error");







<



>
>
>
>
>

|
>
>

|
<
<
<







92
93
94
95
96
97
98

99
100
101
102
103
104
105
106
107
108
109
110
111
112



113
114
115
116
117
118
119
  // In the future we'll be able to use Try to implement support for implicit
  // conversion to ProcRes from a Result using `?`, but for now use this hack.
  ProcRes::into(main2().into())
}

fn main2() -> Result<(), Error> {
  // Derive default service name from executable name.

  let svcname = qsu::default_service_name()
    .expect("Unable to determine default service name");

  let creator = || {
    let handler = Box::new(MyService {});
    SrvAppRt::Rocket(handler)
  };

  // Parse, and process, command line arguments.
  let mut argsproc = argp::AppArgsProc {
    bldr: Box::new(creator)
  };
  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc()?;




  Ok(())
}

#[get("/")]
fn index() -> &'static str {
  log::error!("error");

Changes to examples/hellosvc-tokio.rs.

86
87
88
89
90
91
92
93
94
95
96





97
98


99
100
101
102
103
104
105
106
107
108
  // In the future we'll be able to use Try to implement support for implicit
  // conversion to ProcRes from a Result using `?`, but for now use this hack.
  ProcRes::into(main2().into())
}

fn main2() -> Result<(), Error> {
  // Derive default service name from executable name.
  // (This causes a memory leak).
  let svcname = qsu::default_service_name()
    .expect("Unable to determine default service name");






  // Parse, and process, command line arguments.
  let mut argsproc = argp::AppArgsProc {};


  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc(|| {
    let handler = Box::new(MyService {});
    SrvAppRt::Tokio(None, handler)
  })?;

  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







<



>
>
>
>
>

|
>
>

|
<
<
<





86
87
88
89
90
91
92

93
94
95
96
97
98
99
100
101
102
103
104
105
106



107
108
109
110
111
  // In the future we'll be able to use Try to implement support for implicit
  // conversion to ProcRes from a Result using `?`, but for now use this hack.
  ProcRes::into(main2().into())
}

fn main2() -> Result<(), Error> {
  // Derive default service name from executable name.

  let svcname = qsu::default_service_name()
    .expect("Unable to determine default service name");

  let creator = || {
    let handler = Box::new(MyService {});
    SrvAppRt::Tokio(None, handler)
  };

  // Parse, and process, command line arguments.
  let mut argsproc = argp::AppArgsProc {
    bldr: Box::new(creator)
  };
  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc()?;




  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to examples/hellosvc.rs.

89
90
91
92
93
94
95
96
97
98
99





100
101


102
103
104
105
106
107
108
109
110
111
  // In the future we'll be able to use Try to implement support for implicit
  // conversion to ProcRes from a Result using `?`, but for now use this hack.
  ProcRes::into(main2().into())
}

fn main2() -> Result<(), Error> {
  // Derive default service name from executable name.
  // (This causes a memory leak).
  let svcname = qsu::default_service_name()
    .expect("Unable to determine default service name");






  // Parse, and process, command line arguments.
  let mut argsproc = argp::AppArgsProc {};


  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc(|| {
    let handler = Box::new(MyService {});
    SrvAppRt::Sync(handler)
  })?;

  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







<



>
>
>
>
>

|
>
>

|
<
<
<





89
90
91
92
93
94
95

96
97
98
99
100
101
102
103
104
105
106
107
108
109



110
111
112
113
114
  // In the future we'll be able to use Try to implement support for implicit
  // conversion to ProcRes from a Result using `?`, but for now use this hack.
  ProcRes::into(main2().into())
}

fn main2() -> Result<(), Error> {
  // Derive default service name from executable name.

  let svcname = qsu::default_service_name()
    .expect("Unable to determine default service name");

  let creator = || {
    let handler = Box::new(MyService {});
    SrvAppRt::Sync(handler)
  };

  // Parse, and process, command line arguments.
  let mut argsproc = argp::AppArgsProc {
    bldr: Box::new(creator)
  };
  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc()?;




  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/argp.rs.

1
2
3
4
5

6
7
8
9
10
11
12
13
14
15
16
//! Helpers for integrating clap into an application using _qsu_.

use clap::{builder::Str, Arg, ArgAction, ArgMatches, Args, Command};

use crate::{

  installer::{self, RegSvc},
  lumberjack::LogLevel,
  rt::{RunCtx, SrvAppRt},
  Error
};


/// Modify a `clap` [`Command`] instance to accept common service management
/// subcommands.
///
/// If `inst_subcmd` is `Some()`, it should be the name of the subcommand used





>


|
<







1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
//! Helpers for integrating clap into an application using _qsu_.

use clap::{builder::Str, Arg, ArgAction, ArgMatches, Args, Command};

use crate::{
  err::{AppErr, Error},
  installer::{self, RegSvc},
  lumberjack::LogLevel,
  rt::{RunCtx, SrvAppRt}

};


/// Modify a `clap` [`Command`] instance to accept common service management
/// subcommands.
///
/// If `inst_subcmd` is `Some()`, it should be the name of the subcommand used
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    .short('n')
    .long("name")
    .action(ArgAction::Set)
    .value_name("SVCNAME")
    .default_value(Str::from(svcname.to_string()))
    .help("Set service name");

  /*
  let autostartarg = Arg::new("autostart")
    .short('a')
    .long("auto-start")
    .action(ArgAction::SetTrue)
    .help("Set service to auto-start on boot");
  */

  //Command::new(cmd).arg(namearg).arg(autostartarg)
  let cli = Command::new(cmd.to_string()).arg(namearg);

  RegSvcArgs::augment_args(cli)
}









<
<
<
<
<
<
<
<







100
101
102
103
104
105
106








107
108
109
110
111
112
113
    .short('n')
    .long("name")
    .action(ArgAction::Set)
    .value_name("SVCNAME")
    .default_value(Str::from(svcname.to_string()))
    .help("Set service name");









  //Command::new(cmd).arg(namearg).arg(autostartarg)
  let cli = Command::new(cmd.to_string()).arg(namearg);

  RegSvcArgs::augment_args(cli)
}


178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206







207
208
209

210

211
212
213
214

215

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245


























246
247
248
249
250
251
252
253
254
255
256



257
258
259
260
261
262
263

  let cli = Command::new(cmd.to_string()).arg(namearg);

  RunSvcArgs::augment_args(cli)
}


pub(crate) enum ArgpRes {
  /// Run server application.
  RunApp(RunCtx),

  /// Nothing to do (service was probably registered/deregistred).
  Quit
}


/// Parsed service running arguments.
pub struct RunSvc {
  pub svcname: String
}

impl RunSvc {
  pub fn from_cmd_match(matches: &ArgMatches) -> Self {
    let svcname = matches.get_one::<String>("svcname").unwrap().to_owned();
    Self { svcname }
  }
}









/// Allow application to customise behavior of an [`ArgParser`] instance.
pub trait ArgsProc {
  /// Callback allowing application to configure service installation argument

  /// parser.

  fn inst_subcmd(&mut self) {
    todo!()
  }


  fn rm_subcmd(&mut self) {

    todo!()
  }

  fn run_subcmd(&mut self) {
    todo!()
  }

  /// Callback allowing application to configure the service registration
  /// context just before the service is registered.
  ///
  /// This trait method can, among other things, be used by an application to:
  /// - Configure a service work directory.
  /// - Add environment variables.
  /// - Add command like arguments to the run command.
  ///
  /// The `sub_m` argument represents `clap`'s parsed subcommand context for
  /// the service registration subcommand.  Applications that want to add
  /// custom arguments to the parser should implement the
  /// [`ArgsProc::inst_subcmd()`] trait method and perform the subcommand
  /// augmentation there.
  ///
  /// The default implementation does nothing but return `regsvc` unmodified.
  #[allow(unused_variables)]
  fn proc_inst(
    &self,
    sub_m: &ArgMatches,
    regsvc: RegSvc
  ) -> Result<RegSvc, Error> {
    Ok(regsvc)
  }



























  /// Called when a subcommand is encountered that is not one of the three
  /// subcommands regognized by qsu.
  #[allow(unused_variables)]
  fn proc_other(
    &mut self,
    subcmd: &str,
    sub_m: &ArgMatches
  ) -> Result<(), Error> {
    Ok(())
  }



}


/// High-level argument parser.
///
/// This is suitable for applications that follow a specific pattern:
/// - It has subcommands for:







|

|



















>
>
>
>
>
>
>


|
>
|
>
|
<
<
|
>
|
>
|
<
|
|
<













|





|


|


>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>








|


>
>
>







170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212


213
214
215
216
217

218
219

220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291

  let cli = Command::new(cmd.to_string()).arg(namearg);

  RunSvcArgs::augment_args(cli)
}


pub(crate) enum ArgpRes<'cb> {
  /// Run server application.
  RunApp(RunCtx, &'cb mut dyn ArgsProc),

  /// Nothing to do (service was probably registered/deregistred).
  Quit
}


/// Parsed service running arguments.
pub struct RunSvc {
  pub svcname: String
}

impl RunSvc {
  pub fn from_cmd_match(matches: &ArgMatches) -> Self {
    let svcname = matches.get_one::<String>("svcname").unwrap().to_owned();
    Self { svcname }
  }
}


pub enum Cmd {
  Root,
  Inst,
  Rm,
  Run
}

/// Allow application to customise behavior of an [`ArgParser`] instance.
pub trait ArgsProc {
  /// Give the application an opportunity to modify the root and subcommand
  /// `Command`s.
  ///
  /// `cmdtype` indicates whether `cmd` is the root `Command` or one of the
  /// subcommand `Command`s.


  #[allow(unused_variables)]
  fn conf_cmd(
    &mut self,
    cmdtype: Cmd,
    cmd: Command

  ) -> Result<Command, AppErr> {
    Ok(cmd)

  }

  /// Callback allowing application to configure the service registration
  /// context just before the service is registered.
  ///
  /// This trait method can, among other things, be used by an application to:
  /// - Configure a service work directory.
  /// - Add environment variables.
  /// - Add command like arguments to the run command.
  ///
  /// The `sub_m` argument represents `clap`'s parsed subcommand context for
  /// the service registration subcommand.  Applications that want to add
  /// custom arguments to the parser should implement the
  /// [`ArgsProc::conf_cmd()`] trait method and perform the subcommand
  /// augmentation there.
  ///
  /// The default implementation does nothing but return `regsvc` unmodified.
  #[allow(unused_variables)]
  fn proc_inst(
    &mut self,
    sub_m: &ArgMatches,
    regsvc: RegSvc
  ) -> Result<RegSvc, AppErr> {
    Ok(regsvc)
  }

  #[allow(unused_variables)]
  fn proc_rm(
    &mut self,
    sub_m: &ArgMatches,
    deregsvc: DeregSvc
  ) -> Result<DeregSvc, AppErr> {
    Ok(deregsvc)
  }

  /// Callback allowing application to configure the run context before
  /// launching the server application.
  ///
  /// qsu will have performed all its own initialization of the [`RunCtx`]
  /// before calling this function.
  ///
  /// The application can differentiate between running in a service mode and
  /// running as a foreground by calling [`RunCtx::is_service()`].
  #[allow(unused_variables)]
  fn proc_run(
    &mut self,
    matches: &ArgMatches,
    runctx: RunCtx
  ) -> Result<RunCtx, AppErr> {
    Ok(runctx)
  }

  /// Called when a subcommand is encountered that is not one of the three
  /// subcommands regognized by qsu.
  #[allow(unused_variables)]
  fn proc_other(
    &mut self,
    subcmd: &str,
    sub_m: &ArgMatches
  ) -> Result<(), AppErr> {
    Ok(())
  }

  /// Construct an server application runtime.
  fn build_apprt(&mut self) -> Result<SrvAppRt, AppErr>;
}


/// High-level argument parser.
///
/// This is suitable for applications that follow a specific pattern:
/// - It has subcommands for:
272
273
274
275
276
277
278




279
280
281
282
283
284
285
286
287
288
289
290






291
292
293
294
295
296
297
298
299
300
301
302
303
304
305

306
307
308
309
310

311
312
313
314
315

316
317
318
319
320
321
322
323
324
325
326
327
328
  dereg_subcmd: String,
  run_subcmd: String,
  cli: Command,
  cb: &'cb mut dyn ArgsProc
}

impl<'cb> ArgParser<'cb> {




  pub fn new(svcname: &str, cb: &'cb mut dyn ArgsProc) -> Self {
    let cli = Command::new("");
    Self {
      svcname: svcname.to_string(),
      reg_subcmd: "register-service".into(),
      dereg_subcmd: "deregister-service".into(),
      run_subcmd: "run-service".into(),
      cli,
      cb
    }
  }







  pub fn with_cmd(
    svcname: &str,
    cli: Command,
    cb: &'cb mut dyn ArgsProc
  ) -> Self {
    Self {
      svcname: svcname.to_string(),
      reg_subcmd: "register-service".into(),
      dereg_subcmd: "deregister-service".into(),
      run_subcmd: "run-service".into(),
      cli,
      cb
    }
  }


  pub fn reg_subcmd(mut self, nm: &str) -> Self {
    self.reg_subcmd = nm.to_string();
    self
  }


  pub fn dereg_subcmd(mut self, nm: &str) -> Self {
    self.dereg_subcmd = nm.to_string();
    self
  }


  pub fn run_subcmd(mut self, nm: &str) -> Self {
    self.run_subcmd = nm.to_string();
    self
  }

  fn inner_proc(self) -> Result<ArgpRes, Error> {
    let matches = match self.cli.try_get_matches() {
      Ok(m) => m,
      Err(e) => match e.kind() {
        clap::error::ErrorKind::DisplayHelp => {
          e.exit();
          //return Ok(ArgpRes::Quit);
        }







>
>
>
>












>
>
>
>
>
>















>





>





>





|







300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
  dereg_subcmd: String,
  run_subcmd: String,
  cli: Command,
  cb: &'cb mut dyn ArgsProc
}

impl<'cb> ArgParser<'cb> {
  /// Create a new argument parser.
  ///
  /// `svcname` is the _default_ service name.  It may be overridden using
  /// command line arguments.
  pub fn new(svcname: &str, cb: &'cb mut dyn ArgsProc) -> Self {
    let cli = Command::new("");
    Self {
      svcname: svcname.to_string(),
      reg_subcmd: "register-service".into(),
      dereg_subcmd: "deregister-service".into(),
      run_subcmd: "run-service".into(),
      cli,
      cb
    }
  }


  /// Create a new argument parser, basing the root command parser on an
  /// application-supplied `Command`.
  ///
  /// `svcname` is the _default_ service name.  It may be overridden using
  /// command line arguments.
  pub fn with_cmd(
    svcname: &str,
    cli: Command,
    cb: &'cb mut dyn ArgsProc
  ) -> Self {
    Self {
      svcname: svcname.to_string(),
      reg_subcmd: "register-service".into(),
      dereg_subcmd: "deregister-service".into(),
      run_subcmd: "run-service".into(),
      cli,
      cb
    }
  }

  /// Rename the service registration subcommand.
  pub fn reg_subcmd(mut self, nm: &str) -> Self {
    self.reg_subcmd = nm.to_string();
    self
  }

  /// Rename the service deregistration subcommand.
  pub fn dereg_subcmd(mut self, nm: &str) -> Self {
    self.dereg_subcmd = nm.to_string();
    self
  }

  /// Rename the subcommand for running the service.
  pub fn run_subcmd(mut self, nm: &str) -> Self {
    self.run_subcmd = nm.to_string();
    self
  }

  fn inner_proc(self) -> Result<ArgpRes<'cb>, Error> {
    let matches = match self.cli.try_get_matches() {
      Ok(m) => m,
      Err(e) => match e.kind() {
        clap::error::ErrorKind::DisplayHelp => {
          e.exit();
          //return Ok(ArgpRes::Quit);
        }
366
367
368
369
370
371
372


373
374
375
376
377
378
379
380





381
382
383
384
385
386
387
388
389
390
391





392
393
394
395
396
397
398
399
400

        Ok(ArgpRes::Quit)
      }
      Some((subcmd, sub_m)) if subcmd == self.dereg_subcmd => {
        // Get arguments relating to service deregistration.
        let args = DeregSvc::from_cmd_match(sub_m);



        installer::uninstall(&args.svcname)?;

        Ok(ArgpRes::Quit)
      }
      Some((subcmd, sub_m)) if subcmd == self.run_subcmd => {
        // Get arguments relating to running the service.
        let args = RunSvc::from_cmd_match(sub_m);






        // Return a run context for a background service process.
        Ok(ArgpRes::RunApp(RunCtx::new(&args.svcname).service()))
      }
      Some((subcmd, sub_m)) => {
        // Call application callback for processing "other" subcmd
        self.cb.proc_other(subcmd, sub_m)?;

        // Return a run context for a background service process.
        Ok(ArgpRes::Quit)
      }
      _ => {





        // Return a run context for a foreground process.
        Ok(ArgpRes::RunApp(RunCtx::new(&self.svcname)))
      }
    }
  }

  /// Process command line arguments.
  ///
  /// Calling this method will initialize the command line parser, parse the







>
>








>
>
>
>
>

|









>
>
>
>
>

|







407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453

        Ok(ArgpRes::Quit)
      }
      Some((subcmd, sub_m)) if subcmd == self.dereg_subcmd => {
        // Get arguments relating to service deregistration.
        let args = DeregSvc::from_cmd_match(sub_m);

        let args = self.cb.proc_rm(sub_m, args)?;

        installer::uninstall(&args.svcname)?;

        Ok(ArgpRes::Quit)
      }
      Some((subcmd, sub_m)) if subcmd == self.run_subcmd => {
        // Get arguments relating to running the service.
        let args = RunSvc::from_cmd_match(sub_m);

        // Create a RunCtx, mark it as a service, and allow application the
        // opportunity to modify it based on the parsed command line.
        let rctx = RunCtx::new(&args.svcname).service();
        let rctx = self.cb.proc_run(sub_m, rctx)?;

        // Return a run context for a background service process.
        Ok(ArgpRes::RunApp(rctx, self.cb))
      }
      Some((subcmd, sub_m)) => {
        // Call application callback for processing "other" subcmd
        self.cb.proc_other(subcmd, sub_m)?;

        // Return a run context for a background service process.
        Ok(ArgpRes::Quit)
      }
      _ => {
        // Create a RunCtx, mark it as a service, and allow application the
        // opportunity to modify it based on the parsed command line.
        let rctx = RunCtx::new(&self.svcname);
        let rctx = self.cb.proc_run(&matches, rctx)?;

        // Return a run context for a foreground process.
        Ok(ArgpRes::RunApp(rctx, self.cb))
      }
    }
  }

  /// Process command line arguments.
  ///
  /// Calling this method will initialize the command line parser, parse the
410
411
412
413
414
415
416











417
418
419


420
421

422

423
424
425
426

427
428
429
430

431
432
433
434
435
436
437
438



439
440
441
442
443
444
445
  /// - If an application-defined subcommand was called, then process it using
  ///   [`ArgsProc::proc_other()`] and then exit.
  /// - If none of the above subcommands where issued, then run the server
  ///   application as a foreground process.
  ///
  /// The `bldr` is a closure that will be called to yield the `SrvAppRt` in
  /// case the service was requested to run.











  pub fn proc<F>(mut self, bldr: F) -> Result<(), Error>
  where
    F: FnOnce() -> SrvAppRt


  {
    // Create registration subcommand

    let sub = mk_inst_cmd(&self.reg_subcmd, &self.svcname);

    self.cli = self.cli.subcommand(sub);

    // Create deregistration subcommand
    let sub = mk_rm_cmd(&self.dereg_subcmd, &self.svcname);

    self.cli = self.cli.subcommand(sub);

    // Create run subcommand
    let sub = mk_run_cmd(&self.run_subcmd, &self.svcname);

    self.cli = self.cli.subcommand(sub);

    // Parse command line arguments.  Run the service application if requiested
    // to do so.
    if let ArgpRes::RunApp(runctx) = self.inner_proc()? {
      // Argument parser asked us to run, so call the application to ask it to
      // create the service handler, and then kick off the service runtime.
      let st = bldr();



      runctx.run(st)?;
    }
    Ok(())
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







>
>
>
>
>
>
>
>
>
>
>
|
<
<
>
>
|
|
>

>




>




>




|


|
>
>
>







463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481


482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
  /// - If an application-defined subcommand was called, then process it using
  ///   [`ArgsProc::proc_other()`] and then exit.
  /// - If none of the above subcommands where issued, then run the server
  ///   application as a foreground process.
  ///
  /// The `bldr` is a closure that will be called to yield the `SrvAppRt` in
  /// case the service was requested to run.
  ///
  /// # Service registration behavior
  /// The default service registration behavior in qsu will:
  /// - Assume that the executable being used to register the service is the
  ///   same one that will run the service.
  /// - Add the "run service" subcommand to the service's command line
  ///   arguments.
  /// - If the specified service name is different than the default service
  ///   name (determined by
  ///   [`default_service_name()`](crate::default_service_name), then the
  ///   aguments `--name <service name>` will be added.
  pub fn proc(mut self) -> Result<(), Error> {


    // Give application the opportunity to modify root Command
    self.cli = self.cb.conf_cmd(Cmd::Root, self.cli)?;

    // Create registration subcommand and give application the opportunity to
    // modify the subcommand's Command
    let sub = mk_inst_cmd(&self.reg_subcmd, &self.svcname);
    let sub = self.cb.conf_cmd(Cmd::Inst, sub)?;
    self.cli = self.cli.subcommand(sub);

    // Create deregistration subcommand
    let sub = mk_rm_cmd(&self.dereg_subcmd, &self.svcname);
    let sub = self.cb.conf_cmd(Cmd::Rm, sub)?;
    self.cli = self.cli.subcommand(sub);

    // Create run subcommand
    let sub = mk_run_cmd(&self.run_subcmd, &self.svcname);
    let sub = self.cb.conf_cmd(Cmd::Run, sub)?;
    self.cli = self.cli.subcommand(sub);

    // Parse command line arguments.  Run the service application if requiested
    // to do so.
    if let ArgpRes::RunApp(runctx, cb) = self.inner_proc()? {
      // Argument parser asked us to run, so call the application to ask it to
      // create the service handler, and then kick off the service runtime.
      //let st = bldr(ctx)?;

      let st = cb.build_apprt()?;

      runctx.run(st)?;
    }
    Ok(())
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/err.rs.

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
  pub fn run_failed(&self) -> bool {
    self.run.is_some()
  }

  pub fn shutdown_failed(&self) -> bool {
    self.shutdown.is_some()
  }

  pub fn origin(&self) -> CbOrigin {
    if self.init_failed() {
      CbOrigin::Init
    } else if self.run_failed() {
      CbOrigin::Run
    } else if self.shutdown_failed() {
      CbOrigin::Shutdown
    } else {
      // Can't happen
      unimplemented!()
    }
  }
}


/// Errors that qsu will return to application.
#[derive(Debug)]
pub enum Error {
  /// Application-defined error.
  ///
  /// Applications can use this variant to pass application-specific errors
  /// through the runtime back to itself.
  App(CbOrigin, AppErr),

  ArgP(ArgsError),
  BadFormat(String),
  Internal(String),
  IO(String),

  /// An error related to logging occurred.







<
<
<
<
<
<
<
<
<
<
<
<
<










|







24
25
26
27
28
29
30













31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  pub fn run_failed(&self) -> bool {
    self.run.is_some()
  }

  pub fn shutdown_failed(&self) -> bool {
    self.shutdown.is_some()
  }













}


/// Errors that qsu will return to application.
#[derive(Debug)]
pub enum Error {
  /// Application-defined error.
  ///
  /// Applications can use this variant to pass application-specific errors
  /// through the runtime back to itself.
  App(AppErr),

  ArgP(ArgsError),
  BadFormat(String),
  Internal(String),
  IO(String),

  /// An error related to logging occurred.
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

  Unsupported
}


impl Error {
  pub fn is_apperr(&self) -> bool {
    matches!(self, Error::App(_, _))
  }

  /// Attempt to convert [`Error`] into application-specific error.
  ///
  /// If it's not an `Error::App()` nor can be downcast to type `E`, the error
  /// will be returned back as an `Err()`.
  pub fn try_into_apperr<E: 'static>(self) -> Result<(CbOrigin, E), Error> {
    match self {
      Error::App(origin, e) => match e.try_into_inner::<E>() {
        Ok(e) => Ok((origin, e)),
        Err(e) => Err(Error::App(origin, AppErr::new(e)))
      },
      e => Err(e)
    }
  }

  /// Unwrap application-specific error from an [`Error`].
  ///
  /// # Panic
  /// Panics if `Error` variant is not `Error::App()`.
  pub fn unwrap_apperr<E: 'static>(self) -> (CbOrigin, E) {
    let Ok((origin, e)) = self.try_into_apperr::<E>() else {
      panic!("Unable to unwrap error E");
    };
    (origin, e)
  }

  /*
  pub(crate) fn app<E: Send + 'static>(origin: CbOrigin, e: E) -> Self {
    Error::App(origin, AppErr::new(e))
  }
  */

  pub fn bad_format<S: ToString>(s: S) -> Self {
    Error::BadFormat(s.to_string())
  }

  pub fn internal<S: ToString>(s: S) -> Self {
    Error::Internal(s.to_string())







|






|

|
|
|









|
|


<
<
|
<
<
<

<







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101


102



103

104
105
106
107
108
109
110

  Unsupported
}


impl Error {
  pub fn is_apperr(&self) -> bool {
    matches!(self, Error::App(_))
  }

  /// Attempt to convert [`Error`] into application-specific error.
  ///
  /// If it's not an `Error::App()` nor can be downcast to type `E`, the error
  /// will be returned back as an `Err()`.
  pub fn try_into_apperr<E: 'static>(self) -> Result<E, Error> {
    match self {
      Error::App(e) => match e.try_into_inner::<E>() {
        Ok(e) => Ok(e),
        Err(e) => Err(Error::App(AppErr::new(e)))
      },
      e => Err(e)
    }
  }

  /// Unwrap application-specific error from an [`Error`].
  ///
  /// # Panic
  /// Panics if `Error` variant is not `Error::App()`.
  pub fn unwrap_apperr<E: 'static>(self) -> E {
    let Ok(e) = self.try_into_apperr::<E>() else {
      panic!("Unable to unwrap error E");
    };


    e



  }


  pub fn bad_format<S: ToString>(s: S) -> Self {
    Error::BadFormat(s.to_string())
  }

  pub fn internal<S: ToString>(s: S) -> Self {
    Error::Internal(s.to_string())
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      Error::App(_origin, _e) => {
        write!(f, "Application-defined error")
      }
      Error::ArgP(s) => {
        // ToDo: Handle the ArgsError::Clap and ArgsError::Msg differently
        write!(f, "Argument parser; {:?}", s)
      }
      Error::BadFormat(s) => {







|







124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
}

impl std::error::Error for Error {}

impl fmt::Display for Error {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    match self {
      Error::App(_e) => {
        write!(f, "Application-defined error")
      }
      Error::ArgP(s) => {
        // ToDo: Handle the ArgsError::Clap and ArgsError::Msg differently
        write!(f, "Argument parser; {:?}", s)
      }
      Error::BadFormat(s) => {
269
270
271
272
273
274
275








276
277
278
279
280
281
282
impl AppErr {
  pub fn new<E>(e: E) -> Self
  where
    E: Send + 'static
  {
    Self(Box::new(e))
  }









  /// Attempt to unpack and cast the inner error type.
  ///
  /// If it can't be downcast to `E`, `AppErr` will be returned in the `Err()`
  /// case.
  ///
  /// ```







>
>
>
>
>
>
>
>







250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
impl AppErr {
  pub fn new<E>(e: E) -> Self
  where
    E: Send + 'static
  {
    Self(Box::new(e))
  }

  /// Inspect error type wrapped by the `AppErr`.
  pub fn is<T>(&self) -> bool
  where
    T: Any
  {
    self.0.is::<T>()
  }

  /// Attempt to unpack and cast the inner error type.
  ///
  /// If it can't be downcast to `E`, `AppErr` will be returned in the `Err()`
  /// case.
  ///
  /// ```
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
    let Ok(e) = self.0.downcast::<E>() else {
      panic!("Unable to downcast to error type E");
    };
    *e
  }
}

/// Origin of an application callback error.
#[derive(Debug)]
pub enum CbOrigin {
  /// The application error occurred in the service handler's `init()`
  /// callback.
  Init,

  /// The application error occurred in the service handler's `run()`
  /// callback.
  Run,

  /// The application error occurred in the service handler's `shutdown()`.
  /// callback.
  Shutdown
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :







|
<
<
<
|
<
|
|
<
<
|
<
<
<



309
310
311
312
313
314
315
316



317

318
319


320



321
322
323
    let Ok(e) = self.0.downcast::<E>() else {
      panic!("Unable to downcast to error type E");
    };
    *e
  }
}

impl From<AppErr> for Error {



  /// Wrap an [`AppErr`] in an [`Error`].

  fn from(err: AppErr) -> Self {
    Error::App(err)


  }



}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :

Changes to src/installer/winsvc.rs.

12
13
14
15
16
17
18





19
20
21
22
23
24
25
  err::Error,
  rt::winsvc::{
    create_service_params, get_service_params_subkey, write_service_subkey
  }
};







pub fn install(ctx: super::RegSvc) -> Result<(), Error> {
  let svcname = &ctx.svcname;

  // Create a refrence cell used to keep track of whether to keep system
  // motifications (or not) when  leaving function.
  let status = RefCell::new(false);








>
>
>
>
>







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  err::Error,
  rt::winsvc::{
    create_service_params, get_service_params_subkey, write_service_subkey
  }
};


/// Register a service in the system service's subsystem.
// ToDo: Make notes about Windows-specific semantics:
// - Uses registry
// - Installer
// - Windows Event Log
pub fn install(ctx: super::RegSvc) -> Result<(), Error> {
  let svcname = &ctx.svcname;

  // Create a refrence cell used to keep track of whether to keep system
  // motifications (or not) when  leaving function.
  let status = RefCell::new(false);

Changes to src/lib.rs.

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

use std::{ffi::OsStr, path::Path};

pub use async_trait::async_trait;

pub use lumberjack::LumberJack;

pub use err::{AppErr, CbOrigin, Error};

#[cfg(feature = "tokio")]
pub use tokio;

#[cfg(feature = "rocket")]
pub use rocket;








|







52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

use std::{ffi::OsStr, path::Path};

pub use async_trait::async_trait;

pub use lumberjack::LumberJack;

pub use err::{AppErr, Error};

#[cfg(feature = "tokio")]
pub use tokio;

#[cfg(feature = "rocket")]
pub use rocket;

Changes to src/rt.rs.

475
476
477
478
479
480
481




482
483
484
485
486
487
488

  /// Mark this run context to run under the operating system's subservice, if
  /// one is available on this platform.
  pub fn service_ref(&mut self) -> &mut Self {
    self.service = true;
    self
  }





  /// Launch the application.
  ///
  /// If this `RunCtx` has been marked as a _service_ then it will perform the
  /// appropriate service subsystem integration before running the actual
  /// server application code.
  ///







>
>
>
>







475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492

  /// Mark this run context to run under the operating system's subservice, if
  /// one is available on this platform.
  pub fn service_ref(&mut self) -> &mut Self {
    self.service = true;
    self
  }

  pub fn is_service(&self) -> bool {
    self.service
  }

  /// Launch the application.
  ///
  /// If this `RunCtx` has been marked as a _service_ then it will perform the
  /// appropriate service subsystem integration before running the actual
  /// server application code.
  ///

Changes to www/changelog.md.

1
2
3
4
5
6
7
8
9
10



















11
12
13
14
15
16
17
# Change log

## [Unreleased]

### Added

### Changed

### Removed




















---

## [0.0.3] - 2023-10-23

### Added

- Introduce an `AppErr` type that can wrap application-specific errors that










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Change log

## [Unreleased]

### Added

### Changed

### Removed

---

## [0.0.4] - 2023-10-29

### Added

- Add the remaining `ArgsProc` callbacks in `ArgParser`.

### Changed

- Rather than pass a creation closure to the `ArgParser::proc()` for the run
  case, add a `ArgsProc::build_apprt()` that'll be invoked to create the
  runtime instead.
- More consistently use `AppErr` for callbacks.

### Removed

- Removed `err::CbOrigin`.

---

## [0.0.3] - 2023-10-23

### Added

- Introduce an `AppErr` type that can wrap application-specific errors that

Changes to www/index.md.

86
87
88
89
90
91
92








93
94
95
96
97
98
99
_qsu_ is negligible (or it might even be wasteful to pull in _qsu_).  The
benefits of using _qsu_ will be noticed mostly when targeting the Windows
Services subsystem.  But mostly the benefits become apparent when targetting
multiple service subsystems in the same project, and wanting to have a similar
API when developing non-async and async services.










## Feature labels in documentation

The crate's documentation uses automatically generated feature labels, which
currently requires nightly featuers.  To build the documentation locally use:

```
RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features







>
>
>
>
>
>
>
>







86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
_qsu_ is negligible (or it might even be wasteful to pull in _qsu_).  The
benefits of using _qsu_ will be noticed mostly when targeting the Windows
Services subsystem.  But mostly the benefits become apparent when targetting
multiple service subsystems in the same project, and wanting to have a similar
API when developing non-async and async services.


## General tips

- The logging/tracing facilities aren't initialized until the server
  application's runtime has been initialized, because the runtime type may
  affect the logging/tracing backends.  As an implication of this, services
  that use the `ArgParser` should defer operations that need logging/tracking
  until the service handler's `init()` trait method is called.

## Feature labels in documentation

The crate's documentation uses automatically generated feature labels, which
currently requires nightly featuers.  To build the documentation locally use:

```
RUSTFLAGS="--cfg docsrs" RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features
126
127
128
129
130
131
132
133
134

135


136
The details of changes can always be found in the timeline, but for a
high-level view of changes between released versions there's a manually
maintained [Change Log](./changelog.md).


## Project status

This crate is a work-in-progress, still in early prototyping stage.  It works
for basic use-cases, but the API and some of the semantics are likely to

change.










|
|
>
|
>
>

134
135
136
137
138
139
140
141
142
143
144
145
146
147
The details of changes can always be found in the timeline, but for a
high-level view of changes between released versions there's a manually
maintained [Change Log](./changelog.md).


## Project status

This crate is a work-in-progress -- still in early prototyping stage.  This
means potentially significant API instability between versions and incomplete,
or even incorrect, documentation.

It is recommended that projects wanting to use _qsu_ at this point use the
tests and examples for up-to-date information on how to use the crate.