rlru 0.1.11

Rocket League replay uploader
Documentation
use std::fs;
use std::path::PathBuf;

use anyhow::{Context, Result};

pub const APP_ID: &str = "rlru";

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AppPaths {
    pub config_dir: PathBuf,
    pub cache_dir: PathBuf,
    pub data_dir: PathBuf,
}

impl AppPaths {
    pub fn discover() -> Result<Self> {
        let config_base = dirs::config_dir().context("failed to find the user config directory")?;
        let cache_base = dirs::cache_dir().context("failed to find the user cache directory")?;
        let data_base = dirs::data_dir().context("failed to find the user data directory")?;
        Ok(Self {
            config_dir: config_base.join(APP_ID),
            cache_dir: cache_base.join(APP_ID),
            data_dir: data_base.join(APP_ID),
        })
    }

    pub fn ensure(&self) -> Result<()> {
        create_private_dir(&self.config_dir)?;
        create_private_dir(&self.cache_dir)?;
        create_private_dir(&self.data_dir)?;
        create_private_dir(&self.tokens_dir())?;
        Ok(())
    }

    pub fn config_file(&self) -> PathBuf {
        self.config_dir.join("config.toml")
    }

    pub fn tokens_dir(&self) -> PathBuf {
        self.config_dir.join("tokens")
    }

    pub fn upload_cache_path(&self, target_name: &str) -> PathBuf {
        self.cache_dir.join(format!(
            "uploaded-{}.txt",
            sanitize_path_segment(target_name)
        ))
    }

    pub fn sync_state_file(&self) -> PathBuf {
        self.data_dir.join("sync-state.toml")
    }
}

fn create_private_dir(path: &PathBuf) -> Result<()> {
    fs::create_dir_all(path).with_context(|| format!("failed to create {}", path.display()))?;

    #[cfg(unix)]
    {
        use std::os::unix::fs::PermissionsExt;
        fs::set_permissions(path, fs::Permissions::from_mode(0o700))
            .with_context(|| format!("failed to set private permissions on {}", path.display()))?;
    }

    Ok(())
}

fn sanitize_path_segment(value: &str) -> String {
    value
        .chars()
        .map(|ch| {
            if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
                ch
            } else {
                '_'
            }
        })
        .collect()
}