summaryrefslogtreecommitdiff
path: root/src/songvec.c
blob: ac6e97953dc85624360e36768b2f456e3f66debc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include "songvec.h"
#include "song.h"
#include "tag.h"

#include <glib.h>

#include <assert.h>
#include <string.h>
#include <stdlib.h>

static GMutex *nr_lock = NULL;

/**
 * Compare two tag values which should contain an integer value
 * (e.g. disc or track number).  Either one may be NULL.
 */
static int
compare_number_string(const char *a, const char *b)
{
	long ai = a == NULL ? 0 : strtol(a, NULL, 10);
	long bi = b == NULL ? 0 : strtol(b, NULL, 10);

	if (ai <= 0)
		return bi <= 0 ? 0 : -1;

	if (bi <= 0)
		return 1;

	return ai - bi;
}

static int
compare_tag_item(const struct tag *a, const struct tag *b, enum tag_type type)
{
	if (a == NULL)
		return b == NULL ? 0 : -1;

	if (b == NULL)
		return 1;

	return compare_number_string(tag_get_value(a, type),
				     tag_get_value(b, type));
}

/* Only used for sorting/searchin a songvec, not general purpose compares */
static int songvec_cmp(const void *s1, const void *s2)
{
	const struct song *a = ((const struct song * const *)s1)[0];
	const struct song *b = ((const struct song * const *)s2)[0];
	int ret;

	/* first sort by disc */
	ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_DISC);
	if (ret != 0)
		return ret;

	/* then by track number */
	ret = compare_tag_item(a->tag, b->tag, TAG_ITEM_TRACK);
	if (ret != 0)
		return ret;

	/* still no difference?  compare file name */
	return g_utf8_collate(a->url, b->url);
}

static size_t sv_size(const struct songvec *sv)
{
	return sv->nr * sizeof(struct song *);
}

void songvec_init(void)
{
	g_assert(nr_lock == NULL);
	nr_lock = g_mutex_new();
}

void songvec_deinit(void)
{
	g_assert(nr_lock != NULL);
	g_mutex_free(nr_lock);
	nr_lock = NULL;
}

void songvec_sort(struct songvec *sv)
{
	g_mutex_lock(nr_lock);
	qsort(sv->base, sv->nr, sizeof(struct song *), songvec_cmp);
	g_mutex_unlock(nr_lock);
}

struct song *
songvec_find(const struct songvec *sv, const char *url)
{
	int i;
	struct song *ret = NULL;

	g_mutex_lock(nr_lock);
	for (i = sv->nr; --i >= 0; ) {
		if (strcmp(sv->base[i]->url, url))
			continue;
		ret = sv->base[i];
		break;
	}
	g_mutex_unlock(nr_lock);
	return ret;
}

int
songvec_delete(struct songvec *sv, const struct song *del)
{
	size_t i;

	g_mutex_lock(nr_lock);
	for (i = 0; i < sv->nr; ++i) {
		if (sv->base[i] != del)
			continue;
		/* we _don't_ call song_free() here */
		if (!--sv->nr) {
			g_free(sv->base);
			sv->base = NULL;
		} else {
			memmove(&sv->base[i], &sv->base[i + 1],
				(sv->nr - i) * sizeof(struct song *));
			sv->base = g_realloc(sv->base, sv_size(sv));
		}
		g_mutex_unlock(nr_lock);
		return i;
	}
	g_mutex_unlock(nr_lock);

	return -1; /* not found */
}

void
songvec_add(struct songvec *sv, struct song *add)
{
	g_mutex_lock(nr_lock);
	++sv->nr;
	sv->base = g_realloc(sv->base, sv_size(sv));
	sv->base[sv->nr - 1] = add;
	g_mutex_unlock(nr_lock);
}

void songvec_destroy(struct songvec *sv)
{
	g_mutex_lock(nr_lock);
	sv->nr = 0;
	g_mutex_unlock(nr_lock);

	g_free(sv->base);
	sv->base = NULL;
}

int
songvec_for_each(const struct songvec *sv,
		 int (*fn)(struct song *, void *), void *arg)
{
	size_t i;
	size_t prev_nr;

	g_mutex_lock(nr_lock);
	for (i = 0; i < sv->nr; ) {
		struct song *song = sv->base[i];

		assert(song);
		assert(*song->url);

		prev_nr = sv->nr;
		g_mutex_unlock(nr_lock); /* fn() may block */
		if (fn(song, arg) < 0)
			return -1;
		g_mutex_lock(nr_lock); /* sv->nr may change in fn() */
		if (prev_nr == sv->nr)
			++i;
	}
	g_mutex_unlock(nr_lock);

	return 0;
}