mirror of
				https://github.com/Dummi26/musicdb.git
				synced 2025-10-31 03:55:25 +01:00 
			
		
		
		
	added some comments
This commit is contained in:
		
							parent
							
								
									5add9c477e
								
							
						
					
					
						commit
						0ae0126f04
					
				| @ -204,27 +204,36 @@ pub trait GuiElemTrait { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
|  | /// The config for any gui element.
 | ||||||
| pub struct GuiElemCfg { | pub struct GuiElemCfg { | ||||||
|     pub enabled: bool, |     pub enabled: bool, | ||||||
|     /// if true, indicates that something (text size, screen size, ...) has changed
 |     /// if true, indicates that something (text size, screen size, ...) has changed
 | ||||||
|     /// and you should probably relayout and redraw from scratch.
 |     /// and you should probably relayout and redraw from scratch.
 | ||||||
|     pub redraw: bool, |     pub redraw: bool, | ||||||
|  |     /// Position relative to the parent where this element should be drawn.
 | ||||||
|  |     /// ((0, 0), (1, 1)) is the default and fills all available space.
 | ||||||
|  |     /// ((0, 0.5), (0.5, 1)) fills the bottom left quarter.
 | ||||||
|     pub pos: Rectangle, |     pub pos: Rectangle, | ||||||
|     /// the pixel position after the last call to draw().
 |     /// the pixel position after the last call to draw().
 | ||||||
|     /// in draw, use info.pos instead, as pixel_pos is only updated *after* draw().
 |     /// in draw, use info.pos instead, as pixel_pos is only updated *after* draw().
 | ||||||
|     /// this can act like a "previous pos" field within draw.
 |     /// this can act like a "previous pos" field within draw.
 | ||||||
|     pub pixel_pos: Rectangle, |     pub pixel_pos: Rectangle, | ||||||
|  |     /// which mouse buttons were pressed down while the mouse was on this element and haven't been released since? (Left/Middle/Right)
 | ||||||
|     pub mouse_down: (bool, bool, bool), |     pub mouse_down: (bool, bool, bool), | ||||||
|  |     /// Set this to true to receive mouse click events when the mouse is within this element's bounds
 | ||||||
|     pub mouse_events: bool, |     pub mouse_events: bool, | ||||||
|  |     /// Set this to true to receive scroll events when the mouse is within this element's bounds
 | ||||||
|     pub scroll_events: bool, |     pub scroll_events: bool, | ||||||
|     /// allows elements to watch all keyboard events, regardless of keyboard focus.
 |     /// allows elements to watch all keyboard events, regardless of keyboard focus.
 | ||||||
|     pub keyboard_events_watch: bool, |     pub keyboard_events_watch: bool, | ||||||
|     /// indicates that this element can have the keyboard focus
 |     /// indicates that this element can have the keyboard focus
 | ||||||
|     pub keyboard_events_focus: bool, |     pub keyboard_events_focus: bool, | ||||||
|     /// index of the child that has keyboard focus. if usize::MAX, `self` has focus.
 |     /// index of the child that has keyboard focus. if usize::MAX, `self` has focus.
 | ||||||
|     /// will automatically be changed when Tab is pressed. [TODO]
 |     /// will automatically be changed when Tab is pressed (Tab skips elements with keyboard_events_focus == false)
 | ||||||
|     pub keyboard_focus_index: usize, |     pub keyboard_focus_index: usize, | ||||||
|  |     /// if this is true and ResetKeyboardFocus is returned, this element may get the keyboard focus (guaranteed if no other element has this set to true)
 | ||||||
|     pub request_keyboard_focus: bool, |     pub request_keyboard_focus: bool, | ||||||
|  |     /// if this is true, things can be dragged into this element via drag-n-drop
 | ||||||
|     pub drag_target: bool, |     pub drag_target: bool, | ||||||
| } | } | ||||||
| impl GuiElemCfg { | impl GuiElemCfg { | ||||||
| @ -281,9 +290,10 @@ pub enum GuiAction { | |||||||
|     OpenMain, |     OpenMain, | ||||||
|     SetIdle(bool), |     SetIdle(bool), | ||||||
|     OpenSettings(bool), |     OpenSettings(bool), | ||||||
|  |     /// Build the GuiAction(s) later, when we have access to the Database (can turn an AlbumId into a QueueContent::Folder, etc)
 | ||||||
|     Build(Box<dyn FnOnce(&mut Database) -> Vec<Self>>), |     Build(Box<dyn FnOnce(&mut Database) -> Vec<Self>>), | ||||||
|     SendToServer(Command), |     SendToServer(Command), | ||||||
|     /// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true.
 |     /// unfocuses all gui elements, then assigns keyboard focus to one with config().request_keyboard_focus == true if there is one.
 | ||||||
|     ResetKeyboardFocus, |     ResetKeyboardFocus, | ||||||
|     SetDragging( |     SetDragging( | ||||||
|         Option<( |         Option<( | ||||||
| @ -292,6 +302,7 @@ pub enum GuiAction { | |||||||
|         )>, |         )>, | ||||||
|     ), |     ), | ||||||
|     SetLineHeight(f32), |     SetLineHeight(f32), | ||||||
|  |     /// Run a custom closure with mutable access to the Gui struct
 | ||||||
|     Do(Box<dyn FnMut(&mut Gui)>), |     Do(Box<dyn FnMut(&mut Gui)>), | ||||||
|     Exit, |     Exit, | ||||||
| } | } | ||||||
| @ -301,6 +312,9 @@ pub enum Dragging { | |||||||
|     Song(SongId), |     Song(SongId), | ||||||
|     Queue(Queue), |     Queue(Queue), | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /// GuiElems have access to this within draw.
 | ||||||
|  | /// Except for `actions`, they should not change any of these values - GuiElem::draw will handle everything automatically.
 | ||||||
| pub struct DrawInfo<'a> { | pub struct DrawInfo<'a> { | ||||||
|     pub actions: Vec<GuiAction>, |     pub actions: Vec<GuiAction>, | ||||||
|     pub pos: Rectangle, |     pub pos: Rectangle, | ||||||
| @ -316,6 +330,7 @@ pub struct DrawInfo<'a> { | |||||||
|     pub line_height: f32, |     pub line_height: f32, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /// Generic wrapper over anything that implements GuiElemTrait
 | ||||||
| pub struct GuiElem { | pub struct GuiElem { | ||||||
|     pub inner: Box<dyn GuiElemTrait>, |     pub inner: Box<dyn GuiElemTrait>, | ||||||
| } | } | ||||||
|  | |||||||
| @ -7,6 +7,13 @@ use crate::{ | |||||||
|     gui_text::Label, |     gui_text::Label, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | Some basic structs to use everywhere. | ||||||
|  | Mostly containers for other GuiElems. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| /// A simple container for zero, one, or multiple child GuiElems. Can optionally fill the background with a color.
 | /// A simple container for zero, one, or multiple child GuiElems. Can optionally fill the background with a color.
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct Panel { | pub struct Panel { | ||||||
|  | |||||||
| @ -14,6 +14,13 @@ use crate::{ | |||||||
|     gui_wrappers::WithFocusHotkey, |     gui_wrappers::WithFocusHotkey, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | This is responsible for showing the library, | ||||||
|  | with Regex search and drag-n-drop. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct LibraryBrowser { | pub struct LibraryBrowser { | ||||||
|     config: GuiElemCfg, |     config: GuiElemCfg, | ||||||
|  | |||||||
| @ -9,6 +9,13 @@ use crate::{ | |||||||
|     gui_text::Label, |     gui_text::Label, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | Components for the StatusBar. | ||||||
|  | This file could probably have a better name. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct CurrentSong { | pub struct CurrentSong { | ||||||
|     config: GuiElemCfg, |     config: GuiElemCfg, | ||||||
|  | |||||||
| @ -20,6 +20,15 @@ use crate::{ | |||||||
|     gui_text::Label, |     gui_text::Label, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | This is responsible for showing the current queue, | ||||||
|  | with drag-n-drop only if the mouse leaves the element before it is released, | ||||||
|  | because simple clicks have to be GoTo events. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct QueueViewer { | pub struct QueueViewer { | ||||||
|     config: GuiElemCfg, |     config: GuiElemCfg, | ||||||
|  | |||||||
| @ -12,7 +12,15 @@ use crate::{ | |||||||
|     gui_text::Label, |     gui_text::Label, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// calculates f(p) (f(x) = 3x^2 - 2x^3)):
 | /* | ||||||
|  | 
 | ||||||
|  | The root gui element. | ||||||
|  | Contains the Library, Queue, StatusBar, and sometimes Settings elements. | ||||||
|  | Resizes these elements to show/hide the settings menu and to smoothly switch to/from idle mode. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | /// calculates f(p), where f(x) = 3x^2 - 2x^3, because
 | ||||||
| /// f(0) = 0
 | /// f(0) = 0
 | ||||||
| /// f(0.5) = 0.5
 | /// f(0.5) = 0.5
 | ||||||
| /// f(1) = 1
 | /// f(1) = 1
 | ||||||
| @ -36,9 +44,6 @@ pub struct GuiScreen { | |||||||
|     pub prev_mouse_pos: Vec2, |     pub prev_mouse_pos: Vec2, | ||||||
| } | } | ||||||
| impl GuiScreen { | impl GuiScreen { | ||||||
|     fn i_statusbar() -> usize { |  | ||||||
|         0 |  | ||||||
|     } |  | ||||||
|     pub fn new( |     pub fn new( | ||||||
|         config: GuiElemCfg, |         config: GuiElemCfg, | ||||||
|         line_height: f32, |         line_height: f32, | ||||||
|  | |||||||
| @ -10,6 +10,13 @@ use speedy2d::{ | |||||||
| 
 | 
 | ||||||
| use crate::gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}; | use crate::gui::{GuiAction, GuiElem, GuiElemCfg, GuiElemTrait}; | ||||||
| 
 | 
 | ||||||
|  | /* | ||||||
|  | 
 | ||||||
|  | Some basic structs to use everywhere, | ||||||
|  | except they are all text-related. | ||||||
|  | 
 | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct Label { | pub struct Label { | ||||||
|     config: GuiElemCfg, |     config: GuiElemCfg, | ||||||
|  | |||||||
| @ -18,7 +18,9 @@ use super::{ | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub struct Database { | pub struct Database { | ||||||
|  |     /// the path to the file used to save/load the data
 | ||||||
|     db_file: PathBuf, |     db_file: PathBuf, | ||||||
|  |     /// the path to the directory containing the actual music and cover image files
 | ||||||
|     pub lib_directory: PathBuf, |     pub lib_directory: PathBuf, | ||||||
|     artists: HashMap<ArtistId, Artist>, |     artists: HashMap<ArtistId, Artist>, | ||||||
|     albums: HashMap<AlbumId, Album>, |     albums: HashMap<AlbumId, Album>, | ||||||
| @ -26,13 +28,18 @@ pub struct Database { | |||||||
|     covers: HashMap<CoverId, DatabaseLocation>, |     covers: HashMap<CoverId, DatabaseLocation>, | ||||||
|     // TODO! make sure this works out for the server AND clients
 |     // TODO! make sure this works out for the server AND clients
 | ||||||
|     // cover_cache: HashMap<CoverId, Vec<u8>>,
 |     // cover_cache: HashMap<CoverId, Vec<u8>>,
 | ||||||
|  |     // These will be used for autosave once that gets implemented
 | ||||||
|     db_data_file_change_first: Option<Instant>, |     db_data_file_change_first: Option<Instant>, | ||||||
|     db_data_file_change_last: Option<Instant>, |     db_data_file_change_last: Option<Instant>, | ||||||
|     pub queue: Queue, |     pub queue: Queue, | ||||||
|  |     /// if the database receives an update, it will inform all of its clients so they can stay in sync.
 | ||||||
|  |     /// this is a list containing all the clients.
 | ||||||
|     pub update_endpoints: Vec<UpdateEndpoint>, |     pub update_endpoints: Vec<UpdateEndpoint>, | ||||||
|  |     /// true if a song is/should be playing
 | ||||||
|     pub playing: bool, |     pub playing: bool, | ||||||
|     pub command_sender: Option<mpsc::Sender<Command>>, |     pub command_sender: Option<mpsc::Sender<Command>>, | ||||||
| } | } | ||||||
|  | // for custom server implementations, this enum should allow you to deal with updates from any context (writers such as tcp streams, sync/async mpsc senders, or via closure as a fallback)
 | ||||||
| pub enum UpdateEndpoint { | pub enum UpdateEndpoint { | ||||||
|     Bytes(Box<dyn Write + Sync + Send>), |     Bytes(Box<dyn Write + Sync + Send>), | ||||||
|     CmdChannel(mpsc::Sender<Arc<Command>>), |     CmdChannel(mpsc::Sender<Arc<Command>>), | ||||||
| @ -41,6 +48,7 @@ pub enum UpdateEndpoint { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Database { | impl Database { | ||||||
|  |     /// TODO!
 | ||||||
|     fn panic(&self, msg: &str) -> ! { |     fn panic(&self, msg: &str) -> ! { | ||||||
|         // custom panic handler
 |         // custom panic handler
 | ||||||
|         // make a backup
 |         // make a backup
 | ||||||
| @ -50,6 +58,7 @@ impl Database { | |||||||
|     pub fn get_path(&self, location: &DatabaseLocation) -> PathBuf { |     pub fn get_path(&self, location: &DatabaseLocation) -> PathBuf { | ||||||
|         self.lib_directory.join(&location.rel_path) |         self.lib_directory.join(&location.rel_path) | ||||||
|     } |     } | ||||||
|  |     // NOTE: just use `songs` directly? not sure yet...
 | ||||||
|     pub fn get_song(&self, song: &SongId) -> Option<&Song> { |     pub fn get_song(&self, song: &SongId) -> Option<&Song> { | ||||||
|         self.songs.get(song) |         self.songs.get(song) | ||||||
|     } |     } | ||||||
| @ -72,6 +81,7 @@ impl Database { | |||||||
|         } |         } | ||||||
|         id |         id | ||||||
|     } |     } | ||||||
|  |     /// used internally
 | ||||||
|     pub fn add_song_new_nomagic(&mut self, mut song: Song) -> SongId { |     pub fn add_song_new_nomagic(&mut self, mut song: Song) -> SongId { | ||||||
|         for key in 0.. { |         for key in 0.. { | ||||||
|             if !self.songs.contains_key(&key) { |             if !self.songs.contains_key(&key) { | ||||||
| @ -89,6 +99,7 @@ impl Database { | |||||||
|         let id = self.add_artist_new_nomagic(artist); |         let id = self.add_artist_new_nomagic(artist); | ||||||
|         id |         id | ||||||
|     } |     } | ||||||
|  |     /// used internally
 | ||||||
|     fn add_artist_new_nomagic(&mut self, mut artist: Artist) -> ArtistId { |     fn add_artist_new_nomagic(&mut self, mut artist: Artist) -> ArtistId { | ||||||
|         for key in 0.. { |         for key in 0.. { | ||||||
|             if !self.artists.contains_key(&key) { |             if !self.artists.contains_key(&key) { | ||||||
| @ -110,6 +121,7 @@ impl Database { | |||||||
|         } |         } | ||||||
|         id |         id | ||||||
|     } |     } | ||||||
|  |     /// used internally
 | ||||||
|     fn add_album_new_nomagic(&mut self, mut album: Album) -> AlbumId { |     fn add_album_new_nomagic(&mut self, mut album: Album) -> AlbumId { | ||||||
|         for key in 0.. { |         for key in 0.. { | ||||||
|             if !self.albums.contains_key(&key) { |             if !self.albums.contains_key(&key) { | ||||||
|  | |||||||
| @ -17,11 +17,13 @@ pub type ArtistId = u64; | |||||||
| pub type CoverId = u64; | pub type CoverId = u64; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Default, Debug)] | #[derive(Clone, Default, Debug)] | ||||||
|  | /// general data for songs, albums and artists
 | ||||||
| pub struct GeneralData { | pub struct GeneralData { | ||||||
|     pub tags: Vec<String>, |     pub tags: Vec<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
|  | /// the location of a file relative to the lib directory, often Artist/Album/Song.ext or similar
 | ||||||
| pub struct DatabaseLocation { | pub struct DatabaseLocation { | ||||||
|     pub rel_path: PathBuf, |     pub rel_path: PathBuf, | ||||||
| } | } | ||||||
|  | |||||||
| @ -81,6 +81,8 @@ pub fn run_server( | |||||||
|     sender_sender: Option<tokio::sync::mpsc::Sender<mpsc::Sender<Command>>>, |     sender_sender: Option<tokio::sync::mpsc::Sender<mpsc::Sender<Command>>>, | ||||||
| ) { | ) { | ||||||
|     let mut player = Player::new().unwrap(); |     let mut player = Player::new().unwrap(); | ||||||
|  |     // commands sent to this will be handeled later in this function in an infinite loop.
 | ||||||
|  |     // these commands are sent to the database asap.
 | ||||||
|     let (command_sender, command_receiver) = mpsc::channel(); |     let (command_sender, command_receiver) = mpsc::channel(); | ||||||
|     if let Some(s) = sender_sender { |     if let Some(s) = sender_sender { | ||||||
|         s.blocking_send(command_sender.clone()).unwrap(); |         s.blocking_send(command_sender.clone()).unwrap(); | ||||||
| @ -91,6 +93,7 @@ pub fn run_server( | |||||||
|             Ok(v) => { |             Ok(v) => { | ||||||
|                 let command_sender = command_sender.clone(); |                 let command_sender = command_sender.clone(); | ||||||
|                 let db = Arc::clone(&database); |                 let db = Arc::clone(&database); | ||||||
|  |                 // each connection gets its own thread, but they will be idle most of the time (waiting for data on the tcp stream)
 | ||||||
|                 thread::spawn(move || loop { |                 thread::spawn(move || loop { | ||||||
|                     if let Ok((mut connection, con_addr)) = v.accept() { |                     if let Ok((mut connection, con_addr)) = v.accept() { | ||||||
|                         eprintln!("[info] TCP connection accepted from {con_addr}."); |                         eprintln!("[info] TCP connection accepted from {con_addr}."); | ||||||
| @ -100,10 +103,15 @@ pub fn run_server( | |||||||
|                             // sync database
 |                             // sync database
 | ||||||
|                             let mut db = db.lock().unwrap(); |                             let mut db = db.lock().unwrap(); | ||||||
|                             db.init_connection(&mut connection)?; |                             db.init_connection(&mut connection)?; | ||||||
|  |                             // keep the client in sync:
 | ||||||
|  |                             // the db will send all updates to the client once it is added to update_endpoints
 | ||||||
|                             db.update_endpoints.push(UpdateEndpoint::Bytes(Box::new( |                             db.update_endpoints.push(UpdateEndpoint::Bytes(Box::new( | ||||||
|  |                                 // try_clone is used here to split a TcpStream into Writer and Reader
 | ||||||
|                                 connection.try_clone().unwrap(), |                                 connection.try_clone().unwrap(), | ||||||
|                             ))); |                             ))); | ||||||
|  |                             // drop the mutex lock
 | ||||||
|                             drop(db); |                             drop(db); | ||||||
|  |                             // read updates from the tcp stream and send them to the database, exit on EOF or Err
 | ||||||
|                             loop { |                             loop { | ||||||
|                                 if let Ok(command) = Command::from_bytes(&mut connection) { |                                 if let Ok(command) = Command::from_bytes(&mut connection) { | ||||||
|                                     command_sender.send(command).unwrap(); |                                     command_sender.send(command).unwrap(); | ||||||
| @ -121,6 +129,8 @@ pub fn run_server( | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     // for now, update the player 10 times a second so it can detect when a song has finished and start a new one.
 | ||||||
|  |     // TODO: player should send a NextSong update to the mpsc::Sender to wake up this thread
 | ||||||
|     let dur = Duration::from_secs_f32(0.1); |     let dur = Duration::from_secs_f32(0.1); | ||||||
|     loop { |     loop { | ||||||
|         player.update(&mut database.lock().unwrap()); |         player.update(&mut database.lock().unwrap()); | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ use musicdb_lib::data::database::Database; | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|  |     // parse args
 | ||||||
|     let mut args = std::env::args().skip(1); |     let mut args = std::env::args().skip(1); | ||||||
|     let mut tcp_addr = None; |     let mut tcp_addr = None; | ||||||
|     let mut web_addr = None; |     let mut web_addr = None; | ||||||
| @ -148,20 +149,7 @@ this help was shown because no arguments were provided." | |||||||
|         ); |         ); | ||||||
|         exit(1); |         exit(1); | ||||||
|     }; |     }; | ||||||
|     // database.add_song_new(Song::new(
 |     // database can be shared by multiple threads using Arc<Mutex<_>>
 | ||||||
|     //     "Amaranthe/Manifest/02 Make It Better.mp3".into(),
 |  | ||||||
|     //     "Make It Better".to_owned(),
 |  | ||||||
|     //     None,
 |  | ||||||
|     //     None,
 |  | ||||||
|     //     vec![],
 |  | ||||||
|     //     None,
 |  | ||||||
|     // ));
 |  | ||||||
|     // let mut player = Player::new();
 |  | ||||||
|     // eprintln!("[info] database.songs: {:?}", database.songs());
 |  | ||||||
|     // database.save_database(Some("/tmp/dbfile".into())).unwrap();
 |  | ||||||
|     // eprintln!("{}", database.get_song(&0).unwrap());
 |  | ||||||
|     // database.queue.add_to_end(QueueContent::Song(1).into());
 |  | ||||||
|     // player.update_and_restart_playing_song(&database);
 |  | ||||||
|     let database = Arc::new(Mutex::new(database)); |     let database = Arc::new(Mutex::new(database)); | ||||||
|     if tcp_addr.is_some() || web_addr.is_some() { |     if tcp_addr.is_some() || web_addr.is_some() { | ||||||
|         if let Some(addr) = web_addr { |         if let Some(addr) = web_addr { | ||||||
| @ -177,8 +165,4 @@ this help was shown because no arguments were provided." | |||||||
|     } else { |     } else { | ||||||
|         eprintln!("nothing to do, not starting the server."); |         eprintln!("nothing to do, not starting the server."); | ||||||
|     } |     } | ||||||
|     // std::io::stdin().read_line(&mut String::new()).unwrap();
 |  | ||||||
|     // dbg!(Update::from_bytes(&mut BufReader::new(
 |  | ||||||
|     //     TcpStream::connect("127.0.0.1:26314".parse::<SocketAddr>().unwrap()).unwrap()
 |  | ||||||
|     // )));
 |  | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Mark
						Mark