Implements attachment uploading
This commit is contained in:
@@ -19,7 +19,7 @@ use std::sync::Arc;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use tokio::pin;
|
||||
use uuid::Uuid;
|
||||
|
||||
mod target {
|
||||
pub static ATTACHMENTS: &str = "attachments";
|
||||
@@ -36,6 +36,12 @@ pub enum AttachmentStoreEvent {
|
||||
// - attachment guid
|
||||
// - preview: whether to download the preview (true) or full attachment (false)
|
||||
QueueDownloadAttachment(String, bool),
|
||||
|
||||
// Queue an upload for a given attachment file.
|
||||
// Args:
|
||||
// - path: the path to the attachment file
|
||||
// - reply: a reply channel to send the pending upload guid to
|
||||
QueueUploadAttachment(PathBuf, Reply<String>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -161,6 +167,47 @@ impl AttachmentStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn upload_attachment_impl(
|
||||
store_path: &PathBuf,
|
||||
incoming_path: &PathBuf,
|
||||
upload_guid: &String,
|
||||
database: &mut Arc<Mutex<Database>>,
|
||||
daemon_event_sink: &Sender<DaemonEvent>,
|
||||
) -> Result<String> {
|
||||
use tokio::fs::File;
|
||||
use tokio::io::BufReader;
|
||||
|
||||
// Create uploads directory if it doesn't exist.
|
||||
let uploads_path = store_path.join("uploads");
|
||||
std::fs::create_dir_all(&uploads_path).unwrap();
|
||||
|
||||
// First, copy the file to the store path, under /uploads/.
|
||||
log::trace!(target: target::ATTACHMENTS, "Copying attachment to uploads directory: {}", uploads_path.display());
|
||||
let temporary_path = uploads_path.join(incoming_path.file_name().unwrap());
|
||||
std::fs::copy(incoming_path, &temporary_path).unwrap();
|
||||
|
||||
// Open file handle to the temporary file,
|
||||
log::trace!(target: target::ATTACHMENTS, "Opening stream to temporary file: {}", temporary_path.display());
|
||||
let file = File::open(&temporary_path).await?;
|
||||
let reader: BufReader<File> = BufReader::new(file);
|
||||
|
||||
// Upload the file to the server.
|
||||
let filename = incoming_path.file_name().unwrap().to_str().unwrap();
|
||||
log::trace!(target: target::ATTACHMENTS, "Uploading attachment to server: {}", &filename);
|
||||
let mut client = Daemon::get_client_impl(database).await?;
|
||||
let guid = client.upload_attachment(reader, filename).await?;
|
||||
|
||||
// Delete the temporary file.
|
||||
log::debug!(target: target::ATTACHMENTS, "Upload completed with guid {}, deleting temporary file: {}", guid, temporary_path.display());
|
||||
std::fs::remove_file(&temporary_path).unwrap();
|
||||
|
||||
// Send a signal to the daemon that the attachment has been uploaded.
|
||||
let event = DaemonEvent::AttachmentUploaded(upload_guid.clone(), guid.clone());
|
||||
daemon_event_sink.send(event).await.unwrap();
|
||||
|
||||
Ok(guid)
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
@@ -201,6 +248,30 @@ impl AttachmentStore {
|
||||
let attachment = self.get_attachment(&guid);
|
||||
reply.send(attachment).unwrap();
|
||||
}
|
||||
|
||||
AttachmentStoreEvent::QueueUploadAttachment(path, reply) => {
|
||||
let upload_guid = Uuid::new_v4().to_string();
|
||||
let store_path = self.store_path.clone();
|
||||
let mut database = self.database.clone();
|
||||
let daemon_event_sink = self.daemon_event_sink.clone();
|
||||
|
||||
let _upload_guid = upload_guid.clone();
|
||||
tokio::spawn(async move {
|
||||
let result = Self::upload_attachment_impl(
|
||||
&store_path,
|
||||
&path,
|
||||
&_upload_guid,
|
||||
&mut database,
|
||||
&daemon_event_sink,
|
||||
).await;
|
||||
|
||||
if let Err(e) = result {
|
||||
log::error!(target: target::ATTACHMENTS, "Error uploading attachment {}: {}", &_upload_guid, e);
|
||||
}
|
||||
});
|
||||
|
||||
reply.send(upload_guid).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ use crate::daemon::{Attachment, Message};
|
||||
|
||||
pub type Reply<T> = oneshot::Sender<T>;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
/// Get the version of the daemon.
|
||||
@@ -76,4 +78,16 @@ pub enum Event {
|
||||
/// Parameters:
|
||||
/// - attachment_id: The attachment ID that was downloaded.
|
||||
AttachmentDownloaded(String),
|
||||
|
||||
/// Upload an attachment to the server.
|
||||
/// Parameters:
|
||||
/// - path: The path to the attachment file
|
||||
/// - reply: Reply indicating the upload GUID
|
||||
UploadAttachment(PathBuf, Reply<String>),
|
||||
|
||||
/// Notifies the daemon that an attachment has been uploaded.
|
||||
/// Parameters:
|
||||
/// - upload_id: The upload ID that was uploaded.
|
||||
/// - attachment_id: The attachment ID that was uploaded.
|
||||
AttachmentUploaded(String, String),
|
||||
}
|
||||
|
||||
@@ -324,6 +324,24 @@ impl Daemon {
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Event::UploadAttachment(path, reply) => {
|
||||
self.attachment_store_sink
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.send(AttachmentStoreEvent::QueueUploadAttachment(path, reply))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Event::AttachmentUploaded(upload_guid, attachment_guid) => {
|
||||
log::info!(target: target::ATTACHMENTS, "Daemon: attachment uploaded: {}, {}", upload_guid, attachment_guid);
|
||||
|
||||
self.signal_sender
|
||||
.send(Signal::AttachmentUploaded(upload_guid, attachment_guid))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,4 +12,10 @@ pub enum Signal {
|
||||
/// Parameters:
|
||||
/// - attachment_id: The ID of the attachment that was downloaded.
|
||||
AttachmentDownloaded(String),
|
||||
|
||||
/// Emitted when an attachment has been uploaded.
|
||||
/// Parameters:
|
||||
/// - upload_guid: The GUID of the upload.
|
||||
/// - attachment_guid: The GUID of the attachment on the server.
|
||||
AttachmentUploaded(String, String),
|
||||
}
|
||||
|
||||
@@ -13,5 +13,6 @@ pub mod interface {
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryConversationsUpdated as ConversationsUpdated;
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryMessagesUpdated as MessagesUpdated;
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryAttachmentDownloadCompleted as AttachmentDownloadCompleted;
|
||||
pub use crate::interface::NetBuzzertKordophoneRepositoryAttachmentUploadCompleted as AttachmentUploadCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +264,16 @@ impl DbusRepository for ServerImpl {
|
||||
// For now, just trigger the download event - we'll implement the actual download logic later
|
||||
self.send_event_sync(|r| Event::DownloadAttachment(attachment_id, preview, r))
|
||||
}
|
||||
|
||||
fn upload_attachment(
|
||||
&mut self,
|
||||
path: String,
|
||||
) -> Result<String, dbus::MethodErr> {
|
||||
use std::path::PathBuf;
|
||||
|
||||
let path = PathBuf::from(path);
|
||||
self.send_event_sync(|r| Event::UploadAttachment(path, r))
|
||||
}
|
||||
}
|
||||
|
||||
impl DbusSettings for ServerImpl {
|
||||
|
||||
@@ -108,6 +108,16 @@ async fn main() {
|
||||
0
|
||||
});
|
||||
}
|
||||
|
||||
Signal::AttachmentUploaded(upload_guid, attachment_guid) => {
|
||||
log::debug!("Sending signal: AttachmentUploaded for upload {}, attachment {}", upload_guid, attachment_guid);
|
||||
dbus_registry
|
||||
.send_signal(interface::OBJECT_PATH, DbusSignals::AttachmentUploadCompleted { upload_guid, attachment_guid })
|
||||
.unwrap_or_else(|_| {
|
||||
log::error!("Failed to send signal");
|
||||
0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user