rl_hours_tracker/
lib.rs

1//! # Rocket League Hours Tracker
2//! This was made specifically for the Epic Games version of Rocket League
3//! as the Epic Games launcher has no way of showing the past two hours played in
4//! the same way that steam is able to.
5//!
6//! However, this program can and should still work with the steam version of the game.
7//!
8//! It is `HIGHLY` recommended to not manually alter the files that are created by this program
9//! otherwise it could lead to unwanted behaviour by the program
10//!
11//! ``` rust
12//!     println!("You got it Oneil :)");
13//! ```
14
15//! ## Library
16//! The Rocket League Hours Tracker library contains modules which provide additional
17//! functionality to the Rocket League Hours Tracker binary. This library currently
18//! implements the [`website_files`] module, which provides the functionality to generate
19//! the Html, CSS, and JavaScript for the Rocket League Hours Tracker website, and the [`update`]
20//! module, which is the built in updater for the binary which retrieves the update from the GitHub
21//! repository.
22//!
23//! The website functionality takes adavantage of the [`build_html`] library, which allows us
24//! to generate the Html for the website, alongside the [`webbrowser`] library, which allows us
25//! to open the website in a browser.
26//!
27//! The update module only operates when using the installed version of the program which can be found in the
28//! [releases](https://github.com/OneilNvM/rl-hours-tracker/releases) section on the GitHub repository. This
29//! module uses the [`reqwest`] crate to make HTTP requests to the rl-hours-tracker repository in order to retrieve
30//! the new update from the releases section. This module has the functionality to check for any new updates, update
31//! the program, and clean up any additional files made during the update.
32//!
33//! ### Use Case
34//! Within the [`website_files`] module, there is a public function [`website_files::generate_website_files`],
35//! which writes the files for the website in the website directory in `RlHoursFolder`. This function accepts a
36//! [`bool`] value, which determines whether the option to open the website in a browser should appear when this
37//! function is called.
38//!
39//! ```
40//! use rl_hours_tracker::website_files;
41//!
42//! // This will generate the website files and prompt you with the option to open the
43//! // webstie in a browser.
44//! website_files::generate_website_files(true);
45//!
46//! // This will also generate the website but will not prompt the user to open the website
47//! // in a browser.
48//! website_files::generate_website_files(false);
49//! ```
50//!
51//! The [`update`] module has two public asynchronous functions available: [`update::check_for_update`] and [`update::update`].
52//! The [`update::check_for_update`] function is responsible for sending a HTTP request to the repository and checking the version
53//! number of the latest release, and comparing it to the current version of the program. The [`update::update`] function is responsible
54//! updating the program by sending a HTTP request to the repository to retrieve the update zip from the latest release, and unzipping the
55//! zip files contents to replace the old program files with the newest version.
56//!
57//! ```
58//! use rl_hours_tracker::update;
59//! use tokio::runtime::Runtime;
60//!
61//! // This creates a tokio runtime instance for running our function
62//! let rt = Runtime::new().unwrap();
63//!
64//! // This runs our asynchronous function which checks for an update
65//! rt.block_on(update::check_for_update())?;
66//! ```
67//!
68//! The [`update::check_for_update`] function does use the [`update::update`] function when it finds that there is a new release on the GitHub, however
69//! the update function can be used by itself in a different context if needed.
70//!
71//! ```
72//! use rl_hours_tracker::update;
73//! use tokio::runtime::Runtime;
74//!
75//! // This creates a tokio runtime instance for running our function
76//! let rt = Runtime::new().unwrap();
77//!
78//! // This runs our asynchronous function which updates the program
79//! rt.block_on(update::update())?;
80//! ```
81use chrono::Local;
82use colour::{black_bold, blue_ln_bold, cyan, green, green_ln_bold, red, white, yellow_ln_bold};
83use log::{error, info, trace, warn, LevelFilter};
84use log4rs::{
85    append::{console::ConsoleAppender, file::FileAppender},
86    config::{Appender, Logger, Root},
87    encode::pattern::PatternEncoder,
88    Config, Handle,
89};
90use std::{
91    error::Error,
92    fmt::Display,
93    fs::{self, File},
94    io::{self, Read, Write},
95    process,
96    sync::{
97        atomic::{AtomicBool, Ordering},
98        Arc, Mutex,
99    },
100    thread,
101    time::{Duration, SystemTime},
102};
103use stopwatch::Stopwatch;
104use sysinfo::System;
105use tokio::runtime::Runtime;
106use winit::event_loop::EventLoopProxy;
107
108use crate::{calculate_past_two::calculate_past_two, winit_tray_icon::UserEvent};
109
110pub mod calculate_past_two;
111#[cfg(test)]
112mod tests;
113pub mod update;
114pub mod website_files;
115pub mod winit_tray_icon;
116
117/// Type alias for Results which only return [`std::io::Error`] as its error variant.
118pub type IoResult<T> = Result<T, io::Error>;
119
120/// Contains the relevant data for running the program
121struct ProgramRunVars {
122    proxy: EventLoopProxy<UserEvent>,
123    process_name: String,
124    is_waiting: bool,
125    option: String,
126    currently_tracking: Arc<Mutex<AtomicBool>>,
127    stop_tracker: Arc<Mutex<AtomicBool>>,
128}
129
130impl ProgramRunVars {
131    fn new(
132        proxy: EventLoopProxy<UserEvent>,
133        stop_tracker: Arc<Mutex<AtomicBool>>,
134        currently_tracking: Arc<Mutex<AtomicBool>>,
135    ) -> Self {
136        Self {
137            process_name: String::from("RocketLeague.exe"),
138            is_waiting: false,
139            option: String::with_capacity(1),
140            proxy,
141            stop_tracker,
142            currently_tracking,
143        }
144    }
145}
146
147/// Custom error for [`calculate_past_two`] function
148#[derive(Debug, Clone)]
149pub struct PastTwoError;
150
151impl Display for PastTwoError {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        write!(
154            f,
155            "next closest date to the date two weeks ago could not be found."
156        )
157    }
158}
159
160impl Error for PastTwoError {}
161
162/// Initializes logging configuration for the program
163///
164/// Logs are stored in `C:/RLHoursFolder/logs`
165pub fn initialize_logging() -> Result<Handle, Box<dyn Error>> {
166    // Create appenders
167    let stdout = ConsoleAppender::builder().build();
168    let general_logs = FileAppender::builder()
169        .build("C:/RLHoursFolder/logs/general_$TIME{%Y-%m-%d_%H-%M-%S}.log")?;
170    let wti_logs = FileAppender::builder()
171        .build("C:/RLHoursFolder/logs/tray-icon_$TIME{%Y-%m-%d_%H-%M-%S}.log")?;
172    let requests = FileAppender::builder()
173        .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
174        .build("C:/RLHoursFolder/logs/requests.log")?;
175
176    // Create loggers
177    let rl_hours_tracker_logger = Logger::builder()
178        .additive(false)
179        .appenders(vec!["general_logs"])
180        .build("rl_hours_tracker", LevelFilter::Trace);
181    let rl_hours_tracker_update_logger = Logger::builder()
182        .additive(false)
183        .appenders(vec!["requests", "general_logs"])
184        .build("rl_hours_tracker::update", LevelFilter::Trace);
185    let rl_hours_tracker_cpt_logger = Logger::builder()
186        .additive(false)
187        .appenders(vec!["general_logs"])
188        .build("rl_hours_tracker::calculate_past_two", LevelFilter::Info);
189    let rl_hours_tracker_wti_logger = Logger::builder()
190        .additive(false)
191        .appenders(vec!["wti_logs"])
192        .build("rl_hours_tracker::winit_tray_icon", LevelFilter::Info);
193
194    // Move loggers and appenders into vectors
195    let loggers = vec![
196        rl_hours_tracker_logger,
197        rl_hours_tracker_update_logger,
198        rl_hours_tracker_cpt_logger,
199        rl_hours_tracker_wti_logger,
200    ];
201    let appenders = vec![
202        Appender::builder().build("stdout", Box::new(stdout)),
203        Appender::builder().build("general_logs", Box::new(general_logs)),
204        Appender::builder().build("requests", Box::new(requests)),
205        Appender::builder().build("wti_logs", Box::new(wti_logs)),
206    ];
207
208    let config = Config::builder()
209        .appenders(appenders)
210        .loggers(loggers)
211        .build(Root::builder().appender("stdout").build(LevelFilter::Warn))?;
212
213    // Initialize logging configuration
214    let handle = log4rs::init_config(config)?;
215
216    Ok(handle)
217}
218
219/// This runs the [`update::check_for_update`] function
220pub fn run_self_update() -> Result<(), Box<dyn Error>> {
221    let rt = Runtime::new()?;
222
223    rt.block_on(update::check_for_update())?;
224
225    Ok(())
226}
227
228/// This function runs the program
229pub fn run(
230    proxy: EventLoopProxy<UserEvent>,
231    stop_tracker: Arc<Mutex<AtomicBool>>,
232    currently_tracking: Arc<Mutex<AtomicBool>>,
233) {
234    let mut program = ProgramRunVars::new(proxy, stop_tracker, currently_tracking);
235
236    // Run the main loop
237    run_main_loop(&mut program);
238}
239
240/// This function creates the directories for the program. It creates a local [`Vec<Result>`]
241/// which stores [`fs::create_dir`] results.
242///
243/// This function then returns a [`Vec<Result>`] which stores any errors that may have occurred
244///
245/// # Errors
246/// This function stores an [`io::Error`] in the output Vector if there was any issue creating a folder.
247pub fn create_directory() -> Vec<IoResult<()>> {
248    // Create the folder directories for the program
249    let folder = fs::create_dir("C:\\RLHoursFolder");
250    let website_folder = fs::create_dir("C:\\RLHoursFolder\\website");
251    let website_pages = fs::create_dir("C:\\RLHoursFolder\\website\\pages");
252    let website_css = fs::create_dir("C:\\RLHoursFolder\\website\\css");
253    let website_js = fs::create_dir("C:\\RLHoursFolder\\website\\js");
254    let website_images = fs::create_dir("C:\\RLHoursFolder\\website\\images");
255
256    // Store the folder results in Vector
257    let folder_vec: Vec<IoResult<()>> = vec![
258        folder,
259        website_folder,
260        website_pages,
261        website_css,
262        website_js,
263        website_images,
264    ];
265
266    // Iterate through all the folder creations and filter for any errors
267    let result: Vec<IoResult<()>> = folder_vec.into_iter().filter(|f| f.is_err()).collect();
268
269    result
270}
271
272/// This function runs the main loop of the program. This checks if the `RocketLeague.exe` process is running and
273/// runs the [`record_hours`] function if it is running, otherwise it will continue to wait for the process to start.
274fn run_main_loop(program: &mut ProgramRunVars) {
275    loop {
276        // Check if the process is running
277        if check_for_process(&program.process_name) {
278            record_hours(
279                &program.process_name,
280                program.stop_tracker.clone(),
281                program.currently_tracking.clone(),
282            );
283
284            // Generate the website files
285            website_files::generate_website_files(true)
286                .unwrap_or_else(|e| warn!("failed to generate website files: {e}"));
287
288            program.is_waiting = false;
289
290            print!("End program (");
291            green!("y");
292            print!(" / ");
293            red!("n");
294            print!("): ");
295            std::io::stdout()
296                .flush()
297                .unwrap_or_else(|_| println!("End program (y/n)?\n"));
298            io::stdin()
299                .read_line(&mut program.option)
300                .unwrap_or_default();
301
302            if program.option.trim() == "y" || program.option.trim() == "Y" {
303                print!("{}[2K\r", 27 as char);
304                std::io::stdout()
305                    .flush()
306                    .expect("could not flush the output stream");
307                yellow_ln_bold!("Goodbye!");
308                program
309                    .proxy
310                    .send_event(UserEvent::QuitApp(AtomicBool::new(true)))
311                    .unwrap_or_else(|_| error!("event loop already closed"));
312                break;
313            } else if program.option.trim() == "n" || program.option.trim() == "N" {
314                program.option = String::with_capacity(1);
315                continue;
316            } else {
317                error!("Unexpected input! Ending program.");
318                program
319                    .proxy
320                    .send_event(UserEvent::QuitApp(AtomicBool::new(true)))
321                    .unwrap_or_else(|_| error!("event loop already closed"));
322                break;
323            }
324        } else {
325            // Print 'Waiting for Rocket League to start...' only once by changing the value of is_waiting to true
326            if !program.is_waiting {
327                green!("Waiting for Rocket League to start.\r");
328                io::stdout()
329                    .flush()
330                    .expect("could not flush the output stream");
331                thread::sleep(Duration::from_millis(500));
332                white!("Waiting for Rocket League to start..\r");
333                io::stdout()
334                    .flush()
335                    .expect("could not flush the output stream");
336                thread::sleep(Duration::from_millis(500));
337                black_bold!("Waiting for Rocket League to start...\r");
338                io::stdout()
339                    .flush()
340                    .expect("could not flush the output stream");
341                thread::sleep(Duration::from_millis(500));
342                print!("{}[2K\r", 27 as char);
343                red!("Waiting for Rocket League to start\r");
344                io::stdout()
345                    .flush()
346                    .expect("could not flush the output stream");
347                thread::sleep(Duration::from_millis(500));
348            }
349        }
350    }
351}
352
353/// This function takes in a reference string `process_name: &str` and starts a stopwatch
354/// which keeps track of the amount of seconds that pass whilst the process is running.
355/// The stopwatch is ended and the File operations are run at the end of the process.
356/// The date and elapsed time are stored in the `date.txt` file and the hours is stored in
357/// `hours.txt`
358fn record_hours(
359    process_name: &str,
360    stop_tracker: Arc<Mutex<AtomicBool>>,
361    currently_tracking: Arc<Mutex<AtomicBool>>,
362) {
363    let mut sw = Stopwatch::start_new();
364
365    blue_ln_bold!("\nRocket League is running\n");
366
367    currently_tracking
368        .try_lock()
369        .unwrap_or_else(|e| {
370            error!("error when attempting to access lock for currently_tracking: {e}");
371            panic!("could not access lock for currently_tracking");
372        })
373        .store(true, Ordering::SeqCst);
374
375    trace!(
376        "<< fn record_hours >> currently_tracking set to {} before live_stopwatch",
377        currently_tracking
378            .try_lock()
379            .unwrap()
380            .load(Ordering::Relaxed)
381    );
382    trace!(
383        "<< fn record_hours >> stop_tracker set to {} before live_stopwatch",
384        stop_tracker.try_lock().unwrap().load(Ordering::Relaxed)
385    );
386
387    // Start live stopwatch
388    live_stopwatch(process_name, stop_tracker.clone());
389
390    trace!(
391        "<< fn record_hours >> stop_tracker set to {} after live_stopwatch",
392        stop_tracker.try_lock().unwrap().load(Ordering::Relaxed)
393    );
394
395    currently_tracking
396        .try_lock()
397        .unwrap_or_else(|e| {
398            error!("error when attempting to access lock for currently_tracking: {e}");
399            panic!("could not access lock for currently_tracking");
400        })
401        .store(false, Ordering::SeqCst);
402
403    trace!(
404        "<< fn record_hours >> currently_tracking set to {} after live_stopwatch",
405        currently_tracking
406            .try_lock()
407            .unwrap()
408            .load(Ordering::Relaxed)
409    );
410
411    stop_tracker
412        .try_lock()
413        .unwrap_or_else(|e| {
414            error!("error when attempting to access lock for stop_tracking: {e}");
415            panic!("could not access lock for stop_tracking");
416        })
417        .store(false, Ordering::SeqCst);
418
419    // Stop the stopwatch
420    sw.stop();
421
422    info!("Record Hours: START\n");
423
424    let seconds: u64 = sw.elapsed_ms() as u64 / 1000;
425    let hours: f32 = (sw.elapsed_ms() as f32 / 1000_f32) / 3600_f32;
426
427    trace!("<< fn record_hours >> seconds: {seconds}, hours: {hours:.1}");
428
429    let hours_result = File::open("C:\\RLHoursFolder\\hours.txt");
430    let date_result = File::open("C:\\RLHoursFolder\\date.txt");
431
432    // Write date and seconds to date.txt
433    write_to_date(date_result, &seconds).unwrap_or_else(|e| {
434        error!("error writing to date.txt: {e}");
435        process::exit(1);
436    });
437
438    // Buffer which stores the hours in the past two weeks
439    let hours_buffer = calculate_past_two().unwrap_or_else(|e| {
440        warn!("failed to calculate past two: {e}");
441        0
442    });
443
444    trace!("<< fn record_hours >> hours_buffer is set to {hours_buffer}");
445
446    if hours_buffer != 0 {
447        let hours_past_two = hours_buffer as f32 / 3600_f32;
448
449        write_to_hours(hours_result, &seconds, &hours, &hours_past_two, &sw).unwrap_or_else(|e| {
450            error!("error writing to hours.txt: {e}");
451            process::exit(1);
452        });
453        info!("Record Hours: FINISHED\n")
454    } else {
455        warn!("past two returned zero seconds")
456    }
457}
458
459fn live_stopwatch(process_name: &str, stop_tracker: Arc<Mutex<AtomicBool>>) {
460    let mut timer_early = SystemTime::now();
461
462    let mut seconds: u8 = 0;
463    let mut minutes: u8 = 0;
464    let mut hours: u16 = 0;
465
466    while check_for_process(process_name)
467        && !stop_tracker
468            .try_lock()
469            .unwrap_or_else(|e| {
470                error!("error when attempting to access lock for stop_tracking: {e}");
471                panic!("could not access lock for stop_tracking");
472            })
473            .load(Ordering::SeqCst)
474    {
475        let timer_now = timer_early
476            .checked_add(Duration::from_millis(999))
477            .unwrap_or_else(|| {
478                error!("could not return system time");
479                SystemTime::now()
480            });
481
482        let delay = timer_now.duration_since(timer_early).unwrap_or_else(|e| {
483            warn!(
484                "system time is ahead of the timer. SystemTime difference: {:?}",
485                e.duration()
486            );
487            Duration::from_millis(1000)
488        });
489
490        // Check if current seconds are greater than or equal to 1 minute
491        if seconds == 59 {
492            seconds = 0;
493            minutes += 1;
494
495            // Check if current minutes are greater than or equal to 1 hour
496            if minutes == 60 {
497                minutes = 0;
498                hours += 1;
499            }
500        } else {
501            seconds += 1;
502        }
503        print!("{}[2K\r", 27 as char);
504
505        // Print the output for the timer
506        if hours < 10 && minutes < 10 && seconds < 10 {
507            cyan!("Time Elapsed: 0{}:0{}:0{}\r", hours, minutes, seconds);
508        } else if hours >= 10 {
509            if minutes < 10 && seconds < 10 {
510                cyan!("Time Elapsed: {}:0{}:0{}\r", hours, minutes, seconds);
511            } else if minutes < 10 && seconds >= 10 {
512                cyan!("Time Elapsed: {}:0{}:{}\r", hours, minutes, seconds);
513            } else if minutes >= 10 && seconds < 10 {
514                cyan!("Time Elapsed: {}:{}:0{}\r", hours, minutes, seconds);
515            } else {
516                cyan!("Time Elapsed: {}:{}:{}\r", hours, minutes, seconds);
517            }
518        } else if hours < 10 && minutes >= 10 && seconds < 10 {
519            cyan!("Time Elapsed: 0{}:{}:0{}\r", hours, minutes, seconds);
520        } else if hours < 10 && minutes < 10 && seconds >= 10 {
521            cyan!("Time Elapsed: 0{}:0{}:{}\r", hours, minutes, seconds);
522        } else {
523            cyan!("Time Elapsed: 0{}:{}:{}\r", hours, minutes, seconds);
524        }
525
526        // Flush the output
527        io::stdout()
528            .flush()
529            .unwrap_or_else(|_| warn!("could not flush output stream"));
530
531        thread::sleep(delay);
532
533        timer_early += Duration::from_millis(999)
534    }
535
536    trace!("<< fn live_stopwatch >> hours: {hours}, minutes: {minutes}, seconds: {seconds}");
537}
538
539/// This function takes the `contents: &str` parameter which contains the contents from the `hours.txt` file
540/// and returns a tuple of `(u64, f32)` which contains the seconds and hours from the file.
541fn retrieve_time(contents: &str) -> Result<(u64, f32), Box<dyn Error>> {
542    // Split the contents by newline character
543    let split_new_line: Vec<&str> = contents.split("\n").collect();
544
545    // Split the seconds and hours string references by whitspace
546    let split_whitspace_sec: Vec<&str> = split_new_line[1].split_whitespace().collect();
547    let split_whitespace_hrs: Vec<&str> = split_new_line[2].split_whitespace().collect();
548
549    // Split the seconds and hours string references by characters
550    let split_char_sec = split_whitspace_sec[2].chars();
551    let split_char_hrs = split_whitespace_hrs[2].chars();
552
553    let mut sec_vec: Vec<char> = vec![];
554    let mut hrs_vec: Vec<char> = vec![];
555
556    // Loop through Chars iterator to push only numeric characters to the seconds Vector
557    for num in split_char_sec {
558        if num.is_numeric() {
559            sec_vec.push(num);
560        }
561    }
562
563    trace!("<< fn retrieve_time >> seconds vector: {sec_vec:?}");
564
565    // Loop through the Chars iterator to push numeric characters (plus the period character for decimals) to the hours Vector
566    for num in split_char_hrs {
567        if num.is_numeric() || num == '.' {
568            hrs_vec.push(num);
569        }
570    }
571
572    trace!("<< fn retrieve_time >> hours vector: {hrs_vec:?}");
573
574    let seconds_str: String = sec_vec.iter().collect();
575    let hours_str: String = hrs_vec.iter().collect();
576
577    let old_seconds: u64 = seconds_str.parse()?;
578    let old_hours: f32 = hours_str.parse()?;
579
580    trace!("<< fn retrieve_time >> old seconds: {old_seconds}, old_hours: {old_hours:.1}");
581
582    // Return a tuple of the old seconds and old hours
583    Ok((old_seconds, old_hours))
584}
585
586/// This function constructs a new [`String`] which will have the contents to write to `hours.txt` with new hours and seconds
587/// and returns it.
588fn return_new_hours(
589    contents: &str,
590    seconds: &u64,
591    hours: &f32,
592    past_two: &f32,
593) -> Result<String, Box<dyn Error>> {
594    yellow_ln_bold!("Getting old hours...");
595    // Retrieves the old hours and seconds from the contents String
596    let (old_seconds, old_hours) = retrieve_time(contents)?;
597
598    let added_seconds = old_seconds + *seconds;
599    let added_hours = old_hours + *hours;
600
601    trace!("<< fn return_new_hours >> added_seconds: {added_seconds}, added_hours: {added_hours:.1}");
602
603    Ok(format!(
604        "Rocket League Hours\nTotal Seconds: {}s\nTotal Hours: {:.1}hrs\nHours Past Two Weeks: {:.1}hrs\n",
605        added_seconds, added_hours, past_two
606    ))
607}
608
609/// This function writes the new contents to the `hours.txt` file. This includes the total `seconds`, `hours`, and `hours_past_two`.
610/// This function then returns a [`Result<()>`] when file operations were all successful.
611///
612/// # Errors
613/// This function returns an [`io::Error`] if any file operations failed.
614fn write_to_hours(
615    hours_result: IoResult<File>,
616    seconds: &u64,
617    hours: &f32,
618    hours_past_two: &f32,
619    sw: &Stopwatch,
620) -> Result<(), Box<dyn Error>> {
621    // Check if the file exists
622    if let Ok(mut file) = hours_result {
623        let mut contents = String::new();
624
625        // Attempt to read from the hours.txt file
626        file.read_to_string(&mut contents)?;
627
628        // Stores the new contents for the file as a String
629        let rl_hours_str = return_new_hours(&contents, seconds, hours, hours_past_two)?;
630
631        // Attempt to write to hours.txt
632        let mut truncated_file = File::create("C:\\RLHoursFolder\\hours.txt")?;
633
634        yellow_ln_bold!("Writing to hours.txt...");
635
636        // Check if the write was successful
637        truncated_file.write_all(rl_hours_str.as_bytes())?;
638
639        green_ln_bold!("Successful!\n");
640        Ok(())
641    } else {
642        // Check if the file was created successfully
643        let mut file = File::create("C:\\RLHoursFolder\\hours.txt")?;
644        let total_seconds = sw.elapsed_ms() / 1000;
645        let total_hours: f32 = (sw.elapsed_ms() as f32 / 1000_f32) / 3600_f32;
646        let rl_hours_str = format!(
647                                "Rocket League Hours\nTotal Seconds: {}s\nTotal Hours: {:.1}hrs\nHours Past Two Weeks: {:.1}hrs\n", total_seconds, total_hours, hours_past_two
648                            );
649
650        yellow_ln_bold!("Writing to hours.txt...");
651
652        // Checks if the write was successful
653        file.write_all(rl_hours_str.as_bytes())?;
654
655        green_ln_bold!("The hours file was successfully created");
656        Ok(())
657    }
658}
659
660/// This function writes new contents to the `date.txt` file. This uses the [`Local`] struct which allows us to use the [`Local::now()`]
661/// function to retrieve the local date and time as [`DateTime<Local>`]. The date is then turned into a [`NaiveDate`] by using [`DateTime<Local>::date_naive()`]
662/// which returns us the date by itself.
663///
664/// # Errors
665/// Returns an [`io::Error`] if there were any file operations which failed.
666fn write_to_date(date_result: IoResult<File>, seconds: &u64) -> IoResult<()> {
667    // Check if the date file exists
668    if date_result.is_ok() {
669        let mut append_date_result = File::options()
670            .append(true)
671            .open("C:\\RLHoursFolder\\date.txt")?;
672
673        // Attenot to open the date.txt file
674        let today = Local::now().date_naive();
675
676        let today_str = format!("{} {}s\n", today, seconds);
677
678        yellow_ln_bold!("Appending to date.txt...");
679
680        // Checks if the write was successful
681        append_date_result.write_all(today_str.as_bytes())?;
682
683        green_ln_bold!("Successful!\n");
684        Ok(())
685    } else {
686        // Check if the file was created
687        let mut file = File::create("C:\\RLHoursFolder\\date.txt")?;
688        let today = Local::now().date_naive();
689
690        let today_str = format!("{} {}s\n", today, seconds);
691
692        yellow_ln_bold!("Appending to date.txt...");
693
694        // Checks if the write was successful
695        file.write_all(today_str.as_bytes())?;
696
697        green_ln_bold!("The date file was successfully created");
698        Ok(())
699    }
700}
701
702/// This function checks if the process passed in via `name: &str` is running and returns a [`bool`] value
703fn check_for_process(name: &str) -> bool {
704    let sys = System::new_all();
705    let mut result = false;
706
707    for process in sys.processes_by_exact_name(name.as_ref()) {
708        if process.name() == name {
709            result = true;
710            break;
711        }
712    }
713
714    result
715}