@@ 0,0 1,347 @@
+## A filesystem for viewing trello
+
+Since my last note [here](http://pmikkelsen.com/plan9/basic_9p_server)
+was very simple, here comes
+a small program (around 250 lines of C), which makes it
+possible to view trello as a read-only filesystem.
+
+## Usage example
+
+First set the `trellokey` and `trellotoken` environment
+variables to the API key and token from the trello website.
+
+ term% trellokey=1298791723987937123.....
+ term% trellotoken=923748237497ab798798999999.....
+
+Then `webfs` must be started so the program can make
+requests to the trello REST api.
+
+ term% webfs
+
+Then `trellofs` itself must be started. It mounts the filesystem under `/mnt/trello`.
+
+ term% trellofs
+
+If I now go and see which boards I have, I get the following
+
+ term% cd /mnt/trello
+ term% ls
+ Beaglebone_Black
+ Fly
+ Fysik_Rapport
+ Guld_kobber
+ Ideer_til_2._produkt
+ Lektier
+ P0
+ P1
+ P2
+ Projekt_Materialer
+ Spil
+ Welcome_Board
+
+The names of the folders are almost like the original board names, except
+that that some characters have been replaced by `_`. Lets look at `P1`
+
+ term% cd P1
+ term% ls
+ Backlog
+ Done
+ Estimat
+ In-progress
+ Info
+ Master.C_Top_Down_Ting
+ Sprint_1
+ Test
+ Test_sprint_1
+ Todo
+
+Todo must have something worth looking at
+
+ term% ls
+ Færdig_rapport_aflevering
+ Korrekturlæsning
+
+Those two items are the actual trello "cards", which I think of as tasks. The first
+one means "Final report hand in", so let's have a look at that.
+
+ term% cd Færdig_rapport_aflevering
+ term% ls
+ description
+ duedate
+ url
+
+There are always three files inside each card folder, but since the information in them
+is not mandatory on trello, some of them will be empty.
+
+ term% cat description
+
+ term% cat duedate
+ 2019-12-18T13:00:00.000Z
+ term% cat url
+ https://trello.com/c/u23ZcNAm/9-f%C3%A6rdig-rapport-aflevering
+
+## Limitations
+
+The key and token are right now in environment variables, which is somewhat
+awkward. Also I would like to be able to actually create and move cards directly
+from the filesystem just by creating files, but that is not something I will implement
+right now.
+
+## The code
+
+The code for the `trellofs` program is listed below.
+
+ #include <u.h>
+ #include <libc.h>
+ #include <fcall.h>
+ #include <thread.h>
+ #include <9p.h>
+ #include <json.h>
+
+ #define MAX_RESPONSE_SIZE (1024 * 1024 * 8)
+
+ char *key;
+ char *token;
+
+ void
+ fsread(Req *r)
+ {
+ char *str;
+
+ if (r->fid->file->aux == nil) {
+ readstr(r, "");
+ } else {
+ str = malloc(strlen(r->fid->file->aux) + 2);
+ sprint(str, "%s\n", r->fid->file->aux);
+ readstr(r, str);
+ }
+ respond(r, nil);
+ }
+
+ Srv fs = {
+ .read = fsread,
+ };
+
+ JSON *
+ trelloget(char *endpoint, char *params)
+ {
+ int ctlfd, bodyfd;
+ int n;
+ int err;
+ char *buf;
+ JSON *res;
+
+ res = nil;
+ bodyfd = -1;
+
+ buf = malloc(MAX_RESPONSE_SIZE + 1);
+ if(buf == nil) {
+ perror("malloc");
+ return nil;
+ }
+
+ ctlfd = open("/mnt/web/clone", ORDWR);
+ if(ctlfd < 0) {
+ perror("open");
+ goto fail;
+ }
+
+ if(read(ctlfd, buf, 32) < 0) {
+ perror("read");
+ goto fail;
+ }
+
+ n = atoi(buf);
+
+ sprint(buf, "url https://api.trello.com/%s?key=%s&token=%s", endpoint, key, token);
+ if(params)
+ sprint(buf + strlen(buf), "\&%s\n", params);
+ else
+ sprint(buf + strlen(buf), "\n");
+
+ err = write(ctlfd, buf, strlen(buf));
+ if(err < 0){
+ perror("write");
+ goto fail;
+ }
+
+ sprint(buf, "/mnt/web/%d/body", n);
+ bodyfd = open(buf, OREAD);
+ if(bodyfd < 0){
+ perror("open");
+ goto fail;
+ }
+
+ err = readn(bodyfd, buf, MAX_RESPONSE_SIZE);
+ if (err < 0) {
+ perror("read");
+ goto fail;
+ }
+
+ res = jsonparse(buf);
+
+ fail:
+ close(ctlfd);
+ close(bodyfd);
+ free(buf);
+
+ return res;
+ }
+
+ char *
+ escapename(char *str)
+ {
+ char *new;
+ int i, len;
+
+ len = strlen(str);
+
+ new = malloc(len + 1);
+ for(i = 0; i <= len; i++){
+ switch(str[i]){
+ case '/':
+ case ' ':
+ case ',':
+ case ':':
+ case '"':
+ case '\'':
+ new[i] = '_';
+ break;
+ default:
+ new[i] = str[i];
+ }
+ }
+ return new;
+ }
+
+ void
+ addcard(File *dir, JSON *card)
+ {
+ JSON *element;
+ char *filename;
+ File *carddir;
+ char *description;
+ char *url;
+ char *duedate;
+
+ element = jsonbyname(card, "name");
+ filename = escapename(element->s);
+ carddir = createfile(dir, filename, nil, DMDIR|0555, nil);
+ if(carddir == nil){
+ perror("createfile");
+ return;
+ } else {
+ element = jsonbyname(card, "desc");
+ description = strdup(element->s);
+ element = jsonbyname(card, "url");
+ url = strdup(element->s);
+ element = jsonbyname(card, "due");
+ if (element->t == JSONString)
+ duedate = strdup(element->s);
+ else
+ duedate = nil;
+ createfile(carddir, "description", nil, 0444, description);
+ createfile(carddir, "url", nil, 0444, url);
+ createfile(carddir, "duedate", nil, 0444, duedate);
+ }
+ free(filename);
+ }
+
+ void
+ addlist(File *dir, JSON *list)
+ {
+ JSON *element;
+ char *filename;
+ JSON *cards;
+ JSONEl *card;
+ char cardsEndpoint[128];
+ File *listdir;
+
+ element = jsonbyname(list, "name");
+
+ filename = escapename(element->s);
+ listdir = createfile(dir, filename, nil, DMDIR|0555, nil);
+ if(listdir == nil){
+ perror("createfile");
+ return;
+ } else {
+ element = jsonbyname(list, "id");
+ sprint(cardsEndpoint, "1/lists/%s/cards", element->s);
+ cards = trelloget(cardsEndpoint, "fields=desc,name,url,due");
+ if(cards == nil)
+ return;
+
+ for(card = cards->first; card != nil; card = card->next){
+ addcard(listdir, card->val);
+ }
+ }
+ jsonfree(cards);
+ free(filename);
+ }
+
+ void
+ addboard(File *root, JSON *board)
+ {
+ JSON *element;
+ JSONEl *list;
+ char *filename;
+ File *boarddir;
+
+ element = jsonbyname(board, "name");
+ filename = escapename(element->s);
+ boarddir = createfile(root, filename, nil, DMDIR|0555, nil);
+ if(boarddir == nil) {
+ perror("createfile");
+ return;
+ }
+
+ element = jsonbyname(board, "lists");
+ for(list = element->first; list != nil; list = list->next){
+ addlist(boarddir, list->val);
+ }
+
+ free(filename);
+ }
+
+ void
+ trelloinit(File *root)
+ {
+ JSON *result;
+ JSONEl *board;
+
+ result = trelloget("1/members/me/boards", "fields=name,lists&lists=open");
+
+ for(board = result->first; board != nil; board = board->next){
+ addboard(root, board->val);
+ }
+ jsonfree(result);
+ }
+
+ void
+ main(void)
+ {
+ JSONfmtinstall();
+
+ key = getenv("trellokey");
+ token = getenv("trellotoken");
+
+ Tree *tree;
+
+ tree = alloctree(nil, nil, DMDIR|0555, nil);
+
+ fs.tree = tree;
+ trelloinit(tree->root);
+
+ postmountsrv(&fs, nil, "/mnt/trello", MREPL | MCREATE);
+ }
+
+The program can be compiled by hand or by using the following `mkfile`.
+
+ BIN=/usr/glenda/bin/amd64
+
+ TARG=trellofs
+
+ OFILES=\
+ main.$O\
+
+ </sys/src/cmd/mkone