Skip to main content

frm/
paths.rs

1// Copyright (c) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::env;
10use std::fs;
11use std::path::{Path, PathBuf};
12
13use crate::Result;
14use crate::errors::Error;
15use crate::version::Version;
16
17#[derive(Debug, Clone)]
18pub struct Paths {
19    base_dir: PathBuf,
20}
21
22impl Paths {
23    pub fn new() -> Result<Self> {
24        let base_dir = Self::detect_base_dir()?;
25        Ok(Self { base_dir })
26    }
27
28    pub fn with_base_dir(base_dir: PathBuf) -> Self {
29        Self { base_dir }
30    }
31
32    fn detect_base_dir() -> Result<PathBuf> {
33        if let Ok(dir) = env::var("FRM_DIR") {
34            return Ok(PathBuf::from(dir));
35        }
36
37        let home =
38            dirs::home_dir().ok_or_else(|| Error::Config("cannot find home directory".into()))?;
39
40        Ok(home.join(".local").join("frm"))
41    }
42
43    pub fn base_dir(&self) -> &Path {
44        &self.base_dir
45    }
46
47    pub fn versions_dir(&self) -> PathBuf {
48        self.base_dir.join("versions")
49    }
50
51    pub fn version_dir(&self, version: &Version) -> PathBuf {
52        self.versions_dir().join(version.dir_name())
53    }
54
55    pub fn version_sbin_dir(&self, version: &Version) -> PathBuf {
56        self.version_dir(version).join("sbin")
57    }
58
59    pub fn version_etc_dir(&self, version: &Version) -> PathBuf {
60        self.version_dir(version).join("etc").join("rabbitmq")
61    }
62
63    pub fn version_var_log_dir(&self, version: &Version) -> PathBuf {
64        self.version_dir(version)
65            .join("var")
66            .join("log")
67            .join("rabbitmq")
68    }
69
70    pub fn etc_dir(&self) -> PathBuf {
71        self.base_dir.join("etc").join("rabbitmq")
72    }
73
74    pub fn downloads_dir(&self) -> PathBuf {
75        self.base_dir.join("downloads")
76    }
77
78    pub fn config_file(&self) -> PathBuf {
79        self.base_dir.join("config.toml")
80    }
81
82    pub fn default_file(&self) -> PathBuf {
83        self.base_dir.join("default")
84    }
85
86    pub fn timestamps_file(&self) -> PathBuf {
87        self.base_dir.join("version_timestamps.json")
88    }
89
90    pub fn ensure_dirs(&self) -> Result<()> {
91        fs::create_dir_all(self.versions_dir())?;
92        fs::create_dir_all(self.downloads_dir())?;
93        fs::create_dir_all(self.etc_dir())?;
94        Ok(())
95    }
96
97    pub fn version_installed(&self, version: &Version) -> bool {
98        self.version_dir(version).exists()
99    }
100
101    pub fn installed_versions(&self) -> Result<Vec<Version>> {
102        let versions_dir = self.versions_dir();
103        if !versions_dir.exists() {
104            return Ok(Vec::new());
105        }
106
107        let mut versions = Vec::new();
108        for entry in fs::read_dir(versions_dir)? {
109            let entry = entry?;
110            if entry.file_type()?.is_dir()
111                && let Some(name) = entry.file_name().to_str()
112                && let Ok(version) = name.parse::<Version>()
113            {
114                versions.push(version);
115            }
116        }
117
118        versions.sort();
119        Ok(versions)
120    }
121
122    pub fn latest_ga_version(&self) -> Result<Option<Version>> {
123        let versions = self.installed_versions()?;
124        Ok(versions.into_iter().rev().find(|v| v.is_ga()))
125    }
126
127    pub fn latest_alpha_version(&self) -> Result<Option<Version>> {
128        let versions = self.installed_versions()?;
129        Ok(versions
130            .into_iter()
131            .rev()
132            .find(|v| v.is_distributed_via_server_packages_repository()))
133    }
134}
135
136impl Default for Paths {
137    fn default() -> Self {
138        Self::new().expect("failed to initialize paths")
139    }
140}