Skip to content

Commit

Permalink
more image stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
dadleyy committed Nov 1, 2023
1 parent 21f7535 commit 89c3f74
Show file tree
Hide file tree
Showing 15 changed files with 562 additions and 350 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ orient-beetle-ci.key
env.toml
local-env.toml
.storage
*.png
/*.png
14 changes: 13 additions & 1 deletion src/beetle-pio/lib/redis-events/redis-events.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ class Events final {
// If we ready an array response but the length is -1 or 0,
// we're no longer expecting any messages
if (array_read.size == -1 || array_read.size == 0) {
log_i("empty array received while waiting for message pop");
c.state = NotReceiving{true};
return std::make_pair(c, std::nullopt);
}
Expand Down Expand Up @@ -184,6 +185,16 @@ class Events final {
}
}

if (std::holds_alternative<ReceivingPop>(c.state)) {
ReceivingPop *popping = std::get_if<ReceivingPop>(&c.state);
uint32_t time_diff = time - popping->timeout_start;

if (time_diff > 1000) {
log_i("timeout!");
popping->timeout_start = time;
}
}

return std::make_pair(c, std::nullopt);
}

Expand Down Expand Up @@ -270,7 +281,7 @@ class Events final {
context->device_id_len, context->device_id,
context->device_id_len, context->device_id);
context->client.print(context->outbound);
log_i("wrote auth: '%s'", context->outbound);
log_d("wrote auth: '%s'", context->outbound);
c.authorization_stage = AuthorizationStage::AuthorizationAttempted;
return std::make_pair(c, IdentificationReceived{});
}
Expand Down Expand Up @@ -298,6 +309,7 @@ class Events final {
struct ReceivingPop final {
int32_t payload_count = 0;
int32_t payload_position = 0;
uint32_t timeout_start = 0;
};

struct NotReceiving final {
Expand Down
6 changes: 3 additions & 3 deletions src/beetle-pio/src/xiao-rendering.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ void draw_row(PNGDRAW *draw_context) {
float l = lum(r, g, b);

uint16_t color = GxEPD_WHITE;
if (l < lum(0x7b, 0x7d, 0x7b)) {
if (l < 64) {
color = GxEPD_BLACK;
} else if (l < lum(0xc5, 0xc2, 0xc5)) {
} else if (l < 160) {
color = GxEPD_DARKGREY;
} else if (l < lum(0xaa, 0xaa, 0xaa)) {
} else if (l < 223) {
color = GxEPD_LIGHTGREY;
}

Expand Down
107 changes: 56 additions & 51 deletions src/beetle-srv/src/api/jobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,65 +91,70 @@ pub async fn queue(mut request: tide::Request<super::worker::Worker>) -> tide::R
.content_type()
.ok_or_else(|| tide::Error::from_str(422, "missing content-type"))?;

// TODO[image-upload]: we will circle back to this and make it more graceful:
// 1. handle the parameterization of `device_id` better
// 2. add support for multiple image types & verify the mime "essense" is correct.
if content_type.essence() == "image/jpeg" && request.param("device_id").is_ok() {
let device_id = request.param("device_id").unwrap().to_string();

// TODO: borrow scoping...
{
let worker = request.state();
if worker.user_access(&user.oid, &device_id).await?.is_none() {
return Err(tide::Error::from_str(404, "not-found"));
match content_type.essence() {
image_kind @ "image/jpeg" | image_kind @ "image/png" => {
let device_id = request
.param("device_id")?
// .ok_or_else(|| tide::Error::from_str(422, "bad-id"))
.to_string();

// TODO: borrow scoping...
{
let worker = request.state();
if worker.user_access(&user.oid, &device_id).await?.is_none() {
return Err(tide::Error::from_str(404, "not-found"));
}
}
}

let size = request
.len()
.ok_or_else(|| tide::Error::from_str(422, "missing image upload size"))?;

if size > 800000usize {
return Err(tide::Error::from_str(422, "image too large"));
}

log::debug!("has image upload for device queue '{device_id}' of {size} bytes");
let mut bytes = request.take_body();
let mut storage_dest = std::path::PathBuf::new();
storage_dest.push(&request.state().web_configuration.temp_file_storage);
async_std::fs::create_dir_all(&storage_dest).await.map_err(|error| {
log::error!("unable to ensure temporary file storage dir exists - {error}");
tide::Error::from_str(500, "bad")
})?;
let file_name = uuid::Uuid::new_v4().to_string();
let size = request
.len()
.ok_or_else(|| tide::Error::from_str(422, "missing image upload size"))?;

storage_dest.push(&file_name);
storage_dest.set_extension("jpg");
if size > 800000usize {
return Err(tide::Error::from_str(422, "image too large"));
}

log::info!("writing temporary file to '{storage_dest:?}");
let mut file = async_std::fs::OpenOptions::new()
.write(true)
.create(true)
.open(&storage_dest)
.await
.map_err(|error| {
log::error!("unable to create temporary file for upload - {error}");
log::debug!("has image upload for device queue '{device_id}' of {size} bytes");
let mut bytes = request.take_body();
let mut storage_dest = std::path::PathBuf::new();
storage_dest.push(&request.state().web_configuration.temp_file_storage);
async_std::fs::create_dir_all(&storage_dest).await.map_err(|error| {
log::error!("unable to ensure temporary file storage dir exists - {error}");
tide::Error::from_str(500, "bad")
})?;
let file_name = uuid::Uuid::new_v4().to_string();

storage_dest.push(&file_name);
storage_dest.set_extension(if image_kind == "image/jpeg" { "jpg" } else { "png" });

log::info!("writing temporary file to '{storage_dest:?}");
let mut file = async_std::fs::OpenOptions::new()
.write(true)
.create(true)
.open(&storage_dest)
.await
.map_err(|error| {
log::error!("unable to create temporary file for upload - {error}");
tide::Error::from_str(500, "bad")
})?;

async_std::io::copy(&mut bytes, &mut file).await.map_err(|error| {
log::error!("unable to copy file upload - {error}");
tide::Error::from_str(500, "bad")
})?;

async_std::io::copy(&mut bytes, &mut file).await.map_err(|error| {
log::error!("unable to copy file upload - {error}");
tide::Error::from_str(500, "bad")
})?;

let job = registrar::RegistrarJobKind::Renders(registrar::jobs::RegistrarRenderKinds::SendImage {
location: storage_dest.to_string_lossy().to_string(),
device_id,
});
let worker = request.state();
let id = worker.queue_job_kind(job).await?;
let job = registrar::RegistrarJobKind::Renders(registrar::jobs::RegistrarRenderKinds::SendImage {
location: storage_dest.to_string_lossy().to_string(),
device_id,
});
let worker = request.state();
let id = worker.queue_job_kind(job).await?;

return tide::Body::from_json(&QueueResponse { id }).map(|body| tide::Response::builder(200).body(body).build());
return tide::Body::from_json(&QueueResponse { id }).map(|body| tide::Response::builder(200).body(body).build());
}
other => {
log::warn!("strange content type - '{other}'");
}
}

let queue_payload = request.body_json::<QueuePayload>().await.map_err(|error| {
Expand Down
6 changes: 1 addition & 5 deletions src/beetle-srv/src/rendering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,7 @@ where
.map_err(|error| io::Error::new(io::ErrorKind::Other, error.to_string()))?;

raw_image = raw_image.resize(dimensions.0, dimensions.1, image::imageops::FilterType::CatmullRom);
image = raw_image
.grayscale()
.as_luma8()
.cloned()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "unable to create grayscale"))?;
image = raw_image.grayscale().to_luma8();
log::info!("grayscale of '{}' successful", location.as_ref());
}

Expand Down
2 changes: 1 addition & 1 deletion src/beetle-srv/src/rendering/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl Worker {
let queue_error = match self.send_layout(&mut c, &queue_id, queued_render.layout.clone()).await {
Ok(_) => None,
Err(error) => {
log::warn!("uanble to send layout - {error:}");
log::warn!("unable to send layout - {error:}");
Some(format!("{error:?}"))
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/beetle-ui/src/Route/Device.elm
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ view model env =
let
fileInputNode =
Html.input
[ ATT.accept ".jpg,.jpeg"
[ ATT.accept ".jpg,.jpeg,.png"
, ATT.type_ "file"
, EV.on "change" (D.map ReceivedFiles filesDecoder)
]
Expand Down
11 changes: 11 additions & 0 deletions tools/beetle-mock/src/arguments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use clap::Parser;

#[derive(Parser)]
#[command(author, version = option_env!("BEETLE_VERSION").unwrap_or_else(|| "dev"), about, long_about = None)]
pub struct CommandLineArguments {
#[clap(short, long, default_value = "env.toml")]
pub config: String,

#[clap(short, long, default_value = ".storage")]
pub storage: String,
}
98 changes: 98 additions & 0 deletions tools/beetle-mock/src/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use super::arguments::CommandLineArguments;
use std::io;

pub async fn get_device_id(
args: &CommandLineArguments,
config: &beetle::registrar::Configuration,
mut connection: &mut beetle::redis::RedisConnection,
) -> io::Result<String> {
let mut id_storage_path = std::path::PathBuf::from(&args.storage);
id_storage_path.push(".device_id");

let (id_user, id_password) = config
.registrar
.id_consumer_username
.as_ref()
.zip(config.registrar.id_consumer_password.as_ref())
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"Configuration is missing registrar burn-in credentials",
)
})?;

match async_std::fs::metadata(&id_storage_path).await {
Err(error) if error.kind() == io::ErrorKind::NotFound => {
let burnin_auth_response = match kramer::execute(
&mut connection,
kramer::Command::<&str, &str>::Auth(kramer::AuthCredentials::User((id_user.as_str(), id_password.as_str()))),
)
.await
{
Ok(kramer::Response::Item(kramer::ResponseValue::String(inner))) if inner == "OK" => inner,
other => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("unable to authenticate with redis - {other:?} (as {id_user})"),
))
}
};

log::info!("initial handshake completed {burnin_auth_response:?}, taking a device id");

let mock_device_id = match kramer::execute(
&mut connection,
kramer::Command::<&str, &str>::Lists(kramer::ListCommand::Pop(
kramer::Side::Left,
beetle::constants::REGISTRAR_AVAILABLE,
None,
)),
)
.await
{
Ok(kramer::Response::Item(kramer::ResponseValue::String(id))) => id,
other => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("unable to pull id - {other:?}"),
))
}
};

log::info!("device id taken - {mock_device_id:?}");

match kramer::execute(
&mut connection,
kramer::Command::<&str, &str>::Auth(kramer::AuthCredentials::User((&mock_device_id, &mock_device_id))),
)
.await
{
Ok(kramer::Response::Item(kramer::ResponseValue::String(inner))) if inner == "OK" => (),
other => {
return Err(io::Error::new(
io::ErrorKind::Other,
format!("unable to authenticate with redis - {other:?}"),
))
}
}

log::info!("preparing '{}' for device id storage", args.storage);
async_std::fs::create_dir_all(&args.storage).await?;
async_std::fs::write(&id_storage_path, &mock_device_id).await?;

Ok(mock_device_id)
}

Ok(meta) if meta.is_file() => {
log::info!("found existing device id at '{:?}'", id_storage_path);
let loaded_id = async_std::fs::read_to_string(&id_storage_path).await?;
log::info!("loaded device id - '{loaded_id}'");

Ok(loaded_id)
}
other @ Ok(_) | other @ Err(_) => Err(io::Error::new(
io::ErrorKind::Other,
format!("unable to handle device id storage lookup - {other:?}"),
)),
}
}

0 comments on commit 89c3f74

Please sign in to comment.