fjordgard

A desktop clock application
Log | Files | Refs | README | LICENSE

commit 674db022036fc3b01b86cd84b98b31fe3e87faa9
parent dd7b453e6deb849b1262030d2803abc00abb702c
Author: Sylvia Ivory <git@sivory.net>
Date:   Thu, 19 Jun 2025 17:58:56 -0700

Add downloading photos api

Diffstat:
MCargo.lock | 2++
Mcrates/unsplash/Cargo.toml | 2++
Mcrates/unsplash/src/lib.rs | 11+++++++++++
Mcrates/unsplash/src/model.rs | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 102 insertions(+), 2 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1297,10 +1297,12 @@ dependencies = [ name = "fjordgard-unsplash" version = "0.1.0" dependencies = [ + "bytes", "reqwest", "serde", "serde_json", "serde_with", + "strum", "thiserror 2.0.12", "tokio", ] diff --git a/crates/unsplash/Cargo.toml b/crates/unsplash/Cargo.toml @@ -4,10 +4,12 @@ version = "0.1.0" edition = "2024" [dependencies] +bytes = "1.10.1" reqwest = { version = "0.12.20", features = ["json"] } serde = { version = "1.0.219", features = ["derive"] } serde_json = "1.0.140" serde_with = "3.13.0" +strum = { version = "0.27.1", features = ["derive"] } thiserror = "2.0.12" [dev-dependencies] diff --git a/crates/unsplash/src/lib.rs b/crates/unsplash/src/lib.rs @@ -1,5 +1,6 @@ use std::fmt::Debug; +use bytes::Bytes; use reqwest::{ Client, StatusCode, header::{self, HeaderMap, HeaderValue}, @@ -94,6 +95,16 @@ impl UnsplashClient { photos, }) } + + pub async fn download_photo(&self, photo: &Photo, opts: Option<PhotoFetchOptions>) -> Result<Bytes> { + let mut req = self.client.get(&photo.urls.raw); + + if let Some(ref query) = opts { + req = req.query(query); + } + + Ok(req.send().await?.error_for_status()?.bytes().await?) + } } #[cfg(test)] diff --git a/crates/unsplash/src/model.rs b/crates/unsplash/src/model.rs @@ -1,6 +1,7 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, Serializer}; +use strum::Display; #[derive(Deserialize, Debug)] #[serde(untagged)] @@ -132,3 +133,87 @@ pub struct UserSocials { pub twitter_username: Option<String>, pub paypal_email: Option<String>, } + +#[derive(Display)] +#[strum(serialize_all = "lowercase")] +pub enum Crop { + Top, + Bottom, + Left, + Right, + Faces, + FocalPoint, + Edges, + Entropy +} + +#[derive(Serialize)] +#[serde(rename = "lowercase")] +pub enum Format { + Avif, + Gif, + Jp2, + Jpg, + Json, + Jxr, + PJpg, + Mp4, + Png, + Png8, + Png32, + Webm, + Webp, + BlurHash +} + +#[derive(Serialize)] +#[serde(rename = "lowercase")] +pub enum Auto { + Compress, + Enhance, + True, + Format, + Redeye +} + +#[derive(Serialize)] +#[serde(rename = "lowercase")] +pub enum Fit { + Clamp, + Clip, + Crop, + FaceArea, + Fill, + FillMax, + Max, + Min, + Scale +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Default)] +pub struct PhotoFetchOptions { + pub w: Option<f64>, + pub h: Option<f64>, + #[serde(serialize_with = "csv")] + pub crop: Option<Vec<Crop>>, + pub fm: Option<Format>, + pub auto: Option<Auto>, + pub q: Option<usize>, + pub fit: Option<Fit>, + pub dpr: Option<usize>, +} + +fn csv<S: Serializer, T: Display>(list: &Option<Vec<T>>, serializer: S) -> Result<S::Ok, S::Error> { + if let Some(list) = list { + let s: String = list + .iter() + .map(|v| v.to_string()) + .collect::<Vec<String>>() + .join(","); + + serializer.serialize_str(&s) + } else { + serializer.serialize_none() + } +}