Plume/plume-models/src/medias.rs

366 lines
11 KiB
Rust
Raw Normal View History

2018-10-31 10:40:20 +01:00
use activitypub::object::Image;
use askama_escape::escape;
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl};
2018-10-31 10:40:20 +01:00
use guid_create::GUID;
use reqwest;
use std::{fs, path::Path};
use plume_common::activity_pub::Id;
2018-09-02 22:55:42 +02:00
use instance::Instance;
use safe_string::SafeString;
2018-09-02 13:34:48 +02:00
use schema::medias;
use users::User;
use {ap_url, Connection};
2018-09-02 13:34:48 +02:00
2018-09-27 23:06:40 +02:00
#[derive(Clone, Identifiable, Queryable, Serialize)]
2018-09-02 13:34:48 +02:00
pub struct Media {
pub id: i32,
pub file_path: String,
pub alt_text: String,
pub is_remote: bool,
pub remote_url: Option<String>,
pub sensitive: bool,
2018-09-02 22:55:42 +02:00
pub content_warning: Option<String>,
pub owner_id: i32,
2018-09-02 13:34:48 +02:00
}
#[derive(Insertable)]
#[table_name = "medias"]
pub struct NewMedia {
pub file_path: String,
pub alt_text: String,
pub is_remote: bool,
pub remote_url: Option<String>,
pub sensitive: bool,
2018-09-02 22:55:42 +02:00
pub content_warning: Option<String>,
pub owner_id: i32,
2018-09-02 13:34:48 +02:00
}
#[derive(PartialEq)]
pub enum MediaCategory {
Image,
Audio,
Video,
Unknown,
}
2018-09-02 13:34:48 +02:00
impl Media {
insert!(medias, NewMedia);
get!(medias);
2018-09-02 22:55:42 +02:00
list_by!(medias, for_user, owner_id as i32);
pub fn list_all_medias(conn: &Connection) -> Vec<Media> {
medias::table
.load::<Media>(conn)
.expect("Media::list_all_medias: loading error")
}
pub fn category(&self) -> MediaCategory {
match self
.file_path
.rsplitn(2, '.')
.next()
.expect("Media::category: extension error")
{
"png" | "jpg" | "jpeg" | "gif" | "svg" => MediaCategory::Image,
"mp3" | "wav" | "flac" => MediaCategory::Audio,
"mp4" | "avi" | "webm" | "mov" => MediaCategory::Video,
_ => MediaCategory::Unknown,
}
}
pub fn preview_html(&self, conn: &Connection) -> SafeString {
let url = self.url(conn);
match self.category() {
MediaCategory::Image => SafeString::new(&format!(
r#"<img src="{}" alt="{}" title="{}" class=\"preview\">"#,
url, escape(&self.alt_text), escape(&self.alt_text)
)),
MediaCategory::Audio => SafeString::new(&format!(
r#"<audio src="{}" title="{}" class="preview"></audio>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Video => SafeString::new(&format!(
r#"<video src="{}" title="{}" class="preview"></video>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Unknown => SafeString::new(""),
}
}
pub fn html(&self, conn: &Connection) -> SafeString {
let url = self.url(conn);
match self.category() {
MediaCategory::Image => SafeString::new(&format!(
r#"<img src="{}" alt="{}" title="{}">"#,
url, escape(&self.alt_text), escape(&self.alt_text)
)),
MediaCategory::Audio => SafeString::new(&format!(
r#"<audio src="{}" title="{}"></audio>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Video => SafeString::new(&format!(
r#"<video src="{}" title="{}"></video>"#,
url, escape(&self.alt_text)
)),
MediaCategory::Unknown => SafeString::new(""),
}
}
pub fn markdown(&self, conn: &Connection) -> SafeString {
let url = self.url(conn);
match self.category() {
MediaCategory::Image => SafeString::new(&format!("![{}]({})", escape(&self.alt_text), url)),
MediaCategory::Audio | MediaCategory::Video => self.html(conn),
MediaCategory::Unknown => SafeString::new(""),
}
2018-09-02 22:55:42 +02:00
}
pub fn url(&self, conn: &Connection) -> String {
if self.is_remote {
self.remote_url.clone().unwrap_or_default()
} else {
ap_url(&format!(
"{}/{}",
Instance::get_local(conn)
.expect("Media::url: local instance not found error")
.public_domain,
self.file_path
))
}
2018-09-02 22:55:42 +02:00
}
2018-09-02 23:10:15 +02:00
pub fn delete(&self, conn: &Connection) {
if !self.is_remote {
fs::remove_file(self.file_path.as_str()).expect("Media::delete: file deletion error");
}
diesel::delete(self)
.execute(conn)
.expect("Media::delete: database entry deletion error");
2018-09-02 23:10:15 +02:00
}
2018-12-02 19:07:36 +01:00
pub fn save_remote(conn: &Connection, url: String, user: &User) -> Result<Media, ()> {
if url.contains(&['<', '>', '"'][..]) {
Err(())
} else {
Ok(Media::insert(
conn,
NewMedia {
file_path: String::new(),
alt_text: String::new(),
is_remote: true,
remote_url: Some(url),
sensitive: false,
content_warning: None,
owner_id: user.id,
},
))
}
}
pub fn set_owner(&self, conn: &Connection, user: &User) {
diesel::update(self)
.set(medias::owner_id.eq(user.id))
.execute(conn)
.expect("Media::set_owner: owner update error");
}
2018-10-31 10:40:20 +01:00
// TODO: merge with save_remote?
pub fn from_activity(conn: &Connection, image: &Image) -> Option<Media> {
2018-10-31 10:40:20 +01:00
let remote_url = image.object_props.url_string().ok()?;
let ext = remote_url
.rsplit('.')
.next()
.map(|ext| ext.to_owned())
.unwrap_or_else(|| String::from("png"));
let path =
Path::new("static")
.join("media")
.join(format!("{}.{}", GUID::rand().to_string(), ext));
2018-10-31 10:40:20 +01:00
let mut dest = fs::File::create(path.clone()).ok()?;
reqwest::get(remote_url.as_str())
.ok()?
.copy_to(&mut dest)
.ok()?;
Some(Media::insert(
conn,
NewMedia {
file_path: path.to_str()?.to_string(),
alt_text: image.object_props.content_string().ok()?,
2018-12-02 19:07:36 +01:00
is_remote: false,
remote_url: None,
sensitive: image.object_props.summary_string().is_ok(),
content_warning: image.object_props.summary_string().ok(),
owner_id: User::from_url(
conn,
image
.object_props
.attributed_to_link_vec::<Id>()
.ok()?
.into_iter()
.next()?
.as_ref(),
)?.id,
},
))
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use diesel::Connection;
use std::env::{current_dir, set_current_dir};
use std::fs;
use std::path::Path;
use tests::db;
use users::tests as usersTests;
use Connection as Conn;
pub(crate) fn fill_database(conn: &Conn) -> (Vec<User>, Vec<Media>) {
let mut wd = current_dir().unwrap().to_path_buf();
while wd.pop() {
if wd.join(".git").exists() {
set_current_dir(wd).unwrap();
break;
}
}
let users = usersTests::fill_database(conn);
let user_one = users[0].id;
let user_two = users[1].id;
let f1 = "static/media/1.png".to_owned();
let f2 = "static/media/2.mp3".to_owned();
fs::write(f1.clone(), []).unwrap();
fs::write(f2.clone(), []).unwrap();
(users, vec![
NewMedia {
file_path: f1,
alt_text: "some alt".to_owned(),
is_remote: false,
remote_url: None,
sensitive: false,
content_warning: None,
owner_id: user_one,
},
NewMedia {
file_path: f2,
alt_text: "alt message".to_owned(),
is_remote: false,
remote_url: None,
sensitive: true,
content_warning: Some("Content warning".to_owned()),
owner_id: user_one,
},
NewMedia {
file_path: "".to_owned(),
alt_text: "another alt".to_owned(),
is_remote: true,
remote_url: Some("https://example.com/".to_owned()),
sensitive: false,
content_warning: None,
owner_id: user_two,
},
].into_iter()
.map(|nm| Media::insert(conn, nm))
.collect())
}
pub(crate) fn clean(conn: &Conn) {
//used to remove files generated by tests
for media in Media::list_all_medias(conn) {
media.delete(conn);
}
}
//set_owner
#[test]
fn delete() {
let conn = &db();
conn.test_transaction::<_, (), _>(|| {
let user = fill_database(conn).0[0].id;
let path = "static/media/test_deletion".to_owned();
fs::write(path.clone(), []).unwrap();
let media = Media::insert(
conn,
NewMedia {
file_path: path.clone(),
alt_text: "alt message".to_owned(),
is_remote: false,
remote_url: None,
sensitive: false,
content_warning: None,
owner_id: user,
},
);
assert!(Path::new(&path).exists());
media.delete(conn);
assert!(!Path::new(&path).exists());
clean(conn);
Ok(())
});
}
#[test]
fn set_owner() {
let conn = &db();
conn.test_transaction::<_, (), _>(|| {
let (users, _) = fill_database(conn);
let u1 = &users[0];
let u2 = &users[1];
let path = "static/media/test_set_owner".to_owned();
fs::write(path.clone(), []).unwrap();
let media = Media::insert(
conn,
NewMedia {
file_path: path.clone(),
alt_text: "alt message".to_owned(),
is_remote: false,
remote_url: None,
sensitive: false,
content_warning: None,
owner_id: u1.id,
},
);
assert!(
Media::for_user(conn, u1.id)
.iter()
.any(|m| m.id == media.id)
);
assert!(
!Media::for_user(conn, u2.id)
.iter()
.any(|m| m.id == media.id)
);
media.set_owner(conn, u2);
assert!(
!Media::for_user(conn, u1.id)
.iter()
.any(|m| m.id == media.id)
);
assert!(
Media::for_user(conn, u2.id)
.iter()
.any(|m| m.id == media.id)
);
clean(conn);
Ok(())
});
2018-10-31 10:40:20 +01:00
}
2018-09-02 13:34:48 +02:00
}