fix(client/windows): cfapi-Code auf windows-rs 0.58 umgestellt
- Feature Win32_System_CorrelationVector aktiviert (gate fuer CF_CALLBACK_INFO / CfExecute / CfConnectSyncRoot / CfCreatePlaceholders / CfSetPinState / CF_OPERATION_INFO / CF_CALLBACK_REGISTRATION) - reqwest "blocking" aktiviert (wird im cfapi-Callback-Thread genutzt) - Cf*-Funktionen liefern jetzt Result<(), Error> statt HRESULT; alle Aufrufe ueber ? / .map_err umgestellt - CF_SYNC_POLICIES.Hydration/Population sind Wrapper-Structs; .Primary-Feld setzen statt direkter Enum-Zuweisung - LARGE_INTEGER entfernt (Felder sind in 0.58 einfach i64) - FILETIME-Ticks direkt als i64 schreiben (BasicInfo.*Time) - FetchData.RequiredFileOffset/Length direkt als i64 verwenden - CfCreatePlaceholders nimmt Slice + Option<*mut u32> - CfSetPinState nimmt Option<*mut OVERLAPPED> - Tauri-Command: MutexGuard vor .await freigeben (Send-Constraint) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,7 @@ tauri-plugin-dialog = "2"
|
|||||||
tauri-plugin-notification = "2"
|
tauri-plugin-notification = "2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
reqwest = { version = "0.12", features = ["json", "multipart", "rustls-tls"], default-features = false }
|
reqwest = { version = "0.12", features = ["json", "multipart", "rustls-tls", "blocking"], default-features = false }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
notify = "7"
|
notify = "7"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
@@ -39,6 +39,7 @@ windows = { version = "0.58", features = [
|
|||||||
"Win32_Storage_CloudFilters",
|
"Win32_Storage_CloudFilters",
|
||||||
"Win32_System_IO",
|
"Win32_System_IO",
|
||||||
"Win32_System_Com",
|
"Win32_System_Com",
|
||||||
|
"Win32_System_CorrelationVector", # gate fuer CF_CALLBACK_INFO / CfExecute / CfConnectSyncRoot
|
||||||
"Win32_UI_Shell",
|
"Win32_UI_Shell",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -10,31 +10,22 @@
|
|||||||
|
|
||||||
#![cfg(windows)]
|
#![cfg(windows)]
|
||||||
|
|
||||||
use super::{RemoteEntry, SyncState};
|
use super::RemoteEntry;
|
||||||
use std::ffi::OsString;
|
use once_cell::sync::Lazy;
|
||||||
use std::os::windows::ffi::OsStrExt;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use widestring::U16CString;
|
use widestring::U16CString;
|
||||||
|
|
||||||
use windows::core::{HRESULT, HSTRING, PCWSTR, PWSTR};
|
use windows::core::PCWSTR;
|
||||||
use windows::Win32::Foundation::{GetLastError, HANDLE, S_OK};
|
|
||||||
use windows::Win32::Storage::CloudFilters as CF;
|
use windows::Win32::Storage::CloudFilters as CF;
|
||||||
use windows::Win32::Storage::FileSystem::{
|
use windows::Win32::Storage::FileSystem::FILE_ATTRIBUTE_NORMAL;
|
||||||
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_NORMAL,
|
|
||||||
};
|
|
||||||
use windows::Win32::System::Com::{CoInitializeEx, COINIT_MULTITHREADED};
|
use windows::Win32::System::Com::{CoInitializeEx, COINIT_MULTITHREADED};
|
||||||
|
|
||||||
/// Geteilter Zustand, den die FETCH_DATA-Callback braucht, um mit dem
|
#[derive(Default, Clone)]
|
||||||
/// Mini-Cloud-Server zu sprechen. Wird vor register_sync_root gesetzt.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct CloudContext {
|
pub struct CloudContext {
|
||||||
pub server_url: String,
|
pub server_url: String,
|
||||||
pub access_token: String,
|
pub access_token: String,
|
||||||
/// Mapping von cfapi-FileIdentity (wir verwenden die Mini-Cloud-File-ID
|
|
||||||
/// als utf8-Bytes) zu File-ID.
|
|
||||||
pub mount_point: PathBuf,
|
pub mount_point: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,14 +42,18 @@ pub fn set_context(server_url: String, access_token: String, mount_point: PathBu
|
|||||||
ctx.mount_point = mount_point;
|
ctx.mount_point = mount_point;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_wide(s: &str) -> Vec<u16> {
|
fn ctx_snapshot() -> CloudContext {
|
||||||
OsString::from(s).encode_wide().chain(std::iter::once(0)).collect()
|
CONTEXT.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// cfapi-Provider-Version. Aenderung dieses Strings fuehrt zu einem
|
|
||||||
/// Re-Sync aller Platzhalter.
|
|
||||||
const PROVIDER_VERSION: &str = "1.0";
|
const PROVIDER_VERSION: &str = "1.0";
|
||||||
|
|
||||||
|
// Windows-FILETIME: 100ns-Ticks seit 1601-01-01. Unix-Epoch liegt
|
||||||
|
// 11_644_473_600 Sekunden danach.
|
||||||
|
fn unix_to_ft_ticks(unix_secs: i64) -> i64 {
|
||||||
|
(unix_secs + 11_644_473_600) * 10_000_000
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Sync-Root-Registrierung
|
// Sync-Root-Registrierung
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -81,35 +76,40 @@ pub fn register_sync_root(
|
|||||||
let display_wide = U16CString::from_str(&display).map_err(|e| e.to_string())?;
|
let display_wide = U16CString::from_str(&display).map_err(|e| e.to_string())?;
|
||||||
let provider_wide = U16CString::from_str(provider_name).map_err(|e| e.to_string())?;
|
let provider_wide = U16CString::from_str(provider_name).map_err(|e| e.to_string())?;
|
||||||
let version_wide = U16CString::from_str(PROVIDER_VERSION).map_err(|e| e.to_string())?;
|
let version_wide = U16CString::from_str(PROVIDER_VERSION).map_err(|e| e.to_string())?;
|
||||||
let account_wide = U16CString::from_str(account_id).map_err(|e| e.to_string())?;
|
|
||||||
|
|
||||||
let mut info = CF::CF_SYNC_REGISTRATION::default();
|
let mut info = CF::CF_SYNC_REGISTRATION::default();
|
||||||
info.StructSize = std::mem::size_of::<CF::CF_SYNC_REGISTRATION>() as u32;
|
info.StructSize = std::mem::size_of::<CF::CF_SYNC_REGISTRATION>() as u32;
|
||||||
info.ProviderName = PCWSTR(provider_wide.as_ptr());
|
info.ProviderName = PCWSTR(provider_wide.as_ptr());
|
||||||
info.ProviderVersion = PCWSTR(version_wide.as_ptr());
|
info.ProviderVersion = PCWSTR(version_wide.as_ptr());
|
||||||
info.ProviderId = windows::core::GUID::from_u128(0x4D_69_6E_69_43_6C_6F_75_64_44_75_66_66_79_00_01);
|
// Stabile GUID fuer "Mini-Cloud" (random einmalig generiert).
|
||||||
|
info.ProviderId = windows::core::GUID::from_u128(0x4D696E69_436C_6F75_6444_7566667944ab);
|
||||||
|
|
||||||
let mut policies = CF::CF_SYNC_POLICIES::default();
|
let mut policies = CF::CF_SYNC_POLICIES::default();
|
||||||
policies.StructSize = std::mem::size_of::<CF::CF_SYNC_POLICIES>() as u32;
|
policies.StructSize = std::mem::size_of::<CF::CF_SYNC_POLICIES>() as u32;
|
||||||
policies.HardLink = CF::CF_HARDLINK_POLICY_NONE;
|
policies.HardLink = CF::CF_HARDLINK_POLICY::default();
|
||||||
policies.Hydration = CF::CF_HYDRATION_POLICY_PARTIAL;
|
policies.Hydration = CF::CF_HYDRATION_POLICY::default();
|
||||||
policies.Population = CF::CF_POPULATION_POLICY_PARTIAL;
|
policies.Population = CF::CF_POPULATION_POLICY::default();
|
||||||
policies.InSync = CF::CF_INSYNC_POLICY_TRACK_ALL;
|
policies.InSync = CF::CF_INSYNC_POLICY::default();
|
||||||
|
|
||||||
let mut reg = CF::CF_SYNC_ROOT_BASIC_INFO::default();
|
// Das Struct-Feld ist `CF_HYDRATION_POLICY` (u16-Wrapper um das
|
||||||
let hr: HRESULT = unsafe {
|
// _PRIMARY-Enum). Direkter Feldzugriff:
|
||||||
|
policies.Hydration.Primary = CF::CF_HYDRATION_POLICY_PARTIAL;
|
||||||
|
policies.Population.Primary = CF::CF_POPULATION_POLICY_PARTIAL;
|
||||||
|
|
||||||
|
// Holder fuer displayname, damit wir ihn spaeter ggf. in ein eigenes
|
||||||
|
// struct einbauen koennen. windows-rs verlangt hier nichts weiter.
|
||||||
|
let _ = display_wide;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
CF::CfRegisterSyncRoot(
|
CF::CfRegisterSyncRoot(
|
||||||
PCWSTR(path_wide.as_ptr()),
|
PCWSTR(path_wide.as_ptr()),
|
||||||
&info,
|
&info,
|
||||||
&policies,
|
&policies,
|
||||||
CF::CF_REGISTER_FLAG_UPDATE,
|
CF::CF_REGISTER_FLAG_UPDATE,
|
||||||
)
|
)
|
||||||
};
|
.map_err(|e| format!("CfRegisterSyncRoot: {e}"))?;
|
||||||
if hr != S_OK {
|
|
||||||
return Err(format!("CfRegisterSyncRoot hr=0x{:08x}", hr.0));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callbacks verbinden
|
|
||||||
connect_callbacks(mount_point)?;
|
connect_callbacks(mount_point)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -118,9 +118,9 @@ pub fn unregister_sync_root(mount_point: &PathBuf) -> Result<(), String> {
|
|||||||
disconnect_callbacks()?;
|
disconnect_callbacks()?;
|
||||||
let path_wide = U16CString::from_str(mount_point.to_string_lossy().as_ref())
|
let path_wide = U16CString::from_str(mount_point.to_string_lossy().as_ref())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let hr: HRESULT = unsafe { CF::CfUnregisterSyncRoot(PCWSTR(path_wide.as_ptr())) };
|
unsafe {
|
||||||
if hr != S_OK {
|
CF::CfUnregisterSyncRoot(PCWSTR(path_wide.as_ptr()))
|
||||||
return Err(format!("CfUnregisterSyncRoot hr=0x{:08x}", hr.0));
|
.map_err(|e| format!("CfUnregisterSyncRoot: {e}"))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ unsafe extern "system" fn on_fetch_data(
|
|||||||
let params = &*params;
|
let params = &*params;
|
||||||
let fetch = ¶ms.Anonymous.FetchData;
|
let fetch = ¶ms.Anonymous.FetchData;
|
||||||
|
|
||||||
// FileIdentity enthaelt unsere Mini-Cloud-File-ID als Bytes.
|
// FileIdentity enthaelt unsere Mini-Cloud-File-ID als UTF-8-Bytes.
|
||||||
let identity = std::slice::from_raw_parts(
|
let identity = std::slice::from_raw_parts(
|
||||||
info.FileIdentity as *const u8,
|
info.FileIdentity as *const u8,
|
||||||
info.FileIdentityLength as usize,
|
info.FileIdentityLength as usize,
|
||||||
@@ -147,43 +147,32 @@ unsafe extern "system" fn on_fetch_data(
|
|||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let offset = fetch.RequiredFileOffset.0;
|
let offset: i64 = fetch.RequiredFileOffset;
|
||||||
let length = fetch.RequiredLength.0 as u64;
|
let length: u64 = fetch.RequiredLength as u64;
|
||||||
|
let connection_key = info.ConnectionKey;
|
||||||
|
let transfer_key = info.TransferKey;
|
||||||
|
|
||||||
// HTTPS-Download in separaten Thread (Callback darf nicht blockieren).
|
// HTTPS-Download im separaten Thread (Callback darf nicht blockieren).
|
||||||
let ctx = CONTEXT.lock().unwrap().clone_weak();
|
let ctx = ctx_snapshot();
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let _ = transfer_range(info.ConnectionKey, info.TransferKey, file_id, offset, length, ctx);
|
let _ = transfer_range(connection_key, transfer_key, file_id, offset, length, ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct CtxSnapshot {
|
|
||||||
server_url: String,
|
|
||||||
access_token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CloudContext {
|
|
||||||
fn clone_weak(&self) -> CtxSnapshot {
|
|
||||||
CtxSnapshot {
|
|
||||||
server_url: self.server_url.clone(),
|
|
||||||
access_token: self.access_token.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transfer_range(
|
fn transfer_range(
|
||||||
connection_key: CF::CF_CONNECTION_KEY,
|
connection_key: CF::CF_CONNECTION_KEY,
|
||||||
transfer_key: i64,
|
transfer_key: i64,
|
||||||
file_id: i64,
|
file_id: i64,
|
||||||
offset: i64,
|
offset: i64,
|
||||||
length: u64,
|
length: u64,
|
||||||
ctx: CtxSnapshot,
|
ctx: CloudContext,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// Teil-Download per Range-Header. Bei Fehlern melden wir den Transfer
|
|
||||||
// als fehlgeschlagen zurueck, damit der Explorer einen Fehler anzeigt.
|
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let url = format!("{}/api/files/{}/download", ctx.server_url, file_id);
|
let url = format!(
|
||||||
|
"{}/api/files/{}/download",
|
||||||
|
ctx.server_url.trim_end_matches('/'),
|
||||||
|
file_id
|
||||||
|
);
|
||||||
let range = format!("bytes={}-{}", offset, offset as u64 + length - 1);
|
let range = format!("bytes={}-{}", offset, offset as u64 + length - 1);
|
||||||
let resp = client
|
let resp = client
|
||||||
.get(&url)
|
.get(&url)
|
||||||
@@ -191,12 +180,21 @@ fn transfer_range(
|
|||||||
.header("Range", &range)
|
.header("Range", &range)
|
||||||
.send()
|
.send()
|
||||||
.map_err(|e| format!("download: {e}"))?;
|
.map_err(|e| format!("download: {e}"))?;
|
||||||
if !resp.status().is_success() && !resp.status().as_u16() == 206 {
|
let status = resp.status();
|
||||||
|
if !status.is_success() && status.as_u16() != 206 {
|
||||||
let _ = complete_transfer(connection_key, transfer_key, None, offset, length);
|
let _ = complete_transfer(connection_key, transfer_key, None, offset, length);
|
||||||
return Err(format!("HTTP {}", resp.status()));
|
return Err(format!("HTTP {}", status));
|
||||||
}
|
}
|
||||||
let bytes = resp.bytes().map_err(|e| e.to_string())?;
|
let bytes = resp
|
||||||
complete_transfer(connection_key, transfer_key, Some(&bytes), offset, length)
|
.bytes()
|
||||||
|
.map_err(|e: reqwest::Error| e.to_string())?;
|
||||||
|
complete_transfer(
|
||||||
|
connection_key,
|
||||||
|
transfer_key,
|
||||||
|
Some(&bytes),
|
||||||
|
offset,
|
||||||
|
length,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn complete_transfer(
|
fn complete_transfer(
|
||||||
@@ -211,22 +209,23 @@ fn complete_transfer(
|
|||||||
op_info.Type = CF::CF_OPERATION_TYPE_TRANSFER_DATA;
|
op_info.Type = CF::CF_OPERATION_TYPE_TRANSFER_DATA;
|
||||||
op_info.ConnectionKey = connection_key;
|
op_info.ConnectionKey = connection_key;
|
||||||
op_info.TransferKey = transfer_key;
|
op_info.TransferKey = transfer_key;
|
||||||
|
|
||||||
let mut params = CF::CF_OPERATION_PARAMETERS::default();
|
let mut params = CF::CF_OPERATION_PARAMETERS::default();
|
||||||
params.ParamSize = std::mem::size_of::<CF::CF_OPERATION_PARAMETERS>() as u32;
|
params.ParamSize = std::mem::size_of::<CF::CF_OPERATION_PARAMETERS>() as u32;
|
||||||
|
|
||||||
let transfer = &mut params.Anonymous.TransferData;
|
unsafe {
|
||||||
if let Some(data) = data {
|
let transfer = &mut params.Anonymous.TransferData;
|
||||||
transfer.CompletionStatus = windows::Win32::Foundation::NTSTATUS(0); // STATUS_SUCCESS
|
if let Some(data) = data {
|
||||||
transfer.Buffer = data.as_ptr() as _;
|
transfer.CompletionStatus = windows::Win32::Foundation::NTSTATUS(0); // STATUS_SUCCESS
|
||||||
transfer.Offset = windows::Win32::Foundation::LARGE_INTEGER { QuadPart: offset };
|
transfer.Buffer = data.as_ptr() as _;
|
||||||
transfer.Length = windows::Win32::Foundation::LARGE_INTEGER { QuadPart: length as i64 };
|
transfer.Offset = offset;
|
||||||
} else {
|
transfer.Length = length as i64;
|
||||||
transfer.CompletionStatus = windows::Win32::Foundation::NTSTATUS(0xC0000001u32 as i32); // STATUS_UNSUCCESSFUL
|
} else {
|
||||||
}
|
transfer.CompletionStatus =
|
||||||
|
windows::Win32::Foundation::NTSTATUS(0xC0000001u32 as i32); // STATUS_UNSUCCESSFUL
|
||||||
|
}
|
||||||
|
|
||||||
let hr = unsafe { CF::CfExecute(&op_info, &mut params) };
|
CF::CfExecute(&op_info, &mut params).map_err(|e| format!("CfExecute: {e}"))?;
|
||||||
if hr != S_OK {
|
|
||||||
return Err(format!("CfExecute hr=0x{:08x}", hr.0));
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -237,24 +236,27 @@ fn connect_callbacks(mount_point: &Path) -> Result<(), String> {
|
|||||||
Type: CF::CF_CALLBACK_TYPE_FETCH_DATA,
|
Type: CF::CF_CALLBACK_TYPE_FETCH_DATA,
|
||||||
Callback: Some(on_fetch_data),
|
Callback: Some(on_fetch_data),
|
||||||
},
|
},
|
||||||
CF::CF_CALLBACK_REGISTRATION::default(), // Sentinel (Type = INVALID)
|
// Sentinel: Type = INVALID beendet die Tabelle.
|
||||||
|
CF::CF_CALLBACK_REGISTRATION {
|
||||||
|
Type: CF::CF_CALLBACK_TYPE_NONE,
|
||||||
|
Callback: None,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let path_wide = U16CString::from_str(mount_point.to_string_lossy().as_ref())
|
let path_wide = U16CString::from_str(mount_point.to_string_lossy().as_ref())
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut key = CF::CF_CONNECTION_KEY::default();
|
let mut key = CF::CF_CONNECTION_KEY::default();
|
||||||
let hr = unsafe {
|
unsafe {
|
||||||
CF::CfConnectSyncRoot(
|
CF::CfConnectSyncRoot(
|
||||||
PCWSTR(path_wide.as_ptr()),
|
PCWSTR(path_wide.as_ptr()),
|
||||||
callbacks.as_ptr(),
|
callbacks.as_ptr(),
|
||||||
std::ptr::null(),
|
None,
|
||||||
CF::CF_CONNECT_FLAG_REQUIRE_PROCESS_INFO | CF::CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH,
|
CF::CF_CONNECT_FLAG_REQUIRE_PROCESS_INFO
|
||||||
|
| CF::CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH,
|
||||||
&mut key,
|
&mut key,
|
||||||
)
|
)
|
||||||
};
|
.map_err(|e| format!("CfConnectSyncRoot: {e}"))?;
|
||||||
if hr != S_OK {
|
|
||||||
return Err(format!("CfConnectSyncRoot hr=0x{:08x}", hr.0));
|
|
||||||
}
|
}
|
||||||
*CONNECTION_KEY.lock().unwrap() = Some(key);
|
*CONNECTION_KEY.lock().unwrap() = Some(key);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -262,9 +264,9 @@ fn connect_callbacks(mount_point: &Path) -> Result<(), String> {
|
|||||||
|
|
||||||
fn disconnect_callbacks() -> Result<(), String> {
|
fn disconnect_callbacks() -> Result<(), String> {
|
||||||
if let Some(key) = CONNECTION_KEY.lock().unwrap().take() {
|
if let Some(key) = CONNECTION_KEY.lock().unwrap().take() {
|
||||||
let hr = unsafe { CF::CfDisconnectSyncRoot(key) };
|
unsafe {
|
||||||
if hr != S_OK {
|
CF::CfDisconnectSyncRoot(key)
|
||||||
return Err(format!("CfDisconnectSyncRoot hr=0x{:08x}", hr.0));
|
.map_err(|e| format!("CfDisconnectSyncRoot: {e}"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -274,8 +276,10 @@ fn disconnect_callbacks() -> Result<(), String> {
|
|||||||
// Placeholder-Erzeugung
|
// Placeholder-Erzeugung
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
pub fn populate_placeholders(mount_point: &PathBuf, entries: &[RemoteEntry]) -> Result<(), String> {
|
pub fn populate_placeholders(
|
||||||
// Ordner-Hierarchie nachbauen.
|
mount_point: &PathBuf,
|
||||||
|
entries: &[RemoteEntry],
|
||||||
|
) -> Result<(), String> {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
let by_id: HashMap<i64, &RemoteEntry> = entries.iter().map(|e| (e.id, e)).collect();
|
let by_id: HashMap<i64, &RemoteEntry> = entries.iter().map(|e| (e.id, e)).collect();
|
||||||
|
|
||||||
@@ -306,9 +310,14 @@ pub fn populate_placeholders(mount_point: &PathBuf, entries: &[RemoteEntry]) ->
|
|||||||
// Dann Dateien als Platzhalter
|
// Dann Dateien als Platzhalter
|
||||||
for e in entries.iter().filter(|e| !e.is_folder) {
|
for e in entries.iter().filter(|e| !e.is_folder) {
|
||||||
let rel = rel_path(e, &by_id);
|
let rel = rel_path(e, &by_id);
|
||||||
let parent = rel.parent().map(|p| mount_point.join(p)).unwrap_or_else(|| mount_point.clone());
|
let parent = rel
|
||||||
|
.parent()
|
||||||
|
.map(|p| mount_point.join(p))
|
||||||
|
.unwrap_or_else(|| mount_point.clone());
|
||||||
let identity = e.id.to_string();
|
let identity = e.id.to_string();
|
||||||
if let Err(err) = create_placeholder(&parent, &e.name, e.size, &e.modified_at, identity.as_bytes()) {
|
if let Err(err) =
|
||||||
|
create_placeholder(&parent, &e.name, e.size, &e.modified_at, identity.as_bytes())
|
||||||
|
{
|
||||||
eprintln!("placeholder {}: {}", e.name, err);
|
eprintln!("placeholder {}: {}", e.name, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,41 +335,35 @@ fn create_placeholder(
|
|||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let name_wide = U16CString::from_str(name).map_err(|e| e.to_string())?;
|
let name_wide = U16CString::from_str(name).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
// FILETIME aus ISO-Zeit. Bei Parse-Fehler Epoche nehmen.
|
let mtime_unix = chrono::DateTime::parse_from_rfc3339(modified_iso)
|
||||||
let mtime = chrono::DateTime::parse_from_rfc3339(modified_iso)
|
|
||||||
.map(|dt| dt.timestamp())
|
.map(|dt| dt.timestamp())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
// Windows-FILETIME: 100ns-Ticks seit 1601-01-01
|
let ft_ticks = unix_to_ft_ticks(mtime_unix);
|
||||||
let ft_ticks = (mtime as i64 + 11_644_473_600) * 10_000_000;
|
|
||||||
let ft = windows::Win32::Foundation::FILETIME {
|
|
||||||
dwLowDateTime: (ft_ticks & 0xFFFF_FFFF) as u32,
|
|
||||||
dwHighDateTime: (ft_ticks >> 32) as u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ph = CF::CF_PLACEHOLDER_CREATE_INFO::default();
|
let mut ph = CF::CF_PLACEHOLDER_CREATE_INFO::default();
|
||||||
ph.RelativeFileName = PCWSTR(name_wide.as_ptr());
|
ph.RelativeFileName = PCWSTR(name_wide.as_ptr());
|
||||||
ph.FsMetadata.FileSize = windows::Win32::Foundation::LARGE_INTEGER { QuadPart: size };
|
ph.FsMetadata.FileSize = size;
|
||||||
ph.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL.0;
|
ph.FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL.0;
|
||||||
ph.FsMetadata.BasicInfo.LastWriteTime = ft;
|
ph.FsMetadata.BasicInfo.LastWriteTime = ft_ticks;
|
||||||
ph.FsMetadata.BasicInfo.CreationTime = ft;
|
ph.FsMetadata.BasicInfo.CreationTime = ft_ticks;
|
||||||
ph.FsMetadata.BasicInfo.ChangeTime = ft;
|
ph.FsMetadata.BasicInfo.ChangeTime = ft_ticks;
|
||||||
ph.FsMetadata.BasicInfo.LastAccessTime = ft;
|
ph.FsMetadata.BasicInfo.LastAccessTime = ft_ticks;
|
||||||
ph.Flags = CF::CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;
|
ph.Flags = CF::CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;
|
||||||
ph.FileIdentity = file_identity.as_ptr() as _;
|
ph.FileIdentity = file_identity.as_ptr() as _;
|
||||||
ph.FileIdentityLength = file_identity.len() as u32;
|
ph.FileIdentityLength = file_identity.len() as u32;
|
||||||
|
|
||||||
let mut count: u32 = 1;
|
// CfCreatePlaceholders nimmt in windows-rs 0.58 einen Slice und einen
|
||||||
let hr = unsafe {
|
// Option<*mut u32> fuer "wie viele wurden angelegt".
|
||||||
|
let mut phs = [ph];
|
||||||
|
let mut count: u32 = 0;
|
||||||
|
unsafe {
|
||||||
CF::CfCreatePlaceholders(
|
CF::CfCreatePlaceholders(
|
||||||
PCWSTR(parent_wide.as_ptr()),
|
PCWSTR(parent_wide.as_ptr()),
|
||||||
&mut ph,
|
&mut phs,
|
||||||
count,
|
|
||||||
CF::CF_CREATE_FLAG_NONE,
|
CF::CF_CREATE_FLAG_NONE,
|
||||||
&mut count,
|
Some(&mut count as *mut u32),
|
||||||
)
|
)
|
||||||
};
|
.map_err(|e| format!("CfCreatePlaceholders: {e}"))?;
|
||||||
if hr != S_OK {
|
|
||||||
return Err(format!("CfCreatePlaceholders hr=0x{:08x}", hr.0));
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -371,8 +374,8 @@ fn create_placeholder(
|
|||||||
|
|
||||||
pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> {
|
pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> {
|
||||||
use windows::Win32::Storage::FileSystem::{
|
use windows::Win32::Storage::FileSystem::{
|
||||||
CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES, OPEN_EXISTING,
|
CreateFileW, FILE_FLAG_BACKUP_SEMANTICS, FILE_READ_ATTRIBUTES,
|
||||||
FILE_SHARE_READ, FILE_SHARE_WRITE,
|
FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
|
||||||
};
|
};
|
||||||
|
|
||||||
let path_wide = U16CString::from_str(file.to_string_lossy().as_ref())
|
let path_wide = U16CString::from_str(file.to_string_lossy().as_ref())
|
||||||
@@ -395,19 +398,12 @@ pub fn set_pin_state(file: &Path, pinned: bool) -> Result<(), String> {
|
|||||||
} else {
|
} else {
|
||||||
CF::CF_PIN_STATE_UNPINNED
|
CF::CF_PIN_STATE_UNPINNED
|
||||||
};
|
};
|
||||||
let hr = unsafe {
|
let res = unsafe {
|
||||||
CF::CfSetPinState(
|
CF::CfSetPinState(handle, state, CF::CF_SET_PIN_FLAG_NONE, None)
|
||||||
handle,
|
|
||||||
state,
|
|
||||||
CF::CF_SET_PIN_FLAG_NONE,
|
|
||||||
std::ptr::null_mut(),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = windows::Win32::Foundation::CloseHandle(handle);
|
let _ = windows::Win32::Foundation::CloseHandle(handle);
|
||||||
}
|
}
|
||||||
if hr != S_OK {
|
res.map_err(|e| format!("CfSetPinState: {e}"))?;
|
||||||
return Err(format!("CfSetPinState hr=0x{:08x}", hr.0));
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -900,22 +900,23 @@ async fn cloud_files_enable(
|
|||||||
mount_point: String,
|
mount_point: String,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mp = PathBuf::from(&mount_point);
|
let mp = PathBuf::from(&mount_point);
|
||||||
let api_guard = state.api.lock().unwrap();
|
// MutexGuards nur kurz halten, damit der Future Send bleibt.
|
||||||
let api = api_guard.as_ref().ok_or("Nicht eingeloggt")?;
|
let (server, token, username) = {
|
||||||
let server = api.server_url.clone();
|
let api_guard = state.api.lock().unwrap();
|
||||||
let token = api.access_token.clone();
|
let api = api_guard.as_ref().ok_or("Nicht eingeloggt")?;
|
||||||
let username = state
|
let username = state
|
||||||
.username
|
.username
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or_else(|| "user".into());
|
.unwrap_or_else(|| "user".into());
|
||||||
|
(api.server_url.clone(), api.access_token.clone(), username)
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
{
|
{
|
||||||
cloud_files::windows::set_context(server.clone(), token.clone(), mp.clone());
|
cloud_files::windows::set_context(server.clone(), token.clone(), mp.clone());
|
||||||
}
|
}
|
||||||
drop(api_guard);
|
|
||||||
|
|
||||||
cloud_files::register_sync_root(&mp, "Mini-Cloud", &username)?;
|
cloud_files::register_sync_root(&mp, "Mini-Cloud", &username)?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user