summaryrefslogtreecommitdiffstats
path: root/src/client/paths.rs
blob: b6b8d1ffabbf929503cfd7f8fd5c56ef69689963 (plain) (blame)
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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
//! Client data location and command-line arguments.

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

pub const APP_DIR: &str = "zagan-bet";
pub const DATABASE_FILE: &str = "client.db";

pub const USAGE: &str = "\
usage: client [-d <dir>] [--unsafe]

  -d, --data-dir <dir>   where to keep client.db (default: XDG data dir)
      --unsafe           skip certificate verification
  -h, --help             show this message

The database lives in $XDG_DATA_HOME/zagan-bet/client.db, falling back to
~/.local/share/zagan-bet/client.db (or %APPDATA%\\zagan-bet on Windows).";

#[derive(Debug, Default, PartialEq, Eq)]
pub struct ClientArgs {
    pub unsafe_mode: bool,
    pub data_dir: Option<PathBuf>,
    pub help: bool,
}

/// Parse arguments, without the program name.
pub fn parse_args(args: impl IntoIterator<Item = String>) -> Result<ClientArgs, String> {
    let mut parsed = ClientArgs::default();
    let mut args = args.into_iter();

    while let Some(argument) = args.next() {
        match argument.as_str() {
            "-d" | "--data-dir" => {
                let Some(value) = args.next() else {
                    return Err(format!("{argument} needs a directory\n{USAGE}"));
                };
                if value.is_empty() {
                    return Err(format!("{argument} needs a directory\n{USAGE}"));
                }
                parsed.data_dir = Some(PathBuf::from(value));
            }
            "--unsafe" => parsed.unsafe_mode = true,
            "-h" | "--help" => parsed.help = true,
            _ => {
                if let Some(value) = argument.strip_prefix("--data-dir=") {
                    if value.is_empty() {
                        return Err(format!("--data-dir needs a directory\n{USAGE}"));
                    }
                    parsed.data_dir = Some(PathBuf::from(value));
                } else {
                    return Err(format!("unknown argument: {argument}\n{USAGE}"));
                }
            }
        }
    }

    Ok(parsed)
}

/// `$XDG_DATA_HOME/zagan-bet`, `%APPDATA%\zagan-bet`, or
/// `$HOME/.local/share/zagan-bet`, in that order.
pub fn data_dir_from(
    xdg_data_home: Option<&OsStr>,
    home: Option<&OsStr>,
    appdata: Option<&OsStr>,
) -> PathBuf {
    if let Some(xdg) = xdg_data_home {
        let path = Path::new(xdg);
        // The XDG spec says a relative XDG_DATA_HOME is invalid.
        if !xdg.is_empty() && path.is_absolute() {
            return path.join(APP_DIR);
        }
    }

    if let Some(appdata) = appdata {
        let path = Path::new(appdata);
        if !appdata.is_empty() && path.is_absolute() {
            return path.join(APP_DIR);
        }
    }

    if let Some(home) = home {
        let path = Path::new(home);
        if !home.is_empty() && path.is_absolute() {
            return path.join(".local").join("share").join(APP_DIR);
        }
    }

    // No usable home directory: keep the old behaviour and stay local.
    PathBuf::from(APP_DIR)
}

pub fn default_data_dir() -> PathBuf {
    data_dir_from(
        std::env::var_os("XDG_DATA_HOME").as_deref(),
        std::env::var_os("HOME").as_deref(),
        std::env::var_os("APPDATA").as_deref(),
    )
}

pub fn database_path(data_dir: &Path) -> PathBuf {
    data_dir.join(DATABASE_FILE)
}