A Gemini Client in Rust - 10 Bookmarks
Tags: rust, tutorial, gemini
Published: 2020-11-09 17:13:19

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.

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

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

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

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

``` rust
...
            "b" => {
                for (index, bookmark) in bookmarks.iter().enumerate() {
                    println!("{}. {}", index, bookmark);
                }
            },
...
```

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

``` rust
...
            "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);
 
                }
            },
...
```

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.

``` rust
...
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.");
                }
            },
...
```

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!

``` rust
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
}
```

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.

``` rust
...
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);
...
```

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!