app.rs
1 use std::collections::VecDeque; 2 use freya::prelude::{App, ChildrenExt, ContainerSizeExt, 3 ContainerWithContentExt, Direction, Element, IntoElement, Platform, 4 StyleExt, WinitPlatformExt, rect, spawn, use_init_theme, use_side_effect, 5 use_state}; 6 use freya::radio::{RadioStation, use_init_radio_station, use_radio, 7 use_share_radio}; 8 use freya::winit::dpi::PhysicalSize; 9 use reqwest::Client; 10 use tracing::{info, warn}; 11 use crate::battlenet::{self, BattleNetContentElement}; 12 use crate::components::ProfileState; 13 use crate::components::nav::NavBar; 14 use crate::components::settings::AppSettingsElement; 15 use crate::constants::{AppTheme, BackgroundColor, DefaultHttpRequestRate, 16 TextColor}; 17 use crate::data::radio::{AppDataChannel, DataChannel}; 18 use crate::data::{ActiveContent, AppData}; 19 use crate::gog::{self, GogContentElement}; 20 use crate::io::imagePathExists; 21 use crate::net::limiter::RateLimiter; 22 use crate::net::limiter::request::{DataOperation, RequestEvent}; 23 use crate::retroachievements::{self, RetroAchievementsContent}; 24 use crate::rpcs3::Rpcs3ContentElement; 25 use crate::steam::{self, SteamContent}; 26 use crate::util::cacheImage; 27 28 pub struct LocalAchievementsApp 29 { 30 radioStation: RadioStation<AppData, AppDataChannel>, 31 } 32 33 impl App for LocalAchievementsApp 34 { 35 fn render(&self) -> impl IntoElement 36 { 37 use_init_theme(|| AppTheme); 38 use_share_radio(move || self.radioStation); 39 use_init_radio_station::<Option<ActiveContent>, DataChannel>(Default::default); 40 use_init_radio_station::<VecDeque<String>, DataChannel>(Default::default); 41 use_init_radio_station::<ProfileState, DataChannel>(Default::default); 42 use_init_radio_station::<Option<bool>, DataChannel>(Default::default); 43 use_init_radio_station::<PhysicalSize<u32>, DataChannel>(Default::default); 44 use_init_radio_station::<RateLimiter, DataChannel>(|| RateLimiter::new(DefaultHttpRequestRate)); 45 use_init_radio_station::<RequestEvent, DataChannel>(|| RequestEvent::Done); 46 47 let settingsData = use_radio::<AppData, AppDataChannel>(AppDataChannel::Settings); 48 let rateLimiter = use_radio::<RateLimiter, DataChannel>(DataChannel::RateLimiter); 49 let mut requestEvent = use_radio::<RequestEvent, DataChannel>(DataChannel::RateLimiter); 50 let mut battleNetData = use_radio::<AppData, AppDataChannel>(AppDataChannel::BattleNet); 51 let mut gogData = use_radio::<AppData, AppDataChannel>(AppDataChannel::Gog); 52 let mut retroAchievementsData = use_radio::<AppData, AppDataChannel>(AppDataChannel::RetroAchievements); 53 let mut steamData = use_radio::<AppData, AppDataChannel>(AppDataChannel::Steam); 54 let activeContent = use_radio::<Option<ActiveContent>, DataChannel>(DataChannel::ActiveContent); 55 let mut windowSize = use_radio::<PhysicalSize<u32>, DataChannel>(DataChannel::WindowSize); 56 57 let mut limiterSpawned = use_state(bool::default); 58 59 Platform::get().with_window( 60 None, 61 move |window| **windowSize.write() = window.inner_size() 62 ); 63 64 let active = activeContent.read().clone() 65 .unwrap_or(settingsData.read().app.settings.defaultActivePlatform); 66 67 let activeContent: Option<Element> = match active 68 { 69 ActiveContent::BattleNet => Some(BattleNetContentElement::new().into()), 70 //ActiveContent::EpicGamesStore => Some(EgsContentElement::new().into()), 71 ActiveContent::Gog => Some(GogContentElement::new().into()), 72 ActiveContent::RetroAchievements => Some(RetroAchievementsContent::new().into()), 73 ActiveContent::Rpcs3 => Some(Rpcs3ContentElement::new().into()), 74 ActiveContent::Settings => Some(AppSettingsElement::new().into()), 75 ActiveContent::Steam => Some(SteamContent::new().into()), 76 _ => None, 77 }; 78 79 use_side_effect(move || { 80 if !limiterSpawned() 81 && *requestEvent.read() != RequestEvent::Done 82 && !rateLimiter.read().blockingIsEmpty() 83 { 84 spawn(async move { 85 limiterSpawned.set(true); 86 87 loop 88 { 89 if rateLimiter.read().isEmpty().await 90 { 91 break; 92 } 93 94 if let Some(request) = rateLimiter.read().next().await 95 { 96 //Update the request event with the current number of remaining requests, forces redraw of ui elements that rely on this value 97 **requestEvent.write() = RequestEvent::Processing(rateLimiter.read().len().await); 98 99 match request.operation 100 { 101 DataOperation::CacheImage => if let Some(destination) = request.destination 102 { 103 if let Some(url) = request.url 104 { 105 if !imagePathExists(&destination) 106 { 107 let client = Client::builder() 108 .https_only(true) 109 .build() 110 .unwrap_or_default(); 111 112 match cacheImage(&client, &url, &destination).await 113 { 114 Err(e) => warn!("[Cache] Error caching image {} - {:?}", destination, e), 115 Ok(_) => info!("[Cache] Cached image: {}", destination), 116 } 117 } 118 else 119 { 120 rateLimiter.read().refundUse() 121 .await; 122 } 123 } 124 } 125 126 DataOperation::BattleNet(operation) => { 127 let appData = battleNetData.read().clone(); 128 if let Some(result) = battlenet::handleDataOperation(appData, operation).await 129 { 130 battleNetData.write().platform.battleNet = result.appData.platform.battleNet; 131 battleNetData.write().user.battleNet = result.appData.user.battleNet; 132 rateLimiter.read().pushAll(result.requests).await; 133 } 134 } 135 136 DataOperation::Gog(operation) => { 137 let appData = gogData.read().clone(); 138 if let Some(result) = gog::handleDataOperation(appData, operation).await 139 { 140 gogData.write().user.gog = result.appData.user.gog; 141 rateLimiter.read().pushAll(result.requests).await; 142 } 143 } 144 145 DataOperation::RetroAchievements(operation) => { 146 let appData = retroAchievementsData.read().clone(); 147 if let Some(result) = retroachievements::handleDataOperation(appData, operation).await 148 { 149 retroAchievementsData.write().user.retroAchievements = result.appData.user.retroAchievements; 150 rateLimiter.read().pushAll(result.requests).await; 151 } 152 } 153 154 DataOperation::Steam(operation) => { 155 let appData = steamData.read().clone(); 156 if let Some(result) = steam::handleDataOperation(appData, operation).await 157 { 158 steamData.write().user.steam = result.appData.user.steam; 159 rateLimiter.read().pushAll(result.requests).await; 160 } 161 } 162 } 163 } 164 } 165 166 **requestEvent.write() = RequestEvent::Done; 167 limiterSpawned.set(false); 168 }); 169 } 170 }); 171 172 return rect() 173 .background(BackgroundColor) 174 .color(TextColor) 175 .direction(Direction::Vertical) 176 .expanded() 177 178 .child(NavBar()) 179 180 .child( 181 rect() 182 .direction(Direction::Vertical) 183 .expanded() 184 .maybe_child(activeContent) 185 ); 186 } 187 } 188 189 impl LocalAchievementsApp 190 { 191 pub fn new( 192 radioStation: RadioStation<AppData, AppDataChannel>, 193 ) -> Self 194 { 195 return Self 196 { 197 radioStation, 198 }; 199 } 200 }