shithub: mpl

Download patch

ref: 4973be1f0bfec6883eda0a794a3d43f42a91fdf7
parent: 1e053787ab15808cdd927592f7310f4bd8123308
author: Jacob Moody <jsmoody@iastate.edu>
date: Sun Oct 6 19:33:51 EDT 2019

Move library managment into it's own thread
Refactor album art to lazy load

--- /dev/null
+++ b/dat.c
@@ -1,0 +1,125 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+int
+string2hash(char *s)
+{
+	int hash, i;
+	hash = 7;
+	for(i=0;i<strlen(s);i++)
+		hash = hash*31 + s[i];
+	return hash;
+}
+
+Hmap*
+allocmap(int size)
+{
+	Hmap *h = emalloc(sizeof(Hmap));
+	h->size = size;
+	h->nodes = emalloc(sizeof(Hnode)*size);
+	return h;
+}
+
+void
+mapinsert(Hmap *h, char *key, void *val)
+{
+	Hnode *n;
+	wlock(h);
+	n = h->nodes+(string2hash(key)%h->size);
+	assert(n != nil);
+	for(;n->next!=nil || n->key!=nil ;n=n->next)
+		if(strcmp(key, n->key) == 0){
+			/* update value */
+			n->val = val;
+			wunlock(h);
+			return;
+		}
+
+	/* Set existing free node */
+	if(n->key == nil){
+		n->key = strdup(key);
+		n->val = val;
+		wunlock(h);
+		return;
+	}
+
+	/* create new node */
+	n->next = emalloc(sizeof(Hnode));
+	n->next->key = strdup(key);
+	n->next->val = val;
+	wunlock(h);
+}
+
+void*
+mapget(Hmap *h, char *key)
+{
+	Hnode *n;
+	rlock(h);
+	n = h->nodes+(string2hash(key)%h->size);
+	for(;n!=nil;n=n->next){
+		if(n->key == nil)
+			continue;
+		if(strcmp(key, n->key) == 0){
+			runlock(h);
+			return n->val;
+		}
+	}
+
+	runlock(h);
+	return nil;
+}
+
+int
+mapdel(Hmap *h, char *key)
+{
+	Hnode *n;
+	wlock(h);
+	n = h->nodes+(string2hash(key)%h->size);
+	for(;n!=nil;n=n->next){
+		if(n->key == nil)
+			continue;
+		if(strcmp(key, n->key) == 0){
+			free(n->key);
+			n->key = nil;
+			wunlock(h);
+			return 1;
+		}
+	}
+
+	wunlock(h);
+	return 0;
+}
+
+void
+mapdump(Hmap *h, void **buf, int size)
+{
+	Hnode *n;
+	int i, c;
+
+	rlock(h);
+	for(i=c=0;i<h->size;i++)
+		for(n=h->nodes+i;n!=nil && n->key!=nil;n=n->next)
+			buf[c++] = n->val;
+	runlock(h);
+}
+
+void
+mapclear(Hmap *h)
+{
+	Hnode *n;
+	int i, c;
+
+	wlock(h);
+	for(i=c=0;i<h->size;i++)
+		for(n=h->nodes+i;n!=nil;n=n->next)
+			if(n->key != nil){
+				free(n->key);
+				n->key=nil;
+			}
+	wunlock(h);
+}
\ No newline at end of file
--- a/dat.h
+++ b/dat.h
@@ -1,16 +1,17 @@
-enum decmsg{
-	START,
+enum cmsg{
+	NEXT,
+	PREV,
 	STOP,
+	START,
 	PAUSE,
-	NEXT,
 };
 
 /*
-* ID3v1 represents the first version of ID3 metainformation.
-* The spec does not define character set, so we treat it as 
-* UTF8, which should cover most bases.
-* See: http://id3.org/ID3v1
-*/
+ * ID3v1 represents the first version of ID3 metainformation.
+ * The spec does not define character set, so we treat it as
+ * UTF8, which should cover most bases.
+ * See: http://id3.org/ID3v1
+ */
 typedef struct ID3v1 ID3v1;
 struct ID3v1{
 	Rune 	*title;
@@ -77,3 +78,26 @@
 	int nsong;
 	Song **songs;
 };
+
+typedef struct Lib Lib;
+struct Lib{
+	int nalbum, cursong;
+	Album *start, *stop, *cur;
+};
+
+/*
+ * Simple hashmap implementation.
+ * Hnode key must be non nil.
+ */
+typedef struct Hmap Hmap;
+typedef struct Hnode Hnode;
+struct Hmap{
+	RWLock;
+	int size;
+	int (*hashfn)(void*);
+	struct Hnode{
+		char *key;
+		void *val;
+		Hnode *next;
+	} *nodes;
+};
\ No newline at end of file
--- a/dec.c
+++ b/dec.c
@@ -39,7 +39,7 @@
 	int afd;
 	int bufsize;
 	int n;
-	enum decmsg msg;
+	enum cmsg msg;
 	char *buf;
 	
 	Writearg *a = arg;
@@ -95,7 +95,7 @@
 	free(chans);
 
 	char *path;
-	enum decmsg msg;
+	enum cmsg msg;
 	Waitmsg *wmsg;
 
 	Decodearg a;
@@ -116,7 +116,7 @@
 	a.cpid = chancreate(sizeof(int), 0);
 	a.outpipe = p[0];
 
-	wr.ctl = chancreate(sizeof(enum decmsg), 0);
+	wr.ctl = chancreate(sizeof(enum cmsg), 0);
 	wr.inpipe = p[1];
 
 	/* Start first song to stop blocks on writethread read */
@@ -168,7 +168,7 @@
 /*
 * Spawns the decoder processes.
 * q is a queue for next songs. chan char*
-* c is for sending control messages. chan enum decmsg
+* c is for sending control messages. chan enum cmsg
 * nil msg is sent over pop on song change.
 */
 void
@@ -179,7 +179,7 @@
 	if(*q == nil)
 		*q = chancreate(sizeof(char*), 0);
 	if(*c == nil)
-		*c = chancreate(sizeof(enum decmsg), 0);
+		*c = chancreate(sizeof(enum cmsg), 0);
 	if(*pop == nil)
 		*pop = chancreate(sizeof(char), 0);
 
--- a/dir.c
+++ b/dir.c
@@ -112,11 +112,10 @@
 	int fd;
 	long n;
 	long i;
-	char *dot;
 	Rune *albumtitle;
 	char buf[512];
 	int songcount = 0;
-	int needpic = 1;
+	int needpic = 0;
 
 	fd = open(path, OREAD);
 	if(fd < 0)
@@ -127,30 +126,6 @@
 	if(n <= 0)
 		return 0;
 
-	/* Do a single pass to find cover.^(jp^(eg g) png) */
-	for(i=0;i<n;i++){
-		dot = cistrstr(files[i].name, "cover.");
-		if(dot == nil){
-			dot = cistrstr(files[i].name, "folder.");
-			if(dot == nil)
-				continue;
-		}
-		dot = strrchr(dot, '.');
-		dot++;
-		snprint(buf, 512, "%s/%s", path, files[i].name);
-		fd = open(buf, OREAD);
-		if(fd<0)
-			continue;
-
-		a->cover = convpic(fd, dot);
-		if(a->cover != nil){
-			needpic = 0;
-			close(fd);
-			break;
-		}
-		close(fd);
-	}
-
 	/* Greedy alloc to start, we will trim down later */
 	a->nsong = n;
 	a->songs = emalloc(sizeof(Song*) * n);
@@ -157,7 +132,7 @@
 
 	for(i=0;i<n;i++){
 		snprint(buf, 512, "%s/%s", path, files[i].name);
-		a->songs[songcount] = file2song(buf, needpic);
+		a->songs[songcount] = file2song(buf, needpic++);
 		if(a->songs[songcount] == nil)
 			continue;
 		if(a->name == nil){
@@ -178,13 +153,6 @@
 				a->name = runesmprint("%S",  albumtitle);
 		}
 		a->songs[songcount]->path = strdup(buf);
-		if(needpic == 1 && a->songs[songcount]->type == FLAC && a->songs[songcount]->fmeta->pic != nil){
-			FlacPic *p = a->songs[songcount]->fmeta->pic;
-			a->cover = convpicbuf(p->data, p->size, p->mime);
-			if(a->cover == nil)
-				quit("dir2album: Could not convert image");
-			needpic--;
-		}
 		songcount++;
 	}
 
--- a/draw.c
+++ b/draw.c
@@ -42,7 +42,7 @@
 	ExecArg *a = arg;
 	dup(a->fdin, 0);
 	dup(a->fdout, 1);
-	procexecl(a->cpid, a->cmd, a->cmd, "-c", nil);
+	procexecl(a->cpid, a->cmd, a->cmd, "-9", nil);
 }
 
 void
