use std::env::var;
use std::path::PathBuf;
use anyhow::{Result, anyhow};
pub fn rekordbox_app_dir() -> Result<PathBuf> {
if let Ok(dir) = var("REKORDBOX_DIR") {
let dir = dir.trim();
if !dir.is_empty() {
return Ok(PathBuf::from(dir));
}
}
default_rekordbox_app_dir()
}
fn default_rekordbox_app_dir() -> Result<PathBuf> {
#[cfg(target_os = "windows")]
{
var("APPDATA")
.map(|appdata| PathBuf::from(appdata).join("Pioneer/rekordbox"))
.map_err(|_| anyhow!("APPDATA env var not found"))
}
#[cfg(target_os = "macos")]
{
var("HOME")
.map(|home| PathBuf::from(home).join("Library/Pioneer/rekordbox"))
.map_err(|_| anyhow!("HOME env var not found"))
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
compile_error!("Rekordbox only runs on macOS and Windows.");
}
pub fn data_dir() -> Result<PathBuf> {
#[cfg(target_os = "windows")]
{
var("LOCALAPPDATA")
.map(|d| PathBuf::from(d).join("rekord-ripper"))
.map_err(|_| anyhow!("LOCALAPPDATA env var not found"))
}
#[cfg(target_os = "macos")]
{
var("HOME")
.map(|h| PathBuf::from(h).join("Library/Application Support/rekord-ripper"))
.map_err(|_| anyhow!("HOME env var not found"))
}
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
compile_error!("Rekordbox only runs on macOS and Windows.");
}
pub fn backup_dir() -> Result<PathBuf> {
Ok(data_dir()?.join("backups"))
}
pub fn config_path(override_path: Option<&std::path::Path>) -> Result<PathBuf> {
if let Some(p) = override_path {
return Ok(p.to_path_buf());
}
if let Ok(p) = var("REKORD_RIPPER_CONFIG") {
let p = p.trim();
if !p.is_empty() {
return Ok(PathBuf::from(p));
}
}
Ok(data_dir()?.join("config.toml"))
}
pub fn credentials_path() -> Result<PathBuf> {
Ok(data_dir()?.join("credentials.toml"))
}
pub fn default_download_dir() -> Result<PathBuf> {
Ok(data_dir()?.join("incoming"))
}
pub fn scratch_root() -> Result<PathBuf> {
Ok(data_dir()?.join("scratch"))
}
pub fn pending_db_path() -> Result<PathBuf> {
Ok(data_dir()?.join("pending.sqlite"))
}
pub fn fingerprint_cache_path() -> Result<PathBuf> {
Ok(data_dir()?.join("fpcache.sqlite"))
}
pub fn expand_tilde(s: &str) -> Result<PathBuf> {
match s.strip_prefix("~/") {
Some(rest) => {
let home = var("HOME")
.or_else(|_| var("USERPROFILE"))
.map_err(|_| anyhow!("cannot expand '~' — neither HOME nor USERPROFILE is set"))?;
Ok(PathBuf::from(home).join(rest))
}
None => Ok(PathBuf::from(s)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backup_dir_is_under_data_dir() {
let (data, backups) = (data_dir().unwrap(), backup_dir().unwrap());
assert!(backups.starts_with(&data));
assert_eq!(backups.file_name().unwrap(), "backups");
}
#[test]
fn config_path_prefers_explicit_override() {
let explicit = PathBuf::from("/tmp/somewhere/else.toml");
assert_eq!(config_path(Some(&explicit)).unwrap(), explicit);
}
#[test]
fn expand_tilde_leaves_absolute_and_relative_paths_alone() {
assert_eq!(
expand_tilde("/abs/path").unwrap(),
PathBuf::from("/abs/path")
);
assert_eq!(expand_tilde("rel/path").unwrap(), PathBuf::from("rel/path"));
assert_eq!(expand_tilde("~").unwrap(), PathBuf::from("~"));
}
#[test]
fn expand_tilde_expands_home_prefix() {
let home = var("HOME").or_else(|_| var("USERPROFILE")).unwrap();
assert_eq!(
expand_tilde("~/Music/x").unwrap(),
PathBuf::from(home).join("Music/x")
);
}
}