use std::collections::HashMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use actix_http::header;
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use futures_util::stream::{self};
use crate::core::R;
use crate::prelude2::Render;
#[cfg(target_os = "linux")]
pub async fn progressive_image(file_path: String, request: HttpRequest) -> impl Responder {
use anyhow::anyhow;
use anyhow::Context;
let image = libvips::VipsImage::new_from_file(&file_path)
.context(format!("加载文件异常: {}", file_path))
.map_err(|e| anyhow!(e))?;
let path = Path::new(&file_path);
if let Some(filename) = path.file_name() {
let output_file = format!(
"{}/{}",
crate::commons::temp_dir(),
filename.to_string_lossy()
);
if let Some(ext) = path.extension() {
let _ext = ext.to_string_lossy().trim().to_lowercase();
return if _ext == "jpg" || _ext == "jpeg" {
libvips::ops::jpegsave_with_opts(
&image,
&output_file,
&libvips::ops::JpegsaveOptions {
q: 100,
interlace: true, ..Default::default()
},
)
.context(format!("保存文件异常: {}", output_file))
.map_err(|e| anyhow!(e))?;
request.json(200, R::ok(output_file))
} else if _ext == "png" {
libvips::ops::pngsave_with_opts(
&image,
&output_file,
&libvips::ops::PngsaveOptions {
q: 100,
interlace: true, ..Default::default()
},
)
.context(format!("保存文件异常: {}", output_file))
.map_err(|e| anyhow!(e))?;
request.json(200, R::ok(output_file))
} else {
request.json(200, R::failed(400, "不支持的文件类型, 仅支持: jpeg, png"))
};
}
}
request.json(200, R::failed(400, "未能读取文件名"))
}
#[cfg(not(target_os = "linux"))]
pub async fn progressive_image(file_path: String, request: HttpRequest) -> impl Responder {
let path = Path::new(&file_path);
if let Some(filename) = path.file_name() {
let output_file = format!(
"{}/{}",
crate::commons::temp_dir(),
filename.to_string_lossy()
);
return request.json(200, R::ok(output_file));
}
request.json(200, R::ok(crate::commons::temp_dir()))
}
pub async fn progressive_render(
_query: actix_web::web::Query<HashMap<String, String>>,
req: HttpRequest,
) -> actix_web::Result<HttpResponse, actix_web::Error> {
let file_path = _query.get("file_path").cloned().unwrap_or_default();
let mut file = File::open(&file_path)?;
let metadata = file.metadata()?;
let file_size = metadata.len();
let range_header = req
.headers()
.get(header::RANGE)
.and_then(|v| v.to_str().ok());
let (start, end) = if let Some(range_str) = range_header {
if let Some(range_bytes) = range_str.strip_prefix("bytes=") {
let parts: Vec<&str> = range_bytes.split('-').collect();
let start = parts[0].parse::<u64>().unwrap_or(0);
let end = if parts.len() > 1 && !parts[1].is_empty() {
parts[1].parse::<u64>().unwrap_or(file_size - 1)
} else {
file_size - 1
};
if start >= file_size {
return Ok(HttpResponse::RangeNotSatisfiable()
.append_header((header::CONTENT_RANGE, format!("bytes */{}", file_size)))
.finish());
}
(start, end.min(file_size - 1))
} else {
(0, file_size - 1)
}
} else {
(0, file_size - 1)
};
let chunk_size = 8 * 1024;
file.seek(SeekFrom::Start(start))?;
let total_bytes = end - start + 1;
let stream = stream::unfold((file, start), move |(mut f, mut pos)| async move {
let mut buf = vec![0; chunk_size];
if pos > end {
return None;
}
let remaining = end - pos + 1;
let read_len = remaining.min(chunk_size as u64) as usize;
match f.read(&mut buf[..read_len]) {
Ok(0) => None,
Ok(n) => {
pos += n as u64;
Some((
Ok::<_, std::io::Error>(web::Bytes::copy_from_slice(&buf[..n])),
(f, pos),
))
}
Err(_) => None,
}
});
let mut response = if range_header.is_some() {
HttpResponse::PartialContent()
} else {
HttpResponse::Ok()
};
let content_type = mime_guess::from_path(&file_path)
.first_or_octet_stream()
.to_string();
Ok(response
.append_header((header::CONTENT_TYPE, content_type))
.append_header((header::ACCEPT_RANGES, "bytes"))
.append_header((
header::CONTENT_RANGE,
format!("bytes {}-{}/{}", start, end, file_size),
))
.append_header((header::CONTENT_LENGTH, total_bytes.to_string()))
.streaming(stream))
}