ref: 423f4db8e2a87e2319834ea9896a3d568cb64334
dir: /todofs.c/
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <bio.h>
#include <ndb.h>
/* owner = assignee */
/* group = optional group */
/* filename = UID_short_description_based_on_title */
/* file contents = plain read from source file UID */
/* source directory contents:
index - index file, ndb format
UID1
UID2
UID3
*/
void
usage(void)
{
fprint(2, "usage: %s [-s srvname] [-m mountpoint] directory\n", argv0);
exits("usage");
}
#define HASHLEN 10
enum {
Qdir,
Qctl,
Qstatus,
Qtask,
};
char *qnames[] = {
[Qdir] "/",
[Qctl] "ctl",
[Qstatus] nil,
[Qtask] nil,
};
int
qidtype(ulong path)
{
// -----00
return path & 3;
}
ulong
qidnum(ulong path)
{
// 000000--
return path >> 2;
}
ulong
mkqid(int type, int num)
{
return (num << 2) | type & 3;
}
char *srcdir;
char *idxfile;
Ndb *index = nil;
typedef struct Task Task;
struct Task {
Task* next;
char hash[HASHLEN];
char *title;
ulong id;
Dir *dir;
int cassignee;
int cgroup;
int cfname;
};
typedef struct Status Status;
struct Status {
Status *next;
char *name;
Task *tasks;
};
Status *statuses = nil;
ulong maxtask = 0;
Task* getgtask(ulong id, Status **status);
int
savedata(void)
{
Biobuf *bout;
Status *s;
ulong tid;
Task *t;
ndbclose(index);
index = nil;
bout = Bopen(idxfile, OWRITE);
if (!bout)
goto Errout;
for (s = statuses; s; s = s->next) {
Bprint(bout, "key=status name=\"%s\"\n", s->name);
}
Bprint(bout, "\n");
tid = 0;
while (tid <= maxtask) {
t = getgtask(tid, &s);
if (t) {
Bprint(bout, "key=task id=\"%s\" status=\"%s\"", t->hash, s->name);
if (t->cassignee) {
Bprint(bout, " assignee=\"%s\"", t->dir->uid);
}
if (t->cgroup) {
Bprint(bout, " group=\"%s\"", t->dir->gid);
}
if (t->title) {
Bprint(bout, " title=\"%s\"", t->title);
}
Bprint(bout, "\n");
}
tid++;
}
Bterm(bout);
index = ndbopen(idxfile);
return 1;
Errout:
index = ndbopen(idxfile);
return 0;
}
Status*
getstatus(char *name, int *id)
{
Status *s;
int i;
if (!statuses)
return nil;
i = 0;
s = statuses;
while (s && (strcmp(s->name, name) != 0)) {
i++;
s = s->next;
}
if (id)
*id = i;
return s; /* found or nil */
}
Status*
getnstatus(int id)
{
Status *s;
int i;
i = 0;
s = statuses;
while (i != id && s->next) {
i++;
s = s->next;
}
return i == id ? s : nil;
}
int
addstatus(char *name)
{
Status *s;
if (!statuses) {
statuses = mallocz(sizeof(Status), 1);
statuses->name = strdup(name);
return 1;
}
if (getstatus(name, nil)) {
werrstr("status '%s' already exists", name);
return 0;
}
for (s = statuses; s->next; s = s->next)
;
s->next = mallocz(sizeof(Status), 1);
s = s->next;
s->name = strdup(name);
return 1;
}
Task*
gettask(char *status, char *name, int *task)
{
Status *s;
Task *t;
int i;
s = getstatus(status, nil);
if (!s)
return nil;
t = s->tasks;
i = 0;
while (t && (strcmp(t->hash, name) != 0)) {
t = t->next;
i++;
}
if (task)
*task = i;
return t; /* found or nil */
}
Task*
getstask(char *name, int *status, int *task)
{
Status *s;
Task *t;
int i, j;
i = 0;
for (s = statuses; s; s = s->next, i++) {
t = gettask(s->name, name, &j);
if (t) {
if (status)
*status = i;
if (task)
*task = j;
return t;
}
}
return nil;
}
Task*
getftask(char *fname, int *status, int *task)
{
Task *t;
Status *s;
int i, j;
i = 0;
for (s = statuses; s; s = s->next, i++) {
t = s->tasks;
j = 0;
while (t) {
if (strcmp(t->dir->name, fname) == 0) {
if (status)
*status = i;
if (task)
*task = j;
return t;
}
t = t->next;
j++;
}
}
return nil;
}
Task*
getgtask(ulong id, Status **status)
{
Task *t;
Status *s;
for (s = statuses; s; s = s->next) {
t = s->tasks;
while (t) {
if (t->id == id) {
if (status)
*status = s;
return t;
}
t = t->next;
}
}
return nil;
}
Task*
getntask(char *status, int id)
{
Status *s;
int i;
Task *t;
s = getstatus(status, nil);
if (!s)
return nil;
i = 0;
t = s->tasks;
while (i != id && t->next) {
i++;
t = t->next;
}
return i == id ? t : nil;
}
void
freetask(Task *t)
{
if (t->cassignee)
free(t->dir->uid);
if (t->cgroup)
free(t->dir->gid);
if (t->cfname)
free(t->dir->name);
if (t->title)
free(t->title);
free(t->dir);
}
static int
updatetask(Task *t, char *name, char *assignee, char *group, char *title)
{
char path[256];
char *c, buf[64];
snprint(path, sizeof(path), "%s/%s", srcdir, name);
snprint(t->hash, sizeof(t->hash), "%s", name);
freetask(t);
if (title)
t->title = strdup(title);
t->dir = dirstat(path);
if (assignee) {
t->dir->uid = strdup(assignee);
t->cassignee = 1;
}
if (group) {
t->dir->gid = strdup(group);
t->cgroup = 1;
}
if (title) {
snprint(buf, sizeof(buf), "%s", title);
for (c = buf; *c; c++) {
if (*c == ' ' || *c == '\t') {
*c = '_';
}
}
t->dir->name = smprint("%s-%s", t->hash, buf);
} else
t->dir->name = strdup(t->hash);
t->cfname = 1;
return 1;
}
int
addtask(char *status, char *name, char *assignee, char *group, char *title, ulong id)
{
Status *s;
Task *t;
s = getstatus(status, nil);
if (!s) {
werrstr("status %s not found", status);
return 0;
}
t = getstask(name, nil, nil);
if (t) {
t->id = id;
return updatetask(t, name, assignee, group, title);
}
if (!s->tasks) {
s->tasks = mallocz(sizeof(Task), 1);
t = s->tasks;
} else {
for (t = s->tasks; t->next; t = t->next)
;
t->next = mallocz(sizeof(Task), 1);
t = t->next;
}
t->id = id;
return updatetask(t, name, assignee, group, title);
}
void
readstatuses(void)
{
Ndbtuple *tuple, *val;
Ndbs s;
if (ndbchanged(index))
ndbreopen(index);
for (tuple = ndbsearch(index, &s, "key", "status"); tuple != nil; tuple = ndbsnext(&s, "key", "status")) {
if (val = ndbfindattr(tuple, s.t, "name")) {
addstatus(val->val);
} else
sysfatal("invalid index file");
}
}
static void
readtasks(void)
{
Ndbtuple *tuple, *val, *sval;
Ndbtuple *assignee, *group, *tval;
Ndbs ns;
ulong id;
if (ndbchanged(index))
ndbreopen(index);
id = 0;
for (tuple = ndbsearch(index, &ns, "key", "task"); tuple != nil; tuple = ndbsnext(&ns, "key", "task")) {
if ((val = ndbfindattr(tuple, ns.t, "id")) &&
(sval = ndbfindattr(tuple, ns.t, "status"))) {
assignee = ndbfindattr(tuple, ns.t, "assignee");
group = ndbfindattr(tuple, ns.t, "group");
tval = ndbfindattr(tuple, ns.t, "title");
addtask(sval->val, val->val, assignee ? assignee->val : nil, group ? group->val : nil, tval ? tval->val : nil, id);
id++;
} else
sysfatal("invalid index");
}
maxtask = id;
}
void
fsopen(Req *r)
{
respond(r, nil);
}
static char*
statusname(int id)
{
Status *s = getnstatus(id);
return s ? strdup(s->name) : nil;
}
static void
fillstat(Dir *d, uvlong path)
{
int type = 0;
// memset(d, 0, sizeof(Dir));
d->uid = estrdup9p("todo");
d->gid = estrdup9p("todo");
switch (qidtype(path)) {
case Qdir:
case Qstatus:
type = QTDIR;
break;
case Qctl:
case Qtask:
type = 0;
break;
}
d->qid = (Qid){path, 0, type};
d->atime = d->mtime = 0;
d->length = 0;
if (qidtype(path) == Qtask) {
d->length = 999;
}
switch (qidtype(path)) {
case Qdir:
d->name = estrdup9p(qnames[Qdir]);
d->mode = DMDIR|0555;
break;
case Qstatus:
d->name = statusname(qidnum(path));
d->mode = DMDIR|0555;
break;
case Qctl:
d->name = estrdup9p(qnames[Qctl]);
d->mode = 0666;
break;
}
}
static int
rootgen(int i, Dir *d, void *aux)
{
Status *s;
USED(aux);
switch (i) {
case 0: /* ctl */
fillstat(d, mkqid(Qctl, 0));
return 0;
default: /* statuses */
i -= 1;
s = getnstatus(i);
if (!s)
return -1;
fillstat(d, mkqid(Qstatus, i));
return 0;
}
}
static int
statusgen(int i, Dir *d, void *aux)
{
Status *s = (Status*)aux;
int snum;
Task *t;
getstatus(s->name, &snum);
t = getntask(s->name, i);
if (!t)
return -1;
d->name = strdup(t->dir->name);
d->qid = (Qid){mkqid(Qtask, t->id), 0, 0};
d->mode = 0666;
d->atime = t->dir->atime;
d->mtime = t->dir->mtime;
d->length = t->dir->length;
d->uid = strdup(t->dir->uid);
d->gid = strdup(t->dir->gid);
return 0;
}
int
taskread(Req *r)
{
Task *t;
char path[256];
Biobuf *bin;
long n;
t = getgtask(qidnum(r->fid->qid.path), nil);
if (!t) {
werrstr("task not found");
return 0;
}
snprint(path, sizeof(path), "%s/%s", srcdir, t->hash);
bin = Bopen(path, OREAD);
if (!bin)
return 0;
Bseek(bin, r->ifcall.offset, 0);
n = Bread(bin, r->ofcall.data, r->ifcall.count);
if (n < 0) {
Bterm(bin);
return 0;
}
r->ofcall.count = n;
Bterm(bin);
return 1;
}
void
fsread(Req *r)
{
Status *s;
switch (qidtype(r->fid->qid.path)) {
case Qdir:
readstatuses();
dirread9p(r, rootgen, nil);
respond(r, nil);
break;
case Qstatus:
readstatuses();
s = getnstatus(qidnum(r->fid->qid.path));
readtasks();
dirread9p(r, statusgen, s);
respond(r, nil);
break;
case Qtask:
readtasks();
if (!taskread(r))
responderror(r);
else
respond(r, nil);
break;
case Qctl:
respond(r, nil);
break;
default:
respond(r, "error");
}
}
int
taskwrite(Req *r)
{
Task *t;
char path[256];
Biobuf *bout;
long n;
t = getgtask(qidnum(r->fid->qid.path), nil);
if (!t) {
werrstr("task not found");
return 0;
}
snprint(path, sizeof(path), "%s/%s", srcdir, t->hash);
bout = Bopen(path, OWRITE);
if (!bout)
return 0;
Bseek(bout, r->ifcall.offset, 0);
n = Bwrite(bout, r->ifcall.data, r->ifcall.count);
if (n < 0) {
Bterm(bout);
return 0;
}
r->ofcall.count = n;
Bterm(bout);
return 1;
}
int
ctlwrite(Req *r)
{
char str[256];
char *args[5];
int n;
memset(str, 0, sizeof(str));
memcpy(str, r->ifcall.data, r->ifcall.count);
n = tokenize(str, args, 5);
if (n < 1)
return 1;
if (strcmp(args[0], "addstatus") == 0) {
if (n != 2)
goto Addstatuserr;
if (args[1] && *args[1]) {
addstatus(args[1]);
return 1;
}
Addstatuserr:
werrstr("usage: addstatus status");
return 0;
}
if (strcmp(args[0], "dump") == 0) {
if (n != 1) {
werrstr("usage: dump");
return 0;
}
readtasks();
return savedata();
}
werrstr("invalid command: '%s'", args[0]);
return 0;
}
void
fswrite(Req *r)
{
switch (qidtype(r->fid->qid.path)) {
case Qctl:
if (!ctlwrite(r))
responderror(r);
else
respond(r, nil);
break;
case Qtask:
if (!taskwrite(r))
responderror(r);
else
respond(r, nil);
break;
case Qdir:
case Qstatus:
respond(r, nil);
break;
default:
respond(r, "error");
}
}
void
fscreate(Req *r)
{
respond(r, nil);
}
static void
fsattach(Req *r)
{
r->ofcall.qid = (Qid){Qdir, 0, QTDIR};
r->fid->qid = r->ofcall.qid;
r->fid->aux = 0;
respond(r, nil);
}
static char*
fswalk1(Fid *fid, char *name, Qid *qid)
{
int isdotdot;
Status *s;
Task *t;
int sid, tid;
isdotdot = strcmp(name, "..") == 0;
switch (qidtype(fid->qid.path)) {
case Qdir:
if (isdotdot) {
*qid = fid->qid;
return nil;
}
if (strcmp(name, qnames[Qctl]) == 0) {
*qid = (Qid){mkqid(Qctl, 0), 0, 0};
return nil;
}
s = getstatus(name, &sid);
if (!s)
return "file not found";
*qid = (Qid){mkqid(Qstatus, sid), 0, QTDIR};
return nil;
case Qstatus:
if (isdotdot) {
*qid = (Qid){mkqid(Qdir, 0), 0, QTDIR};
return nil;
}
if (t = getftask(name, &sid, &tid)) {
*qid = (Qid){mkqid(Qtask, t->id), 0, 0};
return nil;
}
return "file not found";
}
return "error";
}
static void
fsstat(Req *r)
{
fillstat(&r->d, r->fid->qid.path);
respond(r, nil);
}
Srv fs = {
.attach = fsattach,
.open = fsopen,
.read = fsread,
.write = fswrite,
.create = fscreate,
.walk1 = fswalk1,
.stat = fsstat,
};
void
main(int argc, char **argv)
{
char *srvname = nil;
char *mtpt = "/mnt/todo";
ARGBEGIN{
case 's':
srvname = EARGF(usage());
break;
case 'm':
mtpt = EARGF(usage());
break;
default:
usage();
}ARGEND;
if (argc != 1)
usage();
quotefmtinstall();
srcdir = *argv;
idxfile = smprint("%s/index", srcdir);
assert(idxfile);
index = ndbopen(idxfile);
if (!index)
sysfatal("unable to open index file: %r");
postmountsrv(&fs, srvname, mtpt, MREPL|MCREATE);
exits(0);
}