mirror of
https://github.com/narrrl/proton-drive-linux.git
synced 2026-11-03 16:41:26 +01:00
FUSE mounts over a non-empty folder without complaint, so files already in the My files folder were hidden under the mount: not uploaded, and back only once the service stopped, so they looked deleted (#67, B277). - pdfs-core: new `mountpoint` module. `not_empty` lists what is in a folder (hidden entries count); `mounted_fs_type` moves here from the CLI so the daemon can use it. `MountFailure` gains an optional `not_empty` field, which older readers ignore. - pdfs-fuse: `mount_with` checks the folder before the root fetch and before any worker starts. A dead mount is cleared first; one of our own live mounts is left to `spawn_session`, as before. - pdfs-cli: `cmd_daemon` records the folder in the failure record. The refusal backs off and retries like any mount failure, so emptying the folder is enough. - pdfs-gui: the status pages explain the refusal, and a dialog offers to open the folder or choose another one, once per run of failures. The folder picker refuses a folder with files in it. Translated in all catalogs. Docs: BUGS (B277), CHANGELOG, FAQ, TROUBLESHOOTING, CONFIGURATION.
90 lines
3.3 KiB
Rust
90 lines
3.3 KiB
Rust
//! Why the daemon's mount keeps failing, for the front ends to read.
|
|
//!
|
|
//! The control socket opens only once a mount has succeeded, so a daemon whose
|
|
//! mount fails on every try had no way to say why. The app and the tray showed
|
|
//! "Connecting…" for days while the reason sat in the journal (`docs/BUGS.md`
|
|
//! B263). The daemon now writes its last failure to a small file in the state
|
|
//! directory, next to the control socket, and removes it once a mount is up.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// The file's name inside the state directory.
|
|
const FILE_NAME: &str = "mount-failure.json";
|
|
|
|
/// The last failed mount attempt.
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
|
pub struct MountFailure {
|
|
/// The error, in English, as the journal has it.
|
|
pub error: String,
|
|
/// Unix seconds of the first failure in this run of failures.
|
|
pub since: i64,
|
|
/// Failed attempts in a row.
|
|
pub attempts: u32,
|
|
/// Unix seconds of the next attempt.
|
|
pub next_attempt: i64,
|
|
/// Set when the mount was refused because the folder already has files in
|
|
/// it, so the app can explain that in the user's language. Absent in a
|
|
/// file an older daemon wrote.
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub not_empty: Option<crate::mountpoint::NotEmpty>,
|
|
}
|
|
|
|
/// Where the failure is kept for the state directory `state_dir`.
|
|
pub fn path(state_dir: &Path) -> PathBuf {
|
|
state_dir.join(FILE_NAME)
|
|
}
|
|
|
|
/// Record `failure`, replacing the one before it. Written to a temporary file
|
|
/// and renamed, so a reader never sees half of it.
|
|
pub fn record(state_dir: &Path, failure: &MountFailure) -> std::io::Result<()> {
|
|
let json = serde_json::to_vec(failure).map_err(std::io::Error::other)?;
|
|
let tmp = state_dir.join(format!("{FILE_NAME}.tmp"));
|
|
std::fs::write(&tmp, json)?;
|
|
std::fs::rename(&tmp, path(state_dir))
|
|
}
|
|
|
|
/// Forget the failure: a mount is up, or a new daemon has started.
|
|
pub fn clear(state_dir: &Path) {
|
|
let _ = std::fs::remove_file(path(state_dir));
|
|
}
|
|
|
|
/// The recorded failure, if there is one and it can be read.
|
|
pub fn read(state_dir: &Path) -> Option<MountFailure> {
|
|
let bytes = std::fs::read(path(state_dir)).ok()?;
|
|
serde_json::from_slice(&bytes).ok()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn a_recorded_failure_reads_back_until_cleared() {
|
|
let dir = std::env::temp_dir().join(format!("pdfs-mountfail-{}", std::process::id()));
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
assert_eq!(read(&dir), None);
|
|
let failure = MountFailure {
|
|
error: "fetch My Files root: missing key".into(),
|
|
since: 1_700_000_000,
|
|
attempts: 3,
|
|
next_attempt: 1_700_000_040,
|
|
not_empty: None,
|
|
};
|
|
record(&dir, &failure).unwrap();
|
|
assert_eq!(read(&dir), Some(failure));
|
|
clear(&dir);
|
|
assert_eq!(read(&dir), None);
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|
|
|
|
#[test]
|
|
fn a_failure_an_older_daemon_wrote_still_reads() {
|
|
let old = r#"{"error":"missing key","since":1,"attempts":2,"next_attempt":3}"#;
|
|
let failure: MountFailure = serde_json::from_str(old).unwrap();
|
|
assert_eq!(failure.not_empty, None);
|
|
assert_eq!(failure.attempts, 2);
|
|
}
|
|
}
|