1use std::{
2 env,
3 fs::read_to_string,
4 io::ErrorKind,
5 net::{IpAddr, Ipv4Addr, SocketAddr},
6 str::FromStr,
7 sync::Arc,
8};
9
10use toml::{Table, Value};
11
12use crate::fnv1a_64;
13use tiny_web_macro::fnv1a_64 as m_fnv1a_64;
14
15use super::{log::Log, route::Route};
16
17#[derive(Debug, Clone)]
25pub(crate) enum AcceptAddr {
26 Any,
28 IpAddr(IpAddr),
30 #[cfg(not(target_family = "windows"))]
32 Uds,
33}
34
35#[derive(Debug, Clone)]
42pub(crate) enum Addr {
43 SocketAddr(SocketAddr),
45 #[cfg(not(target_family = "windows"))]
47 Uds(String),
48}
49
50#[derive(Debug, Clone)]
63pub struct DBConfig {
64 pub host: String,
66 pub port: Option<u16>,
68 pub name: String,
70 pub user: Option<String>,
72 pub pwd: Option<String>,
74 pub sslmode: bool,
76 pub max: usize,
78}
79
80#[derive(Debug, Clone)]
82pub(crate) struct Config {
83 pub is_default: bool,
84 pub name: String,
86 pub desc: String,
88 pub version: String,
90 pub lang: Arc<String>,
92 pub max: usize,
94 pub bind_accept: Arc<AcceptAddr>,
96 pub bind: Addr,
98 pub rpc_accept: AcceptAddr,
100 pub rpc: Arc<Addr>,
102 pub session: Arc<String>,
104 pub salt: Arc<String>,
106 pub db: Arc<DBConfig>,
108 pub stop_signal: i64,
110 pub status_signal: i64,
112 pub action_index: Arc<Route>,
114 pub action_not_found: Arc<Route>,
116 pub action_err: Arc<Route>,
118}
119
120impl Config {
121 fn default(args: InitArgs) -> Config {
122 let num_cpus = num_cpus::get();
123 let bind_accept = AcceptAddr::Any;
124 let bind = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12500));
125 let rpc_accept = AcceptAddr::IpAddr(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
126 let rpc = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12501));
127
128 let db = DBConfig {
129 host: String::new(),
130 port: None,
131 name: String::new(),
132 user: None,
133 pwd: None,
134 sslmode: false,
135 max: 0,
136 };
137 let log = "tiny.log".to_owned();
138 Log::set_path(log.clone());
139
140 let lang = if let Some(lang) = args.lang { lang } else { "en".to_owned() };
141
142 Config {
143 is_default: true,
144 name: args.name.to_owned(),
145 desc: args.desc.to_owned(),
146 version: args.version.to_owned(),
147 lang: Arc::new(lang),
148 max: num_cpus,
149 bind_accept: Arc::new(bind_accept),
150 bind,
151 rpc_accept,
152 rpc: Arc::new(rpc),
153 session: Arc::new("tinysession".to_owned()),
154 salt: Arc::new("salt".to_owned()),
155 db: Arc::new(db),
156 stop_signal: m_fnv1a_64!("stopsalt"),
157 status_signal: m_fnv1a_64!("statussalt"),
158 action_index: Arc::new(Route::default_index()),
159 action_not_found: Arc::new(Route::default_not_found()),
160 action_err: Arc::new(Route::default_err()),
161 }
162 }
163}
164
165#[derive(Debug, Clone, PartialEq)]
175pub(crate) enum Mode {
176 Start,
178 Stop,
180 Status,
182 Help,
184 Go,
186}
187
188#[derive(Debug, Clone)]
199pub(crate) struct Init {
200 pub mode: Mode,
202 pub conf: Config,
204 pub exe_path: Arc<String>,
206 pub exe_file: String,
208 pub root_path: Arc<String>,
210}
211
212#[derive(Debug, Clone)]
214struct InitArgs<'a> {
215 path: Option<String>, lang: Option<String>, file: Option<&'a str>,
219 check_salt: bool,
220 name: &'a str,
221 version: &'a str,
222 desc: &'a str,
223 allow_no_config: bool,
224}
225
226impl Init {
227 pub fn new(name: &str, version: &str, desc: &str, allow_no_config: bool) -> Option<Init> {
231 let exe_file = Init::get_current_exe()?;
232
233 #[cfg(not(debug_assertions))]
234 let exe_path = match exe_file.rfind('/') {
235 Some(i) => exe_file[..i].to_owned(),
236 None => {
237 Log::stop(16, Some(exe_file));
238 return None;
239 }
240 };
241 #[cfg(debug_assertions)]
242 let exe_path = match env::current_dir() {
243 Ok(path) => match path.to_str() {
244 Some(path) => path.to_owned(),
245 None => {
246 Log::stop(16, Some(format!("{:?}", path)));
247 return None;
248 }
249 },
250 Err(e) => {
251 Log::stop(16, Some(e.to_string()));
252 return None;
253 }
254 };
255
256 let mut args = env::args();
257
258 let conf;
259 let mode;
260 let conf_file;
261 let root_path;
262
263 args.next();
264
265 let mut iar = InitArgs {
266 path: None,
267 lang: None,
268 file: None,
269 check_salt: false,
270 name,
271 version,
272 desc,
273 allow_no_config,
274 };
275
276 match args.next() {
278 None => {
280 mode = Mode::Help;
281 (conf_file, root_path) = Init::check_path(&exe_path, allow_no_config)?;
282 iar.file = conf_file.as_deref();
283
284 conf = Init::load_conf(iar)?;
285 }
286 Some(arg) => {
288 match arg.as_str() {
289 "start" => mode = Mode::Start,
290 "stop" => mode = Mode::Stop,
291 "status" => mode = Mode::Status,
292 "go" => mode = Mode::Go,
293 _ => mode = Mode::Help,
294 };
295 if mode != Mode::Help {
296 iar.check_salt = true;
297
298 while let Some(command) = args.next() {
299 if command == "-r" {
300 match args.next() {
301 Some(value) => {
302 iar.path = Some(value);
303 }
304 None => break,
305 }
306 } else if command == "-l" {
307 match args.next() {
308 Some(value) => {
309 if value.len() != 2 {
310 Log::warning(81, Some(value));
311 } else {
312 iar.lang = Some(value);
313 }
314 }
315 None => break,
316 }
317 }
318 }
319
320 if let Some(path) = &iar.path {
321 let file = format!("{}/tiny.toml", path);
322 root_path = match file.rfind('/') {
323 Some(i) => file[..i].to_owned(),
324 None => {
325 Log::stop(16, Some(file));
326 return None;
327 }
328 };
329 iar.file = Some(&root_path);
330 } else {
331 (conf_file, root_path) = Init::check_path(&exe_path, allow_no_config)?;
332 iar.file = conf_file.as_deref();
333 }
334 conf = Init::load_conf(iar)?;
335 } else {
336 root_path = String::new();
337 conf = Config::default(iar);
338 }
339 }
340 };
341
342 Some(Init {
343 mode,
344 conf,
345 exe_file,
346 exe_path: Arc::new(exe_path),
347 root_path: Arc::new(root_path),
348 })
349 }
350
351 pub fn get_current_exe() -> Option<String> {
353 let exe = match env::current_exe() {
354 Ok(e) => match e.to_str() {
355 Some(e) => {
356 if &e[..2] == r"\\" {
357 if &e[..4] == r"\\?\" {
358 e[4..].replace('\\', "/")
359 } else {
360 Log::stop(12, Some(e.to_string()));
361 return None;
362 }
363 } else {
364 e.replace('\\', "/")
365 }
366 }
367 None => {
368 Log::stop(11, Some(e.to_string_lossy().to_string()));
369 return None;
370 }
371 },
372 Err(e) => {
373 Log::stop(10, Some(e.to_string()));
374 return None;
375 }
376 };
377 Some(exe)
378 }
379
380 fn check_path(path: &str, allow_no_config: bool) -> Option<(Option<String>, String)> {
393 let file = format!("{}/tiny.toml", path);
394 match read_to_string(&file) {
395 Ok(_) => Some((Some(file), path.to_owned())),
396 Err(e) => match e.kind() {
397 ErrorKind::NotFound => {
398 if allow_no_config {
399 Some((None, path.to_owned()))
400 } else {
401 Log::stop(15, Some(format!("{}. Error: {}", &file, e)));
402 None
403 }
404 }
405 _ => {
406 Log::stop(14, Some(format!("{}. Error: {}", &file, e)));
407 None
408 }
409 },
410 }
411 }
412
413 fn load_conf(args: InitArgs) -> Option<Config> {
425 let file = match args.file {
426 Some(file) => file,
427 None => {
428 if args.allow_no_config {
429 return Some(Config::default(args));
430 } else {
431 Log::stop(14, None);
432 return None;
433 }
434 }
435 };
436
437 let text = match read_to_string(file) {
438 Ok(text) => text,
439 Err(e) => {
440 Log::stop(14, Some(format!("{}. Error: {}", &file, e)));
441 return None;
442 }
443 };
444
445 let text = match text.parse::<Table>() {
446 Ok(v) => v,
447 Err(e) => {
448 Log::stop(18, Some(e.to_string()));
449 return None;
450 }
451 };
452
453 let num_cpus = num_cpus::get();
454 let mut num_connections = num_cpus * 3;
455 let mut lang = "en".to_owned();
456 let mut max = num_cpus;
457 let mut bind_accept = AcceptAddr::Any;
458 let mut bind = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12500));
459 let mut rpc_accept = AcceptAddr::IpAddr(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
460 let mut rpc = Addr::SocketAddr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 12501));
461 let mut session = "tinysession".to_owned();
462 let mut salt = String::new();
463 let mut stop_signal = 0;
464 let mut status_signal = 0;
465
466 let mut db = DBConfig {
467 host: String::new(),
468 port: None,
469 name: String::new(),
470 user: None,
471 pwd: None,
472 sslmode: false,
473 max: num_connections,
474 };
475 let mut action_index = Route::default_index();
476 let mut action_not_found = Route::default_not_found();
477 let mut action_err = Route::default_err();
478
479 if let Some(value) = text.get("log") {
480 if let Value::String(val) = value {
481 Log::set_path(val.clone());
482 } else {
483 Log::warning(61, Some(value.to_string()));
484 }
485 };
486
487 for (key, value) in text {
488 match key.as_str() {
489 "lang" => {
490 if let Value::String(val) = value {
491 if val.len() != 2 {
492 Log::warning(51, Some(val));
493 } else {
494 lang = val;
495 }
496 } else {
497 Log::warning(51, Some(value.to_string()));
498 }
499 }
500 "max" => match value {
501 Value::String(s) => {
502 if &s != "auto" {
503 Log::warning(52, Some(s));
504 }
505 }
506 Value::Integer(i) => match usize::try_from(i) {
507 Ok(v) => {
508 if v > 0 {
509 max = v;
510 num_connections = v * 3;
511 } else {
512 Log::warning(52, Some(v.to_string()));
513 }
514 }
515 Err(e) => {
516 Log::warning(52, Some(format!("{} {}", i, e)));
517 }
518 },
519 _ => {
520 Log::warning(52, Some(value.to_string()));
521 }
522 },
523 "bind_from" => {
524 if let Value::String(val) = value {
525 if val.is_empty() {
526 #[cfg(not(target_family = "windows"))]
527 {
528 bind_accept = AcceptAddr::Uds;
529 }
530 #[cfg(target_family = "windows")]
531 {
532 Log::warning(53, None);
533 }
534 } else if val == "any" {
535 bind_accept = AcceptAddr::Any;
536 } else {
537 match IpAddr::from_str(&val) {
538 Ok(ip) => bind_accept = AcceptAddr::IpAddr(ip),
539 Err(e) => {
540 Log::warning(53, Some(format!("{} ({})", e, val)));
541 }
542 };
543 }
544 } else {
545 Log::warning(53, Some(value.to_string()));
546 }
547 }
548 "bind" => {
549 if let Value::String(val) = value {
550 if val.contains(':') {
551 match SocketAddr::from_str(&val) {
552 Ok(s) => bind = Addr::SocketAddr(s),
553 Err(e) => {
554 Log::warning(54, Some(format!("{} ({})", e, val)));
555 }
556 }
557 } else {
558 #[cfg(target_family = "windows")]
559 {
560 Log::warning(54, Some(val));
561 }
562 #[cfg(not(target_family = "windows"))]
563 if val.is_empty() || &val[..1] != "/" {
564 Log::warning(54, None);
565 } else {
566 bind = Addr::Uds(val);
567 }
568 }
569 } else {
570 Log::warning(54, Some(value.to_string()));
571 }
572 }
573 "rpc_from" => {
574 if let Value::String(val) = value {
575 if val.is_empty() {
576 #[cfg(not(target_family = "windows"))]
577 {
578 rpc_accept = AcceptAddr::Uds;
579 }
580 #[cfg(target_family = "windows")]
581 {
582 Log::warning(53, None);
583 }
584 } else if val == "any" {
585 rpc_accept = AcceptAddr::Any;
586 } else {
587 match IpAddr::from_str(&val) {
588 Ok(ip) => rpc_accept = AcceptAddr::IpAddr(ip),
589 Err(e) => {
590 Log::warning(55, Some(format!("{} ({})", e, val)));
591 }
592 };
593 }
594 } else {
595 Log::warning(55, Some(value.to_string()));
596 }
597 }
598 "rpc" => {
599 if let Value::String(val) = value {
600 if val.contains(':') {
601 match SocketAddr::from_str(&val) {
602 Ok(s) => rpc = Addr::SocketAddr(s),
603 Err(e) => {
604 Log::warning(56, Some(format!("{} ({})", e, val)));
605 }
606 }
607 } else {
608 #[cfg(target_family = "windows")]
609 {
610 Log::warning(56, Some(val));
611 }
612 #[cfg(not(target_family = "windows"))]
613 if val.is_empty() || &val[..1] != "/" {
614 Log::warning(56, None);
615 } else {
616 rpc = Addr::Uds(val);
617 }
618 }
619 } else {
620 Log::warning(56, Some(value.to_string()));
621 }
622 }
623 "session" => {
624 if let Value::String(val) = value {
625 session = val;
626 } else {
627 Log::warning(71, Some(value.to_string()));
628 }
629 }
630 "salt" => {
631 if let Value::String(val) = value {
632 salt = val;
633 stop_signal = fnv1a_64(format!("stop{}", &salt).as_bytes());
634 status_signal = fnv1a_64(format!("status{}", &salt).as_bytes());
635 } else {
636 Log::warning(62, Some(value.to_string()));
637 }
638 }
639 "db_host" => {
640 if let Value::String(val) = value {
641 if !val.is_empty() {
642 db.host = val;
643 }
644 } else {
645 Log::warning(63, Some(value.to_string()));
646 }
647 }
648 "db_port" => {
649 if let Value::Integer(i) = value {
650 match u16::try_from(i) {
651 Ok(v) => {
652 if v > 0 {
653 db.port = Some(v);
654 } else {
655 Log::warning(57, Some(v.to_string()));
656 }
657 }
658 Err(e) => {
659 Log::warning(57, Some(format!("{} ({})", e, i)));
660 }
661 }
662 } else {
663 Log::warning(57, Some(value.to_string()));
664 }
665 }
666 "db_name" => {
667 if let Value::String(val) = value {
668 db.name = val;
669 } else {
670 Log::warning(64, Some(value.to_string()));
671 }
672 }
673 "db_user" => {
674 if let Value::String(val) = value {
675 db.user = Some(val);
676 } else {
677 Log::warning(65, Some(value.to_string()));
678 }
679 }
680 "db_pwd" => {
681 if let Value::String(val) = value {
682 db.pwd = Some(val);
683 } else {
684 Log::warning(66, Some(value.to_string()));
685 }
686 }
687 "sslmode" => {
688 if let Value::Boolean(val) = value {
689 db.sslmode = val;
690 } else {
691 Log::warning(67, Some(value.to_string()));
692 }
693 }
694 "db_max" => match value {
695 Value::String(s) => {
696 if &s == "auto" {
697 db.max = num_connections;
698 } else {
699 Log::warning(58, Some(s));
700 }
701 }
702 Value::Integer(i) => match usize::try_from(i) {
703 Ok(v) => {
704 if v > 0 {
705 db.max = v;
706 } else {
707 Log::warning(58, Some(v.to_string()));
708 }
709 }
710 Err(e) => {
711 Log::warning(58, Some(format!("{} {}", i, e)));
712 }
713 },
714 _ => {
715 Log::warning(58, Some(value.to_string()));
716 }
717 },
718 "action_index" => {
719 if let Value::String(val) = &value {
720 if let Some(route) = Route::parse(val) {
721 action_index = route;
722 } else {
723 Log::warning(75, Some(value.to_string()));
724 }
725 } else {
726 Log::warning(74, Some(value.to_string()));
727 }
728 }
729 "action_not_found" => {
730 if let Value::String(val) = &value {
731 if let Some(route) = Route::parse(val) {
732 action_not_found = route;
733 } else {
734 Log::warning(77, Some(value.to_string()));
735 }
736 } else {
737 Log::warning(76, Some(value.to_string()));
738 }
739 }
740 "action_err" => {
741 if let Value::String(val) = &value {
742 if let Some(route) = Route::parse(val) {
743 action_err = route;
744 } else {
745 Log::warning(79, Some(value.to_string()));
746 }
747 } else {
748 Log::warning(78, Some(value.to_string()));
749 }
750 }
751 _ => {}
752 }
753 }
754
755 if db.host.is_empty() {
756 Log::stop(59, None);
757 return None;
758 }
759 if args.check_salt && salt.is_empty() {
760 Log::stop(50, None);
761 return None;
762 }
763 if let Some(l) = args.lang {
764 lang = l;
765 }
766
767 let conf = Config {
768 is_default: false,
769 name: args.name.to_owned(),
770 desc: args.desc.to_owned(),
771 version: args.version.to_owned(),
772 lang: Arc::new(lang),
773 max,
774 bind_accept: Arc::new(bind_accept),
775 bind,
776 rpc_accept,
777 rpc: Arc::new(rpc),
778 session: Arc::new(session),
779 salt: Arc::new(salt),
780 db: Arc::new(db),
781 stop_signal,
782 status_signal,
783 action_index: Arc::new(action_index),
784 action_not_found: Arc::new(action_not_found),
785 action_err: Arc::new(action_err),
786 };
787 Some(conf)
788 }
789}