Anki/rslib/src/sync/http_server/media_manager/upload.rs
Damien Elmes dbe51f3802 Split io.rs into separate crate, and use it in proto build
Will be handy to use it in our other scripts in the future too - thanks
Rumo!

Results of benchmarking ./run before and after these crate splits:

- Touching a proto file leads to a slight increase: about +90ms
- Touching an rslib file leads to a bigger decrease, as there's less to
recompile: about -700ms

And ./ninja test is even better: about +200ms and -3800ms.
2023-06-12 15:47:51 +10:00

96 lines
3.5 KiB
Rust

// Copyright: Ankitects Pty Ltd and contributors
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
use std::fs;
use std::io::ErrorKind;
use std::path::Path;
use anki_io::write_file;
use anki_io::FileIoError;
use anki_io::FileIoSnafu;
use anki_io::FileOp;
use snafu::ResultExt;
use tracing::info;
use crate::error;
use crate::sync::error::HttpResult;
use crate::sync::error::OrHttpErr;
use crate::sync::http_server::media_manager::ServerMediaManager;
use crate::sync::media::database::server::entry::upload::UploadedChangeResult;
use crate::sync::media::upload::MediaUploadResponse;
use crate::sync::media::zip::unzip_and_validate_files;
impl ServerMediaManager {
pub fn process_uploaded_changes(
&mut self,
zip_data: Vec<u8>,
) -> HttpResult<MediaUploadResponse> {
let extracted = unzip_and_validate_files(&zip_data).or_bad_request("unzip files")?;
let folder = &self.media_folder;
let mut processed = 0;
let new_usn = self
.db
.with_transaction(|db, meta| {
for change in extracted {
match db.register_uploaded_change(meta, change)? {
UploadedChangeResult::FileAlreadyDeleted { filename } => {
info!(filename, "already deleted");
}
UploadedChangeResult::FileIdentical { filename, sha1 } => {
info!(filename, sha1 = hex::encode(sha1), "already have");
}
UploadedChangeResult::Added {
filename,
data,
sha1,
} => {
info!(filename, sha1 = hex::encode(sha1), "added");
add_or_replace_file(&folder.join(filename), data)?;
}
UploadedChangeResult::Replaced {
filename,
data,
old_sha1,
new_sha1,
} => {
info!(
filename,
old_sha1 = hex::encode(old_sha1),
new_sha1 = hex::encode(new_sha1),
"replaced"
);
add_or_replace_file(&folder.join(filename), data)?;
}
UploadedChangeResult::Removed { filename, sha1 } => {
info!(filename, sha1 = hex::encode(sha1), "removed");
remove_file(&folder.join(filename))?;
}
}
processed += 1;
}
Ok(())
})
.or_internal_err("handle uploaded change")?;
Ok(MediaUploadResponse {
processed,
current_usn: new_usn,
})
}
}
fn add_or_replace_file(path: &Path, data: Vec<u8>) -> error::Result<(), FileIoError> {
write_file(path, data).map_err(Into::into)
}
fn remove_file(path: &Path) -> error::Result<(), FileIoError> {
if let Err(err) = fs::remove_file(path) {
// if transaction was previously aborted, the file may have already been deleted
if err.kind() != ErrorKind::NotFound {
return Err(err).context(FileIoSnafu {
path,
op: FileOp::Remove,
});
}
}
Ok(())
}