@@ -133,6 +133,57 @@
 	return i;
 }
 
+Image*
+readcover(Song *s)
+{
+	FlacPic *p;
+	char buf[512], cover[512];
+	char *dot, *end;
+	int fd, n, i;
+	Dir *files;
+	Image *im;
+
+	if(s->type == FLAC && s->fmeta->pic != nil){
+		p = s->fmeta->pic;
+		return convpicbuf(p->data, p->size, p->mime);
+	}
+
+	dot = strrchr(s->path, '/');
+	if(dot == nil)
+		sysfatal("readcover: bad song path");
+	end = buf+(dot-s->path)+1;
+	if(end - buf > sizeof buf)
+		sysfatal("readcover: buffer too small");
+	seprint(buf, end, "%s", s->path);
+
+	fd = open(buf, OREAD);
+	if(fd < 0)
+		sysfatal("readcover: %r");
+	n = dirreadall(fd, &files);
+	close(fd);
+	if(n <= 0)
+		sysfatal("readcover: no files in dir");
+
+	for(i=0;i<n;i++){
+		dot = cistrstr(files[i].name, "cover.");
+		if(dot == nil){
+			dot = cistrstr(files[i].name, "folder.");
+			if(dot == nil)
+				continue;
+		}
+		dot = strrchr(dot, '.');
+		dot++;
+		snprint(cover, 512, "%s/%s", buf, files[i].name);
+		fd = open(cover, OREAD);
+		if(fd<0)
+			continue;
+		im = convpic(fd, dot);
+		close(fd);
+		return im;
+	}
+	return nil;
+}
+
 Point
 drawalbum(Album *a, Image *textcolor, Image *active, Point start, int cursong)
 {
@@ -141,6 +192,9 @@
 	Rune *tracktitle = nil;
 	Point p = start;
 
+	if(a->cover == nil)
+		a->cover = readcover(a->songs[0]);
+
 	if(a->cover != nil){
 		draw(screen, Rpt(p, addpt(p, a->cover->r.max)), a->cover, nil, ZP);
 		p.x += a->cover->r.max.x;
@@ -182,7 +236,6 @@
 
 	for(;start!=stop;start++)
 		p = drawalbum(start, textcolor, active, p, start == cur ? cursong : -1);
-	flushimage(display, Refnone);
 }
 
 void
@@ -195,6 +248,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);
-	flushimage(display, Refnone);
-}
\ No newline at end of file
+	string(screen, p, color, ZP, f, buf);;
+}
--- a/flac.c
+++ b/flac.c
@@ -40,9 +40,13 @@
 	len = bebtoi(buf, 4);
 	offset+=4;
 
