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::common::env_vars::FRM_DIR;
15use crate::errors::Error;
16use crate::version::Version;
17
18#[derive(Debug, Clone)]
19pub struct Paths {
20    base_dir: PathBuf,
21}
22
23impl Paths {
24    pub fn new() -> Result<Self> {
25        let base_dir = Self::detect_base_dir()?;
26        Ok(Self { base_dir })
27    }
28
29    pub fn with_base_dir(base_dir: PathBuf) -> Self {
30        Self { base_dir }
31    }
32
33    fn detect_base_dir() -> Result<PathBuf> {
34        if let Ok(dir) = env::var(FRM_DIR) {
35            return Ok(PathBuf::from(dir));
36        }
37
38        let home =
39            dirs::home_dir().ok_or_else(|| Error::Config("cannot find home directory".into()))?;
40
41        Ok(home.join(".local").join("frm"))
42    }
43
44    pub fn base_dir(&self) -> &Path {
45        &self.base_dir
46    }
47
48    pub fn versions_dir(&self) -> PathBuf {
49        self.base_dir.join("versions")
50    }
51
52    pub fn version_dir(&self, version: &Version) -> PathBuf {
53        self.versions_dir().join(version.dir_name())
54    }
55
56    pub fn version_sbin_dir(&self, version: &Version) -> PathBuf {
57        self.version_dir(version).join("sbin")
58    }
59
60    pub fn version_etc_dir(&self, version: &Version) -> PathBuf {
61        self.version_dir(version).join("etc").join("rabbitmq")
62    }
63
64    pub fn version_confd_dir(&self, version: &Version) -> PathBuf {
65        self.version_etc_dir(version).join("conf.d")
66    }
67
68    pub fn version_var_log_dir(&self, version: &Version) -> PathBuf {
69        self.version_dir(version)
70            .join("var")
71            .join("log")
72            .join("rabbitmq")
73    }
74
75    pub fn etc_dir(&self) -> PathBuf {
76        self.base_dir.join("etc").join("rabbitmq")
77    }
78
79    pub fn downloads_dir(&self) -> PathBuf {
80        self.base_dir.join("downloads")
81    }
82
83    pub fn config_file(&self) -> PathBuf {
84        self.base_dir.join("config.toml")
85    }
86
87    pub fn default_file(&self) -> PathBuf {
88        self.base_dir.join("default")
89    }
90
91    pub fn timestamps_file(&self) -> PathBuf {
92        self.base_dir.join("version_timestamps.json")
93    }
94
95    pub fn ensure_dirs(&self) -> Result<()> {
96        fs::create_dir_all(self.versions_dir())?;
97        fs::create_dir_all(self.downloads_dir())?;
98        fs::create_dir_all(self.etc_dir())?;
99        Ok(())
100    }
101
102    pub fn version_installed(&self, version: &Version) -> bool {
103        self.version_dir(version).exists()
104    }
105
106    pub fn installed_versions(&self) -> Result<Vec<Version>> {
107        let versions_dir = self.versions_dir();
108        if !versions_dir.exists() {
109            return Ok(Vec::new());
110        }
111
112        let mut versions = Vec::new();
113        for entry in fs::read_dir(versions_dir)? {
114            let entry = entry?;
115            if entry.file_type()?.is_dir()
116                && let Some(name) = entry.file_name().to_str()
117                && let Ok(version) = name.parse::<Version>()
118            {
119                versions.push(version);
120            }
121        }
122
123        versions.sort();
124        Ok(versions)
125    }
126
127    pub fn installed_alpha_versions(&self) -> Result<Vec<Version>> {
128        let versions = self.installed_versions()?;
129        Ok(versions
130            .into_iter()
131            .filter(|v| v.is_distributed_via_server_packages_repository())
132            .collect())
133    }
134
135    pub fn latest_ga_version(&self) -> Result<Option<Version>> {
136        let versions = self.installed_versions()?;
137        Ok(versions.into_iter().rev().find(|v| v.is_ga()))
138    }
139
140    pub fn latest_alpha_version(&self) -> Result<Option<Version>> {
141        let versions = self.installed_versions()?;
142        Ok(versions
143            .into_iter()
144            .rev()
145            .find(|v| v.is_distributed_via_server_packages_repository()))
146    }
147}
148
149impl Default for Paths {
150    fn default() -> Self {
151        Self::new().expect("failed to initialize paths")
152    }
153}