A filesystem for viewing trello
Since my last note here 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