+	/* This seems to be taking a large chunk of time;
+	 * For now we don't need it
 	pread(fd, buf, len, offset);
 	buf[len] = '\0';
 	pic->desc = runesmprint("%s", (char*)buf);
+	*/
+
 	offset+=len;
 
 	pread(fd, buf, 4, offset);
--- a/fncs.h
+++ b/fncs.h
@@ -30,4 +30,13 @@
 void	drawvolume(int, Image*);
 
 /* dir.c */
-int		parselibrary(Album**,char*);
\ No newline at end of file
+int		parselibrary(Album**,char*);
+
+/* dat.c */
+Hmap*	allocmap(int);
+void	mapinsert(Hmap*,char*,void*);
+int		mapdel(Hmap*,char*);
+void*	mapget(Hmap*,char*);
+
+/* lib.c */
+void	spawnlib(Channel*,Channel*,char*);
\ No newline at end of file
--- /dev/null
+++ b/lib.c
@@ -1,0 +1,103 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+
+#include "dat.h"
+#include "fncs.h"
+
+Channel *queuein, *queueout, *decctl;
+Lib lib;
+
+enum{
+	LMSG,
+	QUEUE,
+};
+
+char*
+nextsong(Lib *lib)
+{
+	if(lib->cursong < 0){
+		lib->cur--;
+		if(lib->cur < lib->start)
+			lib->cur = lib->stop;
+		lib->cursong = lib->cur->nsong-1;
+	}
+	if(lib->cursong > lib->cur->nsong-1){
+		lib->cur++;
+		if(lib->cur > lib->stop)
+			lib->cur = lib->start;
+		lib->cursong = 0;
+	}
+	return lib->cur->songs[lib->cursong]->path;
+}
+
+void
+handlemsg(enum cmsg msg)
+{
+	switch(msg){
+	case NEXT:
+		lib.cursong++;
+		sendp(queuein, nextsong(&lib));
+		break;
+	case PREV:
+		lib.cursong--;
+		sendp(queuein, nextsong(&lib));
+		break;
+	case STOP:
+	case START:
+	case PAUSE:
+		send(decctl, &msg);
+		break;	
+	}
+}
+
+void
+libproc(void *arg)
+{
+	Channel **chans = arg;
+	Channel *lctl = chans[0];
+	Channel *out = chans[1];
+	free(chans);
+
+	enum cmsg msg;
+
+	Alt alts[] = {
+		{lctl, &msg, CHANRCV},
+		{queueout, nil, CHANRCV},
+		{out, &lib, CHANSND},
+		{nil, nil, CHANEND},
+	};
+	for(;;)
+		switch(alt(alts)){
+		case LMSG:
+			handlemsg(msg);
+			break;
+		case QUEUE:
+			handlemsg(NEXT);
+			break;
+		}
+}
+
+void
+spawnlib(Channel *ctl, Channel *out, char *path)
+{
+	Channel **chans;
+
+	queuein = queueout = decctl = nil;
+	spawndec(&queuein, &queueout, &decctl);
+
+	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*)*2);
+	chans[0] = ctl;
+	chans[1] = out;
+
+	sendp(queuein, nextsong(&lib));
+	threadcreate(libproc, chans, 8192);
+}
\ No newline at end of file
--- a/mkfile
+++ b/mkfile
@@ -10,6 +10,8 @@
 	vorbis.$O \
 	draw.$O \
 	dir.$O \
