ref: 4973be1f0bfec6883eda0a794a3d43f42a91fdf7
parent: 1e053787ab15808cdd927592f7310f4bd8123308
author: Jacob Moody <[email protected]>
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;
}
}