shithub: todofs

Download patch

ref: a05459a8d6a5a190db2fe52a89ed22bf515834b4
parent: 6dc0bc759e48e1fc98cd5faf7237c128dd8eb914
author: sirjofri <sirjofri@sirjofri.de>
date: Thu Jun 27 12:14:19 EDT 2024

makes each task a directory, with files

--- a/README
+++ b/README
@@ -12,6 +12,8 @@
 
 	key=config nextid=1
 
+todofs -c path/to/dir initializes the directory with this index file.
+
 It will be filled with metadata when creating tasks and statuses.
 
 
@@ -25,16 +27,18 @@
 
 Imagine the following scenario:
 
-	/mnt/todo/StatusA/1-First_Task
-	/mnt/todo/StatusB/2-Second_Task
+	/mnt/todo/StatusA/1-First_Task/
+	/mnt/todo/StatusB/2-Second_Task/
 	; cd /mnt/todo
 
-To move task 1 to StatusB, use create:
+To move task 1 to StatusB, use create with the task id:
 
 	; touch StatusB/1
 
-To edit a task, just open/read/write the task file. This will be forwarded to files-on-disk automatically.
+To edit a task, just open/read/write the task/data file. This will be forwarded to files-on-disk automatically.
 
+The data file can have a file extension, depending on the file extension. This should be used for plumbing. Adding a file extension using the filesystem is a TODO.
+
 To rename a task:
 
 	; mv StatusB/2-Second_Task 'StatusB/My other task'
@@ -53,3 +57,10 @@
 	; chgrp engineering StatusA/3-New_Task
 	; ls -l StatusA
 	... alice engineering ... 3-New_Task
+
+It is also possible to change the metadata by file. The filesystem looks like this:
+
+	/mnt/todo/StatusA/1-First_Task/title
+	/mnt/todo/StatusA/1-First_Task/assignee
+	/mnt/todo/StatusA/1-First_Task/group
+	/mnt/todo/StatusA/1-First_Task/data (or data.extension)
--- a/todofs.c
+++ b/todofs.c
@@ -31,6 +31,10 @@
 	Qctl,
 	Qstatus,
 	Qtask,
+	Qtitle,
+	Qdata,
+	Qassignee,
+	Qgroup,
 };
 
 char *qnames[] = {
@@ -38,26 +42,30 @@
 	[Qctl] "ctl",
 	[Qstatus] nil,
 	[Qtask] nil,
+	[Qtitle] "title",
+	[Qdata] "data",
+	[Qassignee] "assignee",
+	[Qgroup] "group",
 };
 
 int
 qidtype(ulong path)
 {
-	// -----00
-	return path & 3;
+	// -----0000
+	return path & 15;
 }
 
 ulong
 qidnum(ulong path)
 {
-	// 000000--
-	return path >> 2;
+	// 000000----
+	return path >> 4;
 }
 
 ulong
 mkqid(int type, int num)
 {
-	return (num << 2) | type & 3;
+	return (num << 4) | type & 15;
 }
 
 static char*
@@ -82,6 +90,8 @@
 struct Task {
 	Task* next;
 	char *title;
+	char *dataname;
+	char *ext;
 	ulong id;
 	Dir *dir;
 	int cassignee;
@@ -132,10 +142,10 @@
 		t = getgtask(tid, &s);
 		if (t) {
 			Bprint(bout, "key=task id=\"%s\" status=\"%s\"", ultostr(t->id), s->name);
-			if (t->cassignee) {
+			if (t->cassignee && strcmp(t->dir->uid, "na") != 0) {
 				Bprint(bout, " assignee=\"%s\"", t->dir->uid);
 			}
-			if (t->cgroup) {
+			if (t->cgroup && strcmp(t->dir->gid, "ng") != 0) {
 				Bprint(bout, " group=\"%s\"", t->dir->gid);
 			}
 			if (t->title) {
@@ -192,6 +202,18 @@
 	return i == id ? s : nil;
 }
 
+static int
+getsid(Status *s)
+{
+	int i = 0;
+	for (Status *n = statuses; n; n = n->next) {
+		if (s == n)
+			return i;
+		i++;
+	}
+	return -1;
+}
+
 int
 addstatus(char *name)
 {
@@ -281,7 +303,7 @@
 				if (status)
 					*status = i;
 				if (task)
-					*task = j;
+					*task = t->id;
 				return t;
 			}
 			t = t->next;
@@ -308,6 +330,7 @@
 			t = t->next;
 		}
 	}
+	werrstr("task not found");
 	return nil;
 }
 
@@ -342,6 +365,10 @@
 		free(t->dir->name);
 	if (t->title)
 		free(t->title);
+	if (t->dataname)
+		free(t->dataname);
+	if (t->ext)
+		free(t->ext);
 	free(t->dir);
 	
 	t->cassignee = 0;
@@ -349,8 +376,34 @@
 	t->cfname = 0;
 	t->title = nil;
 	t->dir = nil;
+	t->dataname = nil;
+	t->ext = nil;
 }
 
+static void
+settaskassignee(Task *t, char *assignee)
+{
+	assert(t);
+	
+	if (t->cassignee)
+		free(t->dir->uid);
+	
+	t->dir->uid = strdup(assignee && assignee[0] ? assignee : "na");
+	t->cassignee = 1;
+}
+
+static void
+settaskgroup(Task *t, char *group)
+{
+	assert(t);
+	
+	if (t->cgroup)
+		free(t->dir->uid);
+	
+	t->dir->gid = strdup(group && group[0] ? group : "ng");
+	t->cgroup = 1;
+}
+
 static int
 settasktitle(Task *t, char *title)
 {
@@ -372,6 +425,7 @@
 		}
 		t->dir->name = smprint("%s-%s", ultostr(t->id), buf);
 	} else {
+		t->title = nil;
 		t->dir->name = smprint("%s", ultostr(t->id));
 	}
 	t->cfname = 1;
@@ -381,16 +435,58 @@
 static int
 updatetask(Task *t, char *name, char *assignee, char *group, char *title)
 {
+	char buf[32];
+	char *ext;
+	Dir *dir;
+	int fd;
+	int n, i;
 	freetask(t);
 	
-	t->dir = dirstat(name);
+	fd = open(".", OREAD);
+	if (fd < 0)
+		sysfatal("unable to open dir: %r");
 	
-	t->dir->uid = strdup(assignee ? assignee : "na");
-	t->cassignee = 1;
+	ext = nil;
+	i = 0;
+	while (n = dirread(fd, &dir)) {
+		for (i = 0; i < n; i++) {
+			if (strcmp(dir[i].name, "..") == 0)
+				continue;
+			ext = strchr(dir[i].name, '.');
+			if (ext) {
+				snprint(buf, sizeof(buf), "%s.", name);
+				if (strncmp(buf, dir[i].name, strlen(buf)) == 0) {
+					goto Break;
+				}
+			} else {
+				if (strcmp(name, dir[i].name) == 0)
+					goto Break;
+			}
+		}
+		free(dir);
+	}
+Break:
+	close(fd);
 	
-	t->dir->gid = strdup(group ? group : "ng");
-	t->cgroup = 1;
+	if (!dir) {
+		werrstr("task not found: %s", name);
+		return 0;
+	}
 	
+	t->dir = dirstat(dir[i].name);
+	free(dir);
+	
+	if (!ext) {
+		t->dataname = strdup(qnames[Qdata]);
+	} else {
+		t->dataname = smprint("%s%s", qnames[Qdata], ext);
+	}
+	
+	t->ext = ext ? strdup(ext) : nil;
+	
+	settaskassignee(t, assignee);
+	settaskgroup(t, group);
+	
 	return settasktitle(t, title);
 }
 
@@ -516,9 +612,19 @@
 }
 
 static void
+filltaskstat(Dir *d, Task *t)
+{
+	d->uid = estrdup9p(t->dir->uid);
+	d->gid = estrdup9p(t->dir->gid);
+	d->atime = t->dir->atime;
+	d->mtime = t->dir->mtime;
+}
+
+static void
 fillstat(Dir *d, uvlong path)
 {
 	int type = 0;
+	Task *t;
 	
 	// memset(d, 0, sizeof(Dir));
 	d->uid = estrdup9p("todo");
@@ -527,10 +633,10 @@
 	switch (qidtype(path)) {
 	case Qdir:
 	case Qstatus:
+	case Qtask:
 		type = QTDIR;
 		break;
 	case Qctl:
-	case Qtask:
 		type = 0;
 		break;
 	}
@@ -543,6 +649,8 @@
 		d->length = 999;
 	}
 	
+	t = getgtask(qidnum(path), nil);
+	
 	switch (qidtype(path)) {
 	case Qdir:
 		d->name = estrdup9p(qnames[Qdir]);
@@ -552,6 +660,28 @@
 		d->name = statusname(qidnum(path));
 		d->mode = DMDIR|0555;
 		break;
+	case Qtask:
+		d->name = estrdup9p(t->dir->name);
+		d->mode = DMDIR|0555;
+	case Qtitle:
+		d->name = estrdup9p(qnames[Qtitle]);
+		d->mode = 0666;
+		goto Commontask;
+	case Qdata:
+		d->name = estrdup9p(t->dataname);
+		d->mode = 0666;
+		goto Commontask;
+	case Qassignee:
+		d->name = estrdup9p(qnames[Qassignee]);
+		d->mode = 0666;
+		goto Commontask;
+	case Qgroup:
+		d->name = estrdup9p(qnames[Qgroup]);
+		d->mode = 0666;
+		/* falls through */
+	Commontask:
+		filltaskstat(d, t);
+		break;
 	case Qctl:
 		d->name = estrdup9p(qnames[Qctl]);
 		d->mode = 0666;
@@ -583,18 +713,15 @@
 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->qid = (Qid){mkqid(Qtask, t->id), 0, QTDIR};
+	d->mode = DMDIR|0555;
 	d->atime = t->dir->atime;
 	d->mtime = t->dir->mtime;
 	d->length = t->dir->length;
@@ -603,10 +730,45 @@
 	return 0;
 }
 
+static int
+taskgen(int i, Dir *d, void *aux)
+{
+	Task *t = (Task*)aux;
+	
+	switch (i + Qtitle) {
+	case Qtitle:
+		d->name = strdup(qnames[Qtitle]);
+		d->qid = (Qid){mkqid(Qtitle, t->id), 0, 0};
+		d->length = t->title ? strlen(t->title) : 0;
+		break;
+	case Qdata:
+		d->name = strdup(t->dataname);
+		d->qid = (Qid){mkqid(Qdata, t->id), 0, 0};
+		d->length = t->dir->length;
+		break;
+	case Qassignee:
+		d->name = strdup(qnames[Qassignee]);
+		d->qid = (Qid){mkqid(Qassignee, t->id), 0, 0};
+		d->length = t->dir->uid ? strlen(t->dir->uid) : 0;
+		break;
+	case Qgroup:
+		d->name = strdup(qnames[Qgroup]);
+		d->qid = (Qid){mkqid(Qgroup, t->id), 0, 0};
+		d->length = t->dir->gid ? strlen(t->dir->gid) : 0;
+		break;
+	default:
+		return -1;
+	}
+	d->mode = 0666;
+	filltaskstat(d, t);
+	return 0;
+}
+
 int
 taskread(Req *r)
 {
 	Task *t;
+	char *f;
 	Biobuf *bin;
 	long n;
 	
@@ -616,7 +778,10 @@
 		return 0;
 	}
 	
-	bin = Bopen(ultostr(t->id), OREAD);
+	f = t->ext ? smprint("%s%s", ultostr(t->id), t->ext) : strdup(ultostr(t->id));
+	
+	bin = Bopen(f, OREAD);
+	free(f);
 	if (!bin)
 		return 0;
 	
@@ -635,7 +800,10 @@
 fsread(Req *r)
 {
 	Status *s;
+	Task *t;
 	
+	ulong qnum = qidnum(r->fid->qid.path);
+	
 	switch (qidtype(r->fid->qid.path)) {
 	case Qdir:
 		readstatuses();
@@ -644,7 +812,7 @@
 		break;
 	case Qstatus:
 		readstatuses();
-		s = getnstatus(qidnum(r->fid->qid.path));
+		s = getnstatus(qnum);
 		readtasks();
 		dirread9p(r, statusgen, s);
 		respond(r, nil);
@@ -651,11 +819,53 @@
 		break;
 	case Qtask:
 		readtasks();
+		t = getgtask(qnum, nil);
+		if (!t) {
+			respond(r, "task not found");
+			return;
+		}
+		dirread9p(r, taskgen, t);
+		respond(r, nil);
+		break;
+	case Qtitle:
+		readtasks();
+		t = getgtask(qnum, nil);
+		if (!t) {
+			respond(r, "task not found");
+			return;
+		}
+		if (t->title)
+			readstr(r, t->title);
+		respond(r, nil);
+		break;
+	case Qdata:
 		if (!taskread(r))
 			responderror(r);
 		else
 			respond(r, nil);
 		break;
+	case Qassignee:
+		readtasks();
+		t = getgtask(qnum, nil);
+		if (!t) {
+			respond(r, "task not found");
+			return;
+		}
+		if (t->cassignee)
+			readstr(r, t->dir->uid);
+		respond(r, nil);
+		break;
+	case Qgroup:
+		readtasks();
+		t = getgtask(qnum, nil);
+		if (!t) {
+			respond(r, "task not found");
+			return;
+		}
+		if (t->cgroup)
+			readstr(r, t->dir->gid);
+		respond(r, nil);
+		break;
 	case Qctl:
 		respond(r, nil);
 		break;
@@ -710,7 +920,8 @@
 		if (n != 2)
 			goto Addstatuserr;
 		if (args[1] && *args[1]) {
-			addstatus(args[1]);
+			if (addstatus(args[1]))
+				return savedata();
 			return 1;
 		}
 Addstatuserr:
@@ -731,10 +942,68 @@
 	return 0;
 }
 
+static int
+taskwritefield(Task *t, int field, Req *r)
+{
+	char *s;
+	
+	if (r->ifcall.offset != 0) {
+		werrstr("write offset not 0");
+		return 0;
+	}
+	
+	if (r->ifcall.count == 0) {
+		switch(field) {
+		case Qtitle:
+			settasktitle(t, nil);
+			break;
+		case Qassignee:
+			settaskassignee(t, nil);
+			break;
+		case Qgroup:
+			settaskgroup(t, nil);
+			break;
+		}
+		return 1;
+	}
+	
+	s = r->ifcall.data;
+	r->ifcall.data[r->ifcall.count-1] = 0; // last byte 0
+	for (int i = 0; i < r->ifcall.count; i++) {
+		if (s[i] == '\n') {
+			s[i] = 0;
+			break;
+		}
+	}
+	
+	switch (field) {
+	case Qtitle:
+		settasktitle(t, s);
+		break;
+	case Qassignee:
+		settaskassignee(t, s);
+		break;
+	case Qgroup:
+		settaskgroup(t, s);
+		break;
+	default:
+		sysfatal("cannot happen");
+	}
+	
+	return savedata();
+}
+
 void
 fswrite(Req *r)
 {
-	switch (qidtype(r->fid->qid.path)) {
+	Task *t;
+	ulong qnum;
+	int qtype;
+	
+	qnum = qidnum(r->fid->qid.path);
+	qtype = qidtype(r->fid->qid.path);
+	
+	switch (qtype) {
 	case Qctl:
 		if (!ctlwrite(r))
 			responderror(r);
@@ -741,12 +1010,28 @@
 		else
 			respond(r, nil);
 		break;
-	case Qtask:
+	case Qtitle:
+	case Qassignee:
+	case Qgroup:
+		t = getgtask(qnum, nil);
+		if (!t) {
+			responderror(r);
+			break;
+		}
+		if (!taskwritefield(t, qtype, r)) {
+			responderror(r);
+			break;
+		}
+		r->ofcall.count = r->ifcall.count;
+		respond(r, nil);
+		break;
+	case Qdata:
 		if (!taskwrite(r))
 			responderror(r);
 		else
 			respond(r, nil);
 		break;
+	case Qtask:
 	case Qdir:
 	case Qstatus:
 		respond(r, nil);
@@ -786,32 +1071,34 @@
 void
 fscreate(Req *r)
 {
-	Status *s, *s2;
+	Status *s, *sfrom;
 	Task *t;
-	int tid, sid;
-	ulong id;
+	int sid;
+	ulong id, qnum;
 	
+	qnum = qidnum(r->fid->qid.path);
+	
 	switch (qidtype(r->fid->qid.path)) {
 	case Qstatus:
-		s = getnstatus(qidnum(r->fid->qid.path));
+		s = getnstatus(qnum);
 		if (!s) {
 			respond(r, "status not found");
 			return;
 		}
 		id = strtoid(r->ifcall.name);
-		fprint(2, "id of task: %ulx\n", id);
-		t = getgtask(id, &s2);
+		t = getgtask(id, &sfrom);
+		sid = getsid(sfrom);
 		if (t) {
 			/* move existing task */
-			if (sid == qidnum(r->fid->qid.path)) {
+			if (sid == qnum) {
 				respond(r, "task already has status");
 				return;
 			}
-			if (!taskmv(t, s2, s)) {
+			if (!taskmv(t, sfrom, s)) {
 				responderror(r);
 				return;
 			}
-			r->fid->qid = (Qid){mkqid(Qtask, t->id), 0, 0};
+			r->fid->qid = (Qid){mkqid(Qtask, t->id), 0, QTDIR};
 			r->ofcall.qid = r->fid->qid;
 		} else {
 			/* create new task */
@@ -825,7 +1112,7 @@
 				respond(r, "error creating new task");
 				return;
 			}
-			r->fid->qid = (Qid){mkqid(Qtask, t->id), 0, 0};
+			r->fid->qid = (Qid){mkqid(Qtask, t->id), 0, QTDIR};
 			r->ofcall.qid = r->fid->qid;
 		}
 		savedata();
@@ -853,8 +1140,10 @@
 	Status *s;
 	Task *t;
 	int sid, tid;
+	ulong qnum;
 	
 	isdotdot = strcmp(name, "..") == 0;
+	qnum = qidnum(fid->qid.path);
 	
 	switch (qidtype(fid->qid.path)) {
 	case Qdir:
@@ -877,10 +1166,43 @@
 			return nil;
 		}
 		if (t = getftask(name, &sid, &tid)) {
-			*qid = (Qid){mkqid(Qtask, t->id), 0, 0};
+			if (qnum != sid) {
+				return "task has a different status";
+			}
+			*qid = (Qid){mkqid(Qtask, t->id), 0, QTDIR};
 			return nil;
 		}
-		return "file not found";
+		return "task not found";
+	case Qtask:
+		t = getgtask(qnum, &s);
+		if (!t) {
+			return "task not found";
+		}
+		sid = getsid(s);
+		if (isdotdot) {
+			*qid = (Qid){mkqid(Qstatus, sid), 0, QTDIR};
+			return nil;
+		}
+		if (strcmp(name, qnames[Qtitle]) == 0) {
+			*qid = (Qid){mkqid(Qtitle, qnum), 0, 0};
+			return nil;
+		}
+		if (strcmp(name, qnames[Qassignee]) == 0) {
+			*qid = (Qid){mkqid(Qassignee, qnum), 0, 0};
+			return nil;
+		}
+		if (strcmp(name, qnames[Qgroup]) == 0) {
+			*qid = (Qid){mkqid(Qgroup, qnum), 0, 0};
+			return nil;
+		}
+		if (strcmp(name, t->dataname) == 0) {
+			*qid = (Qid){mkqid(Qdata, qnum), 0, 0};
+			return nil;
+		}
+		if (strcmp(name, qnames[Qdata]) == 0) {
+			*qid = (Qid){mkqid(Qdata, qnum), 0, 0};
+			return nil;
+		}
 	}
 	return "error";
 }
@@ -952,6 +1274,8 @@
 {
 	char *srvname = nil;
 	char *mtpt = "/mnt/todo";
+	int gen = 0;
+	int fd;
 	
 	ARGBEGIN{
 	case 's':
@@ -960,6 +1284,9 @@
 	case 'm':
 		mtpt = EARGF(usage());
 		break;
+	case 'c':
+		gen++;
+		break;
 	case '9':
 		chatty9p++;
 		break;
@@ -978,6 +1305,15 @@
 		sysfatal("unable to chdir: %r");
 	
 	idxfile = "index";
+	if (gen) {
+		fd = create(idxfile, OWRITE, 0666);
+		if (fd < 0)
+			sysfatal("unable to create index file: %r");
+		fprint(fd, "key=config nextid=\"1\"\n");
+		close(fd);
+		exits(0);
+	}
+	
 	assert(idxfile);
 	index = ndbopen(idxfile);
 	if (!index)