11use std:: error:: Error ;
22use std:: io:: { Read , Write } ;
3- use std:: sync:: OnceLock ;
3+ use std:: ops:: { Add , Deref , DerefMut } ;
4+ use std:: sync:: { Arc , OnceLock } ;
5+ use std:: time:: { Duration , Instant } ;
46
57use bytes:: BufMut ;
68use gtk:: { gio, glib, Orientation } ;
@@ -12,31 +14,35 @@ use gtk::subclass::prelude::ObjectSubclassIsExt;
1214use image:: { DynamicImage , EncodableLayout , GenericImage , GenericImageView } ;
1315use tokio:: runtime:: Runtime ;
1416use tokio:: sync:: mpsc:: channel;
17+ use tokio:: sync:: Mutex ;
1518
1619use crate :: glium_area:: skin_parser:: TextureType ;
1720use crate :: utils:: guess_model_type;
1821use crate :: window:: Window ;
1922
2023mod imp {
21- use gtk:: { glib, TemplateChild } ;
22- use gtk:: CompositeTemplate ;
24+ use std:: cell:: Cell ;
25+ use std:: sync:: Arc ;
26+
27+ use gtk:: { CompositeTemplate , glib, TemplateChild } ;
2328 use gtk:: subclass:: popover:: PopoverImpl ;
2429 use gtk:: subclass:: prelude:: { CompositeTemplate , CompositeTemplateInitializingExt , ObjectImpl , ObjectSubclass , WidgetImpl } ;
2530 use gtk:: subclass:: widget:: WidgetClassExt ;
2631
27- use crate :: skin_loader_popover:: SkinApiClient ;
32+ use crate :: skin_loader_popover:: SkinClient ;
2833
2934 #[ derive( CompositeTemplate , Default ) ]
3035 #[ template( file = "../resources/ui/skin-loader-popover.ui" ) ]
3136 pub struct SkinLoaderPopover {
3237 #[ template_child]
33- pub search_skin_button : TemplateChild < gtk:: Button > ,
38+ pub nickname_entry : TemplateChild < gtk:: SearchEntry > ,
3439 #[ template_child]
35- pub search_skin_entry : TemplateChild < gtk:: SearchEntry > ,
40+ pub search_button : TemplateChild < gtk:: Button > ,
3641 #[ template_child]
3742 pub popover_content : TemplateChild < gtk:: Box > ,
3843
39- pub skin_loader_api_client : SkinApiClient ,
44+ pub client : Arc < SkinClient > ,
45+ pub is_searching : Cell < bool > ,
4046 }
4147
4248 #[ glib:: object_subclass]
@@ -66,26 +72,46 @@ glib::wrapper! {
6672
6773fn runtime ( ) -> & ' static Runtime {
6874 static RUNTIME : OnceLock < Runtime > = OnceLock :: new ( ) ;
69- // RUNTIME.get_or_init(|| tokio::runtime::Builder::new_current_thread()
70- // .enable_all()
71- // .build()
72- // .expect("Setting up tokio runtime needs to succeed."))
73- RUNTIME . get_or_init ( || Runtime :: new ( ) . expect ( "Setting up tokio runtime needs to succeed." ) )
75+ RUNTIME . get_or_init ( || Runtime :: new ( )
76+ . expect ( "Setting up tokio runtime needs to succeed." ) )
7477}
7578
7679#[ derive( Default ) ]
77- struct SkinApiClient ;
78- impl SkinApiClient {
79- const URI : & ' static str = "https://mc-heads.net/skin" ;
80+ struct SkinClient {
81+ last_request_time : Arc < Mutex < Option < Instant > > > ,
82+ cooldown_duration : Arc < Mutex < Duration > > ,
83+ }
84+ impl SkinClient {
85+ const BASE_URL : & ' static str = "https://mc-heads.net/skin" ;
86+
87+ pub async fn set_cooldown ( & self , secs : f32 ) {
88+ * self . cooldown_duration . lock ( ) . await = Duration :: from_secs_f32 ( secs) ;
89+ }
90+
91+ pub async fn cooldown ( & self ) {
92+ let mut last_request_time = self . last_request_time . lock ( ) . await ;
93+ let cooldown_duration = self . cooldown_duration . lock ( ) . await . clone ( ) ;
94+
95+ if let Some ( last_time) = * last_request_time {
96+ let elapsed = last_time. elapsed ( ) ;
97+ if elapsed < cooldown_duration {
98+ let remained_cooldown = cooldown_duration - elapsed;
99+ tokio:: time:: sleep ( remained_cooldown) . await ;
100+ }
101+ }
80102
81- pub fn new ( ) -> SkinApiClient {
82- SkinApiClient
103+ // Update last request time
104+ * last_request_time = Some ( Instant :: now ( ) ) ;
83105 }
84-
106+
85107 pub async fn get_skin ( & self , nickname : & str ) -> Result < DynamicImage , Box < dyn Error > > {
86- let uri = format ! ( "{}/{}" , Self :: URI , nickname) ;
108+ self . cooldown ( ) . await ;
109+ let uri = format ! ( "{}/{}" , Self :: BASE_URL , nickname) ;
87110 let url = reqwest:: Url :: parse ( uri. as_str ( ) ) . unwrap ( ) ;
88- let mut skin = reqwest:: get ( url) . await ?. bytes ( ) . await ?;
111+ let client = reqwest:: Client :: builder ( )
112+ . timeout ( Duration :: from_secs ( 5 ) )
113+ . build ( ) ?;
114+ let mut skin = client. get ( url) . send ( ) . await ?. bytes ( ) . await ?;
89115 let image = image:: load_from_memory ( skin. as_bytes ( ) ) ?;
90116 Ok ( image)
91117 }
@@ -94,21 +120,22 @@ impl SkinApiClient {
94120impl SkinLoaderPopover {
95121 pub fn new ( win : & Window ) -> Self {
96122 let popover: SkinLoaderPopover = glib:: Object :: new ( ) ;
97-
123+ runtime ( ) . block_on ( async {
124+ popover. imp ( ) . client . set_cooldown ( 1.5 ) . await
125+ } ) ;
98126 popover. connect_signals ( win) ;
99-
100127 popover
101128 }
102129
103130 pub fn connect_signals ( & self , win : & Window ) {
104- self . imp ( ) . search_skin_button . connect_clicked ( self . get_search_skin_button_handler ( win. clone ( ) ) ) ;
131+ self . imp ( ) . search_button . connect_clicked ( self . get_search_skin_button_handler ( win. clone ( ) ) ) ;
105132 }
106133
107134 fn create_texture_button ( win : Window , texture : DynamicImage , title : & str ) -> gtk:: Button {
108135 let texture_button = gtk:: Button :: new ( ) ;
109- let temporary_file = "temporary_file.png" ;
110- texture. save ( temporary_file) . unwrap ( ) ;
111136 let paintable = {
137+ let temporary_file = "temporary_file.png" ;
138+ texture. save ( temporary_file) . unwrap ( ) ;
112139 let f = gio:: File :: for_path ( temporary_file) ;
113140 Texture :: from_file ( & f) . unwrap ( )
114141 } ;
@@ -147,36 +174,75 @@ impl SkinLoaderPopover {
147174
148175 texture_button
149176 }
150-
177+
151178 fn get_search_skin_button_handler ( & self , win : Window ) -> impl Fn ( & gtk:: Button ) {
152179 let popover = self . clone ( ) ;
153180 move |btn| {
154- let popover = popover. clone ( ) ;
155- let win = win. clone ( ) ;
181+ if popover. searching ( ) {
182+ return
183+ }
184+ let nickname = match popover. get_nickname ( ) {
185+ Some ( nickname) => nickname,
186+ None => return
187+ } ;
188+ popover. set_searching ( true ) ;
156189
190+ popover. clear ( ) ;
191+ popover. add_spinner ( ) ;
157192 let ( sender, mut receiver) = channel :: < Result < DynamicImage , ( ) > > ( 10000 ) ;
158- let nickname = popover. imp ( ) . search_skin_entry . text ( ) ;
159193
160- runtime ( ) . spawn ( clone ! ( @strong nickname, @strong sender => async move {
161- let client = SkinApiClient :: new( ) ;
162- let texture = client. get_skin( nickname. as_str( ) ) . await . map_err( |_| ( ) ) ;
163- sender. send( texture) . await . expect( "The channel needs to be open" ) ;
194+ // Spawn a task to fetch the skin
195+ let client = popover. imp ( ) . client . clone ( ) ;
196+ runtime ( ) . spawn ( clone ! ( @strong nickname => async move {
197+ println!( "Fetching the skin..." ) ;
198+ let response = client. get_skin( nickname. as_str( ) ) . await . map_err( |_| ( ) ) ;
199+ sender. send( response) . await . expect( "The channel needs to be open" ) ;
164200 } ) ) ;
165201
166- glib:: spawn_future_local ( async move {
167- while let Some ( texture_result) = receiver. recv ( ) . await {
168- if texture_result. is_err ( ) {
202+ glib:: spawn_future_local ( clone ! ( @strong win, @strong popover => async move {
203+ while let Some ( response) = receiver. recv( ) . await {
204+ popover. set_searching( false ) ;
205+ if response. is_err( ) {
169206 println!( "Bad request" ) ;
207+ popover. clear( ) ;
170208 return
171209 }
172- let texture = texture_result . unwrap ( ) ;
210+ let texture = response . unwrap( ) ;
173211 let texture_button = SkinLoaderPopover :: create_texture_button( win. clone( ) , texture, nickname. as_str( ) ) ;
174- if let Some ( child) = popover. imp ( ) . popover_content . last_child ( ) {
175- popover. imp ( ) . popover_content . remove ( & child) ;
176- }
212+ popover. clear( ) ;
177213 popover. imp( ) . popover_content. append( & texture_button) ;
178214 }
179- } ) ;
215+ } ) ) ;
216+ }
217+ }
218+
219+ fn get_nickname ( & self ) -> Option < String > {
220+ let text = self . imp ( ) . nickname_entry . text ( ) ;
221+ if text. is_empty ( ) {
222+ return None
180223 }
224+ Some ( text. to_string ( ) )
225+ }
226+
227+ fn searching ( & self ) -> bool {
228+ self . imp ( ) . is_searching . get ( )
229+ }
230+
231+ fn set_searching ( & self , searching : bool ) {
232+ self . imp ( ) . is_searching . replace ( searching) ;
233+ }
234+
235+ fn clear ( & self ) {
236+ while let Some ( spinner) = self . imp ( ) . popover_content . last_child ( ) {
237+ self . imp ( ) . popover_content . remove ( & spinner) ;
238+ }
239+ }
240+
241+ fn add_spinner ( & self ) {
242+ let spinner = gtk:: Spinner :: new ( ) ;
243+ spinner. set_height_request ( 40 ) ;
244+ spinner. set_width_request ( 40 ) ;
245+ spinner. set_spinning ( true ) ;
246+ self . imp ( ) . popover_content . append ( & spinner) ;
181247 }
182248}
0 commit comments