1 /* OpenCP Module Player
2  * copyright (c) '94-'10 Niklas Beisert <nbeisert@physik.tu-muenchen.de>
3  * copyright (c) '04-'21 Stian Skjelstad <stian.skjelstad@gmail.com>
4  *
5  * the file-object code used by the File selector ][
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  *
21  * revision history: (please note changes here)
22  *  -ss040613   Stian Skjelstad <stian@nixia.no>
23  *    -first release
24  *  -ss040831   Stian Skjelstad <stian@nixia.no>
25  *    -updated fs_8dot3_name to not crash anymore
26  *    -removed modlist->pathtothis
27  */
28 
29 #include "config.h"
30 #include <assert.h>
31 #include <ctype.h>
32 #include <dirent.h>
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <unistd.h>
41 #include "types.h"
42 #include "dirdb.h"
43 #include "filesystem.h"
44 #include "filesystem-drive.h"
45 #include "mdb.h"
46 #include "modlist.h"
47 #include "stuff/compat.h"
48 #include "stuff/poutput.h"
49 #include "stuff/utf-8.h"
50 
51 static void utf8_XdotY_name (const int X, const int Y, char *shortname, const char *source);
52 
53 void modlist_free (struct modlist *modlist)
54 {
55 	unsigned int i;
56 	for (i=0;i<modlist->num;i++)
57 	{
58 		if (modlist->files[i].dir)
59 		{
60 			modlist->files[i].dir->unref (modlist->files[i].dir);
61 			modlist->files[i].dir = 0;
62 		}
63 		if (modlist->files[i].file)
64 		{
65 			modlist->files[i].file->unref (modlist->files[i].file);
66 			modlist->files[i].file = 0;
67 		}
68 	}
69 	free (modlist->files);
70 	free (modlist->sortindex);
71 	free(modlist);
72 }
73 
74 struct modlistentry *modlist_getcur (const struct modlist *modlist) /* does not Ref() */
75 {
76 	return modlist_get(modlist, modlist->pos);
77 }
78 
79 struct modlistentry *modlist_get (const struct modlist *modlist, unsigned int index) /* does not Ref() */
80 {
81 	if (! modlist->num)
82 	{
83 		return NULL; /* should never happen */
84 	}
85 	if (index >= modlist->num)
86 	{
87 		index = modlist->num - 1;
88 	}
89 	return &modlist->files[modlist->sortindex[index]];
90 }
91 
92 void modlist_append (struct modlist *modlist, struct modlistentry *entry)
93 {
94 	if (!entry)
95 		return;
96 	if (modlist->num==modlist->max)
97 	{
98 		int *newindex;
99 		struct modlistentry *newfiles;
100 
101 		newfiles = realloc(modlist->files, (modlist->max + 50) *sizeof(modlist->files[0]));
102 		if (!newfiles)
103 		{ /* out of memory */
104 			fprintf (stderr, "modlist_append: out of memory\n");
105 			return;
106 		}
107 		modlist->files=newfiles;
108 
109 		newindex = realloc (modlist->sortindex, (modlist->max + 50) *sizeof(modlist->sortindex[0]));
110 		if (!newindex)
111 		{ /* out of memory */
112 			fprintf (stderr, "modlist_append: out of memory\n");
113 			return;
114 		}
115 		modlist->sortindex = newindex;
116 
117 		modlist->max += 50;
118 	}
119 	modlist->files[modlist->num] = *entry;
120 	modlist->sortindex[modlist->num] = modlist->num;
121 
122 	if (entry->file)
123 	{
124 		entry->file->ref (entry->file);
125 	}
126 	if (entry->dir)
127 	{
128 		entry->dir->ref (entry->dir);
129 	}
130 	modlist->num++;
131 }
132 
133 void modlist_append_dir (struct modlist *modlist, struct ocpdir_t *dir)
134 {
135 	struct modlistentry entry = {0};
136 	char *childpath = 0;
137 
138 	if (!dir)
139 	{
140 		return;
141 	}
142 
143 	entry.dir = dir; /* modlist_append will do a ref */
144 	dirdbGetName_internalstr (dir->dirdb_ref, &childpath);
145 	utf8_XdotY_name (8, 3, entry.utf8_8_dot_3, childpath);
146 	utf8_XdotY_name (16, 3, entry.utf8_16_dot_3, childpath);
147 
148 	entry.mdb_ref = UINT32_MAX;
149 
150 	modlist_append (modlist, &entry);
151 }
152 
153 void modlist_append_dotdot (struct modlist *modlist, struct ocpdir_t *dir)
154 {
155 	struct modlistentry entry = {0};
156 
157 	if (!dir)
158 	{
159 		return;
160 	}
161 
162 	entry.dir = dir; /* modlist_append will do a ref */
163 	entry.flags = MODLIST_FLAG_DOTDOT;
164 	strcpy (entry.utf8_8_dot_3,  "..");
165 	strcpy (entry.utf8_16_dot_3, "..");
166 
167 	entry.mdb_ref = UINT32_MAX;
168 
169 	modlist_append (modlist, &entry);
170 }
171 
172 void modlist_append_drive (struct modlist *modlist, struct dmDrive *drive)
173 {
174 	struct modlistentry entry = {0};
175 	char *childpath = 0;
176 
177 	if (!drive)
178 	{
179 		return;
180 	}
181 
182 	entry.dir = drive->cwd; /* modlist_append will do a ref */
183 	entry.flags |= MODLIST_FLAG_DRV;
184 	dirdbGetName_internalstr (drive->basedir->dirdb_ref, &childpath);
185 	utf8_XdotY_name (8, 3, entry.utf8_8_dot_3, childpath);
186 	utf8_XdotY_name (16, 3, entry.utf8_16_dot_3, childpath);
187 
188 	entry.mdb_ref = UINT32_MAX;
189 
190 	modlist_append (modlist, &entry);
191 }
192 
193 void modlist_append_file (struct modlist *modlist, struct ocpfile_t *file)
194 {
195 	struct modlistentry entry = {0};
196 	char *childpath = 0;
197 
198 	if (!file)
199 	{
200 		return;
201 	}
202 
203 	entry.file = file; /* modlist_append will do a ref */
204 	dirdbGetName_internalstr (file->dirdb_ref, &childpath);
205 	utf8_XdotY_name (8, 3, entry.utf8_8_dot_3, childpath);
206 	utf8_XdotY_name (16, 3, entry.utf8_16_dot_3, childpath);
207 
208 	entry.mdb_ref = mdbGetModuleReference2 (file->dirdb_ref, file->filesize (file));
209 
210 	modlist_append (modlist, &entry);
211 }
212 
213 
214 void modlist_remove_all_by_path(struct modlist *modlist, uint32_t ref)
215 {
216 	unsigned int i;
217 	for (i=0;i<modlist->num;)
218 	{
219 		if ( modlist->files[modlist->sortindex[i]].file &&
220 		    (modlist->files[modlist->sortindex[i]].file->dirdb_ref == ref) )
221 		{
222 			modlist_remove(modlist, i);
223 		} else if ( modlist->files[modlist->sortindex[i]].dir &&
224 		           (modlist->files[modlist->sortindex[i]].dir->dirdb_ref == ref) )
225 		{
226 			modlist_remove(modlist, i);
227 		} else {
228 			i++;
229 		}
230 	}
231 }
232 
233 void modlist_clear(struct modlist *modlist)
234 {
235 	int i;
236 	for (i=0;i<modlist->num;i++)
237 	{
238 		if (modlist->files[i].dir)
239 		{
240 			modlist->files[i].dir->unref (modlist->files[i].dir);
241 			modlist->files[i].dir = 0;
242 		}
243 		if (modlist->files[i].file)
244 		{
245 			modlist->files[i].file->unref (modlist->files[i].file);
246 			modlist->files[i].file = 0;
247 		}
248 	}
249 	modlist->num = 0;
250 }
251 
252 void modlist_remove(struct modlist *modlist, unsigned int index) /* by sortindex */
253 {
254 	unsigned int i;
255 	assert (index < modlist->num);
256 	unsigned int realindex = modlist->sortindex[index];
257 
258 	if (modlist->files[realindex].file)
259 	{
260 		modlist->files[realindex].file->unref (modlist->files[realindex].file);
261 	}
262 	if (modlist->files[realindex].dir)
263 	{
264 		modlist->files[realindex].dir->unref (modlist->files[realindex].dir);
265 	}
266 	memmove(&modlist->files[realindex], &modlist->files[realindex+1], (modlist->num - realindex - 1) * sizeof(modlist->files[0]));
267 	memmove(&modlist->sortindex[index], &modlist->sortindex[index+1], (modlist->num - index - 1) * sizeof (modlist->sortindex[0]));
268 	modlist->num -= 1;
269 
270 	/* repair the sort-index */
271 	for (i = 0; i < modlist->num; i++)
272 	{
273 		if (modlist->sortindex[i] >= realindex)
274 		{
275 			modlist->sortindex[i]--;
276 		}
277 	}
278 
279 #if 0
280 	if ((modlist->max - modlist->num>75))
281 	{
282 		modlist->max- = 50;
283 		modlist->files = realloc (modlist->files, modlist->max * sizeof(modlist->files[0]));
284 		modlist->sortindex = realloc (modlist->sortindex, modlist->max * sizeof (modlist->sortindex[0]));
285 	}
286 #endif
287 	if (!modlist->num)
288 		modlist->pos = 0;
289 	else if (modlist->pos >= modlist->num)
290 		modlist->pos = modlist->num - 1;
291 }
292 
293 int modlist_find(struct modlist *modlist, const uint32_t path)
294 {
295 	unsigned int retval;
296 	for (retval=0; retval < modlist->num; retval++)
297 	{
298 		int realindex = modlist->sortindex[retval];
299 		if ( modlist->files[realindex].file &&
300 		    (modlist->files[realindex].file->dirdb_ref == path) )
301 		{
302 			return retval;
303 		}
304 		if ( modlist->files[realindex].dir &&
305 		    (modlist->files[realindex].dir->dirdb_ref == path) )
306 		{
307 			return retval;
308 		}
309 	}
310 	return -1;
311 }
312 
313 void modlist_swap(struct modlist *modlist, unsigned int index1, unsigned int index2)
314 {
315 	int entry;
316 	entry = modlist->sortindex[index1];
317 	modlist->sortindex[index1] = modlist->sortindex[index2];
318 	modlist->sortindex[index2] = entry;
319 }
320 
321 static const char *fuzzycmp(const char *dst, const char *src)
322 {
323 	char DST, SRC;
324 	while ((*dst)&&(*src))
325 	{
326 		DST=toupper(*dst);
327 		SRC=toupper(*src);
328 		if (DST==SRC)
329 		{
330 			dst++;
331 			src++;
332 		} else
333 			break;
334 	}
335 	return dst;
336 }
337 
338 #warning input is CP437, search is done on UTF-8
339 int modlist_fuzzyfind(struct modlist *modlist, const char *filename)
340 {
341 	unsigned int retval=0;
342 	int hitscore=0;
343 	unsigned int i;
344 	unsigned int len = strlen(filename);
345 	if (!len)
346 		return 0;
347 	for (i=0;i<modlist->num;i++)
348 	{
349 		char *temp = 0;
350 		const char *diff;
351 		int score;
352 		int index = modlist->sortindex[i];
353 		struct modlistentry *m = &modlist->files[index];
354 
355 		dirdbGetName_internalstr (m->file ? m->file->dirdb_ref : m->dir->dirdb_ref, &temp);
356 		diff = fuzzycmp(temp, filename);
357 		score = diff - temp;
358 
359 		if ((unsigned)score==len)
360 		{
361 			return i;
362 		} else if (score>hitscore)
363 		{
364 			retval=i;
365 			hitscore=score;
366 		}
367 
368 		diff = fuzzycmp(m->utf8_16_dot_3, filename);
369 		score = diff - m->utf8_16_dot_3;
370 		if ((unsigned)score==len)
371 		{
372 			return i;
373 		} else if (score>hitscore)
374 		{
375 			retval=i;
376 			hitscore=score;
377 		}
378 	}
379 	return retval;
380 }
381 
382 static struct modlist *sorting;
383 static int mlecmp_score (const struct modlistentry *e1)
384 {
385 	int i1;
386 
387 	if (e1->dir)
388 	{
389 		if (e1->flags & MODLIST_FLAG_DOTDOT)
390 		{
391 			i1 = 16;
392 		} else if (e1->flags & MODLIST_FLAG_DRV)
393 		{
394 			i1 = 0;
395 		} else {
396 			i1 = 8;
397 
398 			if (e1->dir->is_archive)
399 			{
400 				i1 = 4;
401 			}
402 			if (e1->dir->is_playlist)
403 			{
404 				i1 = 2;
405 			}
406 		}
407 	} else {
408 		i1 = 1;
409 	}
410 
411 	return i1;
412 }
413 static int mlecmp (const void *a, const void *b)
414 {
415 	int _1 = *(int *)a;
416 	int _2 = *(int *)b;
417 	const struct modlistentry *e1 = &sorting->files[_1];
418 	const struct modlistentry *e2 = &sorting->files[_2];
419 
420 	int i1 = mlecmp_score (e1);
421 	int i2 = mlecmp_score (e2);
422 
423 	char *n1, *n2;
424 
425 	if (i1 != i2)
426 	{
427 		return i2 - i1;
428 	}
429 
430 	dirdbGetName_internalstr (e1->file ? e1->file->dirdb_ref : e1->dir->dirdb_ref, &n1);
431 	dirdbGetName_internalstr (e2->file ? e2->file->dirdb_ref : e2->dir->dirdb_ref, &n2);
432 
433 	return strcasecmp(n1, n2);
434 }
435 
436 void modlist_sort (struct modlist *modlist)
437 {
438 	sorting = modlist; /* dirty HACK that is not thread-safe / reentrant what so ever */
439 	qsort(modlist->sortindex, modlist->num, sizeof(modlist->sortindex[0]), mlecmp);
440 	sorting = 0;
441 }
442 
443 struct modlist *modlist_create (void)
444 {
445 	/* TODO ARCS */
446 	/*
447 	DIR *dir;
448 	*/
449 	struct modlist *retval=calloc(sizeof(struct modlist), 1);
450 
451 	return retval;
452 }
453 
454 void modlist_append_modlist (struct modlist *target, struct modlist *source)
455 {
456 	unsigned int i;
457 	for (i=0;i<source->num;i++)
458 		modlist_append(target, modlist_get(source, i));
459 }
460 
461 static size_t strlen_width (const char *source)
462 {
463 	return measurestr_utf8 (source, strlen (source));
464 }
465 
466 static void strlcat_width (char *dst, char *src, int length)
467 {
468 	while (*dst)
469 	{
470 		dst++;
471 	}
472 	while (length && *src)
473 	{
474 		int inc = 0;
475 		int visuallen;
476 		utf8_decode (src, strlen (src), &inc);
477 		visuallen = measurestr_utf8 (src, inc);
478 		if (visuallen > length)
479 		{
480 			break;
481 		}
482 		length -= visuallen;
483 		memcpy (dst, src, inc);
484 		dst += inc;
485 		src += inc;
486 	}
487 	*dst = 0;
488 }
489 
490 static void strlcpy_width (char *dst, char *src, int length)
491 {
492 	while (length && *src)
493 	{
494 		int inc = 0;
495 		int visuallen;
496 		utf8_decode (src, strlen (src), &inc);
497 		visuallen = measurestr_utf8 (src, inc);
498 		if (visuallen > length)
499 		{
500 			break;
501 		}
502 		length -= visuallen;
503 		memcpy (dst, src, inc);
504 		dst += inc;
505 		src += inc;
506 	}
507 	*dst = 0;
508 }
509 
510 static void utf8_XdotY_name (const int X, const int Y, char *shortname, const char *source)
511 {
512 	char *temppath;
513 	char *lastdot;
514 	int length=strlen(source);
515 
516 	temppath = strdup (source);
517 
518 	if ((lastdot = rindex(temppath + 1, '.'))) /* we allow files to start with . */
519 	{
520 		*lastdot = 0; /* modify the source - most easy way around the problem */
521 
522 		strlcpy_width (shortname, temppath, X);
523 		length = strlen_width (shortname);
524 		if (length < X)
525 		{
526 			char *target = shortname + strlen (shortname);
527 			memset (target, ' ', X - length);
528 			target [X - length] = 0;
529 		}
530 
531 		strcat (shortname, ".");
532 
533 		strlcat_width (shortname, lastdot + 1, Y);
534 		length = strlen_width (lastdot + 1);
535 		if (length < Y)
536 		{
537 			char *target = shortname + strlen (shortname);
538 			memset (target, ' ', Y - length);
539 			target [Y - length] = 0;
540 		}
541 	} else {
542 		strlcpy_width(shortname, temppath, X + Y + 1);
543 		length = strlen_width (temppath);
544 		if (length < (X + Y + 1))
545 		{
546 			char *target = shortname + strlen (shortname);
547 			memset (target, ' ', (X + Y + 1) - length);
548 			target [(X + Y + 1) - length] = 0;
549 		}
550 	}
551 	free (temppath);
552 }
553