feat(cloud-files): Geteilte Ordner + Rechtsklick-Menue
Backend:
- /api/sync/tree liefert jetzt {tree, shared} - shared enthaelt alle
Dateien die MIT dem Benutzer geteilt wurden (FilePermission), nur
Top-Level-Shares, mit Owner-Name im Anzeigenamen
- updated_at zusaetzlich als modified_at im Response fuer Client-
Kompatibilitaet
Client:
- fetch_remote_entries merged Shared-Subtree unter virtuellem Ordner
"Geteilt mit mir" (synthetische ID -1) in den Mount-Point
- modified_at faellt auf updated_at zurueck, falls nicht vorhanden
Kontextmenue:
- Neue HKCU-Registry-Eintraege fuer "Immer offline verfuegbar" und
"Speicher freigeben", AppliesTo filtert auf Mount-Pfad, sodass die
Verben nur bei Dateien unterhalb des Sync-Ordners erscheinen
- Aufruf der eigenen .exe mit --pin / --unpin <file>
- handle_cli_shortcuts fuehrt die Aktion aus und beendet sofort,
ohne die UI/Tray/Single-Instance-Logik anzustossen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -102,12 +102,63 @@ pub fn install(
|
||||
ns.set_value("", &display_name.to_string())
|
||||
.map_err(|e| format!("set ns name: {e}"))?;
|
||||
|
||||
// 7) Explorer informieren (SHChangeNotify)
|
||||
// 7) Kontext-Menue-Verben (Rechtsklick) fuer Dateien unter dem Mount
|
||||
install_context_menu(mount_point)?;
|
||||
|
||||
// 8) Explorer informieren (SHChangeNotify)
|
||||
notify_shell();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Registriert "Immer offline halten" / "Speicher freigeben" als
|
||||
/// Rechtsklick-Menuepunkte, die nur fuer Dateien unterhalb des Mounts
|
||||
/// angezeigt werden (AppliesTo-Filter).
|
||||
fn install_context_menu(mount_point: &Path) -> Result<(), String> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let exe = std::env::current_exe()
|
||||
.map_err(|e| format!("current_exe: {e}"))?
|
||||
.to_string_lossy()
|
||||
.into_owned();
|
||||
let mount_str = mount_point.to_string_lossy();
|
||||
// AppliesTo filtert die Verben auf Pfade, die unter dem Mount liegen.
|
||||
// Syntax: "System.ItemPathDisplay:~< \"C:\\Users\\..\\Mini-Cloud\\\""
|
||||
let applies_to = format!(
|
||||
"System.ItemPathDisplay:~< \"{}\\\"",
|
||||
mount_str.replace('\\', "\\\\")
|
||||
);
|
||||
|
||||
for (verb, label, flag) in [
|
||||
("MiniCloudPin", "Immer offline verfuegbar", "--pin"),
|
||||
("MiniCloudUnpin", "Speicher freigeben", "--unpin"),
|
||||
] {
|
||||
let key_path = format!("Software\\Classes\\*\\shell\\{}", verb);
|
||||
let (k, _) = hkcu
|
||||
.create_subkey(&key_path)
|
||||
.map_err(|e| format!("verb {verb}: {e}"))?;
|
||||
k.set_value("MUIVerb", &label.to_string())
|
||||
.map_err(|e| format!("MUIVerb: {e}"))?;
|
||||
k.set_value("AppliesTo", &applies_to)
|
||||
.map_err(|e| format!("AppliesTo: {e}"))?;
|
||||
k.set_value("Icon", &exe)
|
||||
.map_err(|e| format!("Icon: {e}"))?;
|
||||
|
||||
let (cmd, _) = k
|
||||
.create_subkey("command")
|
||||
.map_err(|e| format!("cmd: {e}"))?;
|
||||
cmd.set_value("", &format!("\"{}\" {} \"%1\"", exe, flag))
|
||||
.map_err(|e| format!("cmdline: {e}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn uninstall_context_menu() {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
for verb in ["MiniCloudPin", "MiniCloudUnpin"] {
|
||||
let _ = hkcu.delete_subkey_all(format!("Software\\Classes\\*\\shell\\{}", verb));
|
||||
}
|
||||
}
|
||||
|
||||
/// Entferne die Shell-Integration wieder.
|
||||
pub fn uninstall() -> Result<(), String> {
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
@@ -121,6 +172,8 @@ pub fn uninstall() -> Result<(), String> {
|
||||
let clsid_path = format!("Software\\Classes\\CLSID\\{}", CLSID_GUID);
|
||||
let _ = hkcu.delete_subkey_all(&clsid_path);
|
||||
|
||||
uninstall_context_menu();
|
||||
|
||||
notify_shell();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1030,8 +1030,15 @@ async fn fetch_remote_entries(
|
||||
.as_array()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let shared = json
|
||||
.get("shared")
|
||||
.and_then(|v| v.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
// Rekursiv flach machen (Struktur parent_id beibehalten).
|
||||
// modified_at akzeptiert beides: das neue "modified_at" oder das
|
||||
// alte "updated_at" als Fallback.
|
||||
fn walk(
|
||||
nodes: &[serde_json::Value],
|
||||
parent: Option<i64>,
|
||||
@@ -1049,6 +1056,7 @@ async fn fetch_remote_entries(
|
||||
let modified_at = n
|
||||
.get("modified_at")
|
||||
.and_then(|x| x.as_str())
|
||||
.or_else(|| n.get("updated_at").and_then(|x| x.as_str()))
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
let checksum = n
|
||||
@@ -1071,11 +1079,55 @@ async fn fetch_remote_entries(
|
||||
}
|
||||
let mut flat = Vec::new();
|
||||
walk(&tree, None, &mut flat);
|
||||
|
||||
// Virtueller Ordner "Geteilt mit mir" nur dann, wenn es geteilte
|
||||
// Dateien gibt. ID -1 ist reserviert dafuer (keine Kollision
|
||||
// mit echten DB-IDs).
|
||||
if !shared.is_empty() {
|
||||
flat.push(cloud_files::RemoteEntry {
|
||||
id: -1,
|
||||
name: "Geteilt mit mir".to_string(),
|
||||
parent_id: None,
|
||||
is_folder: true,
|
||||
size: 0,
|
||||
modified_at: String::new(),
|
||||
checksum: None,
|
||||
});
|
||||
walk(&shared, Some(-1), &mut flat);
|
||||
}
|
||||
|
||||
Ok(flat)
|
||||
}
|
||||
|
||||
/// Short-circuit fuer Shell-Kontextmenue-Aufrufe:
|
||||
/// `minicloud-sync --pin <file>` oder `--unpin <file>` fuehrt die
|
||||
/// Aktion direkt aus und beendet. Kein UI, kein Tray.
|
||||
#[cfg(windows)]
|
||||
fn handle_cli_shortcuts() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 3 {
|
||||
return;
|
||||
}
|
||||
let cmd = args[1].as_str();
|
||||
let path = std::path::PathBuf::from(&args[2]);
|
||||
let result = match cmd {
|
||||
"--pin" => cloud_files::pin_file(&path),
|
||||
"--unpin" => cloud_files::unpin_file(&path),
|
||||
_ => return,
|
||||
};
|
||||
if let Err(e) = result {
|
||||
eprintln!("[cloud_files CLI] {cmd} failed: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn handle_cli_shortcuts() {}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
handle_cli_shortcuts();
|
||||
handle_single_instance();
|
||||
|
||||
tauri::Builder::default()
|
||||
|
||||
Reference in New Issue
Block a user