shithub: mpl

Download patch

ref: 5d597414f63bb9c98aa442d3fd6d33fc230848b4
parent: 4f13cda5bf98bc825ffc7f7ef135f0b919523ad5
author: Jacob Moody <[email protected]>
date: Thu Nov 21 09:25:52 EST 2019

Add different ways to load playlist
Add ability to play form grid radio
Add ability to play single file
Add ability to dump and load the current queue as a playlist
Add ability to play from dumped playlists

--- a/dat.h
+++ b/dat.h
@@ -4,6 +4,7 @@
 	STOP,
 	START,
 	PAUSE,
+	DUMP,
 };
 
 enum volmsg{
@@ -24,7 +25,7 @@
 	Rune 	*title;
 	Rune 	*artist;
 	Rune 	*album;
-	int		year;
+	int	year;
 	Rune	*comment;
 	char	genre;
 };
@@ -46,11 +47,11 @@
 
 typedef struct FlacPic FlacPic;
 struct FlacPic{
-		char *mime;
-		Rune *desc;
-		Point p;
-		uvlong size;
-		uchar *data;
+	char *mime;
+	Rune *desc;
+	Point p;
+	uvlong size;
+	uchar *data;
 };
 
 typedef struct FlacMeta FlacMeta;
@@ -64,6 +65,7 @@
 	FLAC,
 	MP3,
 	VORBIS,
+	RADIO,
 };
 
 typedef struct Song Song;
@@ -73,6 +75,7 @@
 		FlacMeta *fmeta;
 		VorbisMeta *vmeta;
 		ID3v1 *idmeta;
+		Rune *title;
 	};
 	char *path;
 };
@@ -79,11 +82,12 @@
 
 typedef struct Album Album;
 struct Album{
-	char *path;
 	Rune *name;
 	Image *cover;
 	int nsong;
-	Song **songs;
+	Song *songs;
+
+	int nocover;
 };
 
 typedef struct Lib Lib;
@@ -90,6 +94,7 @@
 struct Lib{
 	int nalbum, cursong;
 	Album *start, *stop, *cur;
+	char *name;
 };
 
 typedef struct Click Click;
@@ -114,4 +119,4 @@
 		void *val;
 		Hnode *next;
 	} *nodes;
-};
\ No newline at end of file
+};
--- a/dir.c
+++ b/dir.c
@@ -6,19 +6,19 @@
 #include "dat.h"
 #include "fncs.h"
 
