1.3.21.3.2
Android APK Release / Build Android APK (push) Successful in 55m29s

This commit is contained in:
2026-05-26 16:32:18 +08:00
parent 546cef5ba6
commit 17673b2862
89 changed files with 24098 additions and 272 deletions
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "sync-windows"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["lib"]
[dependencies]
windows = { version = "0.58", features = [
"Win32_Foundation",
"Win32_Storage_CloudFilters",
"Win32_Storage_FileSystem",
"Win32_System_Registry",
"Win32_System_CorrelationVector",
"Win32_System_IO",
] }
thiserror = { workspace = true }
tracing = "0.1"
anyhow = { workspace = true }
tokio = { workspace = true }
async-trait = "0.1"
serde = { workspace = true }
serde_json = { workspace = true }
uuid = { workspace = true }
[features]
default = ["cfapi"]
cfapi = []
+455
View File
@@ -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()),
&registration,
&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 = &params.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
}