Initial commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
use anyhow::Context;
|
||||
use axum::{body::Body, extract::State, http::StatusCode};
|
||||
use futures::{StreamExt, future::ready, stream};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::{io::AsyncWriteExt, process::Command, task::block_in_place};
|
||||
|
||||
use crate::{SharedState, util::e};
|
||||
|
||||
pub async fn flatpak_bundle(
|
||||
State(state): State<SharedState>,
|
||||
body: Body,
|
||||
) -> Result<(), StatusCode> {
|
||||
let mut stream = body.into_data_stream();
|
||||
|
||||
// Get first chunks
|
||||
let Some(Ok(first_chunk)) = stream.next().await else {
|
||||
tracing::warn!("Unexpected EOF");
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
};
|
||||
|
||||
// Sanity check that it looks like a flatpak
|
||||
// Technically not correct to require that the first chunk is 7 bytes or more, but who cares.
|
||||
if !first_chunk.starts_with(b"flatpak") {
|
||||
tracing::warn!("Not a valid flatpak bundle. Missing 'flatpak' magic string.");
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
// Create a tmpfile
|
||||
let (tmp_path, mut tmp_file) = block_in_place(|| {
|
||||
let mut file = NamedTempFile::new()?;
|
||||
file.disable_cleanup(true);
|
||||
let path = file.path().to_path_buf();
|
||||
let file = tokio::fs::File::from_std(file.into_file());
|
||||
std::io::Result::Ok((path, file))
|
||||
})
|
||||
.context("Failed to create tmpfile")
|
||||
.map_err(e(StatusCode::BAD_REQUEST))?;
|
||||
|
||||
// Write bundle to tmpfile
|
||||
tracing::info!("Downloading flatpak to {tmp_path:?}");
|
||||
let mut stream = stream::once(ready(Ok(first_chunk))).chain(stream);
|
||||
loop {
|
||||
let Some(chunk) = stream.next().await else {
|
||||
break;
|
||||
};
|
||||
|
||||
let chunk = chunk
|
||||
.context("Stream error")
|
||||
.map_err(e(StatusCode::BAD_REQUEST))?;
|
||||
|
||||
tmp_file
|
||||
.write_all(&chunk)
|
||||
.await
|
||||
.context("Tmpfile write error")
|
||||
.map_err(e(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
}
|
||||
|
||||
let repo = state.repo.lock().await;
|
||||
|
||||
// Import bundle into repository
|
||||
tracing::info!("Importing {tmp_path:?} into repo {:?}", &*repo);
|
||||
let output = Command::new("flatpak")
|
||||
.arg("build-import-bundle")
|
||||
.args([&*repo, &tmp_path])
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to execute flatpak build-import-bundle")
|
||||
.map_err(e(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
if !output.status.success() {
|
||||
tracing::error!("flatpak build-import-bundle failed");
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
for line in stdout.lines() {
|
||||
tracing::error!("[stdout] {line}");
|
||||
}
|
||||
for line in stderr.lines() {
|
||||
tracing::error!("[stderr] {line}");
|
||||
}
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
drop(tmp_file);
|
||||
if let Err(e) = tokio::fs::remove_file(&tmp_path).await {
|
||||
tracing::warn!("Failed to remove tmpfile at {tmp_path:?}: {e}");
|
||||
}
|
||||
|
||||
tracing::info!("Success");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user