Skip to content
Draft
157 changes: 40 additions & 117 deletions src/config_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ use cluFlock::{ExclusiveFlock, FlockLock, SharedFlock};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, ErrorKind, Seek, SeekFrom, Write};
#[cfg(target_os = "windows")]
use std::mem;
use std::io::{BufReader, ErrorKind, Write};
use tempfile::NamedTempFile;

use crate::global_paths::GlobalPaths;
Expand Down Expand Up @@ -143,12 +141,9 @@ pub struct JuliaupSelfConfig {
}

pub struct JuliaupConfigFile {
pub file: File,
pub lock: FlockLock<File>,
pub data: JuliaupConfig,
#[cfg(feature = "selfupdate")]
pub self_file: File,
#[cfg(feature = "selfupdate")]
pub self_data: JuliaupSelfConfig,
}

Expand Down Expand Up @@ -299,84 +294,65 @@ pub fn load_mut_config_db(paths: &GlobalPaths) -> Result<JuliaupConfigFile> {
}
};

let mut file = std::fs::OpenOptions::new()
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&paths.juliaupconfig)
.with_context(|| "Failed to open juliaup config file.")?;

let stream_len = file
.seek(SeekFrom::End(0))
.with_context(|| "Failed to determine the length of the configuration file.")?;

let data = match stream_len {
0 => {
let new_config = JuliaupConfig {
default: None,
installed_versions: HashMap::new(),
installed_channels: HashMap::new(),
overrides: Vec::new(),
settings: JuliaupConfigSettings {
create_channel_symlinks: false,
versionsdb_update_interval: default_versionsdb_update_interval(),
auto_install_channels: None,
},
last_version_db_update: None,
};

serde_json::to_writer_pretty(&file, &new_config)
.with_context(|| "Failed to write configuration file.")?;

file.sync_all()
.with_context(|| "Failed to write configuration data to disc.")?;

file.rewind()
.with_context(|| "Failed to rewind config file after initial write of data.")?;

new_config
}
_ => {
file.rewind()
.with_context(|| "Failed to rewind existing config file.")?;

.open(&paths.juliaupconfig);

let data = match file {
Err(_file) => JuliaupConfig {
default: None,
installed_versions: HashMap::new(),
installed_channels: HashMap::new(),
overrides: Vec::new(),
settings: JuliaupConfigSettings {
create_channel_symlinks: false,
versionsdb_update_interval: default_versionsdb_update_interval(),
auto_install_channels: None,
},
last_version_db_update: None,
},
Ok(file) => {
let reader = BufReader::new(&file);

serde_json::from_reader(reader)
.with_context(|| "Failed to parse configuration file.")?
}
};

#[cfg(feature = "selfupdate")]
let self_file: File;
#[cfg(feature = "selfupdate")]
let self_data: JuliaupSelfConfig;
#[cfg(feature = "selfupdate")]
{
self_file = std::fs::OpenOptions::new()
let self_file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&paths.juliaupselfconfig)
.with_context(|| "Failed to open juliaup config file.")?;

let reader = BufReader::new(&self_file);
.open(&paths.juliaupselfconfig);

self_data = match self_file {
// TODO Or should we just error when the file can't be read?
Err(_self_file) => JuliaupSelfConfig {
background_selfupdate_interval: None,
startup_selfupdate_interval: None,
modify_path: false,
juliaup_channel: None,
last_selfupdate: None,
},
Ok(self_file) => {
let reader = BufReader::new(&self_file);

self_data = serde_json::from_reader(reader).with_context(|| {
format!(
"Failed to parse self configuration file '{:?}' for reading.",
paths.juliaupselfconfig
)
})?
serde_json::from_reader(reader).with_context(|| {
format!(
"Failed to parse self configuration file '{:?}' for reading.",
paths.juliaupselfconfig
)
})?
}
}
}

let result = JuliaupConfigFile {
file,
lock: file_lock,
data,
#[cfg(feature = "selfupdate")]
self_file,
#[cfg(feature = "selfupdate")]
self_data,
};

Expand Down Expand Up @@ -415,38 +391,11 @@ pub fn save_config_db(juliaup_config_file: &mut JuliaupConfigFile) -> Result<()>
.sync_all()
.with_context(|| "Failed to sync configuration data to disc.")?;

// On Windows, we must close the file before replacing it
// On Unix, the file can remain open during atomic rename
#[cfg(target_os = "windows")]
{
// Create a dummy file to replace the handle we need to close
let dummy = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(config_dir.join(".dummy"))
.with_context(|| "Failed to create dummy file handle.")?;

// Replace the file handle with the dummy, dropping the old handle
let _ = mem::replace(&mut juliaup_config_file.file, dummy);
}

// Atomically replace the old config file with the new one
temp_file
.persist(&paths.juliaupconfig)
.with_context(|| "Failed to persist configuration file.")?;

// Reopen the file to update our file handle (Windows only, since we closed it)
#[cfg(target_os = "windows")]
{
juliaup_config_file.file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&paths.juliaupconfig)
.with_context(|| "Failed to reopen configuration file after save.")?;
}

#[cfg(feature = "selfupdate")]
{
// Use the same atomic write pattern for the self config file
Expand All @@ -470,35 +419,9 @@ pub fn save_config_db(juliaup_config_file: &mut JuliaupConfigFile) -> Result<()>
.sync_all()
.with_context(|| "Failed to sync self configuration data to disc.")?;

// On Windows, we must close the file before replacing it
#[cfg(target_os = "windows")]
{
// Create a dummy file to replace the handle we need to close
let dummy = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(self_config_dir.join(".dummy_self"))
.with_context(|| "Failed to create dummy self config file handle.")?;

// Replace the file handle with the dummy, dropping the old handle
let _ = mem::replace(&mut juliaup_config_file.self_file, dummy);
}

temp_self_file
.persist(&paths.juliaupselfconfig)
.with_context(|| "Failed to persist self configuration file.")?;

// Reopen the self config file (Windows only, since we closed it)
#[cfg(target_os = "windows")]
{
juliaup_config_file.self_file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&paths.juliaupselfconfig)
.with_context(|| "Failed to reopen self configuration file after save.")?;
}
}

Ok(())
Expand Down
Loading