-Song*
-file2song(char *path, int needpic)
+int
+file2song(Song *s, char *path, int needpic)
 {
 	char *dot;
-	Song *s;
 	int fd;
 
+	s->path = strdup(path);
+
 	dot = strrchr(path, '.');
-	if(dot == nil)
-		return nil;
+	if(dot == nil || *dot == '\0')
+		return 0;
 	dot+=1;
 
-	s = emalloc(sizeof(Song));
 	if(strcmp(dot, "flac") == 0){
 		s->type = FLAC;
 		goto done;
@@ -32,12 +32,12 @@
 		goto done;
 	}
 	/* Unsupported file suffix */
-	goto error;
+	return 0;
 
 done:
 	fd = open(path, OREAD);
 	if(fd < 0)
-		goto error;
+		return 0;
 
 	switch(s->type){
 	case FLAC:
@@ -53,20 +53,16 @@
 	close(fd);
 	/* We can check the pointer without having to worry about which one it is */
 	if(s->fmeta == nil)
-		goto error;
+		return 0;
 
-	return s;
-
-error:
-	free(s);
-	return nil;
+	return 1;
 }
 
 int
 songcmp(void *a, void *b)
 {
-	Song *s1 = *((Song**)a);
-	Song *s2 = *((Song**)b);
+	Song *s1 = a;
+	Song *s2 = b;
 	int t1, t2;
 	t1 = t2 = 0;
 
@@ -128,23 +124,22 @@
 
 	/* Greedy alloc to start, we will trim down later */
 	a->nsong = n;
-	a->songs = emalloc(sizeof(Song*) * n);
+	a->songs = emalloc(sizeof(Song) * n);
 
 	for(i=0;i<n;i++){
 		snprint(buf, 512, "%s/%s", path, files[i].name);
-		a->songs[songcount] = file2song(buf, needpic++);
-		if(a->songs[songcount] == nil)
+		if(!file2song(a->songs+songcount, buf, 0))
 			continue;
 		if(a->name == nil){
-			switch(a->songs[songcount]->type){
+			switch((a->songs+songcount)->type){
 			case FLAC:
-				albumtitle = a->songs[songcount]->fmeta->com->album;
+				albumtitle = (a->songs+songcount)->fmeta->com->album;
 				break;
 			case MP3:
-				albumtitle = a->songs[songcount]->idmeta->album;
+				albumtitle = (a->songs+songcount)->idmeta->album;
 				break;
 			case VORBIS:
-				albumtitle = a->songs[songcount]->vmeta->album;
+				albumtitle = (a->songs+songcount)->vmeta->album;
 				break;
 			default:
 				albumtitle = nil;
@@ -152,14 +147,13 @@
 			if(albumtitle != nil)
 				a->name = runesmprint("%S",  albumtitle);
 		}
-		a->songs[songcount]->path = strdup(buf);
 		songcount++;
 	}
 
 	a->nsong = songcount;
-	a->songs = realloc(a->songs, sizeof(Song*) * songcount);
+	a->songs = realloc(a->songs, sizeof(Song) * songcount);
 
-	qsort(a->songs, songcount, sizeof(Song*), songcmp);
+	qsort(a->songs, songcount, sizeof(Song), songcmp);
 
 	free(files);
 	return 1;
@@ -200,4 +194,36 @@
 	*als = realloc(*als, sizeof(Album)*alcount);
 
 	return alcount;
-}
\ No newline at end of file
+}
+
+void
+file2album(Album *a, Rune *aname, char *path)
+{
+	a->name = runestrdup(aname);
+	a->cover = nil;
+	a->nsong = 1;
+	a->songs = emalloc(sizeof(Song));
+	/* As a special case for http streams */
+	if(strstr(path, "http")==path){
+		a->name = runesmprint("%s", path);
+		a->nocover = 1;
+		a->songs->path = strdup(path);
+		a->songs->title = runestrdup(aname);
+		a->songs->type = RADIO;
+		return;
+	}
+	if(!file2song(a->songs, path, 0))
+		sysfatal("Could not parse song %s", path);
+}
+
+void
+radio2album(Album *a, char *path)
+{
+	a->name = runesmprint("Radio");
+	a->cover = nil;
+	a->nsong = 1;
+	a->songs = emalloc(sizeof(Song));
+	a->songs->type = RADIO;
+	a->songs->title = nil;
+	a->songs->path = strdup(path);
+}
--- a/draw.c
+++ b/draw.c
@@ -27,6 +27,7 @@
 		i+=n;
 		towrite-=n;
 	}
+	close(fd);
 }
 
 typedef struct{
@@ -143,6 +144,7 @@
 	Dir *files;
 	Image *im;
 
+
 	if(s->type == FLAC && s->fmeta->pic != nil){
 		p = s->fmeta->pic;
 		return convpicbuf(p->data, p->size, p->mime);
@@ -193,8 +195,9 @@
 	Point p = start;
 	Click c;
 
-	if(a->cover == nil)
-		a->cover = readcover(a->songs[0]);
+	if(a->nocover == 0 && a->cover == nil)
+		if((a->cover = readcover(a->songs)) == nil)
+			a->nocover = 1; /* Don't search again */
 
 	if(a->cover != nil){
 		draw(screen, Rpt(p, addpt(p, a->cover->r.max)), a->cover, nil, ZP);
@@ -205,16 +208,23 @@
 	p.y += f->height * 2;
 
 	for(i=0;i<a->nsong;i++){
-		switch(a->songs[i]->type){
+		switch((a->songs+i)->type){
 		case FLAC:
-			tracktitle = a->songs[i]->fmeta->com->title;
+			tracktitle = (a->songs+i)->fmeta->com->title;
 			break;
 		case MP3:
-			tracktitle = a->songs[i]->idmeta->title;
+			tracktitle = (a->songs+i)->idmeta->title;
 			break;
 		case VORBIS:
-			tracktitle = a->songs[i]->vmeta->title;
+			tracktitle = (a->songs+i)->vmeta->title;
 			break;
+		case RADIO:
+			tracktitle = (a->songs+i)->title;
+			if(tracktitle == nil)
+				return start;
+			break;
+		default:
+			sysfatal("Unknown song type");
 		}
 		runestring(screen, p, i == cursong ? active : textcolor, ZP, f, tracktitle);
 		c.r = Rpt(p, Pt(p.x+runestrlen(tracktitle)*f->width,p.y+f->height));
@@ -239,8 +249,12 @@
 	stop = screenstop < stop ? screenstop : stop;
 	stop+=1;
 
-	for(;start!=stop;start++)
+	if(start==stop)
+		stop++;
+
+	for(;start!=stop;start++){
 		p = drawalbum(start, textcolor, active, p, start == cur ? cursong : -1, clickout);
+	}
 }
 
 void
@@ -253,5 +267,5 @@
 	p.y = screen->r.min.y;
 	p.x = screen->r.max.x;
 	p.x-=(n*f->width);
-	string(screen, p, color, ZP, f, buf);;
+	string(screen, p, color, ZP, f, buf);
 }
--- a/fncs.h
+++ b/fncs.h
@@ -30,7 +30,11 @@
 void	drawvolume(int, Image*);
 
 /* dir.c */
+int		file2song(Song*, char*,int);
+int		dir2album(Album*,char*);
 int		parselibrary(Album**,char*);
+void	radio2album(Album*,char*);
+void	file2album(Album*,Rune*,char*);
 
 /* dat.c */
 Hmap*	allocmap(int);
@@ -39,10 +43,24 @@
 void*	mapget(Hmap*,char*);
 
 /* lib.c */
-void	spawnlib(Channel*,Channel*,Channel*,Channel*,char*);
+void	spawnlib(Channel*,Channel*,Channel*,Channel*,Channel*,char*);
 
 /* vol.c */
 void	spawnvol(Channel*,Channel*);
 
 /* event.c */
-void	spawnevent(Channel*,Channel*,Channel*,Channel*);
\ No newline at end of file
+void	spawnevent(Channel*,Channel*,Channel*,Channel*);
+
+/* index.c */
+void	marshalstr(int,char*);
+void	unmarshalstr(int,char**);
+void	marshalrune(int,Rune*);
+void	unmarshalrun(int,Rune**);
+void	marshalalbum(int,Album*);
+void	unmarshalalbum(int,Album*);
+void	marshallib(int,Lib*);
+void	unmarshallib(int,Lib*);
+
+/* list.c */
+void	dumplib(Lib*);
+void	loadlib(Lib*);
--- /dev/null
+++ b/index.c
@@ -1,0 +1,221 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+const uchar none[] = {0};
+
+/*
+ * Rune and str marshal functions do not write null.
+ */
+
+void
+marshalrune(int fd, Rune *r)
+{
+	static char buf[128];
+	uchar n;
+
+	if(r == nil || r[0] == '\0')
+		write(fd, none, sizeof none);
+	else{
+		n = snprint(buf, 128, "%S", r);
+		write(fd, &n, sizeof n);
+		write(fd, buf, n);
+	}
+}
+
+void
+unmarshalrune(int fd, Rune **r)
+{
+	static uchar buf[128];
+	uchar n;
+	read(fd, &n, sizeof n);
+	if(n == 0)
+		return;
+	read(fd, buf, n);
+	buf[n] = '\0';
+	*r = runesmprint("%s", (char*)buf);
+}
+
+void
+marshalstr(int fd, char *s)
+{
+	uint n = strlen(s);
+	write(fd, &n, sizeof n);
+	write(fd, s, n);
+}
+
+void
+unmarshalstr(int fd, char **s)
+{
+	uint n;
+	read(fd, &n, sizeof n);
+	*s = emalloc(n+1);
+	s[n] = '\0';
+	read(fd, *s, n);
+}
+
+void
+marshalvorbis(int fd, VorbisMeta *v)
+{
+	//TODO: store whole key-value pair
+	marshalrune(fd, v->title);
+	marshalrune(fd, v->artist);
+	marshalrune(fd, v->album);
+	write(fd, &(v->year), sizeof v->year);
+	write(fd, &(v->tracknumber), sizeof v->tracknumber);
+}
+
+void
+unmarshalvorbis(int fd, VorbisMeta **v)
+{
+	*v = emalloc(sizeof(VorbisMeta));
+	unmarshalrune(fd, &((*v)->title));
+	unmarshalrune(fd, &((*v)->artist));
+	unmarshalrune(fd, &((*v)->album));
+	read(fd, &((*v)->year), sizeof (*v)->year);
+	read(fd, &((*v)->tracknumber), sizeof (*v)->tracknumber);
+}
+
+void
+marshalflacpic(int fd, FlacPic *p)
+{
+	//TODO store other fields
+	marshalstr(fd, p->mime);
+	write(fd, &(p->size), sizeof p->size);
+	write(fd, p->data, p->size);
+}
+
+void
+unmarshalflacpic(int fd, FlacPic **p)
+{
+	*p = emalloc(sizeof(FlacPic));
+	unmarshalstr(fd, &((*p)->mime));
+	read(fd, &((*p)->size), sizeof (*p)->size);
+	read(fd, (*p)->data, (*p)->size);
+}
+
+void
+marshalflacmeta(int fd, FlacMeta *f)
+{
+	marshalvorbis(fd, f->com);
+//	marshalflacpic(fd, f->pic);
+}
+
+void
+unmarshalflacmeta(int fd, FlacMeta **f)
+{
+	*f = emalloc(sizeof(FlacMeta));
+	unmarshalvorbis(fd, &((*f)->com));
+//	unmarshalflacpic(fd, &((*f)->pic));
+}
+
+void
+marshalid3(int fd, ID3v1 *id)
+{
+	marshalrune(fd, id->title);
+	marshalrune(fd, id->artist);
+	marshalrune(fd, id->album);
+	write(fd, &(id->year), sizeof id->year);
+	marshalrune(fd, id->comment);
+	write(fd, &(id->genre), sizeof id->genre);
+}
+
+void
+unmarshalid3(int fd, ID3v1 **id)
+{
+	*id = emalloc(sizeof(ID3v1));
+	unmarshalrune(fd, &((*id)->title));
+	unmarshalrune(fd, &((*id)->artist));
+	unmarshalrune(fd, &((*id)->album));
+	read(fd, &((*id)->year), sizeof (*id)->year);
+	unmarshalrune(fd, &((*id)->comment));
+	read(fd, &((*id)->genre), sizeof (*id)->genre);
+}
+
+void
+marshalsong(int fd, Song *s)
+{
+	write(fd, &(s->type), sizeof s->type);
+	switch(s->type){
+	case FLAC:
+		marshalflacmeta(fd, s->fmeta);
+		break;
+	case MP3:
+		marshalid3(fd, s->idmeta);
+		break;
+	default:
+		sysfatal("not recognized or unsupported format");
+	}
+	marshalstr(fd, s->path);
+}
+
+void
+unmarshalsong(int fd, Song *s)
+{
+	read(fd, &(s->type), sizeof s->type);
+	switch(s->type){
+	case FLAC:
+		unmarshalflacmeta(fd, &(s->fmeta));
+		break;
+	case MP3:
+		unmarshalid3(fd, &(s->idmeta));
+		break;
+	default:
+		sysfatal("not recognized or unsupported format");
+	}
+	unmarshalstr(fd, &(s->path));
+}
+
+void
+marshalalbum(int fd, Album *a)
+{
+	int i;
+	int havepic = a->cover == nil ? 0 : 1;
+	marshalrune(fd, a->name);
+	write(fd, &(a->nsong), sizeof a->nsong);
+	write(fd, &havepic, sizeof havepic);
+	if(havepic)
+		writeimage(fd, a->cover, 0);
+	for(i=0;i<a->nsong;i++)
+		marshalsong(fd, a->songs+i);
+}
+
+void
+unmarshalalbum(int fd, Album *a)
+{
+	int i, havepic;
+	unmarshalrune(fd, &(a->name));
+	read(fd, &(a->nsong), sizeof a->nsong);
+	read(fd, &havepic, sizeof havepic);
+	if(havepic)
+		a->cover = readimage(display, fd, 0);
+	a->songs = emalloc(sizeof(Song)*a->nsong);
+	for(i=0;i<a->nsong;i++)
+		unmarshalsong(fd, a->songs+i);
+}
+
+void
+marshallib(int fd, Lib *l)
+{
+	int i;
+	write(fd, &(l->nalbum), sizeof l->nalbum);
+	for(i=0;i<l->nalbum;i++)
+		marshalalbum(fd, l->start+i);
+}
+
+void
+unmarshallib(int fd, Lib *l)
+{
+	int i;
+	l->cursong = 0;
+	read(fd, &(l->nalbum), sizeof l->nalbum);
+	l->start = emalloc(sizeof(Album)*l->nalbum);
+	for(i=0;i<l->nalbum;i++)
+		unmarshalalbum(fd, l->start+i);
+	l->stop = l->start+i;
+	l->cur = l->start;
+}
--- a/lib.c
+++ b/lib.c
@@ -1,5 +1,6 @@
 #include <u.h>
 #include <libc.h>
+#include <bio.h>
 #include <thread.h>
 #include <draw.h>
 
@@ -7,15 +8,9 @@
 #include "fncs.h"
 
 Channel *queuein, *queueout, *decctl;
+Channel *radiochan;
 Lib lib;
 
-enum{
-	LMSG,
-	QUEUEPOP,
-	EIN,
-	OUT,
-};
-
 char*
 nextsong(Lib *lib)
 {
@@ -31,7 +26,7 @@
 			lib->cur = lib->start;
 		lib->cursong = 0;
 	}
-	return lib->cur->songs[lib->cursong]->path;
+	return (lib->cur->songs+(lib->cursong))->path;
 }
 
 void
@@ -38,6 +33,9 @@
 handlemsg(enum cmsg msg)
 {
 	switch(msg){
+	case DUMP:
+		dumplib(&lib);
+		break;
 	case NEXT:
 		lib.cursong++;
 		sendp(queuein, nextsong(&lib));
@@ -61,19 +59,34 @@
 	Channel *lctl = chans[0];
 	Channel *out = chans[1];
 	Channel *ein = chans[2];
-	Channel *resize = chans[3];
+	Channel *redraw = chans[3];
+	Channel *loadc = chans[4];
 	free(chans);
 
 	enum cmsg msg;
 	Click c;
 
+	char *radiotitle = nil;
+	char *toload;
+
 	Alt alts[] = {
 		{lctl, &msg, CHANRCV},
 		{queueout, nil, CHANRCV},
 		{ein, &c, CHANRCV},
 		{out, &lib, CHANSND},
+		{radiochan, &radiotitle, CHANRCV},
+		{loadc,	&toload, CHANRCV},
 		{nil, nil, CHANEND},
 	};
+	enum{
+		LMSG,
+		QUEUEPOP,
+		EIN,
+		OUT,
+		RADIOIN,
+		LOADC,
+	};
+
 	for(;;){
 		switch(alt(alts)){
 		case LMSG:
@@ -89,32 +102,119 @@
 			break;
 		case OUT:
 			continue;
+		case RADIOIN:
+			/* Eat everything in the channel;
+			 * This leaks memory,
+			 * but fixes a race.
+			 * TODO: Find a better fix.
+			 */
+			while(nbrecv(radiochan, &radiotitle))
+				;
+			if(lib.cur->songs->title != nil)
+				free(lib.cur->songs->title);
+			lib.cur->songs->title = runesmprint("%s", radiotitle);
+			free(radiotitle);
+			break;
+		case LOADC:
+			free(lib.name);
+			lib.name = strdup(toload);
+			loadlib(&lib);
+			lib.stop = lib.start+(lib.nalbum-1);
+			lib.cursong = 0;
+			lib.cur = lib.start;
+			break;
 		}
-		send(resize, nil);
+		send(redraw, nil);
 	}
 }
 
 void
-spawnlib(Channel *ctl, Channel *out, Channel *ein, Channel *resize, char *path)
+radioproc(void *arg)
 {
+	char *path = arg;
+	char buf[512];
+	char *dot, *end;
+	Biobuf *b;
+
+	dot = strrchr(path, '/');
+	if(dot == nil)
+		sysfatal("readradiosong: bad song path");
+	end = buf+(dot-path)+1;
+	if(end - buf > sizeof buf)
+		sysfatal("readradiosong: buffer too small");
+	seprint(buf, end, "%s", path);
+	snprint(buf, 512, "%s/playing", buf);
+	free(path);
+	b = Bopen(buf, OREAD);
+	if(b == nil)
+		sysfatal("readradiosong: Bopen %r");
+	for(;;){
+		path = Brdstr(b, '\n', 1);
+		sendp(radiochan, path);
+	}
+}
+
+void
+spawnlib(Channel *ctl, Channel *out, Channel *ein, Channel *redraw, Channel *loadc, char *path)
+{
 	Channel **chans;
+	char *name;
 
 	queuein = queueout = decctl = nil;
+	radiochan = nil;
+
 	spawndec(&queuein, &decctl, &queueout);
+	radiochan = chancreate(sizeof(char*), 1024);
 
+	name = strrchr(path, '/');
+	lib.name = strdup(name == nil ? path : name);
+
+	extern int mflag;
+	extern int sflag;
+	extern int rflag;
+	extern int pflag;
+	extern int fflag;
+
+	if(mflag == 1){
+		chanclose(radiochan);
+		lib.nalbum = parselibrary(&(lib.start), path);
+		if(lib.nalbum == 0)
+			sysfatal("no songs found");
+		lib.stop = lib.start+(lib.nalbum-1);
+	}else if(sflag == 1){
+		chanclose(radiochan);
+		lib.nalbum = 1;
+		lib.start = emalloc(sizeof(Album));
+		if(!dir2album(lib.start, path))
+			sysfatal("no songs found");
+		lib.stop = lib.start;
+	}else if(rflag == 1){
+		lib.nalbum = 1;
+		lib.start = emalloc(sizeof(Album));
+		lib.stop = lib.start;
+		radio2album(lib.start, path);
+		proccreate(radioproc, strdup(path), 8192);
+	}else if(pflag == 1){
+		chanclose(radiochan);
+		loadlib(&lib);
+		lib.stop = lib.start+(lib.nalbum-1);
+	}else if(fflag == 1){
+		lib.nalbum = 1;
+		lib.start = emalloc(sizeof(Album));
+		lib.stop = lib.start;
+		file2album(lib.start, L"", path);
+	}
+
 	lib.cursong = 0;
-	lib.nalbum = parselibrary(&(lib.start), path);
-	if(lib.nalbum == 0)
-		quit("No songs found");
 	lib.cur = lib.start;
-	lib.stop = lib.start+(lib.nalbum-1);
 
-	chans = emalloc(sizeof(Channel*)*4);
+	chans = emalloc(sizeof(Channel*)*5);
 	chans[0] = ctl;
 	chans[1] = out;
 	chans[2] = ein;
-	chans[3] = resize;
+	chans[3] = redraw;
+	chans[4] = loadc;
 
 	sendp(queuein, nextsong(&lib));
 	threadcreate(libproc, chans, 8192);
-}
\ No newline at end of file
+}
--- /dev/null
+++ b/list.c
@@ -1,0 +1,167 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+void
+dumpalbum(int fd, Album *a)
+{
+	int i;
+	char nl = '\n';
+	char buf[128];
+	int n = snprint(buf, 128, "%S", a->name);
+
+	write(fd, buf, n);
+	write(fd, &nl, 1);
+
+	for(i=0;i<a->nsong;i++){
+		write(fd, a->songs[i].path, strlen(a->songs[i].path));
+		write(fd, &nl, 1);
+	}
+}
+
+void
+libdir(char *buf, int n)
+{
+	char *home;
+	int s;
+	home = getenv("home");
+	s = snprint(buf, n, "%s/lib/mpl", home);
+	buf[s] = '\0';
+	free(home);
+}
+
+void
+createlibdir(char *path)
+{
+	int fd;
+	fd = open(path, OREAD);
+	if(fd>0){
+		close(fd);
+		return;
+	}
+	fd = create(path, OREAD, DMDIR|0755);
+	if(fd<0)
+		sysfatal("could not create lib dir: %r");
+	close(fd);
+}
+
+void
+dumplib(Lib *l)
+{
+	int fd;
+	int i;
+	char buf[512];
+	char fname[512];
+	char sep[] = "\n\n";
+
+	libdir(buf, 512);
+	assert(l->name != nil);
+	createlibdir(buf);
+	i = snprint(fname, 512, "%s/%s.list", buf, l->name);
+	fname[i] = '\0';
+	fd = create(fname, ORDWR, 0644);
+	if(fd<0){
+		fprint(2, "Could not create list file: %r\n");
+		quit(nil);
+	}
+	for(i=0;i<l->nalbum;i++){
+		dumpalbum(fd, l->start+i);
+		write(fd, sep, sizeof sep - 1);
+	}
+	close(fd);
+	snprint(fname, 512, "%s/%s.db", buf, l->name);
+	fd = create(fname, ORDWR, 0644);
+	if(fd<0){
+		fprint(2, "Count not create list db file: %r");
+		quit(nil);
+	}
+	marshallib(fd, l);
+	close(fd);
+}
+
+void
+loadalbum(Biobuf *b, Album *a)
+{
+	int size, cap;
+	char *dot;
+	dot = Brdstr(b, '\n', 1);
+	if(dot == nil){
+		return;
+	}
+	a->name = runesmprint("%s", dot);
+	free(dot);
+
+	cap = 10;
+	a->songs = emalloc(sizeof(Song)*cap);
+	for(size=0;(dot = Brdstr(b, '\n', 1));size++){
+		if(strlen(dot) == 0){
+			free(dot);
+			break;
+		}
+		if(size == cap-1){
+			cap = cap * 2;
+			a->songs = realloc(a->songs, sizeof(Song)*cap);
+		}
+		if(file2song(a->songs+size, dot, 0) == 0)
+			sysfatal("Could not parse song %s", dot);
+	}
+
+	a->songs = realloc(a->songs, sizeof(Song)*size);
+	a->nsong = size;
+	a->cover = nil;
+	return;
+}
+
+void
+loadlib(Lib *l)
+{
+	Biobuf *b;
+	char buf[512];
+	char fname[512];
+	int size, cap;
+	int fd;
+	long r;
+
+	assert(l->name != nil);
+	libdir(buf, 512);
+
+	/* Check for db cache file first */
+	snprint(fname, 512, "%s/%s.db", buf, l->name);
+	if((fd = open(fname, OREAD))>0){
+		unmarshallib(fd, l);
+		close(fd);
+		return;
+	}
+
+	snprint(fname, 512, "%s/%s.list", buf, l->name);
+	b = Bopen(fname, OREAD);
+	if(b == nil){
+		fprint(2, "Could not open list file: %s\n", fname);
+		quit(nil);
+	}
+	cap = 10;
+	l->start = emalloc(sizeof(Album)*cap);
+	for(size = 0;(r = Bgetrune(b)) > 0;size++){
+		if(size == cap-1){
+			cap = cap * 2;
+			l->start = realloc(l->start, sizeof(Album)*cap);
+		}
+		if(r != L'\n'){
+			Bungetrune(b);
+		}
+		(l->start+size)->name = nil;
+		loadalbum(b, l->start+size);
+	}
+	if((l->start+size)->name == nil){
+		size--;
+	}
+	close(Bfildes(b));
+	free(b);
+	l->start = realloc(l->start, sizeof(Album)*size);
+	l->nalbum = size;
+}
--- a/mkfile
+++ b/mkfile
@@ -14,6 +14,8 @@
 	lib.$O \
 	vol.$O \
 	event.$O \
+	index.$O \
+	list.$O \
 
 
-</sys/src/cmd/mkone
\ No newline at end of file
+</sys/src/cmd/mkone
--- a/mpl.c
+++ b/mpl.c
@@ -12,16 +12,19 @@
 enum {
 	RESIZEC,
 	KEYC,
+	REDRAW,
 	NONE
 };
 
 Mousectl 	*mctl;
 Keyboardctl *kctl;
-Channel		*ctl, *lout;
+Channel		*ctl, *lout, *loadc;
 Channel		*vctl, *vlevel;
 Channel		*clickin, *clickreset;
-int			decpid;
+int		decpid;
 
+int mflag, sflag, pflag, rflag, fflag;
+
 Image *black;
 Image *red;
 Image *background;
@@ -39,9 +42,7 @@
 void
 quit(char *err)
 {
-	closedisplay(display);
-	closemouse(mctl);
-	closekeyboard(kctl);
+	cleanup(nil, nil);
 	threadexitsall(err);
 }
 
@@ -67,6 +68,7 @@
 {
 	enum volmsg vmsg;
 	enum cmsg msg;
+	char buf[512] = {0};
 	switch(kbd){
 		case Kbs:
 		case Kdel:
@@ -99,15 +101,22 @@
 			vmsg = UP;
 			send(vctl, &vmsg);
 			break;
+		case 'd':
+			msg = DUMP;
+			send(ctl, &msg);
+			break;
+		case 'o':
+			enter("Playlist?", buf, sizeof buf, mctl, kctl, nil);
+			sendp(loadc, buf);
+			break;
 	}
 	eresized(0);
 }
 
-
 void
 usage(void)
 {
-	fprint(2, "Usage: %s file", argv0);
+	fprint(2, "Usage: -mspr %s file", argv0);
 	sysfatal("usage");
 }
 
@@ -118,12 +127,33 @@
 	Channel *clickout;
 	int resize[2];
 	ctl = vctl = vlevel = nil;
+	mflag = sflag = pflag = rflag = fflag = 0;
 
-	//TODO: Use ARGBEGIN
-	argv0 = argv[0];
+	/*
+	 * This shouldn't need to be buffered,
+	 * but for some reason it likes to still be
+	 * blocked by the time the libproc is asked
+	 * to produce the Lib struct. TODO: Investigate.
+	 */
+	Channel *redraw = chancreate(1, 1);
+
+	ARGBEGIN{
+	case 'm': mflag++; break;
+	case 's': sflag++; break;
+	case 'p': pflag++; break;
+	case 'r': rflag++; break;
+	case 'f': fflag++; break;
+	default: usage();
+	}ARGEND
+
+	if(mflag+sflag+pflag+rflag+fflag < 1){
+		fprint(2, "Please specify a playlist flag(m, s, r, or p)\n");
+		threadexits(nil);
+	}
+
 	threadnotify(cleanup, 1);
 
-	if(argc != 2)
+	if(argc != 1)
 		usage();
 
 	if(initdraw(nil, nil, "mpl") < 0)
@@ -140,7 +170,8 @@
 
 	ctl = chancreate(sizeof(enum cmsg), 0);
 	lout = chancreate(sizeof(Lib), 0);
-	spawnlib(ctl, lout, clickout, mctl->resizec, argv[1]);
+	loadc = chancreate(sizeof(char*), 0);
+	spawnlib(ctl, lout, clickout, redraw, loadc, argv[0]);
 
 	vctl = chancreate(sizeof(enum volmsg), 0);
 	vlevel = chancreate(sizeof(int), 0);
@@ -155,6 +186,7 @@
 	Alt alts[] = {
 		{mctl->resizec, resize, CHANRCV},
 		{kctl->c, &kbd, CHANRCV},
+		{redraw, nil, CHANRCV},
 		{nil, nil, CHANEND},
 	};
 
@@ -166,6 +198,9 @@
 			case RESIZEC:
 				eresized(1);
 				break;
+			case REDRAW:
+				eresized(0);
+				break;
 		}
 	}
-}
\ No newline at end of file
+}
--- a/vol.c
+++ b/vol.c
@@ -78,10 +78,12 @@
 		switch(vmsg){
 		case UP:
 			level+=5;
+			level = level > 100 ? 100 : level;
 			writevol(fd, muted == 0 ? level : 0);
 			break;
 		case DOWN:
 			level-=5;
+			level = level < 0 ? 0 : level;
 			writevol(fd, muted == 0 ? level : 0);
 			break;
 		case MUTE: