tmus

TUI Music Player
git clone https://git.sinitax.com/sinitax/tmus
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

data.c (12356B)


      1#include "data.h"
      2
      3#include "tui.h"
      4#include "player.h"
      5#include "list.h"
      6#include "log.h"
      7
      8#include <asm-generic/errno-base.h>
      9#include <fts.h>
     10#include <errno.h>
     11#include <dirent.h>
     12#include <sys/stat.h>
     13#include <unistd.h>
     14#include <stdbool.h>
     15#include <string.h>
     16#include <stdlib.h>
     17
     18const char *datadir;
     19
     20struct list tracks; /* struct track (link) */
     21struct list tags; /* struct track (link) */
     22struct list tags_sel; /* struct tag (link_sel) */
     23
     24struct tag *trash_tag;
     25
     26bool playlist_outdated;
     27
     28static struct tag *tag_alloc(const char *path, const char *fname);
     29static void tag_free(struct tag *tag);
     30
     31static struct track *track_alloc(const char *path, const char *fname);
     32static void track_free(struct track *t);
     33
     34static bool tag_name_cmp(const void *p1, const void *p2, void *user);
     35
     36struct tag *
     37tag_alloc(const char *path, const char *fname)
     38{
     39	struct tag *tag;
     40
     41	tag = malloc(sizeof(struct tag));
     42	if (!tag) ERROR(SYSTEM, "malloc");
     43
     44	tag->fpath = aprintf("%s/%s", path, fname);
     45	tag->name = astrdup(fname);
     46	tag->index_dirty = false;
     47	tag->reordered = false;
     48	tag->link = LIST_LINK_INIT;
     49	tag->link_sel = LIST_LINK_INIT;
     50	list_init(&tag->tracks);
     51
     52	return tag;
     53}
     54
     55void
     56tag_free(struct tag *tag)
     57{
     58	free(tag->fpath);
     59	free(tag->name);
     60	list_clear(&tag->tracks);
     61	free(tag);
     62}
     63
     64struct track *
     65track_alloc(const char *dir, const char *fname)
     66{
     67	struct track *track;
     68
     69	track = malloc(sizeof(struct track));
     70	if (!track) ERROR(SYSTEM, "malloc");
     71
     72	track->fpath = aprintf("%s/%s", dir, fname);
     73	track->name = astrdup(fname);
     74	track->tag = NULL;
     75	track->link = LIST_LINK_INIT;
     76	track->link_pl = LIST_LINK_INIT;
     77	track->link_tt = LIST_LINK_INIT;
     78	track->link_pq = LIST_LINK_INIT;
     79	track->link_hs = LIST_LINK_INIT;
     80
     81	return track;
     82}
     83
     84void
     85track_free(struct track *t)
     86{
     87	free(t->fpath);
     88	free(t->name);
     89	free(t);
     90}
     91
     92bool
     93tag_name_cmp(const void *p1, const void *p2, void *user)
     94{
     95	const struct tag *t1 = p1, *t2 = p2;
     96
     97	return strcmp(t1->name, t2->name) <= 0;
     98}
     99
    100bool
    101path_exists(const char *path)
    102{
    103	return access(path, F_OK) == 0;
    104}
    105
    106bool
    107make_dir(const char *path)
    108{
    109	return mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO) == 0;
    110}
    111
    112bool
    113move_dir(const char *src, const char *dst)
    114{
    115	return rename(src, dst) == 0;
    116}
    117
    118bool
    119rm_dir(const char *path, bool recursive)
    120{
    121	char *files[] = { (char *) path, NULL };
    122	FTSENT *ent;
    123	FTS *fts;
    124	int flags;
    125
    126	if (recursive) {
    127		flags = FTS_NOCHDIR | FTS_PHYSICAL | FTS_XDEV;
    128		fts = fts_open(files, flags, NULL);
    129		if (!fts) return false;
    130
    131		while ((ent = fts_read(fts))) {
    132			switch (ent->fts_info) {
    133			case FTS_NS:
    134			case FTS_DNR:
    135			case FTS_ERR:
    136				fts_close(fts);
    137				return false;
    138			case FTS_D:
    139				break;
    140			default:
    141				if (remove(ent->fts_accpath) < 0) {
    142					fts_close(fts);
    143					return false;
    144				}
    145				break;
    146			}
    147		}
    148
    149		fts_close(fts);
    150	} else {
    151		if (rmdir(path) != 0)
    152			return false;
    153	}
    154
    155	return true;
    156}
    157
    158bool
    159rm_file(const char *path)
    160{
    161	return unlink(path) == 0;
    162}
    163
    164bool
    165copy_file(const char *src, const char *dst)
    166{
    167	FILE *in, *out;
    168	char buf[4096];
    169	int nread;
    170	bool ok;
    171
    172	ok = false;
    173	in = out = NULL;
    174
    175	in = fopen(src, "r");
    176	if (in == NULL) goto cleanup;
    177
    178	out = fopen(dst, "w+");
    179	if (out == NULL) goto cleanup;
    180
    181	while ((nread = fread(buf, 1, sizeof(buf), in)) > 0) {
    182		fwrite(buf, 1, nread, out);
    183	}
    184
    185	if (nread < 0)
    186		goto cleanup;
    187
    188	ok = true;
    189
    190cleanup:
    191	if (in) fclose(in);
    192	if (out) fclose(out);
    193
    194	return ok;
    195}
    196
    197bool
    198dup_file(const char *src, const char *dst)
    199{
    200	if (link(src, dst) == 0)
    201		return true;
    202
    203	return copy_file(src, dst);
    204}
    205
    206bool
    207move_file(const char *src, const char *dst)
    208{
    209	return rename(src, dst) == 0;
    210}
    211
    212void
    213tag_clear_tracks(struct tag *tag)
    214{
    215	struct list_link *link;
    216	struct track *track;
    217
    218	while (!list_empty(&tag->tracks)) {
    219		link = list_pop_front(&tag->tracks);
    220		track = LIST_UPCAST(link, struct track, link_tt);
    221		track_rm(track, false);
    222	}
    223}
    224
    225void
    226tag_load_tracks(struct tag *tag)
    227{
    228	char linebuf[1024];
    229	char *index_path;
    230	FILE *file;
    231
    232	index_path = aprintf("%s/index", tag->fpath);
    233	file = fopen(index_path, "r");
    234	if (file == NULL) {
    235		tag_reindex_tracks(tag);
    236		return;
    237	}
    238
    239	while (fgets(linebuf, sizeof(linebuf), file)) {
    240		if (!*linebuf) continue;
    241		if (linebuf[strlen(linebuf) - 1] == '\n')
    242			linebuf[strlen(linebuf) - 1] = '\0';
    243		track_add(tag, linebuf);
    244	}
    245
    246	tag->index_dirty = false;
    247
    248	fclose(file);
    249	free(index_path);
    250}
    251
    252void
    253tag_save_tracks(struct tag *tag)
    254{
    255	struct track *track;
    256	struct list_link *link;
    257	char *index_path;
    258	FILE *file;
    259
    260	/* write playlist back to index file */
    261
    262	index_path = aprintf("%s/index", tag->fpath);
    263	file = fopen(index_path, "w+");
    264	if (!file) {
    265		WARNX(SYSTEM, "Failed to write to index file: %s",
    266			index_path);
    267		free(index_path);
    268		return;
    269	}
    270
    271	for (LIST_ITER(&tag->tracks, link)) {
    272		track = LIST_UPCAST(link, struct track, link_tt);
    273		fprintf(file, "%s\n", track->name);
    274	}
    275
    276	tag->index_dirty = false;
    277
    278	fclose(file);
    279	free(index_path);
    280}
    281
    282bool
    283tag_reindex_tracks(struct tag *tag)
    284{
    285	struct dirent *ent;
    286	DIR *dir;
    287
    288	dir = opendir(tag->fpath);
    289	if (!dir) return false;
    290
    291	tag_clear_tracks(tag);
    292
    293	while ((ent = readdir(dir))) {
    294		if (!strcmp(ent->d_name, "."))
    295			continue;
    296		if (!strcmp(ent->d_name, ".."))
    297			continue;
    298
    299		/* skip files without extension */
    300		if (!strchr(ent->d_name + 1, '.'))
    301			continue;
    302
    303		track_add(tag, ent->d_name);
    304	}
    305
    306	tag->index_dirty = true;
    307
    308	closedir(dir);
    309
    310	return true;
    311}
    312
    313struct track *
    314tracks_vis_track(struct list_link *link)
    315{
    316	if (tracks_vis == &player.playlist) {
    317		return LIST_UPCAST(link, struct track, link_pl);
    318	} else {
    319		return LIST_UPCAST(link, struct track, link_tt);
    320	}
    321}
    322
    323void
    324playlist_clear(void)
    325{
    326	while (!list_empty(&player.playlist))
    327		list_pop_front(&player.playlist);
    328}
    329
    330void
    331playlist_update(void)
    332{
    333	struct list_link *link, *link2;
    334	struct track *track;
    335	struct tag *tag;
    336
    337	if (!playlist_outdated)
    338		return;
    339
    340	playlist_clear();
    341
    342	for (LIST_ITER(&tags_sel, link)) {
    343		tag = LIST_UPCAST(link, struct tag, link_sel);
    344		for (LIST_ITER(&tag->tracks, link2)) {
    345			track = LIST_UPCAST(link2, struct track, link_tt);
    346			list_link_pop(&track->link_pl);
    347			list_insert_back(&player.playlist, &track->link_pl);
    348		}
    349	}
    350
    351	playlist_outdated = false;
    352}
    353
    354struct tag *
    355tag_create(const char *fname)
    356{
    357	struct tag *tag;
    358	char *path;
    359
    360	path = aprintf("%s/%s", datadir, fname);
    361	if (!make_dir(path)) {
    362		free(path);
    363		return NULL;
    364	}
    365	free(path);
    366
    367	tag = tag_add(fname);
    368	if (!tag) {
    369		rm_dir(path, false);
    370		return NULL;
    371	}
    372
    373	return tag;
    374}
    375
    376struct tag *
    377tag_add(const char *fname)
    378{
    379	struct tag *tag;
    380
    381	tag = tag_alloc(datadir, fname);
    382	if (!tag) return NULL;
    383	list_insert_back(&tags, &tag->link);
    384
    385	return tag;
    386}
    387
    388struct tag *
    389tag_find(const char *name)
    390{
    391	struct list_link *link;
    392	struct tag *tag;
    393
    394	for (LIST_ITER(&tags, link)) {
    395		tag = LIST_UPCAST(link, struct tag, link);
    396		if (!strcmp(tag->name, name))
    397			return tag;
    398	}
    399
    400	return NULL;
    401}
    402
    403bool
    404tag_rm(struct tag *tag, bool sync_fs)
    405{
    406	struct list_link *link;
    407	struct track *track;
    408
    409	/* remove contained tracks */
    410	while (!list_empty(&tag->tracks)) {
    411		link = list_pop_front(&tag->tracks);
    412		track = LIST_UPCAST(link, struct track, link_tt);
    413		if (!track_rm(track, sync_fs))
    414			return false;
    415	}
    416
    417	/* delete dir and remaining non-track files */
    418	if (sync_fs && !rm_dir(tag->fpath, true))
    419		return false;
    420
    421	/* remove from selected */
    422	list_link_pop(&tag->link_sel);
    423
    424	/* remove from tags list */
    425	list_link_pop(&tag->link);
    426
    427	tag_free(tag);
    428
    429	return true;
    430}
    431
    432bool
    433tag_rename(struct tag *tag, const char *name)
    434{
    435	struct list_link *link;
    436	struct track *track;
    437	char *newpath;
    438
    439	newpath = aprintf("%s/%s", datadir, name);
    440
    441	if (!move_dir(tag->fpath, newpath)) {
    442		free(newpath);
    443		return false;
    444	}
    445
    446	free(tag->fpath);
    447	tag->fpath = newpath;
    448
    449	free(tag->name);
    450	tag->name = astrdup(name);
    451
    452	for (LIST_ITER(&tag->tracks, link)) {
    453		track = LIST_UPCAST(link, struct track, link_tt);
    454		free(track->fpath);
    455		track->fpath = aprintf("%s/%s", newpath, track->name);
    456	}
    457
    458	return true;
    459}
    460
    461struct track *
    462track_add(struct tag *tag, const char *fname)
    463{
    464	struct track *track;
    465
    466	track = track_alloc(tag->fpath, fname);
    467	track->tag = tag;
    468
    469	/* insert track into sorted tracks list */
    470	list_insert_back(&tracks, &track->link);
    471
    472	/* add to tag's tracks list */
    473	list_insert_back(&tag->tracks, &track->link_tt);
    474
    475	/* if track's tag is selected, update playlist */
    476	if (list_link_inuse(&tag->link_sel))
    477		playlist_outdated = true;
    478
    479	tag->index_dirty = true;
    480
    481	return track;
    482}
    483
    484bool
    485track_rm(struct track *track, bool sync_fs)
    486{
    487	if (sync_fs && !rm_file(track->fpath))
    488		return false;
    489
    490	track->tag->index_dirty = true;
    491
    492	/* remove from tracks list */
    493	list_link_pop(&track->link);
    494
    495	/* remove from tag's track list */
    496	list_link_pop(&track->link_tt);
    497
    498	/* remove from playlist */
    499	list_link_pop(&track->link_pl);
    500
    501	/* remove from player queue */
    502	list_link_pop(&track->link_pq);
    503
    504	/* remove from player history */
    505	list_link_pop(&track->link_hs);
    506
    507	/* remove the reference as last used track */
    508	if (player.track == track)
    509		player.track = NULL;
    510
    511	track_free(track);
    512
    513	return true;
    514}
    515
    516bool
    517track_rename(struct track *track, const char *name)
    518{
    519	char *newpath;
    520
    521	newpath = aprintf("%s/%s", track->tag->fpath, name);
    522
    523	if (path_exists(newpath)) {
    524		free(newpath);
    525		return false;
    526	}
    527
    528	if (!move_file(track->fpath, newpath)) {
    529		free(newpath);
    530		return false;
    531	}
    532
    533	free(track->fpath);
    534	track->fpath = newpath;
    535
    536	free(track->name);
    537	track->name = astrdup(name);
    538
    539	track->tag->index_dirty = true;
    540
    541	return true;
    542}
    543
    544bool
    545track_move(struct track *track, struct tag *tag)
    546{
    547	struct track *new;
    548	char *newpath;
    549
    550	errno = 0;
    551
    552	newpath = aprintf("%s/%s", tag->fpath, track->name);
    553	if (path_exists(newpath)) {
    554		free(newpath);
    555		errno = EEXIST;
    556		return false;
    557	}
    558
    559	if (!dup_file(track->fpath, newpath)) {
    560		free(newpath);
    561		errno = EACCES;
    562		return false;
    563	}
    564
    565	free(newpath);
    566
    567	new = track_add(tag, track->name);
    568	if (!new) return false;
    569
    570	if (player.track == track)
    571		player.track = new;
    572
    573	if (!track_rm(track, true)) {
    574		track_rm(new, true);
    575		errno = EACCES;
    576		return false;
    577	}
    578
    579	return true;
    580}
    581
    582bool
    583acquire_lock(const char *datadir)
    584{
    585	char *lockpath, *procpath;
    586	char linebuf[512];
    587	FILE *file;
    588	int pid;
    589
    590	lockpath = aprintf("%s/.lock", datadir);
    591	if (path_exists(lockpath)) {
    592		file = fopen(lockpath, "r");
    593		if (file == NULL) {
    594			free(lockpath);
    595			return false;
    596		}
    597
    598		fgets(linebuf, sizeof(linebuf), file);
    599		pid = strtol(linebuf, NULL, 10);
    600		procpath = aprintf("/proc/%i", pid);
    601		if (path_exists(procpath)) {
    602			free(procpath);
    603			free(lockpath);
    604			return false;
    605		}
    606		free(procpath);
    607
    608		fclose(file);
    609	}
    610
    611	file = fopen(lockpath, "w+");
    612	if (file == NULL) {
    613		free(lockpath);
    614		return false;
    615	}
    616	snprintf(linebuf, sizeof(linebuf), "%i", getpid());
    617	fputs(linebuf, file);
    618	fclose(file);
    619
    620	free(lockpath);
    621
    622	return true;
    623}
    624
    625bool
    626release_lock(const char *datadir)
    627{
    628	char *lockpath;
    629	bool status;
    630
    631	lockpath = aprintf("%s/.lock", datadir);
    632
    633	status = rm_file(lockpath);
    634
    635	free(lockpath);
    636
    637	return status;
    638}
    639
    640void
    641data_load(void)
    642{
    643	struct dirent *ent;
    644	struct tag *tag;
    645	struct stat st;
    646	char *path;
    647	DIR *dir;
    648
    649	list_init(&tracks);
    650	list_init(&tags);
    651	list_init(&tags_sel);
    652
    653	datadir = getenv("TMUS_DATA");
    654	if (!datadir) ERRORX(USER, "TMUS_DATA not set");
    655
    656	if (!acquire_lock(datadir))
    657		ERRORX(USER, "Failed to lock datadir");
    658
    659	dir = opendir(datadir);
    660	if (!dir) ERROR(SYSTEM, "opendir %s", datadir);
    661
    662	trash_tag = NULL;
    663	while ((ent = readdir(dir))) {
    664		if (!strcmp(ent->d_name, "."))
    665			continue;
    666		if (!strcmp(ent->d_name, ".."))
    667			continue;
    668
    669		path = aprintf("%s/%s", datadir, ent->d_name);
    670		if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
    671			tag = tag_add(ent->d_name);
    672			if (!strcmp(tag->name, "trash"))
    673				trash_tag = tag;
    674			tag_load_tracks(tag);
    675		}
    676		free(path);
    677	}
    678
    679	list_insertion_sort(&tags, false, tag_name_cmp,
    680		LIST_OFFSET(struct tag, link), NULL);
    681
    682	playlist_outdated = true;
    683
    684	closedir(dir);
    685}
    686
    687void
    688data_save(void)
    689{
    690	struct list_link *link;
    691	struct tag *tag;
    692
    693	for (LIST_ITER(&tags, link)) {
    694		tag = LIST_UPCAST(link, struct tag, link);
    695		if (tag->index_dirty)
    696			tag_save_tracks(tag);
    697	}
    698
    699	release_lock(datadir);
    700}
    701
    702void
    703data_free(void)
    704{
    705	struct list_link *link;
    706	struct tag *tag;
    707
    708	list_clear(&player.playlist);
    709	list_clear(&player.queue);
    710	list_clear(&player.history);
    711
    712	while (!list_empty(&tags)) {
    713		link = list_pop_front(&tags);
    714		tag = LIST_UPCAST(link, struct tag, link);
    715		tag_rm(tag, false);
    716	}
    717}
    718