Skip to content

Commit d4e329e

Browse files
fix: file empty on mv (#54)
1 parent 0d50163 commit d4e329e

File tree

5 files changed

+257
-15
lines changed

5 files changed

+257
-15
lines changed

src/backends/azure.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,4 +328,12 @@ impl Repository for AzureRepository {
328328

329329
Ok(result)
330330
}
331+
async fn copy_object(
332+
&self,
333+
_copy_identifier_path: String,
334+
_key: String,
335+
_range: Option<String>,
336+
) -> Result<(), Box<dyn APIError>> {
337+
Ok(())
338+
}
331339
}

src/backends/common.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ pub trait Repository {
8282
delimiter: Option<String>,
8383
max_keys: NonZeroU32,
8484
) -> Result<ListBucketResult, Box<dyn APIError>>;
85+
async fn copy_object(
86+
&self,
87+
copy_identifier_path: String,
88+
key: String,
89+
range: Option<String>,
90+
) -> Result<(), Box<dyn APIError>>;
8591
}
8692

8793
#[derive(Debug, Serialize)]

src/backends/s3.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -617,4 +617,126 @@ impl Repository for S3Repository {
617617
}
618618
}
619619
}
620+
async fn copy_object(
621+
&self,
622+
copy_identifier_path: String,
623+
key: String,
624+
range: Option<String>,
625+
) -> Result<(), Box<dyn APIError>> {
626+
let client: S3Client;
627+
628+
if self.auth_method == "s3_access_key" {
629+
let credentials = rusoto_credential::StaticProvider::new_minimal(
630+
self.access_key_id.clone().unwrap(),
631+
self.secret_access_key.clone().unwrap(),
632+
);
633+
client = S3Client::new_with(
634+
rusoto_core::request::HttpClient::new().unwrap(),
635+
credentials,
636+
self.region.clone(),
637+
);
638+
} else if self.auth_method == "s3_ecs_task_role" {
639+
let credentials = rusoto_credential::ContainerProvider::new();
640+
client = S3Client::new_with(
641+
rusoto_core::request::HttpClient::new().unwrap(),
642+
credentials,
643+
self.region.clone(),
644+
);
645+
} else if self.auth_method == "s3_local" {
646+
let credentials = rusoto_credential::ChainProvider::new();
647+
client = S3Client::new_with(
648+
rusoto_core::request::HttpClient::new().unwrap(),
649+
credentials,
650+
self.region.clone(),
651+
);
652+
} else {
653+
return Err(Box::new(InternalServerError {
654+
message: format!("Internal Server Error"),
655+
}));
656+
}
657+
let request = HeadObjectRequest {
658+
bucket: self.bucket.clone(),
659+
key: format!("{}", copy_identifier_path),
660+
..Default::default()
661+
};
662+
663+
match client.head_object(request).await {
664+
Ok(result) => {
665+
let url_client = reqwest::Client::new();
666+
let url: String;
667+
668+
if self.auth_method == "s3_local" {
669+
url = format!(
670+
"http://localhost:5050/{}/{}",
671+
self.bucket, copy_identifier_path
672+
)
673+
} else {
674+
url = format!(
675+
"https://s3.{}.amazonaws.com/{}/{}",
676+
self.region.name(),
677+
self.bucket,
678+
copy_identifier_path
679+
);
680+
}
681+
682+
let mut request = url_client.get(url);
683+
684+
if let Some(range_value) = range {
685+
request = request.header(RANGE, range_value);
686+
}
687+
688+
match request.send().await {
689+
Ok(response) => {
690+
let content_bytes = response
691+
.bytes()
692+
.await
693+
.unwrap_or_else(|_| bytes::Bytes::from(vec![]));
694+
match self
695+
.put_object(key.clone(), content_bytes, result.content_type)
696+
.await
697+
{
698+
Ok(_put_res) => Ok(()),
699+
Err(err) => {
700+
return Err(err);
701+
}
702+
}
703+
}
704+
Err(error) => {
705+
if error.is_status() {
706+
let code = error.status().unwrap().as_u16();
707+
if code == 404 {
708+
return Err(Box::new(ObjectNotFoundError {
709+
account_id: self.account_id.clone(),
710+
repository_id: self.repository_id.clone(),
711+
key,
712+
}));
713+
}
714+
}
715+
716+
return Err(Box::new(InternalServerError {
717+
message: "Internal Server Error".to_string(),
718+
}));
719+
}
720+
}
721+
}
722+
Err(error) => {
723+
match error {
724+
RusotoError::Unknown(response) => {
725+
if response.status.eq(&404) {
726+
return Err(Box::new(ObjectNotFoundError {
727+
account_id: self.account_id.clone(),
728+
repository_id: self.repository_id.clone(),
729+
key,
730+
}));
731+
}
732+
}
733+
_ => (),
734+
}
735+
736+
Err(Box::new(InternalServerError {
737+
message: format!("Internal Server Error"),
738+
}))
739+
}
740+
}
741+
}
620742
}

