remove unnecessary command
This commit is contained in:
@@ -127,17 +127,6 @@ export async function schedulerReadLog(taskId: string, which: 'runner' | 'daemon
|
||||
})
|
||||
}
|
||||
|
||||
export interface SchedulerDoctorCheck {
|
||||
name: string
|
||||
ok: boolean
|
||||
detail: string
|
||||
fix?: string
|
||||
}
|
||||
|
||||
export async function schedulerDoctor() {
|
||||
return invoke<SchedulerDoctorCheck[]>('scheduler_doctor')
|
||||
}
|
||||
|
||||
function buildJobSpec(task: ScheduledTask): SchedulerJobSpec {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
|
||||
@@ -885,7 +885,6 @@ pub fn run() {
|
||||
scheduler::scheduler_status,
|
||||
scheduler::scheduler_read_history,
|
||||
scheduler::scheduler_read_log,
|
||||
scheduler::scheduler_doctor,
|
||||
scheduler::scheduler_unregister_all,
|
||||
scheduler::scheduler_sweep_orphans,
|
||||
notifications::notifications_catalog,
|
||||
|
||||
@@ -346,7 +346,7 @@ pub fn scheduler_validate_cron(cron: String) -> CronValidation {
|
||||
valid: true,
|
||||
error: None,
|
||||
next_runs: cronconv::parse(&cron)
|
||||
.map(|spec| cronconv::next_fires(&spec, chrono::Local::now(), 5))
|
||||
.map(|spec| cronconv::next_fires(&spec, chrono::Local::now(), 10))
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
Err(error) => CronValidation {
|
||||
@@ -664,216 +664,6 @@ pub async fn scheduler_read_history(
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DoctorCheck {
|
||||
pub name: String,
|
||||
pub ok: bool,
|
||||
pub detail: String,
|
||||
pub fix: Option<String>,
|
||||
}
|
||||
|
||||
/// Preflight diagnostics with actionable fixes — the "why didn't my task run" surface.
|
||||
#[tauri::command]
|
||||
pub async fn scheduler_doctor() -> Result<Vec<DoctorCheck>, String> {
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let mut checks: Vec<DoctorCheck> = Vec::new();
|
||||
|
||||
if crate::is_flatpak() {
|
||||
// Scheduling works under Flatpak through the host's cron (crontab via
|
||||
// `flatpak-spawn --host`, entries re-launch the app with `flatpak run`). The startup
|
||||
// gate normally guarantees these permissions; verify anyway so the doctor stays
|
||||
// truthful if that gate ever changes.
|
||||
let ok = crate::has_flatpak_permissions();
|
||||
checks.push(DoctorCheck {
|
||||
name: "Sandbox host access".to_string(),
|
||||
ok,
|
||||
detail: if ok {
|
||||
"host filesystem and host-spawn access are granted — scheduling uses the host's cron"
|
||||
.to_string()
|
||||
} else {
|
||||
"missing host filesystem or host-spawn permission — the app cannot reach the host's crontab"
|
||||
.to_string()
|
||||
},
|
||||
fix: if ok {
|
||||
None
|
||||
} else {
|
||||
Some(format!(
|
||||
"Grant it: flatpak override --user --filesystem=host --talk-name=org.freedesktop.Flatpak {}",
|
||||
flatpak_app_id()
|
||||
))
|
||||
},
|
||||
});
|
||||
if !ok {
|
||||
return Ok(checks);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
match crontab::check_available() {
|
||||
Ok(()) => checks.push(DoctorCheck {
|
||||
name: "cron installed".to_string(),
|
||||
ok: true,
|
||||
detail: if crate::is_flatpak() {
|
||||
"crontab found on the host".to_string()
|
||||
} else {
|
||||
"crontab found in PATH".to_string()
|
||||
},
|
||||
fix: None,
|
||||
}),
|
||||
Err(_) => {
|
||||
checks.push(DoctorCheck {
|
||||
name: "cron installed".to_string(),
|
||||
ok: false,
|
||||
detail: if crate::is_flatpak() {
|
||||
"no crontab binary found on the host".to_string()
|
||||
} else {
|
||||
"no crontab binary found in PATH".to_string()
|
||||
},
|
||||
fix: Some(
|
||||
"Install 'cron' (Debian/Ubuntu) or 'cronie' (Fedora/Arch), then restart the app."
|
||||
.to_string(),
|
||||
),
|
||||
});
|
||||
return Ok(checks);
|
||||
}
|
||||
}
|
||||
|
||||
match crontab::host_command("crontab").arg("-l").output() {
|
||||
Ok(output) if output.status.success() => {
|
||||
let managed = String::from_utf8_lossy(&output.stdout)
|
||||
.lines()
|
||||
.filter(|line| line.trim_start().starts_with("# rclone-ui-task:"))
|
||||
.count();
|
||||
checks.push(DoctorCheck {
|
||||
name: "crontab access".to_string(),
|
||||
ok: true,
|
||||
detail: format!("{} scheduled task(s) registered", managed),
|
||||
fix: None,
|
||||
});
|
||||
}
|
||||
Ok(output) => {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if crontab::stderr_means_no_crontab(&stderr) {
|
||||
checks.push(DoctorCheck {
|
||||
name: "crontab access".to_string(),
|
||||
ok: true,
|
||||
detail: "no crontab yet — created on first schedule".to_string(),
|
||||
fix: None,
|
||||
});
|
||||
} else {
|
||||
checks.push(DoctorCheck {
|
||||
name: "crontab access".to_string(),
|
||||
ok: false,
|
||||
detail: format!("crontab -l failed: {}", stderr.trim()),
|
||||
fix: Some(
|
||||
"Your user may be denied cron access (cron.deny / system policy)."
|
||||
.to_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => checks.push(DoctorCheck {
|
||||
name: "crontab access".to_string(),
|
||||
ok: false,
|
||||
detail: format!("could not run crontab: {}", e),
|
||||
fix: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Inside the Flatpak sandbox (own PID namespace) host processes are invisible to
|
||||
// sysinfo — list them on the host instead.
|
||||
let cron_running = if crate::is_flatpak() {
|
||||
crontab::host_command("sh")
|
||||
.arg("-c")
|
||||
.arg("ps -e -o comm=")
|
||||
.output()
|
||||
.map(|o| {
|
||||
String::from_utf8_lossy(&o.stdout)
|
||||
.lines()
|
||||
.any(|name| matches!(name.trim(), "cron" | "crond" | "cronie"))
|
||||
})
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
let mut system = sysinfo::System::new();
|
||||
system.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
|
||||
system.processes().values().any(|process| {
|
||||
let name = process.name().to_string_lossy().to_lowercase();
|
||||
name == "cron" || name == "crond" || name == "cronie"
|
||||
})
|
||||
};
|
||||
checks.push(DoctorCheck {
|
||||
name: "cron service".to_string(),
|
||||
ok: cron_running,
|
||||
detail: if cron_running {
|
||||
"cron daemon is running".to_string()
|
||||
} else {
|
||||
"no cron daemon process found — schedules will not fire".to_string()
|
||||
},
|
||||
fix: if cron_running {
|
||||
None
|
||||
} else {
|
||||
Some("Enable it: sudo systemctl enable --now cron (or cronie)".to_string())
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
// TCC is the common silent failure for tasks touching Desktop/Documents/Downloads or
|
||||
// external/network volumes. The fix differs by run mode: user-mode tasks run as the
|
||||
// app via a LaunchAgent (grant the app), system-mode tasks run under cron (grant cron).
|
||||
checks.push(DoctorCheck {
|
||||
name: "macOS privacy (TCC)".to_string(),
|
||||
ok: true,
|
||||
detail:
|
||||
"Tasks reading protected folders (Desktop, Documents, Downloads) or external/network volumes may be blocked by macOS privacy protections. A scheduled run cannot show a permission prompt, so access must be granted beforehand."
|
||||
.to_string(),
|
||||
fix: Some(
|
||||
"'User' run mode (default): grant Rclone UI access to the folder, or add Rclone UI to Full Disk Access in System Settings → Privacy & Security. 'System' run mode: grant Full Disk Access to /usr/sbin/cron instead."
|
||||
.to_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let query = std::process::Command::new("schtasks").arg("/Query").output();
|
||||
let ok = query.map(|o| o.status.success()).unwrap_or(false);
|
||||
checks.push(DoctorCheck {
|
||||
name: "Task Scheduler".to_string(),
|
||||
ok,
|
||||
detail: if ok {
|
||||
"Task Scheduler service is reachable".to_string()
|
||||
} else {
|
||||
"schtasks query failed — the Task Scheduler service may be disabled".to_string()
|
||||
},
|
||||
fix: if ok {
|
||||
None
|
||||
} else {
|
||||
Some("Start the 'Task Scheduler' service (services.msc).".to_string())
|
||||
},
|
||||
});
|
||||
checks.push(DoctorCheck {
|
||||
name: "Runs while logged out".to_string(),
|
||||
ok: true,
|
||||
detail:
|
||||
"Schedules in 'System' run mode run while logged out (S4U) but cannot access mapped drives or implicit-auth network shares — schedules that need them should use the 'User' run mode. S4U also requires the 'Log on as a batch job' right: on domain-managed machines that deny it, System-mode tasks register but never start."
|
||||
.to_string(),
|
||||
fix: None,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(checks)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
}
|
||||
|
||||
/// Remove every registration this app ever made (Settings escape hatch / pre-uninstall cleanup).
|
||||
/// Sweeps both job files and orphaned OS artifacts by prefix.
|
||||
#[tauri::command]
|
||||
|
||||
Reference in New Issue
Block a user