DEV Community

Nivethan
Nivethan

Posted on • Edited on • Originally published at nivethan.dev

A Gemini Client in Rust - 10 Bookmarks

Hello! At this point we have a functional client and features and enhancements can be done! Let's focus the biggest enhancement we can make. Bookmarks! Currently we need to type in the places we want to go and we have no real way of saving a place.

In this chapter we will add our bookmarking functionality and also save it to a file.

Let's get started!

Bookmarks

We'll first write our bookmarking logic and then once we have the wired up, we'll working on saving our bookmarks to the disk.

We're going to reuse the logic from our history functionality.

...
    let prompt = "\x1b[92m>\x1b[0m";
    let mut cache: Vec<Page> = vec![];
    let mut bookmarks: Vec<String> = vec![];
...
Enter fullscreen mode Exit fullscreen mode

We add a new bookmarks list that we will push things onto.

...
            "add" => {
                if cache.len() > 0 {
                    bookmarks.push(cache.last().unwrap().url.request().trim().to_string());
                } else {
                    println!("Nothing to bookmark.");
                }
            },
...
Enter fullscreen mode Exit fullscreen mode

We then implement the add function that will simply add the last item in the cache to our bookmarks.

...
            "b" => {
                for (index, bookmark) in bookmarks.iter().enumerate() {
                    println!("{}. {}", index, bookmark);
                }
            },
...
Enter fullscreen mode Exit fullscreen mode

We implement another handler for the b option which will print our our bookmarks.

...
            "b" => {
                for (index, bookmark) in bookmarks.iter().enumerate() {
                    println!("{}. {}", index, bookmark);
                }
            },
            _ if tokens[0].starts_with("b") => {
                let option = tokens[0][1..].to_string().parse::<i32>().unwrap_or(-1);
                if option < 0 || option >= bookmarks.len() as i32 {
                    println!("Invalid bookmark option.");
                } else {
                let url = Url::new(&bookmarks[option as usize]);
                let content = visit(&url);
                let page  = Page::new(url, content);
                cache.push(page);

                }
            },
...
Enter fullscreen mode Exit fullscreen mode

Lastly we add the ability to enter bx where x is the bookmark number we want to go to. Voila! We now have bookmarking functionality.

We were able to do this quickly because now we have the major structures of our client done and can now just focus on the tweaks.

Now let's look at writing the bookmarks out to the hard drive and then loading it in as well. This way we can have bookmarks that are persistent!

Persistent Bookmarks

The first thing we need to do is save bookmarks to a file.

...
use std::io;
use std::fs::{OpenOptions, File};
use std::io::{Read, Write};
...
fn save_in_file(path: &str, text: &String) {
    let mut file_handle = OpenOptions::new()
        .read(true)
        .append(true)
        .create(true)
        .open(path)
        .unwrap();

    writeln!(file_handle, "{}", text).unwrap();
}
...
fn main {
...
    let bookmark_path = "/home/nivethan/gemini.bookmarks";
...
            "add" => {
                if cache.len() > 0 {
                    let page = cache.last().unwrap().url.request().trim().to_string();
                    save_in_file(bookmark_path, &page);
                    println!("Added {}", page);
                    bookmarks.push(page);

                } else {
                    println!("Nothing to bookmark.");
                }
            },
...
Enter fullscreen mode Exit fullscreen mode

We include the fs module and we write a save function that will take in a path to a file a string. Our bookmark file will be just a list of strings that we want to be able to quickly access.

In our main function we set up the bookmark path, make sure to create the bookmark file as rust doesn't seem to do this automatically if the file doesn't already exist. I'm not sure why as the create option in our save_in_file function is set to true. It may be a weird interaction with Windows Subsytem for Linux. Let me know in the comments!

Now the next thing is we update our add option so that along with saving the page in our bookmark list we also write it out to the file.

Now let's look at loading bookmarks in!

fn load_file(path: &str) -> Vec<String> {
    let mut lines :Vec<String> = vec![];

    let mut file_handle = OpenOptions::new()
        .read(true)
        .append(true)
        .create(true)
        .open(path)
        .unwrap();

    let mut data = String::new();
    file_handle.read_to_string(&mut data).unwrap();
    let content_lines: Vec<&str> = data.split("\n").collect(); 

    for text in content_lines {
        if text != "" {
            lines.push(text.to_string());
        }
    }

    lines
}
Enter fullscreen mode Exit fullscreen mode

Our load function takes in a path and opens the file for just reading. We read the data in the file and split it by new lines. We then loop through the lines and add it to our vector of lines.

...
fn main() {
    let prompt = "\x1b[92m>\x1b[0m";
    let mut cache: Vec<Page> = vec![];

    let bookmark_path = "/home/nivethan/gemini.bookmarks";
    let mut bookmarks = load_file(bookmark_path);
...
Enter fullscreen mode Exit fullscreen mode

Now instead of initializing our bookmarks with an empty list, we use our load_file function to initialize our bookmarks.

Voila! We now have bookmarks that are persistent. Test it out!

We will add one more option in the next chapter, we're almost done!

Top comments (0)