/ src / components / app.rs
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  }