matrix-sdk: Add docs and cleanup the media upload methods.
parent
3ac3be501f
commit
c500c06e4b
|
@ -29,7 +29,8 @@ use std::{
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use futures_timer::Delay as sleep;
|
use futures_timer::Delay as sleep;
|
||||||
use reqwest::header::{HeaderValue, InvalidHeaderValue};
|
use http::HeaderValue;
|
||||||
|
use reqwest::header::InvalidHeaderValue;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
use zeroize::Zeroizing;
|
use zeroize::Zeroizing;
|
||||||
|
@ -68,7 +69,17 @@ use matrix_sdk_common::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
assign,
|
assign,
|
||||||
identifiers::ServerName,
|
events::{
|
||||||
|
room::{
|
||||||
|
message::{
|
||||||
|
AudioMessageEventContent, FileMessageEventContent, ImageMessageEventContent,
|
||||||
|
MessageEventContent, VideoMessageEventContent,
|
||||||
|
},
|
||||||
|
EncryptedFile,
|
||||||
|
},
|
||||||
|
AnyMessageEventContent,
|
||||||
|
},
|
||||||
|
identifiers::{EventId, RoomId, RoomIdOrAliasId, ServerName, UserId},
|
||||||
instant::{Duration, Instant},
|
instant::{Duration, Instant},
|
||||||
js_int::UInt,
|
js_int::UInt,
|
||||||
locks::RwLock,
|
locks::RwLock,
|
||||||
|
@ -90,15 +101,7 @@ use matrix_sdk_common::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
events::{
|
|
||||||
room::{
|
|
||||||
message::{FileMessageEventContent, MessageEventContent},
|
|
||||||
EncryptedFile,
|
|
||||||
},
|
|
||||||
AnyMessageEventContent,
|
|
||||||
},
|
|
||||||
http_client::{client_with_config, HttpClient, HttpSend},
|
http_client::{client_with_config, HttpClient, HttpSend},
|
||||||
identifiers::{EventId, RoomId, RoomIdOrAliasId, UserId},
|
|
||||||
Error, EventEmitter, OutgoingRequest, Result,
|
Error, EventEmitter, OutgoingRequest, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1017,9 +1020,9 @@ impl Client {
|
||||||
///
|
///
|
||||||
/// * `content` - The content of the message event.
|
/// * `content` - The content of the message event.
|
||||||
///
|
///
|
||||||
/// * `txn_id` - A unique `Uuid` that can be attached to a `MessageEvent` held
|
/// * `txn_id` - A unique `Uuid` that can be attached to a `MessageEvent`
|
||||||
/// in its unsigned field as `transaction_id`. If not given one is created for the
|
/// held in its unsigned field as `transaction_id`. If not given one is
|
||||||
/// message.
|
/// created for the message.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
@ -1081,78 +1084,179 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
/// Send an attachment to a room.
|
||||||
|
///
|
||||||
|
/// This will upload the given data that the reader produces using the
|
||||||
|
/// [`upload()`](#method.upload) method and post an event to the given room.
|
||||||
|
/// If the room is encrypted and the encryption feature is enabled the
|
||||||
|
/// upload will be encrypted.
|
||||||
|
///
|
||||||
|
/// This is a convenience method that calls the [`upload()`](#method.upload)
|
||||||
|
/// and afterwards the [`room_send()`](#method.room_send).
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `room_id` - The id of the room that should receive the media event.
|
||||||
|
///
|
||||||
|
/// * `body` - A textual representation of the media that is going to be
|
||||||
|
/// uploaded. Usually the file name.
|
||||||
|
///
|
||||||
|
/// * `content_type` - The type of the media, this will be used as the
|
||||||
|
/// content-type header.
|
||||||
|
///
|
||||||
|
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
|
||||||
|
/// media.
|
||||||
|
///
|
||||||
|
/// * `txn_id` - A unique `Uuid` that can be attached to a `MessageEvent`
|
||||||
|
/// held in its unsigned field as `transaction_id`. If not given one is
|
||||||
|
/// created for the message.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use std::{path::PathBuf, fs::File, io::Read};
|
||||||
|
/// # use matrix_sdk::{Client, identifiers::room_id};
|
||||||
|
/// # use matrix_sdk_base::crypto::AttachmentEncryptor;
|
||||||
|
/// # use url::Url;
|
||||||
|
/// # use futures::executor::block_on;
|
||||||
|
/// # block_on(async {
|
||||||
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
||||||
|
/// # let mut client = Client::new(homeserver).unwrap();
|
||||||
|
/// # let room_id = room_id!("!test:localhost");
|
||||||
|
/// let path = PathBuf::from("/home/example/my-cat.jpg");
|
||||||
|
/// let mut image = File::open(path).unwrap();
|
||||||
|
///
|
||||||
|
/// let response = client
|
||||||
|
/// .room_send_attachment(&room_id, "My favorite cat", "image/jpg", &mut image, None)
|
||||||
|
/// .await
|
||||||
|
/// .expect("Can't upload my cat.");
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
pub async fn room_send_attachment<R: Read>(
|
pub async fn room_send_attachment<R: Read>(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
|
body: &str,
|
||||||
|
content_type: &str,
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
txn_id: Option<Uuid>,
|
txn_id: Option<Uuid>,
|
||||||
) -> Result<send_message_event::Response> {
|
) -> Result<send_message_event::Response> {
|
||||||
#[cfg(not(feature = "encryption"))]
|
let (new_content_type, reader, keys) =
|
||||||
let encrypted = false;
|
if cfg!(feature = "encryption") && self.is_room_encrypted(room_id).await {
|
||||||
|
let encryptor = AttachmentEncryptor::new(reader);
|
||||||
#[cfg(feature = "encryption")]
|
|
||||||
let encrypted = {
|
|
||||||
let room = self.base_client.get_joined_room(room_id).await;
|
|
||||||
|
|
||||||
match room {
|
|
||||||
Some(r) => r.read().await.is_encrypted(),
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "encryption")]
|
|
||||||
if encrypted {
|
|
||||||
let mut data = Vec::new();
|
|
||||||
let mut encryptor = AttachmentEncryptor::new(reader);
|
|
||||||
|
|
||||||
encryptor.read_to_end(&mut data).unwrap();
|
|
||||||
let keys = encryptor.finish();
|
let keys = encryptor.finish();
|
||||||
|
|
||||||
let upload = self.upload("application/octet-stream", data).await?;
|
("application/octet-stream", reader, Some(keys))
|
||||||
|
} else {
|
||||||
let content = EncryptedFile {
|
(content_type, reader, None)
|
||||||
url: upload.content_uri,
|
|
||||||
key: keys.web_key,
|
|
||||||
iv: keys.iv,
|
|
||||||
hashes: keys.hashes,
|
|
||||||
v: keys.version,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = AnyMessageEventContent::RoomMessage(MessageEventContent::File(
|
let upload = self.upload(new_content_type, reader).await?;
|
||||||
FileMessageEventContent {
|
|
||||||
body: "test".to_owned(),
|
|
||||||
filename: None,
|
|
||||||
info: None,
|
|
||||||
url: None,
|
|
||||||
file: Some(Box::new(content)),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
return self.room_send(room_id, content, txn_id).await;
|
let url = upload.content_uri.clone();
|
||||||
};
|
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let encrypted_file = keys.map(move |k| {
|
||||||
reader.read_to_end(&mut data).unwrap();
|
Box::new(EncryptedFile {
|
||||||
let upload = self.upload("application/octet-stream", data).await?;
|
url,
|
||||||
|
key: k.web_key,
|
||||||
|
iv: k.iv,
|
||||||
|
hashes: k.hashes,
|
||||||
|
v: k.version,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
let content = AnyMessageEventContent::RoomMessage(MessageEventContent::File(
|
let content = if content_type.starts_with("image") {
|
||||||
FileMessageEventContent {
|
// TODO create a thumbnail using the image crate?.
|
||||||
body: "test".to_owned(),
|
MessageEventContent::Image(ImageMessageEventContent {
|
||||||
filename: None,
|
body: body.to_owned(),
|
||||||
info: None,
|
info: None,
|
||||||
url: Some(upload.content_uri),
|
url: Some(upload.content_uri),
|
||||||
file: None,
|
file: encrypted_file,
|
||||||
},
|
})
|
||||||
));
|
} else if content_type.starts_with("audio") {
|
||||||
|
MessageEventContent::Audio(AudioMessageEventContent {
|
||||||
|
body: body.to_owned(),
|
||||||
|
info: None,
|
||||||
|
url: Some(upload.content_uri),
|
||||||
|
file: encrypted_file,
|
||||||
|
})
|
||||||
|
} else if content_type.starts_with("video") {
|
||||||
|
MessageEventContent::Video(VideoMessageEventContent {
|
||||||
|
body: body.to_owned(),
|
||||||
|
info: None,
|
||||||
|
url: Some(upload.content_uri),
|
||||||
|
file: encrypted_file,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
MessageEventContent::File(FileMessageEventContent {
|
||||||
|
filename: None,
|
||||||
|
body: body.to_owned(),
|
||||||
|
info: None,
|
||||||
|
url: Some(upload.content_uri),
|
||||||
|
file: encrypted_file,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
self.room_send(room_id, content, txn_id).await
|
self.room_send(
|
||||||
|
room_id,
|
||||||
|
AnyMessageEventContent::RoomMessage(content),
|
||||||
|
txn_id,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn upload(&self, content_type: &str, data: Vec<u8>) -> Result<create_content::Response> {
|
/// Upload some media to the server.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `content_type` - The type of the media, this will be used as the
|
||||||
|
/// content-type header.
|
||||||
|
///
|
||||||
|
/// * `reader` - A `Reader` that will be used to fetch the raw bytes of the
|
||||||
|
/// media.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # use std::{path::PathBuf, fs::File, io::Read};
|
||||||
|
/// # use matrix_sdk::{Client, identifiers::room_id};
|
||||||
|
/// # use matrix_sdk_base::crypto::AttachmentEncryptor;
|
||||||
|
/// # use url::Url;
|
||||||
|
/// # use futures::executor::block_on;
|
||||||
|
/// # block_on(async {
|
||||||
|
/// # let homeserver = Url::parse("http://localhost:8080").unwrap();
|
||||||
|
/// # let mut client = Client::new(homeserver).unwrap();
|
||||||
|
/// let path = PathBuf::from("/home/example/my-cat.jpg");
|
||||||
|
/// let mut image = File::open(path).unwrap();
|
||||||
|
///
|
||||||
|
/// let response = client
|
||||||
|
/// .upload("image/jpg", &mut image)
|
||||||
|
/// .await
|
||||||
|
/// .expect("Can't upload my cat.");
|
||||||
|
///
|
||||||
|
/// println!("Cat URI: {}", response.content_uri);
|
||||||
|
///
|
||||||
|
/// // Upload an encrypted cat, err file.
|
||||||
|
/// let path = PathBuf::from("/home/example/my-secret-cat.jpg");
|
||||||
|
/// let mut image = File::open(path).unwrap();
|
||||||
|
/// let mut encryptor = AttachmentEncryptor::new(&mut image);
|
||||||
|
///
|
||||||
|
/// let response = client
|
||||||
|
/// .upload("image/jpg", &mut encryptor)
|
||||||
|
/// .await
|
||||||
|
/// .expect("Can't upload my cat.");
|
||||||
|
/// println!("Secret cat URI: {}", response.content_uri);
|
||||||
|
/// # });
|
||||||
|
/// ```
|
||||||
|
pub async fn upload(
|
||||||
|
&self,
|
||||||
|
content_type: &str,
|
||||||
|
reader: &mut impl Read,
|
||||||
|
) -> Result<create_content::Response> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
reader.read_to_end(&mut data)?;
|
||||||
|
|
||||||
let request = create_content::Request::new(content_type, data);
|
let request = create_content::Request::new(content_type, data);
|
||||||
|
|
||||||
self.send(request).await
|
self.http_client.upload(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send an arbitrary request to the server, without updating client state.
|
/// Send an arbitrary request to the server, without updating client state.
|
||||||
|
|
|
@ -21,6 +21,7 @@ use matrix_sdk_common::{
|
||||||
};
|
};
|
||||||
use reqwest::Error as ReqwestError;
|
use reqwest::Error as ReqwestError;
|
||||||
use serde_json::Error as JsonError;
|
use serde_json::Error as JsonError;
|
||||||
|
use std::io::Error as IoError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
|
@ -44,6 +45,10 @@ pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SerdeJson(#[from] JsonError),
|
SerdeJson(#[from] JsonError),
|
||||||
|
|
||||||
|
/// An IO error happened.
|
||||||
|
#[error(transparent)]
|
||||||
|
IO(#[from] IoError),
|
||||||
|
|
||||||
/// An error converting between ruma_client_api types and Hyper types.
|
/// An error converting between ruma_client_api types and Hyper types.
|
||||||
#[error("can't parse the JSON response as a Matrix response")]
|
#[error("can't parse the JSON response as a Matrix response")]
|
||||||
RumaResponse(RumaResponseError<RumaClientError>),
|
RumaResponse(RumaResponseError<RumaClientError>),
|
||||||
|
|
|
@ -19,7 +19,7 @@ use reqwest::{Client, Response};
|
||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use matrix_sdk_common::{locks::RwLock, FromHttpResponseError};
|
use matrix_sdk_common::{api::r0::media::create_content, locks::RwLock, FromHttpResponseError};
|
||||||
use matrix_sdk_common_macros::async_trait;
|
use matrix_sdk_common_macros::async_trait;
|
||||||
|
|
||||||
use crate::{ClientConfig, Error, OutgoingRequest, Result, Session};
|
use crate::{ClientConfig, Error, OutgoingRequest, Result, Session};
|
||||||
|
@ -87,6 +87,7 @@ impl HttpClient {
|
||||||
&self,
|
&self,
|
||||||
request: Request,
|
request: Request,
|
||||||
session: Arc<RwLock<Option<Session>>>,
|
session: Arc<RwLock<Option<Session>>>,
|
||||||
|
content_type: Option<HeaderValue>,
|
||||||
) -> Result<http::Response<Vec<u8>>> {
|
) -> Result<http::Response<Vec<u8>>> {
|
||||||
let mut request = {
|
let mut request = {
|
||||||
let read_guard;
|
let read_guard;
|
||||||
|
@ -106,21 +107,35 @@ impl HttpClient {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let HttpMethod::POST | HttpMethod::PUT | HttpMethod::DELETE = *request.method() {
|
if let HttpMethod::POST | HttpMethod::PUT | HttpMethod::DELETE = *request.method() {
|
||||||
request.headers_mut().append(
|
if let Some(content_type) = content_type {
|
||||||
http::header::CONTENT_TYPE,
|
request
|
||||||
HeaderValue::from_static("application/json"),
|
.headers_mut()
|
||||||
);
|
.append(http::header::CONTENT_TYPE, content_type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.inner.send_request(request).await
|
self.inner.send_request(request).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn upload(
|
||||||
|
&self,
|
||||||
|
request: create_content::Request<'_>,
|
||||||
|
) -> Result<create_content::Response> {
|
||||||
|
let response = self
|
||||||
|
.send_request(request, self.session.clone(), None)
|
||||||
|
.await?;
|
||||||
|
Ok(create_content::Response::try_from(response)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send<Request>(&self, request: Request) -> Result<Request::IncomingResponse>
|
pub async fn send<Request>(&self, request: Request) -> Result<Request::IncomingResponse>
|
||||||
where
|
where
|
||||||
Request: OutgoingRequest,
|
Request: OutgoingRequest,
|
||||||
Error: From<FromHttpResponseError<Request::EndpointError>>,
|
Error: From<FromHttpResponseError<Request::EndpointError>>,
|
||||||
{
|
{
|
||||||
let response = self.send_request(request, self.session.clone()).await?;
|
let content_type = HeaderValue::from_static("application/json");
|
||||||
|
let response = self
|
||||||
|
.send_request(request, self.session.clone(), Some(content_type))
|
||||||
|
.await?;
|
||||||
|
|
||||||
trace!("Got response: {:?}", response);
|
trace!("Got response: {:?}", response);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue