92 lines
2.9 KiB
Rust
92 lines
2.9 KiB
Rust
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(())
|
|
}
|