This commit is contained in:
@@ -0,0 +1,455 @@
|
||||
#![cfg(target_os = "windows")]
|
||||
|
||||
//! Windows 平台适配层 - Cloud Filter API (CFApi) 集成
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::os::windows::io::AsRawHandle;
|
||||
use std::path::Path;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use windows::core::*;
|
||||
use windows::Win32::Foundation::*;
|
||||
use windows::Win32::Storage::CloudFilters::*;
|
||||
use windows::Win32::Storage::FileSystem::*;
|
||||
|
||||
/// 全局回调通道发送端(CFApi 回调 → 异步运行时)
|
||||
/// 使用 Mutex 包装以支持重连时替换
|
||||
static CALLBACK_SENDER: std::sync::Mutex<Option<mpsc::Sender<FetchDataRequest>>> =
|
||||
std::sync::Mutex::new(None);
|
||||
|
||||
/// CFApi FETCH_DATA 回调请求数据
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FetchDataRequest {
|
||||
pub connection_key: i64,
|
||||
pub transfer_key: i64,
|
||||
pub file_identity: Vec<u8>,
|
||||
pub required_offset: i64,
|
||||
pub required_length: i64,
|
||||
}
|
||||
|
||||
pub struct WindowsAdapter {
|
||||
connection_key: Option<CF_CONNECTION_KEY>,
|
||||
callback_rx: Option<mpsc::Receiver<FetchDataRequest>>,
|
||||
}
|
||||
|
||||
/// 占位符创建信息
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlaceholderEntry {
|
||||
pub relative_path: String,
|
||||
pub file_size: u64,
|
||||
pub is_dir: bool,
|
||||
pub file_identity: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for WindowsAdapter {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsAdapter {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
connection_key: None,
|
||||
callback_rx: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 注册同步根目录(先清理残留注册)
|
||||
pub fn register_sync_root(
|
||||
&self,
|
||||
root_path: &Path,
|
||||
provider_name: &str,
|
||||
provider_version: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
unsafe {
|
||||
let sync_root_path = to_pcwstr(root_path);
|
||||
|
||||
// 先尝试注销残留的同步根(忽略未注册的错误)
|
||||
let unregister_result = CfUnregisterSyncRoot(PCWSTR(sync_root_path.as_ptr()));
|
||||
if let Err(e) = unregister_result {
|
||||
tracing::debug!("注销旧同步根(可忽略): {}", e);
|
||||
}
|
||||
|
||||
let provider_name_w = to_pcwstr_str(provider_name);
|
||||
let provider_version_w = to_pcwstr_str(provider_version);
|
||||
|
||||
let provider_name_pcw = PCWSTR(provider_name_w.as_ptr());
|
||||
let provider_version_pcw = PCWSTR(provider_version_w.as_ptr());
|
||||
|
||||
let provider_id = GUID::from_values(
|
||||
0x4E_5F_8C_D7, 0xA2_B1, 0x43_9F, [0x8C, 0x12, 0xD5, 0xE7, 0xF3, 0x21, 0x9A, 0xB4],
|
||||
);
|
||||
|
||||
let root_identity = b"cloudreve-sync-root".to_vec();
|
||||
|
||||
let registration = CF_SYNC_REGISTRATION {
|
||||
StructSize: std::mem::size_of::<CF_SYNC_REGISTRATION>() as u32,
|
||||
ProviderName: provider_name_pcw,
|
||||
ProviderVersion: provider_version_pcw,
|
||||
SyncRootIdentity: root_identity.as_ptr() as *const std::os::raw::c_void,
|
||||
SyncRootIdentityLength: root_identity.len() as u32,
|
||||
FileIdentity: std::ptr::null(),
|
||||
FileIdentityLength: 0,
|
||||
ProviderId: provider_id,
|
||||
};
|
||||
|
||||
let policies = CF_SYNC_POLICIES {
|
||||
StructSize: std::mem::size_of::<CF_SYNC_POLICIES>() as u32,
|
||||
Hydration: CF_HYDRATION_POLICY {
|
||||
Primary: CF_HYDRATION_POLICY_PARTIAL,
|
||||
Modifier: CF_HYDRATION_POLICY_MODIFIER_NONE,
|
||||
},
|
||||
Population: CF_POPULATION_POLICY {
|
||||
Primary: CF_POPULATION_POLICY_ALWAYS_FULL,
|
||||
Modifier: CF_POPULATION_POLICY_MODIFIER_NONE,
|
||||
},
|
||||
InSync: CF_INSYNC_POLICY_NONE,
|
||||
HardLink: CF_HARDLINK_POLICY_NONE,
|
||||
PlaceholderManagement: CF_PLACEHOLDER_MANAGEMENT_POLICY_DEFAULT,
|
||||
};
|
||||
|
||||
CfRegisterSyncRoot(
|
||||
PCWSTR(sync_root_path.as_ptr()),
|
||||
®istration,
|
||||
&policies,
|
||||
CF_REGISTER_FLAG_UPDATE,
|
||||
)?;
|
||||
|
||||
tracing::info!("同步根已注册: {}", root_path.display());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 连接同步根,注册回调(先断开残留连接)
|
||||
pub fn connect_sync_root(&mut self, root_path: &Path) -> anyhow::Result<()> {
|
||||
// 先断开残留的旧连接
|
||||
if let Some(key) = self.connection_key.take() {
|
||||
unsafe {
|
||||
let _ = CfDisconnectSyncRoot(key);
|
||||
}
|
||||
tracing::debug!("已断开旧的同步根连接");
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::channel(256);
|
||||
// 替换全局回调发送端
|
||||
{
|
||||
let mut guard = CALLBACK_SENDER.lock().unwrap();
|
||||
*guard = Some(tx);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let sync_root_path = to_pcwstr(root_path);
|
||||
|
||||
let callbacks = [
|
||||
CF_CALLBACK_REGISTRATION {
|
||||
Type: CF_CALLBACK_TYPE_FETCH_DATA,
|
||||
Callback: Some(cf_fetch_data_callback),
|
||||
},
|
||||
CF_CALLBACK_REGISTRATION {
|
||||
Type: CF_CALLBACK_TYPE_CANCEL_FETCH_DATA,
|
||||
Callback: Some(cf_cancel_fetch_callback),
|
||||
},
|
||||
CF_CALLBACK_REGISTRATION {
|
||||
Type: CF_CALLBACK_TYPE_NONE,
|
||||
Callback: None,
|
||||
},
|
||||
];
|
||||
|
||||
let connection_key = CfConnectSyncRoot(
|
||||
PCWSTR(sync_root_path.as_ptr()),
|
||||
callbacks.as_ptr(),
|
||||
None,
|
||||
CF_CONNECT_FLAG_NONE,
|
||||
)?;
|
||||
|
||||
self.connection_key = Some(connection_key);
|
||||
self.callback_rx = Some(rx);
|
||||
|
||||
tracing::info!("同步根已连接: {}", root_path.display());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 取走回调接收端(供 sync-core 消费)
|
||||
pub fn take_callback_receiver(&mut self) -> Option<mpsc::Receiver<FetchDataRequest>> {
|
||||
self.callback_rx.take()
|
||||
}
|
||||
|
||||
/// 创建占位符文件(批量)
|
||||
pub fn create_placeholders(
|
||||
&self,
|
||||
base_dir: &Path,
|
||||
entries: &[PlaceholderEntry],
|
||||
) -> anyhow::Result<u32> {
|
||||
if entries.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let base_path = to_pcwstr(base_dir);
|
||||
|
||||
let mut placeholders: Vec<CF_PLACEHOLDER_CREATE_INFO> = entries
|
||||
.iter()
|
||||
.map(|entry| {
|
||||
let relative_name_w = to_pcwstr_str(&entry.relative_path);
|
||||
let file_attributes = if entry.is_dir {
|
||||
FILE_ATTRIBUTE_DIRECTORY.0
|
||||
} else {
|
||||
FILE_ATTRIBUTE_NORMAL.0
|
||||
};
|
||||
|
||||
CF_PLACEHOLDER_CREATE_INFO {
|
||||
RelativeFileName: PCWSTR(relative_name_w.as_ptr()),
|
||||
FsMetadata: CF_FS_METADATA {
|
||||
BasicInfo: FILE_BASIC_INFO {
|
||||
CreationTime: 0,
|
||||
LastAccessTime: 0,
|
||||
LastWriteTime: 0,
|
||||
ChangeTime: 0,
|
||||
FileAttributes: file_attributes,
|
||||
},
|
||||
FileSize: entry.file_size as i64,
|
||||
},
|
||||
FileIdentity: entry.file_identity.as_ptr() as *const std::os::raw::c_void,
|
||||
FileIdentityLength: entry.file_identity.len() as u32,
|
||||
Flags: CF_PLACEHOLDER_CREATE_FLAG_NONE,
|
||||
Result: S_OK,
|
||||
CreateUsn: 0,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut entries_processed: u32 = 0;
|
||||
|
||||
CfCreatePlaceholders(
|
||||
PCWSTR(base_path.as_ptr()),
|
||||
&mut placeholders,
|
||||
CF_CREATE_FLAG_NONE,
|
||||
Some(&mut entries_processed),
|
||||
)?;
|
||||
|
||||
tracing::debug!("创建占位符: {} 个", entries_processed);
|
||||
Ok(entries_processed)
|
||||
}
|
||||
}
|
||||
|
||||
/// 创建单个占位符文件
|
||||
pub fn create_single_placeholder(
|
||||
&self,
|
||||
base_dir: &Path,
|
||||
file_name: &str,
|
||||
file_size: u64,
|
||||
file_identity: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let entry = PlaceholderEntry {
|
||||
relative_path: file_name.to_string(),
|
||||
file_size,
|
||||
is_dir: false,
|
||||
file_identity: file_identity.to_vec(),
|
||||
};
|
||||
self.create_placeholders(base_dir, &[entry])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 水合文件
|
||||
pub fn hydrate_placeholder(&self, file_path: &Path) -> anyhow::Result<()> {
|
||||
unsafe {
|
||||
let file = std::fs::File::open(file_path)?;
|
||||
let handle = HANDLE(file.as_raw_handle() as *mut _);
|
||||
|
||||
CfHydratePlaceholder(
|
||||
handle,
|
||||
0,
|
||||
-1i64,
|
||||
CF_HYDRATE_FLAG_NONE,
|
||||
None,
|
||||
)?;
|
||||
|
||||
tracing::debug!("水合完成: {}", file_path.display());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 脱水文件
|
||||
pub fn dehydrate_placeholder(&self, file_path: &Path) -> anyhow::Result<()> {
|
||||
unsafe {
|
||||
let file = std::fs::File::open(file_path)?;
|
||||
let handle = HANDLE(file.as_raw_handle() as *mut _);
|
||||
|
||||
CfDehydratePlaceholder(
|
||||
handle,
|
||||
0,
|
||||
-1i64,
|
||||
CF_DEHYDRATE_FLAG_NONE,
|
||||
None,
|
||||
)?;
|
||||
|
||||
tracing::debug!("脱水完成: {}", file_path.display());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 断开同步根连接
|
||||
pub fn disconnect(&mut self) -> anyhow::Result<()> {
|
||||
if let Some(key) = self.connection_key.take() {
|
||||
unsafe {
|
||||
CfDisconnectSyncRoot(key)?;
|
||||
}
|
||||
tracing::info!("同步根已断开");
|
||||
}
|
||||
self.callback_rx = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 通过 CfExecute 将数据推送回 CFApi 完成水合
|
||||
/// 这是由内核层写入文件,绕过应用锁
|
||||
pub fn fulfill_fetch_data(
|
||||
connection_key: i64,
|
||||
transfer_key: i64,
|
||||
data: &[u8],
|
||||
offset: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
unsafe {
|
||||
let op_info = CF_OPERATION_INFO {
|
||||
StructSize: std::mem::size_of::<CF_OPERATION_INFO>() as u32,
|
||||
Type: CF_OPERATION_TYPE_TRANSFER_DATA,
|
||||
ConnectionKey: CF_CONNECTION_KEY(connection_key),
|
||||
TransferKey: transfer_key,
|
||||
CorrelationVector: std::ptr::null(),
|
||||
SyncStatus: std::ptr::null(),
|
||||
RequestKey: 0,
|
||||
};
|
||||
|
||||
let mut op_params = CF_OPERATION_PARAMETERS {
|
||||
ParamSize: std::mem::size_of::<CF_OPERATION_PARAMETERS>() as u32,
|
||||
Anonymous: CF_OPERATION_PARAMETERS_0 {
|
||||
TransferData: CF_OPERATION_PARAMETERS_0_6 {
|
||||
Flags: CF_OPERATION_TRANSFER_DATA_FLAG_NONE,
|
||||
CompletionStatus: NTSTATUS(0), // STATUS_SUCCESS
|
||||
Buffer: data.as_ptr() as *const core::ffi::c_void,
|
||||
Offset: offset,
|
||||
Length: data.len() as i64,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
CfExecute(&op_info, &mut op_params)?;
|
||||
tracing::debug!("CfExecute TRANSFER_DATA 成功: offset={}, len={}", offset, data.len());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 通过 CfExecute 报告水合失败
|
||||
pub fn reject_fetch_data(
|
||||
connection_key: i64,
|
||||
transfer_key: i64,
|
||||
) -> anyhow::Result<()> {
|
||||
unsafe {
|
||||
let op_info = CF_OPERATION_INFO {
|
||||
StructSize: std::mem::size_of::<CF_OPERATION_INFO>() as u32,
|
||||
Type: CF_OPERATION_TYPE_TRANSFER_DATA,
|
||||
ConnectionKey: CF_CONNECTION_KEY(connection_key),
|
||||
TransferKey: transfer_key,
|
||||
CorrelationVector: std::ptr::null(),
|
||||
SyncStatus: std::ptr::null(),
|
||||
RequestKey: 0,
|
||||
};
|
||||
|
||||
let mut op_params = CF_OPERATION_PARAMETERS {
|
||||
ParamSize: std::mem::size_of::<CF_OPERATION_PARAMETERS>() as u32,
|
||||
Anonymous: CF_OPERATION_PARAMETERS_0 {
|
||||
TransferData: CF_OPERATION_PARAMETERS_0_6 {
|
||||
Flags: CF_OPERATION_TRANSFER_DATA_FLAG_NONE,
|
||||
CompletionStatus: NTSTATUS(0xC000_0001_u32 as i32), // STATUS_UNSUCCESSFUL
|
||||
Buffer: std::ptr::null(),
|
||||
Offset: 0,
|
||||
Length: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
CfExecute(&op_info, &mut op_params)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CFApi 回调:水合请求 — 将请求发送到全局通道
|
||||
unsafe extern "system" fn cf_fetch_data_callback(
|
||||
callback_info: *const CF_CALLBACK_INFO,
|
||||
callback_parameters: *const CF_CALLBACK_PARAMETERS,
|
||||
) {
|
||||
if callback_info.is_null() {
|
||||
return;
|
||||
}
|
||||
let info = &*callback_info;
|
||||
|
||||
// 提取 FileIdentity
|
||||
let file_identity = if info.FileIdentityLength > 0 && !info.FileIdentity.is_null() {
|
||||
std::slice::from_raw_parts(
|
||||
info.FileIdentity as *const u8,
|
||||
info.FileIdentityLength as usize,
|
||||
).to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// 提取请求范围
|
||||
let (required_offset, required_length) = if callback_parameters.is_null() {
|
||||
(0i64, -1i64)
|
||||
} else {
|
||||
let params = &*callback_parameters;
|
||||
let fetch = ¶ms.Anonymous.FetchData;
|
||||
(fetch.RequiredFileOffset, fetch.RequiredLength)
|
||||
};
|
||||
|
||||
let request = FetchDataRequest {
|
||||
connection_key: info.ConnectionKey.0,
|
||||
transfer_key: info.TransferKey,
|
||||
file_identity,
|
||||
required_offset,
|
||||
required_length,
|
||||
};
|
||||
|
||||
tracing::debug!(
|
||||
"CFApi 水合请求: transfer_key={}, identity_len={}, offset={}, length={}",
|
||||
info.TransferKey,
|
||||
info.FileIdentityLength,
|
||||
required_offset,
|
||||
required_length,
|
||||
);
|
||||
|
||||
if let Ok(guard) = CALLBACK_SENDER.lock() {
|
||||
if let Some(ref sender) = *guard {
|
||||
let _ = sender.blocking_send(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CFApi 回调:取消水合
|
||||
unsafe extern "system" fn cf_cancel_fetch_callback(
|
||||
callback_info: *const CF_CALLBACK_INFO,
|
||||
_callback_parameters: *const CF_CALLBACK_PARAMETERS,
|
||||
) {
|
||||
if callback_info.is_null() {
|
||||
return;
|
||||
}
|
||||
tracing::debug!("CFApi 取消水合请求: transfer_key={}", (*callback_info).TransferKey);
|
||||
}
|
||||
|
||||
/// Path → wide null-terminated Vec<u16>
|
||||
fn to_pcwstr(path: &Path) -> Vec<u16> {
|
||||
let mut wide: Vec<u16> = OsStr::new(path).encode_wide().collect();
|
||||
wide.push(0);
|
||||
wide
|
||||
}
|
||||
|
||||
/// &str → wide null-terminated Vec<u16>
|
||||
fn to_pcwstr_str(s: &str) -> Vec<u16> {
|
||||
let mut wide: Vec<u16> = s.encode_utf16().collect();
|
||||
wide.push(0);
|
||||
wide
|
||||
}
|
||||
Reference in New Issue
Block a user