Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/bin/julialauncher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ fn run_app() -> Result<i32> {
do_initial_setup(&paths.juliaupconfig)
.with_context(|| "The Julia launcher failed to run the initial setup steps.")?;

let config_file = load_config_db(&paths, None)
let config_file = load_config_db(&paths)
.with_context(|| "The Julia launcher failed to load a configuration file.")?;

let versiondb_data = load_versions_db(&paths)
Expand Down
2 changes: 1 addition & 1 deletion src/command_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ pub fn run_command_api(command: &str, paths: &GlobalPaths) -> Result<()> {
other_versions: Vec::new(),
};

let config_file = load_config_db(paths, None).with_context(|| {
let config_file = load_config_db(paths).with_context(|| {
"Failed to load configuration file while running the getconfig1 API command."
})?;

Expand Down
2 changes: 1 addition & 1 deletion src/command_config_backgroundselfupdate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub fn run_command_config_backgroundselfupdate(
}
}
None => {
let config_file = load_config_db(paths, None)
let config_file = load_config_db(paths)
.with_context(|| "`config` command failed to load configuration data.")?;

if !quiet {
Expand Down
2 changes: 1 addition & 1 deletion src/command_config_modifypath.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub fn run_command_config_modifypath(
}
}
None => {
let config_file = load_config_db(paths, None)
let config_file = load_config_db(paths)
.with_context(|| "`config` command failed to load configuration data.")?;

if !quiet {
Expand Down
2 changes: 1 addition & 1 deletion src/command_config_startupselfupdate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub fn run_command_config_startupselfupdate(
}
}
None => {
let config_file = load_config_db(paths, None)
let config_file = load_config_db(paths)
.with_context(|| "`config` command failed to load configuration data.")?;

if !quiet {
Expand Down
2 changes: 1 addition & 1 deletion src/command_config_symlinks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn run_command_config_symlinks(
}
}
None => {
let config_file = load_config_db(paths, None)
let config_file = load_config_db(paths)
.with_context(|| "`config` command failed to load configuration data.")?;

if !quiet {
Expand Down
2 changes: 1 addition & 1 deletion src/command_config_versionsdbupdate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub fn run_command_config_versionsdbupdate(
}
}
None => {
let config_file = load_config_db(paths, None)
let config_file = load_config_db(paths)
.with_context(|| "`config` command failed to load configuration data.")?;

if !quiet {
Expand Down
4 changes: 2 additions & 2 deletions src/command_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use anyhow::{bail, Context, Result};

pub fn run_command_info(paths: &GlobalPaths) -> Result<()> {
#[cfg(feature = "selfupdate")]
let config_file = load_config_db(paths, None).with_context(|| {
let config_file = load_config_db(paths).with_context(|| {
"`run_command_update_version_db` command failed to load configuration db."
})?;

Expand All @@ -21,7 +21,7 @@ pub fn run_command_info(paths: &GlobalPaths) -> Result<()> {
};

#[cfg(not(feature = "selfupdate"))]
let _config_file = load_config_db(paths, None).with_context(|| {
let _config_file = load_config_db(paths).with_context(|| {
"`run_command_update_version_db` command failed to load configuration db."
})?;

Expand Down
2 changes: 1 addition & 1 deletion src/command_override.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct OverrideRow {
}

pub fn run_command_override_status(paths: &GlobalPaths) -> Result<()> {
let config_file = load_config_db(paths, None)
let config_file = load_config_db(paths)
.with_context(|| "`override status` command failed to load configuration file.")?;

let rows_in_table: Vec<_> = config_file
Expand Down
2 changes: 1 addition & 1 deletion src/command_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct ChannelRow {
}

pub fn run_command_status(paths: &GlobalPaths) -> Result<()> {
let config_file = load_config_db(paths, None)
let config_file = load_config_db(paths)
.with_context(|| "`status` command failed to load configuration file.")?;

let versiondb_data =
Expand Down
186 changes: 55 additions & 131 deletions src/config_file.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use anyhow::{anyhow, bail, Context, Result};
use chrono::{DateTime, Utc};
use cluFlock::{ExclusiveFlock, FlockLock, SharedFlock};
use cluFlock::{ExclusiveFlock, FlockLock};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, ErrorKind, Seek, SeekFrom};
use std::io::{BufReader, ErrorKind, Seek};
use tempfile::NamedTempFile;

use crate::global_paths::GlobalPaths;

Expand Down Expand Up @@ -127,11 +128,11 @@ pub struct JuliaupSelfConfig {
}

pub struct JuliaupConfigFile {
pub file: File,
pub config_path: std::path::PathBuf,
pub lock: FlockLock<File>,
pub data: JuliaupConfig,
#[cfg(feature = "selfupdate")]
pub self_file: File,
pub self_config_path: std::path::PathBuf,
#[cfg(feature = "selfupdate")]
pub self_data: JuliaupSelfConfig,
}
Expand All @@ -142,45 +143,8 @@ pub struct JuliaupReadonlyConfigFile {
pub self_data: JuliaupSelfConfig,
}

pub fn get_read_lock(paths: &GlobalPaths) -> Result<FlockLock<File>> {
std::fs::create_dir_all(&paths.juliauphome)
.with_context(|| "Could not create juliaup home folder.")?;

let lock_file = match OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&paths.lockfile)
{
Ok(file) => file,
Err(e) => return Err(anyhow!("Could not create lockfile: {}.", e)),
};

let file_lock = match SharedFlock::try_lock(lock_file) {
Ok(lock) => lock,
Err(e) => {
eprintln!(
"Juliaup configuration is locked by another process, waiting for it to unlock."
);

SharedFlock::wait_lock(e.into()).unwrap()
}
};

Ok(file_lock)
}

pub fn load_config_db(
paths: &GlobalPaths,
existing_lock: Option<&FlockLock<File>>,
) -> Result<JuliaupReadonlyConfigFile> {
let mut file_lock: Option<FlockLock<File>> = None;

if existing_lock.is_none() {
file_lock = Some(get_read_lock(paths)?);
}

pub fn load_config_db(paths: &GlobalPaths) -> Result<JuliaupReadonlyConfigFile> {
let v = match std::fs::OpenOptions::new()
.read(true)
.open(&paths.juliaupconfig)
Expand Down Expand Up @@ -243,12 +207,6 @@ pub fn load_config_db(
};
}

if let Some(file_lock) = file_lock {
file_lock
.unlock()
.with_context(|| "Failed to unlock configuration file.")?;
}

Ok(JuliaupReadonlyConfigFile {
data: v,
#[cfg(feature = "selfupdate")]
Expand All @@ -260,43 +218,40 @@ pub fn load_mut_config_db(paths: &GlobalPaths) -> Result<JuliaupConfigFile> {
std::fs::create_dir_all(&paths.juliauphome)
.with_context(|| "Could not create juliaup home folder.")?;

let lock_file = match OpenOptions::new()
// Open or create the config file and acquire an exclusive lock
let config_file = match OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(false)
.open(&paths.lockfile)
.open(&paths.juliaupconfig)
{
Ok(file) => file,
Err(e) => return Err(anyhow!("Could not create lockfile: {}.", e)),
Err(e) => return Err(anyhow!("Could not open config file: {}.", e)),
};

let file_lock = match ExclusiveFlock::try_lock(lock_file) {
// Acquire exclusive lock on the config file
let mut file_lock = match ExclusiveFlock::try_lock(config_file) {
Ok(lock) => lock,
Err(e) => {
eprintln!(
"Juliaup configuration is locked by another process, waiting for it to unlock."
);

ExclusiveFlock::wait_lock(e.into()).unwrap()
}
};

let mut 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 {
// Read the configuration
let data = {
file_lock.rewind()
.with_context(|| "Failed to rewind config file for reading.")?;

let metadata = file_lock.metadata()
.with_context(|| "Failed to get config file metadata.")?;

if metadata.len() == 0 {
// File is empty, create default config
JuliaupConfig {
default: None,
installed_versions: HashMap::new(),
installed_channels: HashMap::new(),
Expand All @@ -306,43 +261,25 @@ pub fn load_mut_config_db(paths: &GlobalPaths) -> Result<JuliaupConfigFile> {
versionsdb_update_interval: default_versionsdb_update_interval(),
},
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.")?;

let reader = BufReader::new(&file);

}
} else {
// Read existing config
let reader = BufReader::new(&*file_lock);
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 file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&paths.juliaupselfconfig)
.with_context(|| "Failed to open juliaup config file.")?;
.with_context(|| "Failed to open juliaup self config file.")?;

let reader = BufReader::new(&self_file);
let reader = BufReader::new(&file);

self_data = serde_json::from_reader(reader).with_context(|| {
format!(
Expand All @@ -353,11 +290,11 @@ pub fn load_mut_config_db(paths: &GlobalPaths) -> Result<JuliaupConfigFile> {
}

let result = JuliaupConfigFile {
file,
config_path: paths.juliaupconfig.clone(),
lock: file_lock,
data,
#[cfg(feature = "selfupdate")]
self_file,
self_config_path: paths.juliaupselfconfig.clone(),
#[cfg(feature = "selfupdate")]
self_data,
};
Expand All @@ -366,45 +303,32 @@ pub fn load_mut_config_db(paths: &GlobalPaths) -> Result<JuliaupConfigFile> {
}

pub fn save_config_db(juliaup_config_file: &mut JuliaupConfigFile) -> Result<()> {
juliaup_config_file
.file
.rewind()
.with_context(|| "Failed to rewind config file for write.")?;

juliaup_config_file
.file
.set_len(0)
.with_context(|| "Failed to set len to 0 for config file before writing new content.")?;

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

juliaup_config_file
.file
.sync_all()
.with_context(|| "Failed to write config data to disc.")?;
// Write main config file atomically
let parent_dir = juliaup_config_file.config_path.parent()
.ok_or_else(|| anyhow!("Config path has no parent directory"))?;

let temp_file = NamedTempFile::new_in(parent_dir)
.with_context(|| "Failed to create temp file for config.")?;

serde_json::to_writer_pretty(&temp_file, &juliaup_config_file.data)
.with_context(|| "Failed to write configuration to temp file.")?;

temp_file.persist(&juliaup_config_file.config_path)
.with_context(|| "Failed to atomically replace configuration file.")?;

#[cfg(feature = "selfupdate")]
{
juliaup_config_file
.self_file
.rewind()
.with_context(|| "Failed to rewind self config file for write.")?;

juliaup_config_file.self_file.set_len(0).with_context(|| {
"Failed to set len to 0 for self config file before writing new content."
})?;

serde_json::to_writer_pretty(
&juliaup_config_file.self_file,
&juliaup_config_file.self_data,
)
.with_context(|| "Failed to write self configuration file.".to_string())?;

juliaup_config_file
.self_file
.sync_all()
.with_context(|| "Failed to write config data to disc.")?;
let self_parent_dir = juliaup_config_file.self_config_path.parent()
.ok_or_else(|| anyhow!("Self config path has no parent directory"))?;

let self_temp_file = NamedTempFile::new_in(self_parent_dir)
.with_context(|| "Failed to create temp file for self config.")?;

serde_json::to_writer_pretty(&self_temp_file, &juliaup_config_file.self_data)
.with_context(|| "Failed to write self configuration to temp file.")?;

self_temp_file.persist(&juliaup_config_file.self_config_path)
.with_context(|| "Failed to atomically replace self configuration file.")?;
}

Ok(())
Expand Down
Loading
Loading