+	dat.$O \
+	lib.$O \
 
 
-</sys/src/cmd/mkone
+</sys/src/cmd/mkone
\ No newline at end of file
--- a/mpl.c
+++ b/mpl.c
@@ -13,7 +13,6 @@
 	MOUSEC,
 	RESIZEC,
 	KEYC,
-	QUEUEPOP,
 	NONE
 };
 
@@ -22,18 +21,13 @@
 	DOWN,
 	MUTE,
 	UNMUTE,
-	DRAW,
 };
 
 
 Mousectl 	*mctl;
 Keyboardctl *kctl;
-Channel		*queuein;
-Channel		*queueout;
-Channel		*ctl;
-Channel		*vctl;
-Album		*start, *cur, *stop;
-int			cursong;
+Channel		*ctl, *lout;
+Channel		*vctl, *vlevel;
 int			decpid;
 
 Image *black;
@@ -40,6 +34,16 @@
 Image *red;
 Image *background;
 
+int
+cleanup(void*,char*)
+{
+	killgrp(decpid);
+	closedisplay(display);
+	closemouse(mctl);
+	closekeyboard(kctl);
+	return 0;
+}
+
 void
 quit(char *err)
 {
@@ -52,78 +56,58 @@
 void
 eresized(int isnew)
 {
-	enum volmsg vmsg = DRAW;
+	int level;
+	Lib lib;
 	if(isnew && getwindow(display, Refnone) < 0)
 		quit("eresized: Can't reattach to window");
 
 	draw(screen, screen->r, background, nil, ZP);
-	drawlibrary(cur, stop, cur, black, red, cursong);
-	send(vctl, &vmsg);
+	recv(lout, &lib);
+	drawlibrary(lib.cur, lib.stop, lib.cur, black, red, lib.cursong);
+	recv(vlevel, &level);
+	drawvolume(level, black);
+	flushimage(display, Refnone);
 }
 
-char*
-nextsong(void)
-{
-	if(cursong < 0){
-		cur--;
-		if(cur < start)
-			cur = stop;
-		cursong = cur->nsong-1;
-	}
-	if(cursong > cur->nsong-1){
-		cur++;
-		if(cur > stop)
-			cur = start;
-		cursong = 0;
-	}
-	return cur->songs[cursong]->path;
-}
-
 void
 handleaction(Rune kbd)
 {
 	enum volmsg vmsg;
-	enum decmsg msg;
+	enum cmsg msg;
 	switch(kbd){
 		case Kbs:
 		case Kdel:
 			killgrp(decpid);
 			quit(nil);
-			break;
+			return;
 		case 'w':
-			eresized(0);
 			break;
 		case 'p':
 			msg = PAUSE;
 			send(ctl, &msg);
-			break;
+			return;
 		case 'l':
 			msg = START;
 			send(ctl, &msg);
-			break;
+			return;
 		case 'n':
-			cursong++;
-			nextsong();
-			sendp(queuein, nextsong());
-			eresized(0);
+			msg = NEXT;
+			send(ctl, &msg);
 			break;
 		case 'm':
-			cursong--;
-			nextsong();
-			sendp(queuein, nextsong());
-			eresized(0);
+			msg = PREV;
+			send(ctl, &msg);
 			break;
 		case '9':
 			vmsg = DOWN;
 			send(vctl, &vmsg);
-			eresized(0);
 			break;
 		case '0':
 			vmsg = UP;
 			send(vctl, &vmsg);
-			eresized(0);
 			break;
 	}
+	eresized(0);
 }
 
 void
@@ -165,64 +149,75 @@
 }
 
 void
