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
104
105
106
107
108
109
110
111
112
113
114
mod communication;
mod device;
mod primary_worker;
mod profile;
mod settings;
mod shutdown;
mod audio;

use crate::primary_worker::handle_changes;
use crate::settings::SettingsHandle;
use crate::shutdown::Shutdown;
use anyhow::{anyhow, Context, Result};
use communication::listen_for_connections;
use directories::ProjectDirs;
use goxlr_ipc::Socket;
use goxlr_ipc::{DaemonRequest, DaemonResponse};
use log::{info, warn, LevelFilter};
use simplelog::{ColorChoice, CombinedLogger, Config, TermLogger, TerminalMode};
use std::fs;
use std::fs::remove_file;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use tokio::net::{UnixListener, UnixStream};
use tokio::sync::mpsc;
use tokio::{join, signal};

#[tokio::main]
async fn main() -> Result<()> {
    CombinedLogger::init(vec![TermLogger::new(
        LevelFilter::Debug,
        Config::default(),
        TerminalMode::Mixed,
        ColorChoice::Auto,
    )])
    .context("Could not configure the logger")?;

    let proj_dirs = ProjectDirs::from("org", "GoXLR-on-Linux", "GoXLR-Utility")
        .context("Couldn't find project directories")?;

    let settings = SettingsHandle::load(
        proj_dirs.config_dir().join("settings.json"),
        proj_dirs.data_dir(),
    )
    .await?;
    let listener = create_listener("/tmp/goxlr.socket").await?;

    let mut perms = fs::metadata("/tmp/goxlr.socket")?.permissions();
    perms.set_mode(0o777);
    fs::set_permissions("/tmp/goxlr.socket", perms)?;

    let mut shutdown = Shutdown::new();
    let (usb_tx, usb_rx) = mpsc::channel(32);
    let usb_handle = tokio::spawn(handle_changes(usb_rx, shutdown.clone(), settings));
    let communications_handle =
        tokio::spawn(listen_for_connections(listener, usb_tx, shutdown.clone()));

    tokio::spawn(await_ctrl_c(shutdown.clone()));

    shutdown.recv().await;
    info!("Shutting down daemon");
    let _ = join!(usb_handle, communications_handle);

    info!("Removing Socket");
    fs::remove_file("/tmp/goxlr.socket")?;
    Ok(())
}

async fn await_ctrl_c(shutdown: Shutdown) {
    if signal::ctrl_c().await.is_ok() {
        shutdown.trigger();
    }
}

async fn create_listener<P: AsRef<Path>>(path: P) -> Result<UnixListener> {
    let path = path.as_ref();
    let mut error = anyhow!("Could not create Unix socket listener");

    for _ in 0..3 {
        if path.exists() {
            if is_already_running(path).await {
                return Err(anyhow!("A GoXLR daemon is already running"));
            } else {
                warn!("Removing unused socket file {}", path.to_string_lossy());
                let _ = remove_file(path);
            }
        }
        match UnixListener::bind(path) {
            Ok(listener) => return Ok(listener),
            Err(e) => {
                error = anyhow::Error::from(e).context("Could not bind the Unix socket");
            }
        }
    }

    Err(error)
}

async fn is_already_running(path: &Path) -> bool {
    let stream = match UnixStream::connect(path).await {
        Ok(stream) => stream,
        Err(_) => return false,
    };
    let address = match stream.peer_addr() {
        Ok(address) => address,
        Err(_) => return false,
    };
    let mut socket: Socket<DaemonResponse, DaemonRequest> = Socket::new(address, stream);

    if socket.send(DaemonRequest::Ping).await.is_err() {
        return false;
    }

    socket.read().await.is_some()
}