patch: simplify FUSE

Signed-off-by: ClSlaid <cailue@bupt.edu.cn>
This commit is contained in:
ClSlaid 2023-10-16 00:51:12 +08:00
parent 796e2ec825
commit 9adda25e00
No known key found for this signature in database
GPG Key ID: E0A5F564C51C056E
6 changed files with 387 additions and 598 deletions

1
Cargo.lock generated
View File

@ -941,7 +941,6 @@ dependencies = [
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"rand 0.8.5", "rand 0.8.5",
"rayon",
"serde 1.0.163", "serde 1.0.163",
"serde_derive", "serde_derive",
"thiserror", "thiserror",

View File

@ -23,7 +23,6 @@ x11rb = {version = "0.12", features = ["all-extensions"]}
rand = {version = "0.8"} rand = {version = "0.8"}
fuser = {version = "0.13"} fuser = {version = "0.13"}
libc = {version = "0.2"} libc = {version = "0.2"}
rayon = {version = "1.7"}
dashmap = "5.5" dashmap = "5.5"
percent-encoding = "2.3" percent-encoding = "2.3"
utf16string = "0.2" utf16string = "0.2"

View File

@ -24,22 +24,21 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{
atomic::{AtomicBool, AtomicU64, Ordering}, atomic::{AtomicBool, AtomicU64, Ordering},
mpsc::{Receiver, Sender},
Arc, Arc,
}, },
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
use dashmap::DashMap; use fuser::{ReplyDirectory, FUSE_ROOT_ID};
use fuser::{ReplyDirectory, Request, FUSE_ROOT_ID};
use hbb_common::{ use hbb_common::{
bytes::{Buf, Bytes}, bytes::{Buf, Bytes},
log, log,
}; };
use parking_lot::{Condvar, Mutex, RwLock}; use parking_lot::{Condvar, Mutex};
use rayon::prelude::*;
use utf16string::WStr; use utf16string::WStr;
use crate::{ClipboardFile, CliprdrError}; use crate::{send_data, ClipboardFile, CliprdrError};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use super::LDAP_EPOCH_DELTA; use super::LDAP_EPOCH_DELTA;
@ -54,175 +53,71 @@ const PERM_READ: u16 = 0o444;
/// max length of file name /// max length of file name
const MAX_NAME_LEN: usize = 255; const MAX_NAME_LEN: usize = 255;
// fuse server state /// fuse client
#[derive(Debug, Clone, Copy, PartialEq, Eq)] /// this is a proxy to the fuse server
enum Status {
// active and ready for all incoming requests
Active,
// marking and waiting for all FDs to be closed
// only serve read requests
Gc,
// gc completes
// serve no requests
GcComplete,
// fetching new files from remote
// serve no requests
// this state is to make sure only one fetching is running
Fetching,
// fetched, building new FS
Building,
}
#[derive(Debug, Default)]
struct PendingRequest {
content: Mutex<Option<ClipboardFile>>,
cvar: Condvar,
}
impl PendingRequest {
pub fn new() -> Self {
Self {
content: Mutex::new(None),
cvar: Condvar::new(),
}
}
pub fn recv_timeout(&self, timeout: Duration) -> Result<ClipboardFile, std::io::Error> {
let mut guard = self.content.lock();
let res = self.cvar.wait_for(&mut guard, timeout);
if res.timed_out() {
Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"))
} else {
let content = guard.take();
match content {
Some(content) => Ok(content),
None => Err(std::io::Error::new(std::io::ErrorKind::Other, "no content")),
}
}
}
pub fn set(&self, content: ClipboardFile) {
let mut guard = self.content.lock();
let _ = guard.insert(content);
self.cvar.notify_all();
}
}
/// clipboard message dispatcher
#[derive(Debug, Default)]
struct CliprdrTxnDispatcher {
txn_handler: DashMap<(i32, Option<i32>), Arc<PendingRequest>>,
}
impl CliprdrTxnDispatcher {
pub fn send(&self, conn_id: i32, request: ClipboardFile) -> Arc<PendingRequest> {
let stream_id = match &request {
ClipboardFile::FormatDataRequest { .. } => None,
ClipboardFile::FileContentsRequest { stream_id, .. } => Some(stream_id),
_ => unreachable!(),
};
let req = Arc::new(PendingRequest::new());
self.txn_handler
.insert((conn_id, stream_id.copied()), req.clone());
log::debug!(
"send request to conn_id={}, stream_id={:?}",
conn_id,
stream_id
);
crate::send_data(conn_id, request);
req
}
pub fn recv(&self, conn_id: i32, response: ClipboardFile) {
let stream_id = match &response {
ClipboardFile::FormatDataResponse { .. } => None,
ClipboardFile::FileContentsResponse { stream_id, .. } => Some(stream_id),
_ => unreachable!(),
};
let key = (conn_id, stream_id.cloned());
log::debug!("recv response for {:?}", key);
match self.txn_handler.remove(&key) {
Some((_, tx)) => tx.set(response),
None => log::warn!("no request found for {:?}", key),
}
}
}
/// this is a proxy type
/// to avoid occupy FuseServer with &mut self
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct FuseClient { pub struct FuseClient {
server: Arc<FuseServer>, server: Arc<Mutex<FuseServer>>,
}
impl FuseClient {
pub fn new(server: Arc<FuseServer>) -> Self {
Self { server }
}
} }
impl fuser::Filesystem for FuseClient { impl fuser::Filesystem for FuseClient {
fn init( fn init(
&mut self, &mut self,
_req: &fuser::Request<'_>, req: &fuser::Request<'_>,
_config: &mut fuser::KernelConfig, config: &mut fuser::KernelConfig,
) -> Result<(), libc::c_int> { ) -> Result<(), libc::c_int> {
log::debug!("init fuse server"); let mut server = self.server.lock();
server.init(req, config)
self.server.init();
Ok(())
} }
fn lookup( fn lookup(
&mut self, &mut self,
_req: &Request, req: &fuser::Request<'_>,
parent: u64, parent: u64,
name: &std::ffi::OsStr, name: &std::ffi::OsStr,
reply: fuser::ReplyEntry, reply: fuser::ReplyEntry,
) { ) {
log::debug!("lookup: parent={}, name={:?}", parent, name); let mut server = self.server.lock();
self.server.look_up(parent, name, reply) server.lookup(req, parent, name, reply)
} }
fn opendir(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) { fn opendir(&mut self, req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) {
log::debug!("opendir: ino={}, flags={}", ino, flags); let mut server = self.server.lock();
self.server.opendir(ino, flags, reply) server.opendir(req, ino, flags, reply)
} }
fn readdir( fn readdir(
&mut self, &mut self,
_req: &Request<'_>, req: &fuser::Request<'_>,
ino: u64, ino: u64,
fh: u64, fh: u64,
offset: i64, offset: i64,
reply: ReplyDirectory, reply: fuser::ReplyDirectory,
) { ) {
log::debug!("readdir: ino={}, fh={}, offset={}", ino, fh, offset); let mut server = self.server.lock();
self.server.readdir(ino, fh, offset, reply) server.readdir(req, ino, fh, offset, reply)
} }
fn releasedir( fn releasedir(
&mut self, &mut self,
_req: &Request<'_>, req: &fuser::Request<'_>,
ino: u64, ino: u64,
fh: u64, fh: u64,
flags: i32, _flags: i32,
reply: fuser::ReplyEmpty, reply: fuser::ReplyEmpty,
) { ) {
log::debug!("releasedir: ino={}, fh={}, flags={}", ino, fh, flags); let mut server = self.server.lock();
self.server.releasedir(ino, fh, flags, reply) server.releasedir(req, ino, fh, _flags, reply)
} }
fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) { fn open(&mut self, req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) {
log::debug!("open: ino={}, flags={}", ino, flags); let mut server = self.server.lock();
self.server.open(ino, flags, reply) server.open(req, ino, flags, reply)
} }
fn read( fn read(
&mut self, &mut self,
_req: &Request<'_>, req: &fuser::Request<'_>,
ino: u64, ino: u64,
fh: u64, fh: u64,
offset: i64, offset: i64,
@ -231,31 +126,22 @@ impl fuser::Filesystem for FuseClient {
lock_owner: Option<u64>, lock_owner: Option<u64>,
reply: fuser::ReplyData, reply: fuser::ReplyData,
) { ) {
log::debug!( let mut server = self.server.lock();
"read: ino={}, fh={}, offset={}, size={}, flags={}", server.read(req, ino, fh, offset, size, flags, lock_owner, reply)
ino,
fh,
offset,
size,
flags
);
self.server
.read(ino, fh, offset, size, flags, lock_owner, reply)
} }
fn release( fn release(
&mut self, &mut self,
_req: &Request<'_>, req: &fuser::Request<'_>,
ino: u64, ino: u64,
fh: u64, fh: u64,
flags: i32, _flags: i32,
lock_owner: Option<u64>, _lock_owner: Option<u64>,
flush: bool, _flush: bool,
reply: fuser::ReplyEmpty, reply: fuser::ReplyEmpty,
) { ) {
log::debug!("release: ino={}, fh={}, flush={}", ino, fh, flush); let mut server = self.server.lock();
self.server server.release(req, ino, fh, _flags, _lock_owner, _flush, reply)
.release(ino, fh, flags, lock_owner, flush, reply)
} }
} }
@ -263,58 +149,75 @@ impl fuser::Filesystem for FuseClient {
/// provides a read-only file system /// provides a read-only file system
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct FuseServer { pub(crate) struct FuseServer {
status: RwLock<Status>, generation: AtomicU64,
dispatcher: CliprdrTxnDispatcher, files: Vec<FuseNode>,
// timeout
// current files
// inode mapping:
// 1 -> root (parent of all files)
// 2~n+1 -> nth file in the list (n is the length of the list)
// 0 | n+2.. -> not found
// Note that the file tree is pre-ordered
files: RwLock<Vec<FuseNode>>,
// file handle counter // file handle counter
file_handle_counter: AtomicU64, file_handle_counter: AtomicU64,
// file system generations
generation: AtomicU64,
// timeout // timeout
timeout: Duration, timeout: Duration,
// file read reply channel
tx: Sender<ClipboardFile>,
// file read reply channel
rx: Receiver<ClipboardFile>,
} }
impl FuseServer { impl FuseServer {
/// create a new fuse server /// create a new fuse server
pub fn new(timeout: Duration) -> Self { pub fn new(timeout: Duration) -> Self {
let (tx, rx) = std::sync::mpsc::channel();
Self { Self {
status: RwLock::new(Status::Active),
dispatcher: CliprdrTxnDispatcher::default(),
files: RwLock::new(Vec::new()),
file_handle_counter: AtomicU64::new(0),
generation: AtomicU64::new(0), generation: AtomicU64::new(0),
files: Vec::new(),
file_handle_counter: AtomicU64::new(0),
timeout, timeout,
rx,
tx,
} }
} }
pub fn client(self: &Arc<Self>) -> FuseClient { pub fn client(server: Arc<Mutex<Self>>) -> FuseClient {
FuseClient::new(self.clone()) FuseClient { server }
} }
}
pub fn init(&self) { impl FuseServer {
let mut w_guard = self.files.write(); pub fn serve(&mut self, reply: ClipboardFile) -> Result<(), CliprdrError> {
if w_guard.is_empty() { self.tx.send(reply).map_err(|e| {
log::error!("failed to serve cliprdr reply from endpoint: {:?}", e);
CliprdrError::ClipboardInternalError
})?;
Ok(())
}
}
impl fuser::Filesystem for FuseServer {
fn init(
&mut self,
_req: &fuser::Request<'_>,
_config: &mut fuser::KernelConfig,
) -> Result<(), libc::c_int> {
if self.files.is_empty() {
// create a root file // create a root file
let root = FuseNode::new_root(); let root = FuseNode::new_root();
w_guard.push(root); self.files.push(root);
} }
Ok(())
} }
pub fn look_up(&self, parent: u64, name: &std::ffi::OsStr, reply: fuser::ReplyEntry) { fn lookup(
&mut self,
_req: &fuser::Request<'_>,
parent: u64,
name: &std::ffi::OsStr,
reply: fuser::ReplyEntry,
) {
if name.len() > MAX_NAME_LEN { if name.len() > MAX_NAME_LEN {
log::debug!("fuse: name too long"); log::debug!("fuse: name too long");
reply.error(libc::ENAMETOOLONG); reply.error(libc::ENAMETOOLONG);
return; return;
} }
let entries = self.files.read(); let entries = &self.files;
let generation = self.generation.load(Ordering::Relaxed); let generation = self.generation.load(Ordering::Relaxed);
@ -353,8 +256,14 @@ impl FuseServer {
return; return;
} }
pub fn opendir(&self, ino: u64, flags: i32, reply: fuser::ReplyOpen) { fn opendir(
let files = self.files.read(); &mut self,
_req: &fuser::Request<'_>,
ino: u64,
flags: i32,
reply: fuser::ReplyOpen,
) {
let files = &self.files;
let Some(entry) = files.get(ino as usize - 1) else { let Some(entry) = files.get(ino as usize - 1) else {
reply.error(libc::ENOENT); reply.error(libc::ENOENT);
log::error!("fuse: opendir: entry not found"); log::error!("fuse: opendir: entry not found");
@ -383,8 +292,15 @@ impl FuseServer {
return; return;
} }
pub fn readdir(&self, ino: u64, fh: u64, offset: i64, mut reply: ReplyDirectory) { fn readdir(
let files = self.files.read(); &mut self,
_req: &fuser::Request<'_>,
ino: u64,
fh: u64,
offset: i64,
mut reply: ReplyDirectory,
) {
let files = &self.files;
let Some(entry) = files.get(ino as usize - 1) else { let Some(entry) = files.get(ino as usize - 1) else {
reply.error(libc::ENOENT); reply.error(libc::ENOENT);
log::error!("fuse: readdir: entry not found"); log::error!("fuse: readdir: entry not found");
@ -429,8 +345,15 @@ impl FuseServer {
return; return;
} }
pub fn releasedir(&self, ino: u64, fh: u64, _flags: i32, reply: fuser::ReplyEmpty) { fn releasedir(
let files = self.files.read(); &mut self,
_req: &fuser::Request<'_>,
ino: u64,
fh: u64,
_flags: i32,
reply: fuser::ReplyEmpty,
) {
let files = &self.files;
let Some(entry) = files.get(ino as usize - 1) else { let Some(entry) = files.get(ino as usize - 1) else {
reply.error(libc::ENOENT); reply.error(libc::ENOENT);
log::error!("fuse: releasedir: entry not found"); log::error!("fuse: releasedir: entry not found");
@ -452,8 +375,8 @@ impl FuseServer {
return; return;
} }
pub fn open(&self, ino: u64, flags: i32, reply: fuser::ReplyOpen) { fn open(&mut self, _req: &fuser::Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) {
let files = self.files.read(); let files = &self.files;
let Some(entry) = files.get(ino as usize - 1) else { let Some(entry) = files.get(ino as usize - 1) else {
reply.error(libc::ENOENT); reply.error(libc::ENOENT);
log::error!("fuse: open: entry not found"); log::error!("fuse: open: entry not found");
@ -485,8 +408,9 @@ impl FuseServer {
return; return;
} }
pub fn read( fn read(
&self, &mut self,
_req: &fuser::Request<'_>,
ino: u64, ino: u64,
fh: u64, fh: u64,
offset: i64, offset: i64,
@ -495,7 +419,7 @@ impl FuseServer {
_lock_owner: Option<u64>, _lock_owner: Option<u64>,
reply: fuser::ReplyData, reply: fuser::ReplyData,
) { ) {
let files = self.files.read(); let files = &self.files;
let Some(entry) = files.get(ino as usize - 1) else { let Some(entry) = files.get(ino as usize - 1) else {
reply.error(libc::ENOENT); reply.error(libc::ENOENT);
log::error!("fuse: read: entry not found"); log::error!("fuse: read: entry not found");
@ -536,8 +460,9 @@ impl FuseServer {
reply.data(bytes.as_slice()); reply.data(bytes.as_slice());
} }
pub fn release( fn release(
&self, &mut self,
_req: &fuser::Request<'_>,
ino: u64, ino: u64,
fh: u64, fh: u64,
_flags: i32, _flags: i32,
@ -545,7 +470,7 @@ impl FuseServer {
_flush: bool, _flush: bool,
reply: fuser::ReplyEmpty, reply: fuser::ReplyEmpty,
) { ) {
let files = self.files.read(); let files = &self.files;
let Some(entry) = files.get(ino as usize - 1) else { let Some(entry) = files.get(ino as usize - 1) else {
reply.error(libc::ENOENT); reply.error(libc::ENOENT);
log::error!("fuse: release: entry not found"); log::error!("fuse: release: entry not found");
@ -560,10 +485,12 @@ impl FuseServer {
reply.ok(); reply.ok();
return; return;
} }
}
impl FuseServer {
// get files and directory path right in root of FUSE fs // get files and directory path right in root of FUSE fs
pub fn list_root(&self) -> Vec<PathBuf> { pub fn list_root(&self) -> Vec<PathBuf> {
let files = self.files.read(); let files = &self.files;
let children = &files[0].children; let children = &files[0].children;
let mut paths = Vec::with_capacity(children.len()); let mut paths = Vec::with_capacity(children.len());
for idx in children.iter().copied() { for idx in children.iter().copied() {
@ -572,59 +499,19 @@ impl FuseServer {
paths paths
} }
/// gc filesystem
fn gc_files(&self) {
{
let mut status = self.status.write();
// received update after fetching complete
// should fetch again
if *status == Status::Building {
*status = Status::GcComplete;
return;
}
// really update only when:
// running: Active
if *status != Status::Active {
return;
}
*status = Status::Gc;
}
let mut old = self.files.write();
let _ = old.par_iter_mut().fold(|| (), |_, f| f.gc());
let mut status = self.status.write();
*status = Status::GcComplete;
}
/// fetch file list from remote /// fetch file list from remote
fn sync_file_system( fn sync_file_system(
&self, &mut self,
conn_id: i32, conn_id: i32,
file_group_format_id: i32, file_group_format_id: i32,
_file_contents_format_id: i32, _file_contents_format_id: i32,
) -> Result<bool, CliprdrError> { ) -> Result<bool, CliprdrError> {
{ let resp = self.send_sync_fs_request(conn_id, file_group_format_id, self.timeout)?;
let mut status = self.status.write();
if *status != Status::GcComplete {
return Ok(false);
}
*status = Status::Fetching;
}
// request file list
let request = ClipboardFile::FormatDataRequest {
requested_format_id: file_group_format_id,
};
let rx = self.dispatcher.send(conn_id, request);
let resp = rx.recv_timeout(self.timeout);
let descs = match resp { let descs = match resp {
Ok(ClipboardFile::FormatDataResponse { ClipboardFile::FormatDataResponse {
msg_flags, msg_flags,
format_data, format_data,
}) => { } => {
if msg_flags != 0x1 { if msg_flags != 0x1 {
log::error!("clipboard FUSE server: received unexpected response flags"); log::error!("clipboard FUSE server: received unexpected response flags");
return Err(CliprdrError::ClipboardInternalError); return Err(CliprdrError::ClipboardInternalError);
@ -633,155 +520,61 @@ impl FuseServer {
descs descs
} }
Ok(_) => { _ => {
log::error!("clipboard FUSE server: received unexpected response type"); log::error!("clipboard FUSE server: received unexpected response type");
// rollback status
let mut status = self.status.write();
*status = Status::GcComplete;
return Err(CliprdrError::ClipboardInternalError);
}
Err(e) => {
log::error!("clipboard FUSE server: failed to fetch file list, {:?}", e);
// rollback status
let mut status = self.status.write();
*status = Status::GcComplete;
return Err(CliprdrError::ClipboardInternalError); return Err(CliprdrError::ClipboardInternalError);
} }
}; };
{
// fetch successful, start building
let mut status = self.status.write();
*status = Status::Building;
}
let mut new_tree = FuseNode::build_tree(descs)?; let mut new_tree = FuseNode::build_tree(descs)?;
let res = new_tree let res = new_tree
.par_iter_mut() .iter_mut()
.filter(|f_node| f_node.is_file() && f_node.attributes.size == 0) .filter(|f_node| f_node.is_file() && f_node.attributes.size == 0)
.fold(|| Ok(()), |_, f_node| self.sync_node_size(f_node)) .try_for_each(|f_node| self.sync_node_size(f_node));
.find_last(|p| p.is_err());
if res.is_some() { if let Err(err) = res {
// rollback status on failure log::error!(
let mut status = self.status.write(); "clipboard FUSE server: failed to fetch file size: {:?}",
if *status == Status::Building { err
*status = Status::GcComplete; );
}
log::error!("clipboard FUSE server: failed to fetch file size");
return Err(CliprdrError::ClipboardInternalError); return Err(CliprdrError::ClipboardInternalError);
} }
// replace current file system // replace current file system
let mut old = self.files.write(); self.files = new_tree;
{
let mut status = self.status.write();
if *status != Status::Building {
// build interrupted, meaning fetched data is outdated
// do not replace
return Ok(false);
}
*status = Status::Active;
}
*old = new_tree;
self.generation.fetch_add(1, Ordering::Relaxed); self.generation.fetch_add(1, Ordering::Relaxed);
Ok(true) Ok(true)
} }
/// replace current files with new files, cucurrently fn send_sync_fs_request(
///
/// # Note
///
/// This function should allow concurrent calls. In short, the server can handle multiple update_file calles
/// at a short period of time and make sure it call RPCs as few and late as possible.
///
/// ## Function Phases
///
/// ### clear phase
///
/// - just mark all files to be deleted, all new `open` operations will be denied
/// - current FDs will not be affected, listing (in this level of directory) and reading operations can still be performed.
/// - this will return only when all FDs are closed, or some unexpected error occurs
/// - after all FDs are closed and no more FDs can be opened, dropping the current file list will be safe
///
/// ### request phase
///
/// - after all FDs are closed, send a format data request to the clipboard server
///
/// ### replace phase
///
/// - after all FDs are closed, the file list will be replaced with the new file list
///
/// ## Concurrent calls
///
/// ### server is Active
///
/// threads calling this function may win getting the write lock on server.status:
/// - the winner will start [clear phase], changing the server to Gc.
/// - the loser or later comming threads calling `server.gc_files` will return directly.
///
/// movement: Active -> Gc
///
/// ### server is Gc
///
/// this indicates there must be exactly one thread running in [clear phase].
/// - the thread will run `server.sync_file_system` after this phase
/// - other threads try to call `server.gc_files` will return directly
/// - other threads try to call `server.sync_file_system` will return directly
/// - no other threads could be running `server.sync_file_system`
///
/// after all, only one thread will successfully complete the [clear phase], and that thread will try to complete the whole updating.
///
/// movement: Gc -> GcComplete
///
/// ### server is GcComplete
///
/// This indicates there must be at least one thread trying to call `server.sync_file_system`.
/// threads will trying to get the write lock of status.
/// - the winner will set status to Fetching.
/// - the latter threads get the write lock, only to find the status is not `GcComplete`, return directly.
/// - there might be threads trying to call `server.gc_files`, but will return directly and call `server.sync_file_system`.
///
/// movement: GcComplete -> Fetching
///
/// ### server is Fetching
///
/// This indicates there must be exactly one thread running in `server.sync_file_system`, in its fetching phase.
/// - any other threads calling this function will return directly.
/// - after fetching finishes, it will set status to Building
/// - timeout may reach, then we rollback
///
/// movement: Fetching -> Building
/// failure: Fetching -> GcComplete
///
/// ### server is Building
///
/// The reason why we have this status is to prevent requesting outdated data.
/// There should be exactly one thread start running [replace phase] and might be other threads trying to call `gc_files`
/// - if the building phase is finished, the thread will set status to Active, and other threads may run [clear phase]
/// - if the building phase is interrupted, the thread will quit, and other threads will skip the clear phase, try to fetch directly.
///
/// movements: Building -> Active, Building -> GcComplete
///
pub fn update_files(
&self, &self,
conn_id: i32, conn_id: i32,
file_group_format_id: i32, file_group_format_id: i32,
timeout: std::time::Duration,
) -> Result<ClipboardFile, CliprdrError> {
// request file list
let data = ClipboardFile::FormatDataRequest {
requested_format_id: file_group_format_id,
};
send_data(conn_id, data);
self.rx.recv_timeout(timeout).map_err(|e| {
log::error!("failed to receive file list from channel: {:?}", e);
CliprdrError::ClipboardInternalError
})
}
pub fn update_files(
&mut self,
conn_id: i32,
file_group_format_id: i32,
file_contents_format_id: i32, file_contents_format_id: i32,
) -> Result<bool, CliprdrError> { ) -> Result<bool, CliprdrError> {
self.gc_files();
self.sync_file_system(conn_id, file_group_format_id, file_contents_format_id) self.sync_file_system(conn_id, file_group_format_id, file_contents_format_id)
} }
pub fn recv(&self, conn_id: i32, clip_file: ClipboardFile) {
self.dispatcher.recv(conn_id, clip_file)
}
/// allocate a new file descriptor /// allocate a new file descriptor
fn alloc_fd(&self) -> u64 { fn alloc_fd(&self) -> u64 {
self.file_handle_counter.fetch_add(1, Ordering::Relaxed) self.file_handle_counter.fetch_add(1, Ordering::Relaxed)
@ -807,7 +600,7 @@ impl FuseServer {
clip_data_id: 0, clip_data_id: 0,
}; };
let rx = self.dispatcher.send(node.conn_id, request); send_data(node.conn_id, request);
log::debug!( log::debug!(
"waiting for metadata sync reply for {:?} on channel {}", "waiting for metadata sync reply for {:?} on channel {}",
@ -815,8 +608,10 @@ impl FuseServer {
node.conn_id node.conn_id
); );
let reply = rx.recv_timeout(self.timeout)?; let reply = self
.rx
.recv_timeout(self.timeout)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::TimedOut, e))?;
log::debug!( log::debug!(
"got metadata sync reply for {:?} on channel {}", "got metadata sync reply for {:?} on channel {}",
node.name, node.name,
@ -897,7 +692,7 @@ impl FuseServer {
clip_data_id: 0, clip_data_id: 0,
}; };
let rx = self.dispatcher.send(node.conn_id, request); send_data(node.conn_id, request);
log::debug!( log::debug!(
"waiting for read reply for {:?} on stream: {}", "waiting for read reply for {:?} on stream: {}",
@ -905,7 +700,10 @@ impl FuseServer {
node.stream_id node.stream_id
); );
let reply = rx.recv_timeout(self.timeout)?; let reply = self.rx.recv_timeout(self.timeout).map_err(|e| {
log::error!("failed to receive file list from channel: {:?}", e);
std::io::Error::new(std::io::ErrorKind::TimedOut, e)
})?;
match reply { match reply {
ClipboardFile::FileContentsResponse { ClipboardFile::FileContentsResponse {
@ -961,7 +759,7 @@ impl FileDescription {
// skip reserved 32 bytes // skip reserved 32 bytes
bytes.advance(32); bytes.advance(32);
let attributes = bytes.get_u32_le(); let attributes = bytes.get_u32_le();
// skip reserverd 16 bytes // skip reserved 16 bytes
bytes.advance(16); bytes.advance(16);
// last write time from 1601-01-01 00:00:00, in 100ns // last write time from 1601-01-01 00:00:00, in 100ns
let last_write_time = bytes.get_u64_le(); let last_write_time = bytes.get_u64_le();
@ -1126,11 +924,6 @@ impl FuseNode {
self.file_handlers.marked() self.file_handlers.marked()
} }
/// mark all files to be deleted
pub fn gc(&mut self) {
self.file_handlers.mark_and_wait()
}
pub fn add_handler(&self, fh: u64) { pub fn add_handler(&self, fh: u64) {
self.file_handlers.add_handler(fh) self.file_handlers.add_handler(fh)
} }
@ -1336,18 +1129,6 @@ impl FileHandles {
self.handlers.lock().push(fh); self.handlers.lock().push(fh);
} }
// wait till gc completes
pub fn mark_and_wait(&self) {
let mut handlers = self.handlers.lock();
self.gc.store(true, Ordering::Relaxed);
loop {
if handlers.is_empty() {
return;
}
self.waiter.wait(&mut handlers);
}
}
pub fn marked(&self) -> bool { pub fn marked(&self) -> bool {
self.gc.load(Ordering::Relaxed) self.gc.load(Ordering::Relaxed)
} }

View File

@ -3,10 +3,7 @@ use std::{
fs::File, fs::File,
os::unix::prelude::FileExt, os::unix::prelude::FileExt,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::Arc,
atomic::{AtomicUsize, Ordering},
Arc,
},
time::{Duration, SystemTime}, time::{Duration, SystemTime},
}; };
@ -20,7 +17,7 @@ use lazy_static::lazy_static;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
use utf16string::WString; use utf16string::WString;
use crate::{send_data, send_data_to_all, ClipboardFile, CliprdrError, CliprdrServiceContext}; use crate::{send_data, ClipboardFile, CliprdrError, CliprdrServiceContext};
use super::{fuse::FuseServer, LDAP_EPOCH_DELTA}; use super::{fuse::FuseServer, LDAP_EPOCH_DELTA};
@ -47,13 +44,16 @@ fn add_remote_format(local_name: &str, remote_id: i32) {
} }
trait SysClipboard: Send + Sync { trait SysClipboard: Send + Sync {
fn wait_file_list(&self) -> Result<Option<Vec<PathBuf>>, CliprdrError>;
fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>; fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError>;
fn stop(&self); fn stop(&self);
fn start(&self); fn start(&self);
/// send to 0 will send to all channels
fn send_format_list(&self, conn_id: i32) -> Result<(), CliprdrError>;
/// send to 0 will send to all channels
fn send_file_list(&self, conn_id: i32) -> Result<(), CliprdrError>;
} }
fn get_sys_clipboard() -> Result<Box<dyn SysClipboard>, CliprdrError> { fn get_sys_clipboard(ignore_path: &PathBuf) -> Result<Box<dyn SysClipboard>, CliprdrError> {
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
{ {
unimplemented!() unimplemented!()
@ -61,7 +61,7 @@ fn get_sys_clipboard() -> Result<Box<dyn SysClipboard>, CliprdrError> {
#[cfg(not(feature = "wayland"))] #[cfg(not(feature = "wayland"))]
{ {
pub use x11::*; pub use x11::*;
let x11_clip = X11Clipboard::new()?; let x11_clip = X11Clipboard::new(ignore_path)?;
Ok(Box::new(x11_clip) as Box<_>) Ok(Box::new(x11_clip) as Box<_>)
} }
} }
@ -300,38 +300,14 @@ enum FileContentsRequest {
}, },
} }
/// this is a proxy type for the clipboard context
pub struct CliprdrClient {
pub context: Arc<ClipboardContext>,
}
impl Drop for CliprdrClient {
fn drop(&mut self) {
self.context.ref_decrease();
}
}
impl CliprdrServiceContext for CliprdrClient {
fn set_is_stopped(&mut self) -> Result<(), CliprdrError> {
self.context.set_is_stopped()
}
fn empty_clipboard(&mut self, conn_id: i32) -> Result<bool, CliprdrError> {
self.context.empty_clipboard(conn_id)
}
fn server_clip_file(&mut self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> {
self.context.serve(conn_id, msg)
}
}
pub struct ClipboardContext { pub struct ClipboardContext {
pub client_count: AtomicUsize,
pub fuse_mount_point: PathBuf, pub fuse_mount_point: PathBuf,
fuse_server: Arc<FuseServer>, fuse_handle: Mutex<Option<fuser::BackgroundSession>>,
fuse_server: Arc<Mutex<FuseServer>>,
file_list: RwLock<Vec<LocalFile>>, file_list: RwLock<Vec<LocalFile>>,
clipboard: Arc<dyn SysClipboard>, clipboard: Arc<dyn SysClipboard>,
fuse_handle: Mutex<Option<fuser::BackgroundSession>>,
} }
impl ClipboardContext { impl ClipboardContext {
@ -342,14 +318,13 @@ impl ClipboardContext {
CliprdrError::CliprdrInit CliprdrError::CliprdrInit
})?; })?;
let fuse_server = Arc::new(FuseServer::new(timeout)); let fuse_server = Arc::new(Mutex::new(FuseServer::new(timeout)));
let clipboard = get_sys_clipboard()?; let clipboard = get_sys_clipboard(&fuse_mount_point)?;
let clipboard = Arc::from(clipboard); let clipboard = Arc::from(clipboard) as Arc<_>;
let file_list = RwLock::new(vec![]); let file_list = RwLock::new(vec![]);
Ok(Self { Ok(Self {
client_count: AtomicUsize::new(0),
fuse_mount_point, fuse_mount_point,
fuse_server, fuse_server,
fuse_handle: Mutex::new(None), fuse_handle: Mutex::new(None),
@ -358,60 +333,48 @@ impl ClipboardContext {
}) })
} }
pub fn client(self: Arc<Self>) -> Result<CliprdrClient, CliprdrError> { pub fn run(&self) -> Result<(), CliprdrError> {
if self.client_count.fetch_add(1, Ordering::Relaxed) == 0 { if !self.is_stopped() {
let mut fuse_handle = self.fuse_handle.lock(); return Ok(());
if fuse_handle.is_none() {
let mount_path = &self.fuse_mount_point;
let mnt_opts = [
MountOption::FSName("rustdesk-cliprdr-fs".to_string()),
MountOption::RO,
MountOption::NoAtime,
];
log::info!(
"mounting clipboard FUSE to {}",
self.fuse_mount_point.display()
);
let new_handle =
fuser::spawn_mount2(self.fuse_server.client(), mount_path, &mnt_opts).map_err(
|e| {
log::error!("failed to mount cliprdr fuse: {:?}", e);
CliprdrError::CliprdrInit
},
)?;
*fuse_handle = Some(new_handle);
}
self.clipboard.start();
let clip = self.clone();
std::thread::spawn(move || {
let res = clip.listen_clipboard();
if let Err(e) = res {
log::error!("failed to listen clipboard: {:?}", e);
}
log::info!("stopped listening clipboard");
});
}
Ok(CliprdrClient { context: self })
}
/// set context to be inactive
pub fn ref_decrease(&self) {
if self.client_count.fetch_sub(1, Ordering::Relaxed) > 1 {
return;
} }
let mut fuse_handle = self.fuse_handle.lock(); let mut fuse_handle = self.fuse_handle.lock();
if let Some(fuse_handle) = fuse_handle.take() {
log::debug!("unmounting clipboard FUSE"); let mount_path = &self.fuse_mount_point;
fuse_handle.join();
} let mnt_opts = [
self.clipboard.stop(); MountOption::FSName("rustdesk-cliprdr-fs".to_string()),
std::fs::remove_dir(&self.fuse_mount_point).unwrap(); MountOption::RO,
MountOption::NoAtime,
];
log::info!(
"mounting clipboard FUSE to {}",
self.fuse_mount_point.display()
);
let new_handle = fuser::spawn_mount2(
FuseServer::client(self.fuse_server.clone()),
mount_path,
&mnt_opts,
)
.map_err(|e| {
log::error!("failed to mount cliprdr fuse: {:?}", e);
CliprdrError::CliprdrInit
})?;
*fuse_handle = Some(new_handle);
let clipboard = self.clipboard.clone();
std::thread::spawn(move || {
log::debug!("start listening clipboard");
clipboard.start();
});
Ok(())
}
pub fn stop(&self) -> Result<(), CliprdrError> {
self.set_is_stopped()
} }
/// set clipboard data from file list /// set clipboard data from file list
@ -422,108 +385,6 @@ impl ClipboardContext {
self.clipboard.set_file_list(&paths) self.clipboard.set_file_list(&paths)
} }
pub fn listen_clipboard(&self) -> Result<(), CliprdrError> {
log::debug!("start listening clipboard");
while let Some(v) = self.clipboard.wait_file_list()? {
let filtered: Vec<_> = v
.into_iter()
.filter(|pb| !pb.starts_with(&self.fuse_mount_point))
.collect();
log::debug!("clipboard file list update (filtered): {:?}", filtered);
if filtered.is_empty() {
continue;
}
log::debug!("send file list update to remote");
// construct format list update and send
let data = ClipboardFile::FormatList {
format_list: vec![
(
FILEDESCRIPTOR_FORMAT_ID,
FILEDESCRIPTORW_FORMAT_NAME.to_string(),
),
(FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME.to_string()),
],
};
send_data_to_all(data);
log::debug!("format list update sent");
}
Ok(())
}
fn send_format_list(&self, conn_id: i32) -> Result<(), CliprdrError> {
log::debug!("send format list to remote, conn={}", conn_id);
let data = self.clipboard.wait_file_list()?;
if data.is_none() {
log::debug!("clipboard disabled, skip sending");
return Ok(());
}
let data = data.unwrap();
let filtered: Vec<_> = data
.into_iter()
.filter(|pb| !pb.starts_with(&self.fuse_mount_point))
.collect();
if filtered.is_empty() {
log::debug!("no files in format list, skip sending");
return Ok(());
}
let format_list = ClipboardFile::FormatList {
format_list: vec![
(
FILEDESCRIPTOR_FORMAT_ID,
FILEDESCRIPTORW_FORMAT_NAME.to_string(),
),
(FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME.to_string()),
],
};
send_data(conn_id, format_list);
log::debug!("format list to remote dispatched, conn={}", conn_id);
Ok(())
}
fn send_file_list(&self, conn_id: i32) -> Result<(), CliprdrError> {
log::debug!("send file list to remote, conn={}", conn_id);
let data = self.clipboard.wait_file_list()?;
if data.is_none() {
log::debug!("clipboard disabled, skip sending");
return Ok(());
}
let data = data.unwrap();
let filtered: Vec<_> = data
.into_iter()
.filter(|pb| !pb.starts_with(&self.fuse_mount_point))
.collect();
let files = construct_file_list(filtered.as_slice())?;
let mut data = BytesMut::with_capacity(4 + 592 * files.len());
data.put_u32_le(filtered.len() as u32);
for file in files.iter() {
data.put(file.as_bin().as_slice());
}
{
let mut w_list = self.file_list.write();
*w_list = files;
}
let format_data = data.to_vec();
send_data(
conn_id,
ClipboardFile::FormatDataResponse {
msg_flags: 1,
format_data,
},
);
Ok(())
}
fn serve_file_contents( fn serve_file_contents(
&self, &self,
conn_id: i32, conn_id: i32,
@ -663,6 +524,7 @@ impl ClipboardContext {
if let Some(fuse_handle) = self.fuse_handle.lock().take() { if let Some(fuse_handle) = self.fuse_handle.lock().take() {
fuse_handle.join(); fuse_handle.join();
} }
self.clipboard.stop();
Ok(()) Ok(())
} }
@ -673,8 +535,11 @@ impl ClipboardContext {
return Ok(true); return Ok(true);
} }
self.fuse_server self.fuse_server.lock().update_files(
.update_files(conn_id, FILEDESCRIPTOR_FORMAT_ID, FILECONTENTS_FORMAT_ID) conn_id,
FILEDESCRIPTOR_FORMAT_ID,
FILECONTENTS_FORMAT_ID,
)
} }
pub fn serve(&self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> { pub fn serve(&self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> {
@ -691,7 +556,7 @@ impl ClipboardContext {
// ignore capabilities for now // ignore capabilities for now
self.send_file_list(0)?; self.clipboard.send_file_list(0)?;
Ok(()) Ok(())
} }
@ -722,14 +587,17 @@ impl ClipboardContext {
add_remote_format(FILECONTENTS_FORMAT_NAME, file_contents_id); add_remote_format(FILECONTENTS_FORMAT_NAME, file_contents_id);
add_remote_format(FILEDESCRIPTORW_FORMAT_NAME, file_descriptor_id); add_remote_format(FILEDESCRIPTORW_FORMAT_NAME, file_descriptor_id);
self.fuse_server self.fuse_server.lock().update_files(
.update_files(conn_id, file_descriptor_id, file_contents_id)?; conn_id,
file_descriptor_id,
file_contents_id,
)?;
Ok(()) Ok(())
} }
ClipboardFile::FormatListResponse { msg_flags } => { ClipboardFile::FormatListResponse { msg_flags } => {
log::debug!("server_format_list_response called"); log::debug!("server_format_list_response called");
if msg_flags != 0x1 { if msg_flags != 0x1 {
self.send_format_list(conn_id) self.clipboard.send_format_list(conn_id)
} else { } else {
Ok(()) Ok(())
} }
@ -749,7 +617,7 @@ impl ClipboardContext {
}; };
if format == FILEDESCRIPTORW_FORMAT_NAME { if format == FILEDESCRIPTORW_FORMAT_NAME {
self.send_file_list(requested_format_id)?; self.clipboard.send_file_list(conn_id)?;
} else if format == FILECONTENTS_FORMAT_NAME { } else if format == FILECONTENTS_FORMAT_NAME {
log::error!( log::error!(
"try to read file contents with FormatDataRequest from conn={}", "try to read file contents with FormatDataRequest from conn={}",
@ -769,16 +637,17 @@ impl ClipboardContext {
ClipboardFile::FormatDataResponse { .. } => { ClipboardFile::FormatDataResponse { .. } => {
// we don't know its corresponding request, no resend can be performed // we don't know its corresponding request, no resend can be performed
log::debug!("server_format_data_response called"); log::debug!("server_format_data_response called");
let mut fuse_server = self.fuse_server.lock();
self.fuse_server.recv(conn_id, msg); fuse_server.serve(msg)?;
let paths = self.fuse_server.list_root(); let paths = fuse_server.list_root();
self.set_clipboard(&paths)?; self.set_clipboard(&paths)?;
Ok(()) Ok(())
} }
ClipboardFile::FileContentsResponse { .. } => { ClipboardFile::FileContentsResponse { .. } => {
log::debug!("server_file_contents_response called"); log::debug!("server_file_contents_response called");
// we don't know its corresponding request, no resend can be performed // we don't know its corresponding request, no resend can be performed
self.fuse_server.recv(conn_id, msg); self.fuse_server.lock().serve(msg)?;
Ok(()) Ok(())
} }
ClipboardFile::FileContentsRequest { ClipboardFile::FileContentsRequest {
@ -818,6 +687,19 @@ impl ClipboardContext {
} }
} }
impl CliprdrServiceContext for ClipboardContext {
fn set_is_stopped(&mut self) -> Result<(), CliprdrError> {
self.stop()
}
fn empty_clipboard(&mut self, _conn_id: i32) -> Result<bool, CliprdrError> {
self.clipboard.set_file_list(&[])?;
Ok(true)
}
fn server_clip_file(&mut self, conn_id: i32, msg: ClipboardFile) -> Result<(), CliprdrError> {
self.serve(conn_id, msg)
}
}
fn resp_format_data_failure(conn_id: i32) { fn resp_format_data_failure(conn_id: i32) {
let data = ClipboardFile::FormatDataResponse { let data = ClipboardFile::FormatDataResponse {
msg_flags: 0x2, msg_flags: 0x2,

View File

@ -3,12 +3,21 @@ use std::{
sync::atomic::{AtomicBool, Ordering}, sync::atomic::{AtomicBool, Ordering},
}; };
use hbb_common::log; use hbb_common::{
bytes::{BufMut, BytesMut},
log,
};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use x11_clipboard::Clipboard; use x11_clipboard::Clipboard;
use x11rb::protocol::xproto::Atom; use x11rb::protocol::xproto::Atom;
use crate::CliprdrError; use crate::{
platform::linux::{
construct_file_list, FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME,
FILEDESCRIPTORW_FORMAT_NAME, FILEDESCRIPTOR_FORMAT_ID,
},
send_data, ClipboardFile, CliprdrError,
};
use super::{encode_path_to_uri, parse_plain_uri_list, SysClipboard}; use super::{encode_path_to_uri, parse_plain_uri_list, SysClipboard};
@ -20,12 +29,13 @@ fn get_clip() -> Result<&'static Clipboard, CliprdrError> {
pub struct X11Clipboard { pub struct X11Clipboard {
stop: AtomicBool, stop: AtomicBool,
ignore_path: PathBuf,
text_uri_list: Atom, text_uri_list: Atom,
gnome_copied_files: Atom, gnome_copied_files: Atom,
} }
impl X11Clipboard { impl X11Clipboard {
pub fn new() -> Result<Self, CliprdrError> { pub fn new(ignore_path: &PathBuf) -> Result<Self, CliprdrError> {
let clipboard = get_clip()?; let clipboard = get_clip()?;
let text_uri_list = clipboard let text_uri_list = clipboard
.setter .setter
@ -36,6 +46,7 @@ impl X11Clipboard {
.get_atom("x-special/gnome-copied-files") .get_atom("x-special/gnome-copied-files")
.map_err(|_| CliprdrError::CliprdrInit)?; .map_err(|_| CliprdrError::CliprdrInit)?;
Ok(Self { Ok(Self {
ignore_path: ignore_path.to_owned(),
stop: AtomicBool::new(false), stop: AtomicBool::new(false),
text_uri_list, text_uri_list,
gnome_copied_files, gnome_copied_files,
@ -58,9 +69,7 @@ impl X11Clipboard {
.store_batch(clip, batch) .store_batch(clip, batch)
.map_err(|_| CliprdrError::ClipboardInternalError) .map_err(|_| CliprdrError::ClipboardInternalError)
} }
}
impl SysClipboard for X11Clipboard {
fn wait_file_list(&self) -> Result<Option<Vec<PathBuf>>, CliprdrError> { fn wait_file_list(&self) -> Result<Option<Vec<PathBuf>>, CliprdrError> {
if self.stop.load(Ordering::Relaxed) { if self.stop.load(Ordering::Relaxed) {
return Ok(None); return Ok(None);
@ -70,7 +79,16 @@ impl SysClipboard for X11Clipboard {
let p = parse_plain_uri_list(v)?; let p = parse_plain_uri_list(v)?;
Ok(Some(p)) Ok(Some(p))
} }
}
impl X11Clipboard {
#[inline]
fn is_stopped(&self) -> bool {
self.stop.load(Ordering::Relaxed)
}
}
impl SysClipboard for X11Clipboard {
fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> { fn set_file_list(&self, paths: &[PathBuf]) -> Result<(), CliprdrError> {
let uri_list: Vec<String> = paths.iter().map(|pb| encode_path_to_uri(pb)).collect(); let uri_list: Vec<String> = paths.iter().map(|pb| encode_path_to_uri(pb)).collect();
let uri_list = uri_list.join("\n"); let uri_list = uri_list.join("\n");
@ -90,5 +108,121 @@ impl SysClipboard for X11Clipboard {
fn start(&self) { fn start(&self) {
self.stop.store(false, Ordering::Relaxed); self.stop.store(false, Ordering::Relaxed);
while let Ok(sth) = self.wait_file_list() {
if self.is_stopped() {
break;
}
let Some(paths) = sth else {
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
};
let filtered = paths
.into_iter()
.filter(|pb| !pb.starts_with(&self.ignore_path))
.collect::<Vec<_>>();
if filtered.is_empty() {
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
// send update to server
log::debug!("clipboard updated: {:?}", filtered);
if let Err(e) = send_format_list(0) {
log::warn!("failed to send format list: {}", e);
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
}
fn send_format_list(&self, conn_id: i32) -> Result<(), CliprdrError> {
if self.is_stopped() {
log::debug!("clipboard stopped, skip sending");
return Ok(());
}
let Some(paths) = self.wait_file_list()? else {
log::debug!("no files in format list, skip sending");
return Ok(());
};
let filtered: Vec<_> = paths
.into_iter()
.filter(|pb| !pb.starts_with(&self.ignore_path))
.collect();
if filtered.is_empty() {
log::debug!("no files in format list, skip sending");
return Ok(());
}
send_format_list(conn_id)
}
fn send_file_list(&self, conn_id: i32) -> Result<(), CliprdrError> {
if self.is_stopped() {
log::debug!("clipboard stopped, skip sending");
return Ok(());
}
let Some(paths) = self.wait_file_list()? else {
log::debug!("no files in format list, skip sending");
return Ok(());
};
let filtered: Vec<_> = paths
.into_iter()
.filter(|pb| !pb.starts_with(&self.ignore_path))
.collect();
if filtered.is_empty() {
log::debug!("no files in format list, skip sending");
return Ok(());
}
send_file_list(filtered, conn_id)
} }
} }
fn send_format_list(conn_id: i32) -> Result<(), CliprdrError> {
log::debug!("send format list to remote, conn={}", conn_id);
let format_list = ClipboardFile::FormatList {
format_list: vec![
(
FILEDESCRIPTOR_FORMAT_ID,
FILEDESCRIPTORW_FORMAT_NAME.to_string(),
),
(FILECONTENTS_FORMAT_ID, FILECONTENTS_FORMAT_NAME.to_string()),
],
};
send_data(conn_id, format_list);
log::debug!("format list to remote dispatched, conn={}", conn_id);
Ok(())
}
fn send_file_list(paths: Vec<PathBuf>, conn_id: i32) -> Result<(), CliprdrError> {
log::debug!("send file list to remote, conn={}", conn_id);
let files = construct_file_list(paths.as_slice())?;
let mut data = BytesMut::with_capacity(4 + 592 * files.len());
data.put_u32_le(paths.len() as u32);
for file in files.iter() {
data.put(file.as_bin().as_slice());
}
let format_data = data.to_vec();
send_data(
conn_id,
ClipboardFile::FormatDataResponse {
msg_flags: 1,
format_data,
},
);
Ok(())
}

View File

@ -25,9 +25,7 @@ pub fn create_cliprdr_context(
_enable_others: bool, _enable_others: bool,
response_wait_timeout_secs: u32, response_wait_timeout_secs: u32,
) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> { ) -> crate::ResultType<Box<dyn crate::CliprdrServiceContext>> {
use std::sync::Arc; use hbb_common::log;
use hbb_common::{anyhow, log};
if !enable_files { if !enable_files {
return Ok(Box::new(DummyCliprdrContext {}) as Box<_>); return Ok(Box::new(DummyCliprdrContext {}) as Box<_>);
@ -53,13 +51,9 @@ pub fn create_cliprdr_context(
tmp_path tmp_path
}; };
let linux_ctx = Arc::new(linux::ClipboardContext::new(timeout, rd_mnt)?); let linux_ctx = linux::ClipboardContext::new(timeout, rd_mnt)?;
let client = linux_ctx.client().map_err(|e| {
log::error!("create clipboard client: {:?}", e);
anyhow::anyhow!("create clipboard client: {:?}", e)
})?;
Ok(Box::new(client) as Box<_>) Ok(Box::new(linux_ctx) as Box<_>)
} }
struct DummyCliprdrContext {} struct DummyCliprdrContext {}