Skip to content

Commit 7c2ec62

Browse files
committed
fix db bug
1 parent b99392f commit 7c2ec62

File tree

4 files changed

+52
-89
lines changed

4 files changed

+52
-89
lines changed

api/src/database/mod.rs

Lines changed: 35 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -48,35 +48,25 @@ impl Database {
4848
.transpose()
4949
}
5050

51-
pub async fn fetch_count(&self, file_path: &FilePath) -> ApiResult<Option<MetaDataFile>> {
52-
let mut output = None;
53-
self
51+
pub fn update(&self, file_path: &FilePath, old: MetaDataFile, new: MetaDataFile) -> ApiResult {
52+
let old = IVec::try_from(old)?;
53+
let new = IVec::try_from(new)?;
54+
let file_path = IVec::try_from(file_path)?;
55+
let result = self
5456
.inner
55-
.fetch_and_update(IVec::try_from(file_path)?, |value| {
56-
value
57-
.map(|val| {
58-
MetaDataFile::try_from(val)
59-
.map(|mut meta| {
60-
meta.count_downloads += 1;
61-
let val = IVec::try_from(&meta);
62-
output = Some(meta);
63-
val
64-
.map_err(|err| {
65-
tracing::error!("Covnert MetaDataFile to IVec unsuccessfully: {err}");
66-
err
67-
})
68-
.ok()
69-
})
70-
.map_err(|err| {
71-
tracing::error!("Covnert IVec to MetaDataFile unsuccessfully: {err}");
72-
err
73-
})
74-
.ok()
75-
})
76-
.flatten()
77-
.flatten()
78-
})?;
79-
Ok(output)
57+
.compare_and_swap(&file_path, Some(old), Some(new))?;
58+
match result {
59+
Ok(_) => Ok(()),
60+
Err(err) if err.current.is_some() => Err(ApiError::BadRequestError(
61+
"Update meta data failed".to_string(),
62+
)),
63+
Err(err) => {
64+
tracing::error!("Compare and swap error: {err}");
65+
Err(ApiError::DatabaseError(sled::Error::ReportableBug(
66+
"Storing the meta data file in the database faild.".to_string(),
67+
)))
68+
}
69+
}
8070
}
8171

8272
pub fn exist(&self, path: &FilePath) -> ApiResult<bool> {
@@ -95,11 +85,11 @@ impl Database {
9585
let expire = (expire_time, path);
9686
match self.expires.write() {
9787
Ok(mut guard) => {
98-
let is_gc_notify = guard
99-
.iter()
100-
.next()
101-
.filter(|(exp, _)| *exp < Utc::now())
102-
.is_some();
88+
let is_gc_notify = if let Some((first_expire, _)) = guard.iter().next() {
89+
*first_expire > expire_time
90+
} else {
91+
true
92+
};
10393
guard.insert(expire.clone());
10494
drop(guard);
10595
if is_gc_notify {
@@ -113,7 +103,9 @@ impl Database {
113103
}
114104
}
115105
Err(err) if err.current.is_some() => {
116-
return Err(ApiError::ResourceExistsError("File path exists".to_string()));
106+
return Err(ApiError::ResourceExistsError(
107+
"File path exists".to_string(),
108+
));
117109
}
118110
Err(err) => {
119111
tracing::error!("Compare and swap error: {err}");
@@ -138,7 +130,7 @@ impl Database {
138130
guard.remove(&(meta.expiration_date, path));
139131
}
140132
Err(err) => {
141-
tracing::error!("Get expires lock unsuccessfully: {err}");
133+
tracing::error!("Failed to acquire expires lock: {err}");
142134
}
143135
}
144136
Ok(Some(meta))
@@ -148,21 +140,20 @@ impl Database {
148140
}
149141

150142
pub async fn purge(&self) -> ApiResult<Option<Duration>> {
151-
let now = Utc::now();
152143
match self.expires.write() {
153144
Ok(mut guard) => {
154145
let expires = &mut *guard;
155146
while let Some((expire_date, path)) = expires.iter().next().cloned() {
156-
if expire_date < now {
147+
if expire_date < Utc::now() {
157148
self.inner.remove(&IVec::try_from(&path)?)?;
158149
expires.remove(&(expire_date, path));
159150
} else {
160-
return Ok(Some((expire_date - now).to_std()?));
151+
return Ok(Some((expire_date - Utc::now()).to_std()?));
161152
}
162153
}
163154
}
164155
Err(err) => {
165-
tracing::error!("Get expires lock unsuccessfully: {err}");
156+
tracing::error!("Failed to acquire expires lock: {err}");
166157
return Err(ApiError::LockError(err.to_string()));
167158
}
168159
}
@@ -331,7 +322,7 @@ mod tests {
331322

332323
#[test_context(StateTestContext)]
333324
#[tokio::test]
334-
async fn test_store_file_and_fetch_count(ctx: &mut StateTestContext) {
325+
async fn test_store_and_update_file(ctx: &mut StateTestContext) {
335326
let path: FilePath = Faker.fake();
336327
let meta = MetaDataFile {
337328
created_at: Utc::now(),
@@ -347,34 +338,14 @@ mod tests {
347338
.store(path.clone(), meta.clone())
348339
.await
349340
.unwrap();
350-
let result = ctx.state.db.fetch_count(&path).await.unwrap().unwrap();
351-
assert_eq!(result.created_at, meta.created_at);
352-
assert_eq!(result.expiration_date, meta.expiration_date);
353-
assert_eq!(result.secret, meta.secret);
354-
assert_eq!(result.max_download, meta.max_download);
355-
assert_eq!(result.count_downloads, meta.count_downloads);
356-
}
357-
358-
#[test_context(StateTestContext)]
359-
#[tokio::test]
360-
async fn test_store_file_and_double_fetch_count(ctx: &mut StateTestContext) {
361-
let path: FilePath = Faker.fake();
362-
let meta = MetaDataFile {
363-
created_at: Utc::now(),
364-
expiration_date: Utc::now() + chrono::Duration::seconds(10),
365-
secret: None,
366-
delete_manually: true,
367-
max_download: None,
368-
count_downloads: 0,
369-
};
341+
let mut updated_meta = meta.clone();
342+
updated_meta.count_downloads += 1;
370343
ctx
371344
.state
372345
.db
373-
.store(path.clone(), meta.clone())
374-
.await
346+
.update(&path, meta.clone(), updated_meta)
375347
.unwrap();
376-
ctx.state.db.fetch_count(&path).await.unwrap().unwrap();
377-
let result = ctx.state.db.fetch_count(&path).await.unwrap().unwrap();
348+
let result = ctx.state.db.fetch(&path).unwrap().unwrap();
378349
assert_eq!(result.created_at, meta.created_at);
379350
assert_eq!(result.expiration_date, meta.expiration_date);
380351
assert_eq!(result.secret, meta.secret);

api/src/service/file.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::configure::ApiConfig;
12
use crate::error::{ApiError, ApiResult, ToApiResult};
23
use crate::util::secret::{Secret, SecretHash};
34
use anyhow::anyhow;
@@ -119,22 +120,26 @@ pub async fn fetch(
119120
file_name: &str,
120121
secret: Option<Secret>,
121122
) -> ApiResult<ServeFile> {
122-
let path = FilePath {
123+
let file_path = FilePath {
123124
code: code.to_string(),
124125
file_name: file_name.to_string(),
125126
};
126-
let meta = state.db.fetch_count(&path).await?.to_result()?;
127-
if let Some(max) = meta.max_download {
128-
if meta.count_downloads >= max {
129-
state.db.delete(path.clone()).await?;
127+
let meta_data = state.db.fetch(&file_path)?.to_result()?;
128+
authorize_user(secret, &meta_data.secret)?;
129+
if let Some(max) = meta_data.max_download {
130+
if meta_data.count_downloads >= max {
131+
state.db.delete(file_path.clone()).await?;
130132
return Err(ApiError::NotFoundError(format!(
131133
"{} not found",
132-
path.url_path()
134+
file_path.url_path()
133135
)));
136+
} else {
137+
let mut updated_meta_data = meta_data.clone();
138+
updated_meta_data.count_downloads += 1;
139+
state.db.update(&file_path, meta_data, updated_meta_data)?;
134140
}
135141
}
136-
authorize_user(secret, &meta.secret)?;
137-
read_file(&state.config.fs.base_dir.join(&path.url_path())).await
142+
Ok(read_file(&state.config, &file_path))
138143
}
139144

140145
pub async fn delete(
@@ -164,8 +169,8 @@ pub async fn delete(
164169
Ok(())
165170
}
166171

167-
pub async fn read_file(file_path: &PathBuf) -> ApiResult<ServeFile> {
168-
Ok(ServeFile::new(file_path))
172+
pub fn read_file(config: &ApiConfig, file_path: &FilePath) -> ServeFile {
173+
ServeFile::new(&file_path.fs_path(&config.fs.base_dir))
169174
}
170175

171176
pub fn authorize_user(secret: Option<Secret>, secret_hash: &Option<SecretHash>) -> ApiResult<()> {

api/tests/api/info_api_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub async fn test_get_info_when_file_not_exist(ctx: &mut ApiTestContext) {
2727
#[tokio::test]
2828
pub async fn test_get_info_when_file_exceed_max_dl(ctx: &mut ApiTestContext) {
2929
let file = ctx.upload_dummy_file(Some(1), None, None, None, None).await;
30-
let (status, _) = ctx.download(&file.path, None).await.unwrap();
30+
let (status, _resp) = ctx.download(&file.path, None).await.unwrap();
3131
assert!(status.is_success());
3232
let (status, resp) = ctx.info(&file.path, None).await.unwrap();
3333
assert_err!(resp, |e: &BodyResponseError| e.error_type == "NOT_FOUND");

sdk/src/client.rs

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use crate::{
88
};
99

1010
use log_derive::logfn;
11-
use multer::Multipart;
1211
use once_cell::sync::Lazy;
1312
use reqwest::StatusCode;
1413

@@ -89,19 +88,7 @@ impl PasteFileClient {
8988
let error = resp.json::<BodyResponseError>().await?;
9089
return Ok((status, ApiResponseResult::Err(error)));
9190
}
92-
let body = resp.bytes().await;
93-
let boundary = std::str::from_utf8(body.as_ref().unwrap());
94-
// TODO FIXME multer::parse_boundary(ct);
95-
let boundary = boundary.unwrap().to_string().lines().next().unwrap()[2..]
96-
.trim()
97-
.to_string();
98-
let stream = futures_util::stream::once(async { body });
99-
let mut mp = Multipart::new(stream, boundary);
100-
let f = mp.next_field().await.unwrap().unwrap();
101-
Ok((
102-
status,
103-
ApiResponseResult::Ok(f.bytes().await.unwrap().to_vec()),
104-
))
91+
Ok((status, ApiResponseResult::Ok(resp.bytes().await?.to_vec())))
10592
}
10693

10794
#[logfn(Info)]

0 commit comments

Comments
 (0)