diff --git a/src/api.rs b/src/api.rs index df47eeb..51bd38a 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,44 +1,38 @@ -use std::{collections::HashMap, iter::repeat, sync::Arc}; +use std::{ + collections::HashMap, + iter::repeat, + sync::{Arc, Mutex}, +}; use anyhow::{Context as _, anyhow}; use immich_sdk::{AssetId, AssetVisibility}; -use kameo::{ - Actor, - prelude::{Context, Message}, -}; use slint::{Rgba8Pixel, SharedPixelBuffer}; use crate::thumbhash::thumbhashes_to_pixels; pub type TimeBucketKey = String; -#[derive(Actor)] pub struct Api { client: immich_sdk::Client, - buckets: HashMap>, - thumbnails: HashMap>, // TODO + buckets: Mutex>>, + thumbnails: Mutex>>, } impl Api { - pub fn new(client: immich_sdk::Client) -> Self { - Self { + pub fn new(client: immich_sdk::Client) -> Arc { + Arc::new(Self { client, buckets: Default::default(), thumbnails: Default::default(), - } + }) } } -pub struct GetTimeBuckets; pub struct TimeBucketRef { pub key: TimeBucketKey, pub count: usize, } -pub struct GetTimeBucket { - pub time_bucket: TimeBucketKey, -} - pub struct TimeBucket { pub key: TimeBucketKey, pub entries: Arc<[TimeBucketEntry]>, @@ -54,23 +48,13 @@ pub struct TimeBucketEntry { pub visibility: AssetVisibility, } -pub struct GetAssetThumbnail { - pub id: AssetId, -} - pub struct AssetThumbnail { pub id: AssetId, pub thumbnail: SharedPixelBuffer, } -impl Message for Api { - type Reply = anyhow::Result>; - - async fn handle( - &mut self, - _msg: GetTimeBuckets, - _ctx: &mut Context, - ) -> Self::Reply { +impl Api { + pub async fn get_time_buckets(&self) -> anyhow::Result> { let buckets = self .client .timeline() @@ -90,30 +74,22 @@ impl Message for Api { }) .collect()) } -} -impl Message for Api { - type Reply = anyhow::Result>; - - async fn handle( - &mut self, - msg: GetTimeBucket, - _ctx: &mut Context, - ) -> Self::Reply { - if let Some(time_bucket) = self.buckets.get(&msg.time_bucket).cloned() { + pub async fn get_time_bucket( + &self, + time_bucket: TimeBucketKey, + ) -> anyhow::Result> { + if let Some(time_bucket) = self.buckets.lock().unwrap().get(&time_bucket).cloned() { return Ok(time_bucket); } let bucket = self .client .timeline() - .bucket(&msg.time_bucket) + .bucket(&time_bucket) .execute() .await - .context(anyhow!( - "Failed to fetch time bucket {:?}", - &msg.time_bucket - )) + .context(anyhow!("Failed to fetch time bucket {:?}", &time_bucket)) .inspect_err(|e| { tracing::error!("{e:?}"); })?; @@ -144,40 +120,38 @@ impl Message for Api { .collect(); let bucket = Arc::new(TimeBucket { - key: msg.time_bucket, + key: time_bucket, entries, }); - self.buckets.insert(bucket.key.clone(), bucket.clone()); + self.buckets + .lock() + .unwrap() + .insert(bucket.key.clone(), bucket.clone()); Ok(bucket) } -} -impl Message for Api { - type Reply = anyhow::Result>; - - async fn handle( - &mut self, - msg: GetAssetThumbnail, - _ctx: &mut Context, - ) -> Self::Reply { - if let Some(thumbnail) = self.thumbnails.get(&msg.id).cloned() { + pub async fn get_asset_thumbnail( + &self, + asset_id: AssetId, + ) -> anyhow::Result> { + if let Some(thumbnail) = self.thumbnails.lock().unwrap().get(&asset_id).cloned() { return Ok(thumbnail); } let response = self .client .assets() - .thumbnail(msg.id) + .thumbnail(asset_id) .size(immich_sdk::AssetMediaSize::Thumbnail) .execute() .await - .context(anyhow!("Failed to get asset thumbnail for {}", msg.id))?; + .context(anyhow!("Failed to get asset thumbnail for {asset_id}"))?; let thumbnail = response .decode() - .context(anyhow!("Failed to decode asset thumbnail for {}", msg.id))? + .context(anyhow!("Failed to decode asset thumbnail for {asset_id}"))? .into_rgba8(); let pixel_buffer = SharedPixelBuffer::::clone_from_slice( @@ -187,11 +161,14 @@ impl Message for Api { ); let thumbnail = Arc::new(AssetThumbnail { - id: msg.id, + id: asset_id, thumbnail: pixel_buffer, }); - self.thumbnails.insert(msg.id, Arc::clone(&thumbnail)); + self.thumbnails + .lock() + .unwrap() + .insert(asset_id, Arc::clone(&thumbnail)); Ok(thumbnail) } diff --git a/src/main.rs b/src/main.rs index c5c8964..672631b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,10 @@ // Prevent console window in addition to Slint window in Windows release builds when, e.g., starting the app via file manager. Ignored on other platforms. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -use std::{mem, ops::Deref}; +use std::{mem, ops::Deref, sync::Arc}; use clap::Parser; use immich_sdk::AssetId; -use kameo::actor::{ActorRef, Spawn}; use slint::{ ComponentHandle as _, Model, ModelRc, SharedPixelBuffer, SharedString, ToSharedString, VecModel, Weak, @@ -13,7 +12,7 @@ use slint::{ use tracing::Level; use crate::{ - api::{Api, GetAssetThumbnail, GetTimeBucket, GetTimeBuckets, TimeBucketKey}, + api::{Api, TimeBucketKey}, ui::{AppWindow, ImageBucket}, }; @@ -66,8 +65,7 @@ fn main() -> anyhow::Result<()> { let immich_config = immich_sdk::Config::new(opt.immich_base_url).with_api_key(opt.immich_api_key); - let api = Api::new(immich_sdk::Client::new(immich_config).unwrap()); - let api_ = Api::spawn(api); + let api_ = Api::new(immich_sdk::Client::new(immich_config).unwrap()); let app = ui::AppWindow::new()?; let global = app.global::(); @@ -76,7 +74,7 @@ fn main() -> anyhow::Result<()> { let app_weak = app.as_weak(); let api = api_.clone(); tokio::spawn(async move { - let Ok(buckets) = api.ask(GetTimeBuckets).await else { + let Ok(buckets) = api.get_time_buckets().await else { return; }; @@ -132,7 +130,7 @@ fn main() -> anyhow::Result<()> { Ok(()) } -fn calculate_timeline_visibility(app: &AppWindow, api: ActorRef, scroll: f32) { +fn calculate_timeline_visibility(app: &AppWindow, api: Arc, scroll: f32) { let global = app.global::(); global.set_timeline_scroll(scroll); @@ -178,7 +176,7 @@ fn calculate_timeline_visibility(app: &AppWindow, api: ActorRef, scroll: f3 } } -fn calculate_timeline_layout(app: &AppWindow, api: ActorRef, timeline_width: f32) { +fn calculate_timeline_layout(app: &AppWindow, api: Arc, timeline_width: f32) { let global = app.global::(); let min_image_size = global.get_min_image_size(); let image_margin = global.get_image_margin(); @@ -267,14 +265,9 @@ fn unload_bucket(time_bucket: &TimeBucketKey, app: &AppWindow) { // TODO: write `bucket` into `buckets?` } -fn load_bucket(time_bucket: TimeBucketKey, app_weak: Weak, api: ActorRef) { +fn load_bucket(time_bucket: TimeBucketKey, app_weak: Weak, api: Arc) { tokio::spawn(async move { - let Ok(api_bucket) = api - .ask(GetTimeBucket { - time_bucket: time_bucket.clone(), - }) - .await - else { + let Ok(api_bucket) = api.get_time_bucket(time_bucket.clone()).await else { return; }; @@ -312,13 +305,13 @@ fn load_bucket(time_bucket: TimeBucketKey, app_weak: Weak, api: Actor fn load_thumbnail( time_bucket: TimeBucketKey, - id: AssetId, + asset_id: AssetId, app_weak: Weak, - api: ActorRef, + api: Arc, ) { tokio::spawn(async move { - tracing::debug!("Fetching thumbnail for {id}"); - let thumbnail = match api.ask(GetAssetThumbnail { id }).await { + tracing::debug!("Fetching thumbnail for {asset_id}"); + let thumbnail = match api.get_asset_thumbnail(asset_id).await { Ok(thumbnail) => thumbnail, Err(e) => { tracing::error!("{e:?}"); @@ -333,7 +326,7 @@ fn load_thumbnail( }; let bucket = buckets.row_data(i).expect("i is in the list"); - let id_str = id.to_string(); + let id_str = asset_id.to_string(); let Some(i) = bucket.previews.iter().position(|p| &p.asset_id == &id_str) else { return; };