src/main.rs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -259,21 +259,36 @@ async fn put_object(
259259
}
260260

261261
if params.part_number.is_none() && params.upload_id.is_none() {
262-
// Found the repository, now try to upload the object
263-
match client
264-
.put_object(
265-
key.clone(),
266-
bytes,
267-
headers
268-
.get(CONTENT_TYPE)
269-
.and_then(|h| h.to_str().ok())
270-
.map(|s| s.to_string()),
271-
)
272-
.await
273-
{
274-
Ok(_) => HttpResponse::NoContent().finish(),
275-
276-
Err(_) => HttpResponse::NotFound().finish(),
262+
// Check if this is a server-side copy operation
263+
if let Some(header_copy_identifier) = req.headers().get("x-amz-copy-source") {
264+
let copy_identifier_path = header_copy_identifier.to_str().unwrap_or("");
265+
match client
266+
.copy_object((&copy_identifier_path).to_string(), key.clone(), None)
267+
.await
268+
{
269+
Ok(_) => HttpResponse::NoContent().finish(),
270+
Err(_) => {
271+
return HttpResponse::InternalServerError()
272+
.body("Failed to store copied object")
273+
}
274+
}
275+
} else {
276+
// Found the repository, now try to upload the object
277+
match client
278+
.put_object(
279+
key.clone(),
280+
bytes,
281+
headers
282+
.get(CONTENT_TYPE)
283+
.and_then(|h| h.to_str().ok())
284+
.map(|s| s.to_string()),
285+
)
286+
.await
287+
{
288+
Ok(_) => HttpResponse::NoContent().finish(),
289+
290+
Err(_) => HttpResponse::NotFound().finish(),
291+
}
277292
}
278293
} else if params.part_number.is_some() && params.upload_id.is_some() {
279294
match client

src/utils/apache_logger.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
2+
use actix_web::{Error, HttpMessage};
3+
use chrono::Local;
4+
use futures::future::{ok, Ready};
5+
use std::future::Future;
6+
use std::pin::Pin;
7+
use std::task::{Context, Poll};
8+
9+
use crate::utils::auth::UserIdentity;
10+
11+
/// Public struct to enable the middleware in your app
12+
pub struct ApacheLogger;
13+
14+
impl<S, B> Transform<S, ServiceRequest> for ApacheLogger
15+
where
16+
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
17+
B: 'static,
18+
{
19+
type Response = ServiceResponse<B>;
20+
type Error = Error;
21+
type Transform = ApacheLoggerMiddleware<S>;
22+
type InitError = ();
23+
type Future = Ready<Result<Self::Transform, Self::InitError>>;
24+
25+
fn new_transform(&self, service: S) -> Self::Future {
26+
ok(ApacheLoggerMiddleware { service })
27+
}
28+
}
29+
30+
/// Middleware implementation that handles request logging in Apache log format
31+
pub struct ApacheLoggerMiddleware<S> {
32+
pub service: S, // Make the field public if you need access to it
33+
}
34+
35+
impl<S, B> Service<ServiceRequest> for ApacheLoggerMiddleware<S>
36+
where
37+
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
38+
B: 'static,
39+
{
40+
type Response = ServiceResponse<B>;
41+
type Error = Error;
42+
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + 'static>>;
43+
44+
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
45+
self.service.poll_ready(cx)
46+
}
47+
48+
fn call(&self, req: ServiceRequest) -> Self::Future {
49+
// Capture the start time
50+
let start_time = Local::now();
51+
let user_identity = req
52+
.extensions_mut()
53+
.get_mut::<UserIdentity>()
54+
.map(|identity| identity.clone()) // If the value exists, clone it
55+
.unwrap_or(UserIdentity { api_key: None }); // Otherwise, provide a default value
56+
57+
let fut = self.service.call(req);
58+
59+
Box::pin(async move {
60+
// Format the time in Apache style: 10/Oct/2000:13:55:36 -0700
61+
let formatted_time = start_time.format("%d/%b/%Y:%H:%M:%S %z").to_string();
62+
63+
let res = fut.await?;
64+
let method = res.request().method().clone();
65+
let path = res.request().uri().clone();
66+
let status = res.response().status();
67+
68+
let client_ip = res
69+
.request()
70+
.connection_info()
71+
.realip_remote_addr()
72+
.unwrap_or("-")
73+
.to_string();
74+
75+
println!(
76+
"{} - {} [{}] \"{} {} HTTP/1.1\" {} 0",
77+
client_ip,
78+
match &user_identity.api_key {
79+
Some(api_key) => api_key.account_id.clone(), // Safely access account_id
80+
None => "default_account_id".to_string(),
81+
},
82+
formatted_time,
83+
method,
84+
path,
85+
status.as_u16()
86+
);
87+
88+
Ok(res)
89+
})
90+
}
91+
}

0 commit comments

Comments
 (0)