use crate::dvrip::DVRIPCam;
use crate::error::Result;
use async_trait::async_trait;
use chrono::{DateTime, Local};
use serde_json::{Value, json};
use std::path::Path;
use tokio::{fs::File, io::AsyncWriteExt};
#[async_trait]
pub trait FileManagement: Send + Sync {
async fn list_local_files(
&self,
start_time: DateTime<Local>,
end_time: DateTime<Local>,
file_type: &str,
channel: u8,
) -> Result<Vec<Value>>;
async fn download_file(
&self,
start_time: DateTime<Local>,
end_time: DateTime<Local>,
filename: &str,
target_path: &str,
) -> Result<()>;
}
#[async_trait]
impl FileManagement for DVRIPCam {
async fn list_local_files(
&self,
start_time: DateTime<Local>,
end_time: DateTime<Local>,
file_type: &str,
channel: u8,
) -> Result<Vec<Value>> {
let start_str = start_time.format("%Y-%m-%d %H:%M:%S").to_string();
let end_str = end_time.format("%Y-%m-%d %H:%M:%S").to_string();
let data = json!({
"Name": "OPFileQuery",
"OPFileQuery": {
"BeginTime": start_str,
"Channel": channel,
"DriverTypeMask": "0x0000FFFF",
"EndTime": end_str,
"Event": "*",
"StreamType": "0x00000000",
"Type": file_type,
},
});
let mut reply = self
.send_command(1440, data, true)
.await?
.ok_or_else(|| crate::error::DVRIPError::ProtocolError("Empty response".to_string()))?;
let mut result = Vec::new();
if let Some(ret) = reply.get("Ret").and_then(|r| r.as_u64())
&& ret != 100
{
return Ok(vec![]);
}
if let Some(files) = reply.get_mut("OPFileQuery").and_then(|f| f.as_array_mut()) {
result.append(files);
}
while let Some(files) = reply.get("OPFileQuery").and_then(|f| f.as_array()) {
if files.len() != 64 {
break;
};
let Some(last_file) = files.last() else {
break;
};
let Some(new_start) = last_file.get("BeginTime").and_then(|t| t.as_str()) else {
break;
};
let data = json!({
"Name": "OPFileQuery",
"OPFileQuery": {
"BeginTime": new_start,
"Channel": channel,
"DriverTypeMask": "0x0000FFFF",
"EndTime": end_str,
"Event": "*",
"StreamType": "0x00000000",
"Type": file_type,
},
});
reply = self.send_command(1440, data, true).await?.ok_or_else(|| {
crate::error::DVRIPError::ProtocolError("Resposta vazia".to_string())
})?;
let Some(new_files) = reply.get("OPFileQuery").and_then(|f| f.as_array()) else {
break;
};
if new_files.is_empty() {
break;
}
result.extend(new_files.clone());
}
Ok(result)
}
async fn download_file(
&self,
start_time: DateTime<Local>,
end_time: DateTime<Local>,
filename: &str,
target_path: &str,
) -> Result<()> {
if let Some(parent) = Path::new(target_path).parent() {
tokio::fs::create_dir_all(parent).await?;
}
let start_str = start_time.format("%Y-%m-%d %H:%M:%S").to_string();
let end_str = end_time.format("%Y-%m-%d %H:%M:%S").to_string();
let claim_data = json!({
"Name": "OPPlayBack",
"OPPlayBack": {
"Action": "Claim",
"Parameter": {
"PlayMode": "ByName",
"FileName": filename,
"StreamType": 0,
"Value": 0,
"TransMode": "TCP",
},
"StartTime": start_str,
"EndTime": end_str,
},
});
self.send_command(1424, claim_data, true).await?;
let (tx, mut rx) = tokio::sync::mpsc::channel(100);
let stream_ids = [0x1FC, 0x1FD, 0x1FA, 0x1F9, 0x5FC, 0x0592]; for &id in &stream_ids {
self.stream_handlers.insert(id, tx.clone());
}
let download_start_data = json!({
"Name": "OPPlayBack",
"OPPlayBack": {
"Action": "DownloadStart",
"Parameter": {
"PlayMode": "ByName",
"FileName": filename,
"StreamType": 0,
"Value": 0,
"TransMode": "TCP",
},
"StartTime": start_str,
"EndTime": end_str,
},
});
self.send_command(1420, download_start_data, false).await?;
let mut file = File::create(target_path).await?;
while let Some((header, data)) = rx.recv().await {
if header.data_len == 0 {
break;
}
file.write_all(&data).await?;
}
file.sync_all().await?;
for &id in &stream_ids {
self.stream_handlers.remove(&id);
}
let download_stop_data = json!({
"Name": "OPPlayBack",
"OPPlayBack": {
"Action": "DownloadStop",
"Parameter": {
"FileName": filename,
"PlayMode": "ByName",
"StreamType": 0,
"TransMode": "TCP",
"Channel": 0,
"Value": 0,
},
"StartTime": start_str,
"EndTime": end_str,
},
});
self.send_command(1420, download_stop_data, false).await?;
Ok(())
}
}