Waffle: Gopher Client

__      __          __    __   _         _
\ \    / /  __ _   / _|  / _| | |  ___  (_)
 \ \/\/ /  / _` | |  _| |  _| | | / -_)  _
  \_/\_/   \__,_| |_|   |_|   |_| \___| (_)


  ___                _
 / __|  ___   _ __  | |_    ___   _ _
| (_ | / _ \ | '_ \ | ' \  / -_) | '_|
 \___| \___/ | .__/ |_||_| \___| |_|
             |_|

  ___   _   _                _
 / __| | | (_)  ___   _ _   | |_
| (__  | | | | / -_) | ' \  |  _|
 \___| |_| |_| \___| |_||_|  \__|


╔─*──*──*──*──*──*──*──*──*──*──*──*──*──*──*──*──╗
║1   .........................................   1║
║2*  .........................................  *2║
║3   .........................................   3║
║1   ...........Posted: 2024-03-10............   1║
║2*  Tags: nix gopher haskell debian showcase   *2║
║3   .........................................   3║
║1   .........................................   1║
╚─────────────────────────────────────────────────╝

I made a Gopher client called waffle[1]. Basically, a web browser, but for the
Gopher Protocol. I started it about 4 years ago now (2020?).

A truly big, complicated beast of a project for me.

I feel like this project touched upon some core areas:

* Rapid acceleration of my Haskell proficiency
* Ability to read and implement RFCs (namely RFC 1436[2])
* Network programming
* Concurrency
* TUI (I used bricks, I think)
* Large project, requiring careful architecture and separation of concerns

Some other highlights:

* I got to talk to one of the people who worked on the Gopher Protocol (P.
 Lindner).
* Caching system
* Titlebar thing
* Search
* Profiling for efficiency, working with someone who made a tool I used to
 analyze profiler output
* Bookmarks
* Color
* UTF-8 support, emojis
* Just using Cabal, basically, not Stack
* Build/run with nix!

## Examples of Waffle

I believe Waffle has an INI file for customization:

!Waffle style configuration => /assets/showcase/waffle/waffle-customization.gif

Playing a hacking game in Waffle on mozz.us:

!Playing a hacking game with Waffle =>
/assets/showcase/waffle/waffle-playing-hacking-game.gif

Waffle playing solitaire:

!Waffle playing solitaire =>
/assets/showcase/waffle/waffle-playing-solitaire.png

Waffle playing Zork:

!Waffle playing Zork => /assets/showcase/waffle/waffle-playing-zork.png

An example of Waffle's search and refresh abilities (also kind of showing off
caching):

!Search and refresh in Waffle =>
/assets/showcase/waffle/waffle-search-and-refresh-quotes.gif

## The development journey

I found it very satisfying to create a project based around reading an RFC and
other research.

### Gopher: High Level

The Gopher client, hell, even the Gopher Protocol in general, is very
lines-based. As in everything is just composed by a serires of lines, it can
feel.

I believe the Gopher Protocol was designed around being simple, easy to hack
things together for.

You browse Gopherspace using menus. Menus are basically a bunch of lines that
indicate some kind of option you can select to do something, like go to a new
directory, download a file, or start a query.

The menus themselves are simple text files that are primarily tab-delimited,
looking like this:

```
1About Gopher   /about  gopher.server.com       70
0What's New!    /whatsnew.txt   gopher.server.com       70
1Gopher Clients /clients        gopher.server.com       70
1Search Gopher Space    /search gopher.server.com       70
0Gopher Protocol Specification  /rfc1436.txt    gopher.server.com       70
iThis is an information line            fake    0
1Fun & Games    /fun    gopher.server.com       70
1University Information /university     gopher.server.com       70
1Libraries around the World     /libraries      gopher.server.com       70
7Search a gopherhole    /search gopher.example.org      70

```

As you can see these menus are pretty easy to parse. Each line represents
something actionable. For example if the first line is selected in the client,
we know the following information:

* Represent the line as `About Gopher` to the user/client
* The line intends to bring the user to another gopher menu (I also see menus
 called "directories"), because the first character on the line is `1`, which
 is the directory (menu) type. Check out RFC1436, 3.8: Item Type Characters[3]
 for a comprehensive list of such characters.
* The "selector string" is `/about` (the path to request)
* The server to make the request to is `gopher.server.com` on port `70`

An `i` line is just an information line that's just there for display. There are
various kinds of downloadable files, like type `9` (binary file) and even a
specific type for GIF (`g`).

Maybe the most interesting type is the `7` item type character. It indicates
that the entry intends to perform a query if selected, where a query string is
sent to the server and a response is (hopefully) given based off that query.
Some gopher enthusiasts, myself included, really enjoy abusing this feature for
everything from games (I've included some screenshots and even video of games in
Waffle!) to, say, my gopher forum software[4].

Clients are also kind of expected to view type `0` in the client, I think.
That's text files.

### Interesting bits

* I found it interesting that the way Gopher Clients, Waffle included, tend to
 get the title bar text or the like, is from the label of the menu item which
 lead to it.
* I decided to implement a loading screen (there's a whole interesting
 `Progress.hs`[5] module), which uses `Brick.BChan`[6], and does some fun
 networking/socket things
* Caching and history
* The TUI/BrickApp gets broken up into different states I call a `ModeAction`,
 so this could be things like bookmark, help, homepage, menu, open, progress,
 search, or stuff to do with the menu. The user interface is kind of thought of
 in relation to a state machine.
* INI configuration and storage for bookmarks, styling, default associations,
 homepage
* Can be piped with tor or whatever, so you can access hidden service
 Gopherholes

### Profiling, benchmarking, optimizing

One of the first major issues I came across was Waffle was extremely slow. I
wasn't sure why, because it felt like I was using brick[7] the way I was
supposed to.

I used a terminal-based viewer called profold[8] for GHC `.prof` files generated
by profiling with `+RTS -p`, made by someone I had the pleasure of talking to
(@garmelon[9]). I believe it allowed to to suss out what Waffle was spending the
most time doing and why, specifically, scrolling up and down felt so laggy and
intensive on very long menus. I can't remember what I was doing before, but the
solution turned out to be to instead use `BrickList` to display menus,
basically.

## Footnotes

[1]: waffle: https://github.com/someodd/waffle
[2]: RFC 1436: https://www.rfc-editor.org/rfc/rfc1436
[3]: RFC1436, 3.8: Item Type Characters: https://www.rfc-editor.org/rfc/rfc1436
[4]: my gopher forum software: /showcase/gopherden
[5]: `Progress.hs`: https://github.com/someodd/waffle/blob/master/src/BrickApp/ModeAction/Progress.hs
[6]: `Brick.BChan`: https://hackage.haskell.org/package/brick-2.3.1/docs/Brick-BChan.html
[7]: brick: https://hackage.haskell.org/package/brick
[8]: profold: https://github.com/Garmelon/profold
[9]: @garmelon: https://github.com/Garmelon/