Implement Upload Hook for momentry integration (Phase 1)
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

- Add upload_hook.rs module: trigger video_probe + video_register on upload
- Add UploadHookSection to config: video extensions, binary paths
- Integrate with SFTP: handle_close triggers hook on write files
- Integrate with SCP/rsync: child process exit triggers hook
- All 155 tests pass
This commit is contained in:
Warren
2026-06-19 06:26:20 +08:00
parent c71811090b
commit e2d58538f9
7 changed files with 336 additions and 42 deletions

View File

@@ -4,11 +4,12 @@
use crate::ssh_server::packet::{PacketType, SshPacket};
use crate::ssh_server::port_forward::{
DirectTcpipChannel, ForwardedTcpipChannel, PortForwardManager,
}; // Phase 13.3
use crate::ssh_server::rsync_handler::RsyncHandler; // Phase 8: rsync handler
use crate::ssh_server::scp_handler::ScpHandler; // Phase 8: SCP handler
use crate::ssh_server::sftp_handler::SftpHandler; // Phase 7: SFTP handler
use crate::ssh_server::ssh_security_config::SshSecurityConfig; // Phase 13.3: 安全配置
};
use crate::ssh_server::rsync_handler::RsyncHandler;
use crate::ssh_server::scp_handler::ScpHandler;
use crate::ssh_server::sftp_handler::SftpHandler;
use crate::ssh_server::ssh_security_config::SshSecurityConfig;
use crate::ssh_server::upload_hook::UploadHook;
use anyhow::{anyhow, Result};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use log::{info, warn};
@@ -25,10 +26,10 @@ use std::process::{Child, ChildStderr, ChildStdin, ChildStdout}; // Phase 14:
pub struct ChannelManager {
channels: HashMap<u32, Channel>,
next_channel_id: u32,
/// ⭐⭐⭐⭐⭐ Phase 15.1: 待发送packet队列用于同时发送WINDOW_ADJUST和SFTP响应
pub pending_packets: VecDeque<SshPacket>,
/// 用户home目录SFTP/SCP/rsync根目录SFTPGo兼容
pub home_dir: PathBuf,
pub upload_hook: Option<std::sync::Arc<UploadHook>>,
pub user_uuid: String,
}
/// Phase 14: 交互式Exec进程管理参考OpenSSH session.c: do_exec_no_pty
@@ -44,12 +45,18 @@ pub struct ExecProcess {
}
impl ChannelManager {
pub fn new(home_dir: PathBuf) -> Self {
pub fn new(
home_dir: PathBuf,
upload_hook: Option<std::sync::Arc<UploadHook>>,
user_uuid: String,
) -> Self {
Self {
channels: HashMap::new(),
next_channel_id: 0,
pending_packets: VecDeque::new(),
home_dir,
upload_hook,
user_uuid,
}
}
@@ -574,7 +581,13 @@ impl ChannelManager {
};
let vfs = Box::new(crate::vfs::local_fs::LocalFs::new());
let sftp_handler = SftpHandler::new(root_dir, vfs, maxpacket); // ⭐⭐⭐⭐⭐ Phase 4: 传入 maxpack
let sftp_handler = SftpHandler::new(
root_dir,
vfs,
maxpacket,
self.upload_hook.clone(),
self.user_uuid.clone(),
);
// 存储到channel
if let Some(ch) = self.channels.get_mut(&channel) {
@@ -1374,7 +1387,10 @@ impl ChannelManager {
);
child_exited = true;
// ⭐⭐⭐⭐⭐ Child exited读取剩余stdout如果有
let command_str = exec_process.command.clone();
let should_trigger_hook = status.success()
&& (command_str.contains("scp") || command_str.contains("rsync"));
if let Some(stdout) = &mut exec_process.stdout {
let mut buffer = vec![0u8; 32768];
match stdout.read(&mut buffer) {
@@ -1395,6 +1411,17 @@ impl ChannelManager {
}
}
if should_trigger_hook {
let dest_path = Self::extract_dest_path_from_command(&command_str, &self.home_dir);
if let Some(path) = dest_path {
if let Some(hook) = &self.upload_hook {
if let Err(e) = hook.trigger(&path, &self.user_uuid) {
warn!("Upload hook failed for {:?}: {}", path, e);
}
}
}
}
// 没有剩余数据返回child_exited标志
return Ok((None, false, true));
}
@@ -1823,6 +1850,29 @@ impl ChannelManager {
Ok(Some(packets))
}
}
fn extract_dest_path_from_command(command: &str, home_dir: &PathBuf) -> Option<PathBuf> {
if command.contains("scp") {
if command.contains("scp -t") {
let parts: Vec<&str> = command.split_whitespace().collect();
for part in parts.iter().rev() {
if !part.starts_with("-") && *part != "scp" && *part != "-t" {
return Some(home_dir.join(part));
}
}
}
} else if command.contains("rsync") {
if command.contains("--server") {
let parts: Vec<&str> = command.split_whitespace().collect();
for part in parts.iter().rev() {
if !part.starts_with("-") && !part.contains("--") && *part != "rsync" && *part != "--server" && *part != "--sender" {
return Some(home_dir.join(part));
}
}
}
}
None
}
}
/// SSH Channel结构参考OpenSSH channel.c: struct channel
@@ -1967,13 +2017,13 @@ mod tests {
#[test]
fn test_channel_manager_creation() {
let manager = ChannelManager::new(PathBuf::from("/tmp"));
let manager = ChannelManager::new(PathBuf::from("/tmp"), None, "test_user".to_string());
assert_eq!(manager.next_channel_id, 0);
}
#[test]
fn test_channel_open_confirmation() {
let manager = ChannelManager::new(PathBuf::from("/tmp"));
let manager = ChannelManager::new(PathBuf::from("/tmp"), None, "test_user".to_string());
let packet = manager
.build_channel_open_confirmation(0, 100, 2097152, 32768)
.unwrap();
@@ -1986,7 +2036,7 @@ mod tests {
#[test]
fn test_channel_success() {
let manager = ChannelManager::new(PathBuf::from("/tmp"));
let manager = ChannelManager::new(PathBuf::from("/tmp"), None, "test_user".to_string());
let packet = manager.build_channel_success(0).unwrap();
assert_eq!(packet.payload[0], PacketType::SSH_MSG_CHANNEL_SUCCESS as u8);