mirror of
https://git.asonix.dog/asonix/pict-rs
synced 2024-12-22 11:21:24 +00:00
Include file extension in identifiers
This commit is contained in:
parent
d13f7fe969
commit
04dcc9a0c8
10 changed files with 96 additions and 34 deletions
|
@ -59,7 +59,9 @@ impl Backgrounded {
|
||||||
});
|
});
|
||||||
|
|
||||||
// use octet-stream, we don't know the upload's real type yet
|
// use octet-stream, we don't know the upload's real type yet
|
||||||
let identifier = store.save_stream(stream, APPLICATION_OCTET_STREAM).await?;
|
let identifier = store
|
||||||
|
.save_stream(stream, APPLICATION_OCTET_STREAM, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
self.identifier = Some(identifier);
|
self.identifier = Some(identifier);
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,10 @@ impl Details {
|
||||||
(*self.inner.content_type).clone()
|
(*self.inner.content_type).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn file_extension(&self) -> &'static str {
|
||||||
|
self.inner.format.file_extension()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn system_time(&self) -> std::time::SystemTime {
|
pub(crate) fn system_time(&self) -> std::time::SystemTime {
|
||||||
self.inner.created_at.into()
|
self.inner.created_at.into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ impl ImageFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) const fn file_extension(self) -> &'static str {
|
pub(crate) const fn file_extension(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Avif => ".avif",
|
Self::Avif => ".avif",
|
||||||
Self::Jpeg => ".jpeg",
|
Self::Jpeg => ".jpeg",
|
||||||
|
|
|
@ -133,7 +133,11 @@ async fn process<S: Store + 'static>(
|
||||||
|
|
||||||
let identifier = state
|
let identifier = state
|
||||||
.store
|
.store
|
||||||
.save_stream(bytes.into_io_stream(), details.media_type())
|
.save_stream(
|
||||||
|
bytes.into_io_stream(),
|
||||||
|
details.media_type(),
|
||||||
|
Some(details.file_extension()),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Err(VariantAlreadyExists) = state
|
if let Err(VariantAlreadyExists) = state
|
||||||
|
@ -173,7 +177,7 @@ where
|
||||||
.await?
|
.await?
|
||||||
.ok_or(UploadError::MissingIdentifier)?;
|
.ok_or(UploadError::MissingIdentifier)?;
|
||||||
|
|
||||||
let (reader, media_type) =
|
let (reader, media_type, file_extension) =
|
||||||
if let Some(processable_format) = original_details.internal_format().processable_format() {
|
if let Some(processable_format) = original_details.internal_format().processable_format() {
|
||||||
let thumbnail_format = state.config.media.image.format.unwrap_or(ImageFormat::Webp);
|
let thumbnail_format = state.config.media.image.format.unwrap_or(ImageFormat::Webp);
|
||||||
|
|
||||||
|
@ -185,6 +189,7 @@ where
|
||||||
(
|
(
|
||||||
process.drive_with_stream(stream),
|
process.drive_with_stream(stream),
|
||||||
thumbnail_format.media_type(),
|
thumbnail_format.media_type(),
|
||||||
|
thumbnail_format.file_extension(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let thumbnail_format = match state.config.media.image.format {
|
let thumbnail_format = match state.config.media.image.format {
|
||||||
|
@ -205,11 +210,20 @@ where
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
(reader, thumbnail_format.media_type())
|
(
|
||||||
|
reader,
|
||||||
|
thumbnail_format.media_type(),
|
||||||
|
thumbnail_format.file_extension(),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let motion_identifier = reader
|
let motion_identifier = reader
|
||||||
.with_stdout(|stdout| async { state.store.save_async_read(stdout, media_type).await })
|
.with_stdout(|stdout| async {
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.save_async_read(stdout, media_type, Some(file_extension))
|
||||||
|
.await
|
||||||
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
state
|
state
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub(super) enum ThumbnailFormat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThumbnailFormat {
|
impl ThumbnailFormat {
|
||||||
const fn as_ffmpeg_codec(self) -> &'static str {
|
const fn ffmpeg_codec(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Jpeg => "mjpeg",
|
Self::Jpeg => "mjpeg",
|
||||||
Self::Png => "png",
|
Self::Png => "png",
|
||||||
|
@ -26,7 +26,7 @@ impl ThumbnailFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn to_file_extension(self) -> &'static str {
|
pub(super) const fn file_extension(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Jpeg => ".jpeg",
|
Self::Jpeg => ".jpeg",
|
||||||
Self::Png => ".png",
|
Self::Png => ".png",
|
||||||
|
@ -34,7 +34,7 @@ impl ThumbnailFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn as_ffmpeg_format(self) -> &'static str {
|
const fn ffmpeg_format(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Jpeg | Self::Png => "image2",
|
Self::Jpeg | Self::Png => "image2",
|
||||||
Self::Webp => "webp",
|
Self::Webp => "webp",
|
||||||
|
@ -57,7 +57,7 @@ pub(super) async fn thumbnail<S: Store>(
|
||||||
input_format: InternalVideoFormat,
|
input_format: InternalVideoFormat,
|
||||||
format: ThumbnailFormat,
|
format: ThumbnailFormat,
|
||||||
) -> Result<ProcessRead, FfMpegError> {
|
) -> Result<ProcessRead, FfMpegError> {
|
||||||
let output_file = state.tmp_dir.tmp_file(Some(format.to_file_extension()));
|
let output_file = state.tmp_dir.tmp_file(Some(format.file_extension()));
|
||||||
|
|
||||||
crate::store::file_store::safe_create_parent(&output_file)
|
crate::store::file_store::safe_create_parent(&output_file)
|
||||||
.await
|
.await
|
||||||
|
@ -90,9 +90,9 @@ pub(super) async fn thumbnail<S: Store>(
|
||||||
"-frames:v".as_ref(),
|
"-frames:v".as_ref(),
|
||||||
"1".as_ref(),
|
"1".as_ref(),
|
||||||
"-codec".as_ref(),
|
"-codec".as_ref(),
|
||||||
format.as_ffmpeg_codec().as_ref(),
|
format.ffmpeg_codec().as_ref(),
|
||||||
"-f".as_ref(),
|
"-f".as_ref(),
|
||||||
format.as_ffmpeg_format().as_ref(),
|
format.ffmpeg_format().as_ref(),
|
||||||
output_path,
|
output_path,
|
||||||
],
|
],
|
||||||
&[],
|
&[],
|
||||||
|
|
|
@ -88,7 +88,11 @@ where
|
||||||
|
|
||||||
state
|
state
|
||||||
.store
|
.store
|
||||||
.save_async_read(hasher_reader, input_type.media_type())
|
.save_async_read(
|
||||||
|
hasher_reader,
|
||||||
|
input_type.media_type(),
|
||||||
|
Some(input_type.file_extension()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map(move |identifier| (hash_state, identifier))
|
.map(move |identifier| (hash_state, identifier))
|
||||||
})
|
})
|
||||||
|
@ -131,7 +135,11 @@ where
|
||||||
|
|
||||||
let identifier = state
|
let identifier = state
|
||||||
.store
|
.store
|
||||||
.save_async_read(hasher_reader, input_type.media_type())
|
.save_async_read(
|
||||||
|
hasher_reader,
|
||||||
|
input_type.media_type(),
|
||||||
|
Some(input_type.file_extension()),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let details = Details::danger_dummy(input_type);
|
let details = Details::danger_dummy(input_type);
|
||||||
|
|
|
@ -409,7 +409,7 @@ where
|
||||||
|
|
||||||
let new_identifier = to
|
let new_identifier = to
|
||||||
.store
|
.store
|
||||||
.save_stream(stream, details.media_type())
|
.save_stream(stream, details.media_type(), Some(details.file_extension()))
|
||||||
.await
|
.await
|
||||||
.map_err(MigrateError::To)?;
|
.map_err(MigrateError::To)?;
|
||||||
|
|
||||||
|
|
20
src/store.rs
20
src/store.rs
|
@ -89,6 +89,7 @@ pub(crate) trait Store: Clone + Debug {
|
||||||
&self,
|
&self,
|
||||||
reader: Reader,
|
reader: Reader,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
Reader: AsyncRead;
|
Reader: AsyncRead;
|
||||||
|
@ -97,6 +98,7 @@ pub(crate) trait Store: Clone + Debug {
|
||||||
&self,
|
&self,
|
||||||
stream: S,
|
stream: S,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = std::io::Result<Bytes>>;
|
S: Stream<Item = std::io::Result<Bytes>>;
|
||||||
|
@ -148,22 +150,24 @@ where
|
||||||
&self,
|
&self,
|
||||||
reader: Reader,
|
reader: Reader,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
Reader: AsyncRead,
|
Reader: AsyncRead,
|
||||||
{
|
{
|
||||||
T::save_async_read(self, reader, content_type).await
|
T::save_async_read(self, reader, content_type, extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_stream<S>(
|
async fn save_stream<S>(
|
||||||
&self,
|
&self,
|
||||||
stream: S,
|
stream: S,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = std::io::Result<Bytes>>,
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
{
|
{
|
||||||
T::save_stream(self, stream, content_type).await
|
T::save_stream(self, stream, content_type, extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_url(&self, identifier: &Arc<str>) -> Option<url::Url> {
|
fn public_url(&self, identifier: &Arc<str>) -> Option<url::Url> {
|
||||||
|
@ -211,22 +215,24 @@ where
|
||||||
&self,
|
&self,
|
||||||
reader: Reader,
|
reader: Reader,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
Reader: AsyncRead,
|
Reader: AsyncRead,
|
||||||
{
|
{
|
||||||
T::save_async_read(self, reader, content_type).await
|
T::save_async_read(self, reader, content_type, extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_stream<S>(
|
async fn save_stream<S>(
|
||||||
&self,
|
&self,
|
||||||
stream: S,
|
stream: S,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = std::io::Result<Bytes>>,
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
{
|
{
|
||||||
T::save_stream(self, stream, content_type).await
|
T::save_stream(self, stream, content_type, extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_url(&self, identifier: &Arc<str>) -> Option<url::Url> {
|
fn public_url(&self, identifier: &Arc<str>) -> Option<url::Url> {
|
||||||
|
@ -274,22 +280,24 @@ where
|
||||||
&self,
|
&self,
|
||||||
reader: Reader,
|
reader: Reader,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
Reader: AsyncRead,
|
Reader: AsyncRead,
|
||||||
{
|
{
|
||||||
T::save_async_read(self, reader, content_type).await
|
T::save_async_read(self, reader, content_type, extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_stream<S>(
|
async fn save_stream<S>(
|
||||||
&self,
|
&self,
|
||||||
stream: S,
|
stream: S,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = std::io::Result<Bytes>>,
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
{
|
{
|
||||||
T::save_stream(self, stream, content_type).await
|
T::save_stream(self, stream, content_type, extension).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn public_url(&self, identifier: &Arc<str>) -> Option<url::Url> {
|
fn public_url(&self, identifier: &Arc<str>) -> Option<url::Url> {
|
||||||
|
|
|
@ -56,13 +56,14 @@ impl Store for FileStore {
|
||||||
&self,
|
&self,
|
||||||
reader: Reader,
|
reader: Reader,
|
||||||
_content_type: mime::Mime,
|
_content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
Reader: AsyncRead,
|
Reader: AsyncRead,
|
||||||
{
|
{
|
||||||
let mut reader = std::pin::pin!(reader);
|
let mut reader = std::pin::pin!(reader);
|
||||||
|
|
||||||
let path = self.next_file();
|
let path = self.next_file(extension);
|
||||||
|
|
||||||
if let Err(e) = self.safe_save_reader(&path, &mut reader).await {
|
if let Err(e) = self.safe_save_reader(&path, &mut reader).await {
|
||||||
self.safe_remove_file(&path).await?;
|
self.safe_remove_file(&path).await?;
|
||||||
|
@ -76,11 +77,12 @@ impl Store for FileStore {
|
||||||
&self,
|
&self,
|
||||||
stream: S,
|
stream: S,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = std::io::Result<Bytes>>,
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
{
|
{
|
||||||
self.save_async_read(StreamReader::new(stream), content_type)
|
self.save_async_read(StreamReader::new(stream), content_type, extension)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,9 +171,14 @@ impl FileStore {
|
||||||
self.root_dir.join(file_id.as_ref())
|
self.root_dir.join(file_id.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_file(&self) -> PathBuf {
|
fn next_file(&self, extension: Option<&str>) -> PathBuf {
|
||||||
let target_path = crate::file_path::generate_disk(self.root_dir.clone());
|
let target_path = crate::file_path::generate_disk(self.root_dir.clone());
|
||||||
let filename = uuid::Uuid::new_v4().to_string();
|
let file_id = uuid::Uuid::new_v4().to_string();
|
||||||
|
let filename = if let Some(ext) = extension {
|
||||||
|
file_id + ext
|
||||||
|
} else {
|
||||||
|
file_id
|
||||||
|
};
|
||||||
|
|
||||||
target_path.join(filename)
|
target_path.join(filename)
|
||||||
}
|
}
|
||||||
|
|
|
@ -211,12 +211,17 @@ impl Store for ObjectStore {
|
||||||
&self,
|
&self,
|
||||||
reader: Reader,
|
reader: Reader,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
Reader: AsyncRead,
|
Reader: AsyncRead,
|
||||||
{
|
{
|
||||||
self.save_stream(ReaderStream::with_capacity(reader, 1024 * 64), content_type)
|
self.save_stream(
|
||||||
.await
|
ReaderStream::with_capacity(reader, 1024 * 64),
|
||||||
|
content_type,
|
||||||
|
extension,
|
||||||
|
)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
|
@ -224,14 +229,18 @@ impl Store for ObjectStore {
|
||||||
&self,
|
&self,
|
||||||
stream: S,
|
stream: S,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<Arc<str>, StoreError>
|
) -> Result<Arc<str>, StoreError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = std::io::Result<Bytes>>,
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
{
|
{
|
||||||
match self.start_upload(stream, content_type.clone()).await? {
|
match self
|
||||||
|
.start_upload(stream, content_type.clone(), extension)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
UploadState::Single(first_chunk) => {
|
UploadState::Single(first_chunk) => {
|
||||||
let (req, object_id) = self
|
let (req, object_id) = self
|
||||||
.put_object_request(first_chunk.len(), content_type)
|
.put_object_request(first_chunk.len(), content_type, extension)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let response = req
|
let response = req
|
||||||
|
@ -447,6 +456,7 @@ impl ObjectStore {
|
||||||
&self,
|
&self,
|
||||||
stream: S,
|
stream: S,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<UploadState, StoreError>
|
) -> Result<UploadState, StoreError>
|
||||||
where
|
where
|
||||||
S: Stream<Item = std::io::Result<Bytes>>,
|
S: Stream<Item = std::io::Result<Bytes>>,
|
||||||
|
@ -461,7 +471,9 @@ impl ObjectStore {
|
||||||
|
|
||||||
let mut first_chunk = Some(first_chunk);
|
let mut first_chunk = Some(first_chunk);
|
||||||
|
|
||||||
let (req, object_id) = self.create_multipart_request(content_type).await?;
|
let (req, object_id) = self
|
||||||
|
.create_multipart_request(content_type, extension)
|
||||||
|
.await?;
|
||||||
let response = req
|
let response = req
|
||||||
.send()
|
.send()
|
||||||
.with_metrics(crate::init_metrics::OBJECT_STORAGE_CREATE_MULTIPART_REQUEST)
|
.with_metrics(crate::init_metrics::OBJECT_STORAGE_CREATE_MULTIPART_REQUEST)
|
||||||
|
@ -574,8 +586,9 @@ impl ObjectStore {
|
||||||
&self,
|
&self,
|
||||||
length: usize,
|
length: usize,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<(RequestBuilder, Arc<str>), StoreError> {
|
) -> Result<(RequestBuilder, Arc<str>), StoreError> {
|
||||||
let path = self.next_file();
|
let path = self.next_file(extension);
|
||||||
|
|
||||||
let mut action = self.bucket.put_object(Some(&self.credentials), &path);
|
let mut action = self.bucket.put_object(Some(&self.credentials), &path);
|
||||||
|
|
||||||
|
@ -592,8 +605,9 @@ impl ObjectStore {
|
||||||
async fn create_multipart_request(
|
async fn create_multipart_request(
|
||||||
&self,
|
&self,
|
||||||
content_type: mime::Mime,
|
content_type: mime::Mime,
|
||||||
|
extension: Option<&str>,
|
||||||
) -> Result<(RequestBuilder, Arc<str>), StoreError> {
|
) -> Result<(RequestBuilder, Arc<str>), StoreError> {
|
||||||
let path = self.next_file();
|
let path = self.next_file(extension);
|
||||||
|
|
||||||
let mut action = self
|
let mut action = self
|
||||||
.bucket
|
.bucket
|
||||||
|
@ -763,9 +777,14 @@ impl ObjectStore {
|
||||||
self.build_request(action)
|
self.build_request(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_file(&self) -> String {
|
fn next_file(&self, extension: Option<&str>) -> String {
|
||||||
let path = crate::file_path::generate_object();
|
let path = crate::file_path::generate_object();
|
||||||
let filename = uuid::Uuid::new_v4().to_string();
|
let file_id = uuid::Uuid::new_v4().to_string();
|
||||||
|
let filename = if let Some(ext) = extension {
|
||||||
|
file_id + ext
|
||||||
|
} else {
|
||||||
|
file_id
|
||||||
|
};
|
||||||
|
|
||||||
format!("{path}/{filename}")
|
format!("{path}/{filename}")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue