shithub: calfs

Download patch

ref: 3cd1ee7c4f5d66a1f2bc1f0a1df429d9829c28df
author: phil9 <telephil9@gmail.com>
date: Mon Mar 7 15:08:39 EST 2022

initial import

--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 phil9 <telephil9@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+++ b/README.md
@@ -1,0 +1,33 @@
+# calfs
+A 9p calendar filesystem. 
+calfs reads ical files and exposes calendar events with the following structure:
+```
+<mtpt>
+|- <year>
+  |- <month>
+    |- <day>
+	  |- <n>
+	    |- uid
+		|- summary
+		|- description
+		|- location
+		|- start
+		|- end
+		|- created
+		|- last-modifed
+```
+
+*NB:* this is work in progress, the ical parser is just a quick hack and will surely break.
+
+## Usage
+```sh
+% mk install
+% calfs <file.ics>...
+```
+
+## License
+MIT
+
+## Bugs
+Guaranteed.
+
--- /dev/null
+++ b/a.h
@@ -1,0 +1,35 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include <bio.h>
+
+typedef struct Event Event;
+
+struct Event
+{
+	char *uid;
+	char *summary;
+	char *description;
+	char *location;
+	Tm start;
+	Tm end;
+	Tm lastmod;
+	Tm created;
+};
+
+/* ical */
+int readical(const char*);
+
+/* utils */
+void* emalloc(ulong);
+void* erealloc(void*, ulong);
+File* ecreatefile(File*, char*, char*, ulong, void*);
+void  readtm(Req*, Tm*);
+int   cmpevent(void*, void*);
+
+extern Event **events;
+extern usize nevents;
+extern usize eventsz;
+
--- /dev/null
+++ b/calfs.c
@@ -1,0 +1,153 @@
+#include "a.h"
+
+int debug = 0;
+Event **events;
+usize nevents;
+usize eventsz;
+
+void
+fsread(Req *r)
+{
+	File *f;
+	Event *e;
+
+	f = r->fid->file;
+	e = f->aux;
+	if(strcmp(f->name, "uid") == 0)
+		readstr(r, e->uid);
+	else if(strcmp(f->name, "summary") == 0)
+		readstr(r, e->summary);
+	else if(strcmp(f->name, "description") == 0)
+		readstr(r, e->description);
+	else if(strcmp(f->name, "location") == 0)
+		readstr(r, e->location);
+	else if(strcmp(f->name, "start") == 0)
+		readtm(r, &e->start);
+	else if(strcmp(f->name, "end") == 0)
+		readtm(r, &e->end);
+	else if(strcmp(f->name, "last-modified") == 0)
+		readtm(r, &e->lastmod);
+	else if(strcmp(f->name, "created") == 0)
+		readtm(r, &e->created);
+	else{
+		respond(r, "no such file or directory");
+		return;
+	}
+	respond(r, nil);
+}
+
+Srv fs = {
+	.read = fsread,
+};
+
+void
+createevtfiles(File *p, Event *e)
+{
+	const char *filenames[] = {
+		"uid",
+		"summary",
+		"description",
+		"location",
+		"start",
+		"end",
+		"last-modified",
+		"created",
+	};
+	int i;
+
+	for(i = 0; i < nelem(filenames); i++)
+		createfile(p, filenames[i], nil, 0644, e);
+}
+
+
+enum { Cnone, Cyear, Cmonth, Cday };
+
+int
+change(Event *e, int y, int m, int d)
+{
+	if(e->start.year != y)
+		return Cyear;
+	else if(e->start.mon != m)
+		return Cmonth;
+	else if(e->start.mday != d)
+		return Cday;
+	return Cnone;
+}
+
+Tree*
+buildtree(void)
+{
+	Tree *tree;
+	int i, n, y, m, d;
+	File *yd, *md, *dd, *nd;
+	Event *e;
+	char buf[16] = {0};
+
+	tree = alloctree(nil, nil, DMDIR|0555, nil);
+	y = m = d = -1;
+	yd = md = dd = nil;
+	n = 1;
+	for(i = 0; i < nevents; i++){
+		e = events[i];
+		switch(change(e, y, m, d)){
+		case Cyear:
+			y = e->start.year;
+			snprint(buf, sizeof buf, "%d", y+1900);
+			yd = ecreatefile(tree->root, buf, nil, DMDIR|0555, nil);
+		case Cmonth:
+			m = e->start.mon;
+			snprint(buf, sizeof buf, "%02d", m+1);
+			md = ecreatefile(yd, buf, nil, DMDIR|0555, nil);
+		case Cday:
+			d = e->start.mday;
+			n = 1;
+			snprint(buf, sizeof buf, "%02d", d);
+			dd = ecreatefile(md, buf, nil, DMDIR|0555, nil);
+		}
+		snprint(buf, sizeof buf, "%d", n);
+		nd = createfile(dd, buf, nil, DMDIR|0555, nil);
+		createevtfiles(nd, e);
+		n += 1;
+	}
+	return tree;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s <filename>...\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char **argv)
+{
+	char *mtpt;
+	int i;
+
+	mtpt = "/mnt/cal";
+
+	ARGBEGIN{
+	case 'D':
+		chatty9p++;
+		break;
+	case 'd':
+		debug++;
+		break;
+	case 'm':
+		mtpt = ARGF();
+		break;
+	}ARGEND;
+
+	tmfmtinstall();
+	if(argc < 1)
+		usage();
+	for(i = 0; i < argc; i++){
+		if(readical(argv[i]) < 0)
+			sysfatal("unable to read ical file '%s': %r", argv[i]);
+	}
+	qsort(events, nevents, sizeof(Event*), cmpevent);
+	fs.tree = buildtree();
+	postmountsrv(&fs, nil, mtpt, 0);
+	exits(0);
+}
--- /dev/null
+++ b/ical.c
@@ -1,0 +1,99 @@
+#include "a.h"
+
+void
+addevent(Event *e)
+{
+	if(events == nil){
+		eventsz = 128;
+		nevents = 0;
+		events = emalloc(128 * sizeof(Event*));
+	}else if(nevents == eventsz){
+		eventsz *= 2;
+		events = erealloc(events, eventsz * sizeof(Event*));
+	}
+	events[nevents++] = e;
+}
+
+int
+startswith(char *s, char *t)
+{
+	return strncmp(s, t, strlen(t)) == 0;
+}
+
+Tm
+parsedate(char *s)
+{
+	Tm tm, *res;
+
+	res = tmparse(&tm, "YYYYMMDDThhmmss", s, nil, nil);
+	if(res == nil)
+		fprint(2, "unable to parse date '%s'\n", s);
+	return tm;
+}
+
+Event*
+readevent(Biobuf *bp)
+{
+	Event *e;
+	char *s;
+	int done;
+	
+	e = emalloc(sizeof *e);
+	done = 0;
+	while(!done){
+		s = Brdstr(bp, '\n', 1);
+		if(s == nil){
+			free(e);
+			werrstr("unexpected end of file while parsing event");
+			return nil;
+		}
+		if(startswith(s, "END:VEVENT"))
+			done = 1;
+		else if(startswith(s, "UID"))
+			e->uid = strdup(s+4);
+		else if(startswith(s, "SUMMARY"))
+			e->summary = strdup(s+8);
+		else if(startswith(s, "DESCRIPTION"))
+			e->description = strdup(s+13);
+		else if(startswith(s, "LOCATION"))
+			e->location = strdup(s+9);
+		else if(startswith(s, "DTSTART"))
+			e->start = parsedate(s+8);
+		else if(startswith(s, "DTEND"))
+			e->end = parsedate(s+6);
+		else if(startswith(s, "LAST-MODIFIED"))
+			e->lastmod = parsedate(s+14);
+		else if(startswith(s, "CREATED"))
+			e->created = parsedate(s+8);
+		free(s);
+	}
+	return e;
+}
+
+int
+readical(const char *f)
+{
+	Biobuf *bp;
+	char *s;
+	Event *e;
+
+	bp = Bopen(f, OREAD);
+	if(bp == nil)
+		return -1;
+	for(;;){
+		s = Brdstr(bp, '\n', 1);
+		if(s == nil)
+			break;
+		if(strncmp(s, "BEGIN:VEVENT", 12) == 0){
+			e = readevent(bp);
+			free(s);
+			if(e != nil)
+				addevent(e);
+			else
+				fprint(2, "unable to parse event: %r\n");
+		}
+	}
+	Bterm(bp);
+	return 0;
+}
+
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,9 @@
+</$objtype/mkfile
+
+TARG=calfs
+BIN=/$objtype/bin
+OFILES=calfs.$O ical.$O utils.$O
+HFILES=a.h
+
+</sys/src/cmd/mkone
+
--- /dev/null
+++ b/utils.c
@@ -1,0 +1,57 @@
+#include "a.h"
+
+void*
+emalloc(ulong size)
+{
+	void *p;
+
+	p = malloc(size);
+	if(p == nil)
+		sysfatal("malloc: %r");
+	return p;
+}
+
+void*
+erealloc(void *p, ulong size)
+{
+	void *q;
+
+	q = realloc(p, size);
+	if(q == nil)
+		sysfatal("realloc: %r");
+	return q;
+}
+
+File*
+ecreatefile(File *dir, char *name, char *uid, ulong mode, void *aux)
+{
+	File *f;
+
+	f = createfile(dir, name, uid, mode, aux);
+	if(f == nil)
+		sysfatal("createfile: %r");
+	return f;
+}
+
+void
+readtm(Req *r, Tm *tm)
+{
+	char buf[64] = {0};
+
+	snprint(buf, sizeof buf, "%τ", tmfmt(tm, nil));
+	readstr(r, buf);
+}
+
+int
+cmpevent(void *a, void *b)
+{
+	Event **e0, **e1;
+	vlong t0, t1;
+
+	e0 = a;
+	e1 = b;
+	t0 = tmnorm(&(*e0)->start);
+	t1 = tmnorm(&(*e1)->start);
+	return t0 - t1;
+}
+