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
1
2

3
4
5
6
7
8
9
10


-
+







[package]
name = "qsu"
version = "0.0.3"
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
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 = [
clap = { version = "4.4.7", optional = true, features = [
  "derive", "env", "string", "wrap_help"
] }
env_logger = { version = "0.10.0" }
futures = { version = "0.3.28" }
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
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;
use qsu::{installer::RegSvc, rt::SrvAppRt, AppErr};

use crate::err::Error;

pub(crate) struct AppArgsProc {}
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(
    &self,
    &mut 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");
  ) -> 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")?;
  // 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(())
  });

    Ok(regsvc)
  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
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 an application-defined errors from the
/// qsu inner runtime back out from the qsu runtime.
/// 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
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.
  // (This causes a memory leak).
  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 {};
  let mut argsproc = argp::AppArgsProc {
    bldr: Box::new(creator)
  };
  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc(|| {
  ap.proc()?;
    let handler = Box::new(MyService {});
    SrvAppRt::Rocket(handler)
  })?;

  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
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.
  // (This causes a memory leak).
  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 {};
  let mut argsproc = argp::AppArgsProc {
    bldr: Box::new(creator)
  };
  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc(|| {
  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 :

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
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.
  // (This causes a memory leak).
  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 {};
  let mut argsproc = argp::AppArgsProc {
    bldr: Box::new(creator)
  };
  let ap = ArgParser::new(&svcname, &mut argsproc);
  ap.proc(|| {
  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 :

Changes to src/argp.rs.

1
2
3
4
5

6
7
8

9
10
11
12
13
14
15
16
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},
  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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
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");

  /*
  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)
}


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
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 {
pub(crate) enum ArgpRes<'cb> {
  /// Run server application.
  RunApp(RunCtx),
  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 {
  /// Callback allowing application to configure service installation argument
  /// parser.
  fn inst_subcmd(&mut self) {
  /// 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.
    todo!()
  }

  fn rm_subcmd(&mut self) {
    todo!()
  #[allow(unused_variables)]
  fn conf_cmd(
    &mut self,
    cmdtype: Cmd,
    cmd: Command
  }

  fn run_subcmd(&mut self) {
  ) -> Result<Command, AppErr> {
    Ok(cmd)
    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
  /// [`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(
    &self,
    &mut self,
    sub_m: &ArgMatches,
    regsvc: RegSvc
  ) -> Result<RegSvc, Error> {
  ) -> 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<(), Error> {
  ) -> 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
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, Error> {
  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
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(RunCtx::new(&args.svcname).service()))
        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(RunCtx::new(&self.svcname)))
        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
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<F>(mut self, bldr: F) -> Result<(), Error>
  pub fn proc(mut self) -> Result<(), Error> {
  where
    F: FnOnce() -> SrvAppRt
  {
    // Create registration subcommand
    // 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) = self.inner_proc()? {
    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();
      //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
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()
  }

  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),
  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
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(_, _))
    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> {
  pub fn try_into_apperr<E: 'static>(self) -> Result<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)))
      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) -> (CbOrigin, E) {
    let Ok((origin, e)) = self.try_into_apperr::<E>() else {
  pub fn unwrap_apperr<E: 'static>(self) -> E {
    let Ok(e) = self.try_into_apperr::<E>() else {
      panic!("Unable to unwrap error E");
    };
    (origin, e)
  }

    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())
143
144
145
146
147
148
149
150

151
152
153
154
155
156
157
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(_origin, _e) => {
      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
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
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
  }
}

/// Origin of an application callback error.
impl From<AppErr> for Error {
#[derive(Debug)]
pub enum CbOrigin {
  /// The application error occurred in the service handler's `init()`
  /// callback.
  /// Wrap an [`AppErr`] in an [`Error`].
  Init,

  /// The application error occurred in the service handler's `run()`
  fn from(err: AppErr) -> Self {
    Error::App(err)
  /// 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 :

Changes to src/installer/winsvc.rs.

12
13
14
15
16
17
18





19
20
21
22
23
24
25
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
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};
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
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
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
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
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.  It works
for basic use-cases, but the API and some of the semantics are likely to
change.
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.