-usage(void)
-{
-	fprint(2, "Usage: %s file", argv0);
-	sysfatal("usage");
-}
-
-void
 volthread(void *arg)
 {
-	Channel *ctl = arg;
+	Channel **chans = arg;
+	Channel *ctl = chans[0];
+	Channel *out = chans[1];
+
 	int fd;
-	int mlevel, level;
+	int level;
+	int muted = 0;
 	enum volmsg vmsg;
 
 	if((fd = open("/dev/volume", ORDWR))<0){
 		/* Make volume controls NOP */
 		chanclose(ctl);
+		chanclose(out);
 		return;
 	}
+
+	Alt alts[] = {
+		{ctl, &vmsg, CHANRCV},
+		{out, &level, CHANSND},
+		{nil, nil, CHANEND},
+	};
+
+	readvol(fd, &level);
 	for(;;){
-		recv(ctl, &vmsg);
+		if(alt(alts) != 0)
+			continue;
 		readvol(fd, &level);
 		switch(vmsg){
 		case UP:
 			level+=5;
-			writevol(fd, level);
+			writevol(fd, muted == 0 ? level : 0);
 			break;
 		case DOWN:
 			level-=5;
-			writevol(fd, level);
+			writevol(fd, muted == 0 ? level : 0);
 			break;
 		case MUTE:
-			mlevel = level;
-			level = 0;
-			writevol(fd, level);
+			muted = 1;
+			writevol(fd, 0);
 			break;
 		case UNMUTE:
-			level = mlevel;
+			muted = 0;
 			writevol(fd, level);
 			break;
-		case DRAW:
-			drawvolume(level, black);
 		}
 	}
 }
 
 void
+usage(void)
+{
+	fprint(2, "Usage: %s file", argv0);
+	sysfatal("usage");
+}
+
+void
 threadmain(int argc, char *argv[])
 {
 	Mouse mouse;
 	Rune kbd;
 	int resize[2];
-	int nalbum;
-	cursong = 0;
-	queuein = queueout = ctl = vctl = nil;
+	Channel *vchans[2];
+	ctl = vctl = vlevel = nil;
 
 	//TODO: Use ARGBEGIN
-	argv0 = argv[0];	
+	argv0 = argv[0];
+	threadnotify(cleanup, 1);
 
 	if(argc != 2)
 		usage();
@@ -234,27 +229,26 @@
 	if((kctl = initkeyboard(nil)) == nil)
 		sysfatal("initkeyboard: %r");
 
+	ctl = chancreate(sizeof(enum cmsg), 0);
+	lout = chancreate(sizeof(Lib), 0);
+	spawnlib(ctl, lout, argv[1]);
+
 	vctl = chancreate(sizeof(enum volmsg), 0);
-	threadcreate(volthread, vctl, 8192);
+	vlevel = chancreate(sizeof(int), 0);
+	vchans[0] = vctl;
+	vchans[1] = vlevel;
+	threadcreate(volthread, vchans, 8192);
 
 	red = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DBlue);
 	black = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, DBlack);
 	background = allocimagemix(display, DPaleyellow, DPalegreen);
 
-	nalbum = parselibrary(&start, argv[1]);
-	if(nalbum == 0)
-		quit("No songs found");
-	cur = start;
-	stop = start+(nalbum-1);
-	spawndec(&queuein, &ctl, &queueout);
-	send(queuein, &(cur->songs[0]->path));
-	handleaction('w');
+	eresized(0);
 
 	Alt alts[] = {
 		{mctl->c, &mouse, CHANRCV},
 		{mctl->resizec, resize, CHANRCV},
 		{kctl->c, &kbd, CHANRCV},
-		{queueout, nil, CHANRCV},
 		{nil, nil, CHANEND},
 	};
 
@@ -265,9 +259,6 @@
 				break;
 			case RESIZEC:
 				eresized(1);
-				break;
-			case QUEUEPOP:
-				handleaction(L'n');
 				break;
 		}
 	}