<!DOCTYPE HTML>
<html>
<head>
<title>plan9 - trellofs</title>
<style media="screen, handheld">
body { display: flex; flex-wrap: wrap; font-family: sans;}
header { flex-basis: 100%; flex-shrink: 0; }
article { flex-basis: 60%; padding-left: 1em; }
footer { flex-basis: 100%; flex-shrink: 0; }
header nav { display: flex; justify-content: space-between; }
nav a, header a { text-decoration: none ; color: inherit; }
header h1 span { margin-left: 1em; font-size: 50%; font-style: italic; }
.mainContent > nav { flex-basis: content; padding-right: 1vw; min-width: 16em; }
nav ul { display: flex; flex-direction: column; list-style-type: none; list-style-position: outside; padding-left: 0; }
nav li ul { padding-left: 0.6em }
footer { display: flex; justify-content: space-between; }
/* cut here to leave vanity behind */
body { margin:0; padding: 0; font-size: 84%; font-family: Helvetica, Verdana, Arial, 'Liberation Sans', FreeSans, sans-serif; }
a { text-decoration: none; color: }
a:hover { text-decoration: underline; }
.thisPage { color: black; }
/* header and top bar */
header nav { background-color: rgb(100,135,220); color: white; padding: 0.3em; border-bottom: 2px solid black; font-size: 91%; }
header h1 { background-color: #ff6d06; color: black; margin: 0; border-bottom: 2px solid black; font-weight: normal; padding: 0.25ex; font-size: 233%; }
header a:hover { text-decoration: none; }
/* sidebar */
.mainContent > nav { border-right: 1px solid #ddd; padding: 0; }
.mainContent > nav > div { border-bottom: 1px solid #ddd; }
.mainContent > nav > div a { color: rgb(0, 102, 204); display: block; text-transform: capitalize; font-weight: bold; padding: 0.25em 1ex 0.25em 2mm; font-size: 102%}
.mainContent > nav > div a:hover { color: white; background-color: rgb(100,135,220); border-left: black solid 0.2em; text-decoration: none; }
.mainContent > nav > div p { font-weight: bold; margin: 0 0 0.5em 2mm; padding: 1em 0 0 0; }
/* main copy */
article { padding: 0.5ex 0 5vh 1vw; }
article h1, article h2 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; border-bottom: 2px solid rgb(0,102,204); }
article h3, article h4, article h5 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; }
article h6, article h7, article h8 { color: rgb(0,102,204); font-weight: bold; margin: 2em 0 0 0; }
article a { color: rgb(0,102,204); }
article a:hover { color: rgb(100,135,220); }
article pre { font-size: 1.2em; }
/* footer */
footer { color: white; background-color: rgb(100,135,220); }
footer a { color: inherit; }
footer div { padding: 1em; }
/* tables */
table { border: 1px solid rgba(128,128,128,0.5); padding: 0; }
th { color: white; background-color: rgb(100,135,220); }
tr:nth-child(odd) { background-color: rgba(128,128,128,0.1) }
/* modifications */
img {
max-width: 100%;
border: 1px solid black;
}
body {
}
header h1 {
background-color: #c2d2c6;
}
html {
font-size: 1.2em;
}
header nav, footer {
background-color: #442a13;
}
code > pre {
border: 2px solid #442a13;
background-color: #c2d2c6;
width: max-content;
padding: 0 0.5em;
}
.mainContent > nav > div a, article a:hover {
color: #442a13;
}
.mainContent > nav > div a:hover {
color: #442a13;
background-color: #c2d2c6;
}
article h1, article h2 {
color: #c48f3b;
border-bottom: 2px solid #442a13;
}
article a, .thisPage, footer, header nav {
color: #c48f3b;
}
footer {
padding: 0.5em;
flex-shrink: 1;
flex-basis: unset;
}
header {
flex-basis: unset;
}
html {
display: flex;
min-height: 100%;
}
body {
flex-direction: column;
flex: 1;
background-color: #e3dfd7;
}
.mainContent {
display: flex;
flex-grow: 1;
} </style>
<link rel="shortcut icon" href="https://images.pmikkelsen.com/favicon.ico" type="image/vnd.microsoft.icon">
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<header>
<nav>
<div class="left">
<a href="https://pmikkelsen.com">notes</a> |
<a href="https://git.pmikkelsen.com">git</a> |
<a href="https://images.pmikkelsen.com">images</a> |
<a href="https://files.pmikkelsen.com">files</a> |
<a href="https://prolog.pmikkelsen.com">PProlog</a> |
<a href="https://apl.pmikkelsen.com">APL9</a> |
<a href="https://aplwc.org">aplwc</a>
</div>
<div class="right">
<a href="https://git.sr.ht/~pmikkelsen">sourcehut</a> |
<a href="https://9front.org">9front</a> |
<a href="https://openbsd.org">OpenBSD</a> |
<a href="https://dyalog.com">Dyalog</a>
</div>
</nav>
<h1><a href="../index.html">Peter's website <span id="headerSubTitle">- random notes</span></a></h1>
</header>
<div class=mainContent>
<nav id="side-bar">
<div>
<ul>
<li><a href="../APL-and-J/index.html">› APL and J/</a></li>
<li><a href="../OpenBSD/index.html">› OpenBSD/</a></li>
<li><a href="../contact.html">› contact</a></li>
<li><a href="../facts/index.html">› facts/</a></li>
<li><a href="../haskell/index.html">› haskell/</a></li>
<li><a href="../linux/index.html">› linux/</a></li>
<li><a href="../mainframe/index.html">› mainframe/</a></li>
<li><a href="../me/index.html">› me/</a></li>
<li><a href="../opinions/index.html">› opinions/</a></li>
<li><a href="index.html" class="thisPage">»<i> plan9/</i></a></li>
<li><ul>
<li><a href="basic_9p_server.html">› basic 9p server</a></li>
<li><a href="discord.html">› discord</a></li>
<li><a href="dns.html">› dns</a></li>
<li><a href="fonts.html">› fonts</a></li>
<li><a href="lets_encrypt.html">› lets encrypt</a></li>
<li><a href="mounting-9p-over-drawterm.html">› mounting 9p over drawterm</a></li>
<li><a href="network_booting.html">› network booting</a></li>
<li><a href="trellofs.html" class="thisPage">»<i> trellofs</i></a></li>
<li><a href="using_irc.html">› using irc</a></li>
<li><a href="webfs-websocket.html">› webfs websocket</a></li>
</ul></li>
<li><a href="../prolog/index.html">› prolog/</a></li>
<li><a href="../web/index.html">› web/</a></li>
</ul>
</div>
</nav>
<article>
<h2>A filesystem for viewing trello</h2>
<p>Since my last note <a href="basic_9p_server.html">here</a> 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.</p>
<h2>Usage example</h2>
<p>First set the <code>trellokey</code> and <code>trellotoken</code> environment variables to the API key and token from the trello website.</p>
<code><pre>
term% trellokey=1298791723987937123.....
term% trellotoken=923748237497ab798798999999.....
</pre></code>
<p>Then <code>webfs</code> must be started so the program can make requests to the trello REST api.</p>
<code><pre>
term% webfs
</pre></code>
<p>Then <code>trellofs</code> itself must be started. It mounts the filesystem under <code>/mnt/trello</code>.</p>
<code><pre>
term% trellofs
</pre></code>
<p>If I now go and see which boards I have, I get the following</p>
<code><pre>
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
</pre></code>
<p>The names of the folders are almost like the original board names, except that that some characters have been replaced by <code>_</code>. Lets look at <code>P1</code></p>
<code><pre>
term% cd P1
term% ls
Backlog
Done
Estimat
In-progress
Info
Master.C_Top_Down_Ting
Sprint_1
Test
Test_sprint_1
Todo
</pre></code>
<p>Todo must have something worth looking at</p>
<code><pre>
term% ls
Færdig_rapport_aflevering
Korrekturlæsning
</pre></code>
<p>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.</p>
<code><pre>
term% cd Færdig_rapport_aflevering
term% ls
description
duedate
url
</pre></code>
<p>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.</p>
<code><pre>
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
</pre></code>
<h2>Limitations</h2>
<p>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.</p>
<h2>The code</h2>
<p>The code for the <code>trellofs</code> program is listed below.</p>
<code><pre>
#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);
}
</pre></code>
<p>The program can be compiled by hand or by using the following <code>mkfile</code>.</p>
<code><pre>
BIN=/usr/glenda/bin/amd64
TARG=trellofs
OFILES=\
main.$O\
</sys/src/cmd/mkone
</pre></code>
</article>
</div>
<footer>
<a href="http://werc.cat-v.org">Powered by werc</a> © Peter Mikkelsen 2019-2025
</footer>
</body></html>