MiniDLNA: Add support for DSD (dsf, dff) music files
[tomato.git] / release / src / router / minidlna / utils.c
blobdf13aa333633bca7a22a8d24db0f74effac9f4b5
1 /* MiniDLNA media server
2 * Copyright (C) 2008-2009 Justin Maggard
4 * This file is part of MiniDLNA.
6 * MiniDLNA is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
10 * MiniDLNA is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>.
18 #include "config.h"
20 #include <stdio.h>
21 #include <ctype.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <sys/param.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <limits.h>
30 #include <fcntl.h>
31 #include <errno.h>
33 #include "minidlnatypes.h"
34 #include "upnpglobalvars.h"
35 #include "utils.h"
36 #include "log.h"
38 int
39 xasprintf(char **strp, char *fmt, ...)
41 va_list args;
42 int ret;
44 va_start(args, fmt);
45 ret = vasprintf(strp, fmt, args);
46 va_end(args);
47 if( ret < 0 )
49 DPRINTF(E_WARN, L_GENERAL, "xasprintf: allocation failed\n");
50 *strp = NULL;
52 return ret;
55 int
56 ends_with(const char * haystack, const char * needle)
58 const char * end;
59 int nlen = strlen(needle);
60 int hlen = strlen(haystack);
62 if( nlen > hlen )
63 return 0;
64 end = haystack + hlen - nlen;
66 return (strcasecmp(end, needle) ? 0 : 1);
69 char *
70 trim(char *str)
72 int i;
73 int len;
75 if (!str)
76 return(NULL);
78 len = strlen(str);
79 for (i=len-1; i >= 0 && isspace(str[i]); i--)
81 str[i] = '\0';
82 len--;
84 while (isspace(*str))
86 str++;
87 len--;
90 if (str[0] == '"' && str[len-1] == '"')
92 str[0] = '\0';
93 str[len-1] = '\0';
94 str++;
97 return str;
100 /* Find the first occurrence of p in s, where s is terminated by t */
101 char *
102 strstrc(const char *s, const char *p, const char t)
104 char *endptr;
105 size_t slen, plen;
107 endptr = strchr(s, t);
108 if (!endptr)
109 return strstr(s, p);
111 plen = strlen(p);
112 slen = endptr - s;
113 while (slen >= plen)
115 if (*s == *p && strncmp(s+1, p+1, plen-1) == 0)
116 return (char*)s;
117 s++;
118 slen--;
121 return NULL;
124 char *
125 strcasestrc(const char *s, const char *p, const char t)
127 char *endptr;
128 size_t slen, plen;
130 endptr = strchr(s, t);
131 if (!endptr)
132 return strcasestr(s, p);
134 plen = strlen(p);
135 slen = endptr - s;
136 while (slen >= plen)
138 if (*s == *p && strncasecmp(s+1, p+1, plen-1) == 0)
139 return (char*)s;
140 s++;
141 slen--;
144 return NULL;
147 char *
148 modifyString(char *string, const char *before, const char *after, int noalloc)
150 int oldlen, newlen, chgcnt = 0;
151 char *s, *p;
153 /* If there is no match, just return */
154 s = strstr(string, before);
155 if (!s)
156 return string;
158 oldlen = strlen(before);
159 newlen = strlen(after);
160 if (newlen > oldlen)
162 if (noalloc)
163 return string;
165 while ((p = strstr(s, before)))
167 chgcnt++;
168 s = p + oldlen;
170 s = realloc(string, strlen(string)+((newlen-oldlen)*chgcnt)+1);
171 /* If we failed to realloc, return the original alloc'd string */
172 if( s )
173 string = s;
174 else
175 return string;
178 s = string;
179 while (s)
181 p = strstr(s, before);
182 if (!p)
183 return string;
184 memmove(p + newlen, p + oldlen, strlen(p + oldlen) + 1);
185 memcpy(p, after, newlen);
186 s = p + newlen;
189 return string;
192 char *
193 unescape_tag(const char *tag, int force_alloc)
195 char *esc_tag = NULL;
197 if( strstr(tag, "&amp;") || strstr(tag, "&lt;") || strstr(tag, "&gt;")
198 || strstr(tag, "&quot;") )
200 esc_tag = strdup(tag);
201 esc_tag = modifyString(esc_tag, "&amp;", "&", 1);
202 esc_tag = modifyString(esc_tag, "&lt;", "<", 1);
203 esc_tag = modifyString(esc_tag, "&gt;", ">", 1);
204 esc_tag = modifyString(esc_tag, "&quot;", "\"", 1);
206 else if( force_alloc )
207 esc_tag = strdup(tag);
209 return esc_tag;
212 char *
213 escape_tag(const char *tag, int force_alloc)
215 char *esc_tag = NULL;
217 if( strchr(tag, '&') || strchr(tag, '<') || strchr(tag, '>') || strchr(tag, '"') )
219 esc_tag = strdup(tag);
220 esc_tag = modifyString(esc_tag, "&", "&amp;amp;", 0);
221 esc_tag = modifyString(esc_tag, "<", "&amp;lt;", 0);
222 esc_tag = modifyString(esc_tag, ">", "&amp;gt;", 0);
223 esc_tag = modifyString(esc_tag, "\"", "&amp;quot;", 0);
225 else if( force_alloc )
226 esc_tag = strdup(tag);
228 return esc_tag;
231 char *
232 strip_ext(char *name)
234 char *period;
236 period = strrchr(name, '.');
237 if (period)
238 *period = '\0';
240 return period;
243 /* Code basically stolen from busybox */
245 make_dir(char * path, mode_t mode)
247 char * s = path;
248 char c;
249 struct stat st;
251 do {
252 c = '\0';
254 /* Before we do anything, skip leading /'s, so we don't bother
255 * trying to create /. */
256 while (*s == '/')
257 ++s;
259 /* Bypass leading non-'/'s and then subsequent '/'s. */
260 while (*s) {
261 if (*s == '/') {
262 do {
263 ++s;
264 } while (*s == '/');
265 c = *s; /* Save the current char */
266 *s = '\0'; /* and replace it with nul. */
267 break;
269 ++s;
272 if (mkdir(path, mode) < 0) {
273 /* If we failed for any other reason than the directory
274 * already exists, output a diagnostic and return -1.*/
275 if ((errno != EEXIST && errno != EISDIR)
276 || (stat(path, &st) < 0 || !S_ISDIR(st.st_mode))) {
277 DPRINTF(E_WARN, L_GENERAL, "make_dir: cannot create directory '%s'\n", path);
278 if (c)
279 *s = c;
280 return -1;
283 if (!c)
284 return 0;
286 /* Remove any inserted nul from the path. */
287 *s = c;
289 } while (1);
292 /* Simple, efficient hash function from Daniel J. Bernstein */
293 unsigned int
294 DJBHash(uint8_t *data, int len)
296 unsigned int hash = 5381;
297 unsigned int i = 0;
299 for(i = 0; i < len; data++, i++)
301 hash = ((hash << 5) + hash) + (*data);
304 return hash;
307 const char *
308 mime_to_ext(const char * mime)
310 switch( *mime )
312 /* Audio extensions */
313 case 'a':
314 if( strcmp(mime+6, "mpeg") == 0 )
315 return "mp3";
316 else if( strcmp(mime+6, "mp4") == 0 )
317 return "m4a";
318 else if( strcmp(mime+6, "x-ms-wma") == 0 )
319 return "wma";
320 else if( strcmp(mime+6, "x-flac") == 0 )
321 return "flac";
322 else if( strcmp(mime+6, "flac") == 0 )
323 return "flac";
324 else if( strcmp(mime+6, "x-wav") == 0 )
325 return "wav";
326 else if( strncmp(mime+6, "L16", 3) == 0 )
327 return "pcm";
328 else if( strcmp(mime+6, "3gpp") == 0 )
329 return "3gp";
330 else if( strcmp(mime, "application/ogg") == 0 )
331 return "ogg";
332 else if( strcmp(mime+6, "x-dsd") == 0 )
333 return "dsd";
334 break;
335 case 'v':
336 if( strcmp(mime+6, "avi") == 0 )
337 return "avi";
338 else if( strcmp(mime+6, "divx") == 0 )
339 return "avi";
340 else if( strcmp(mime+6, "x-msvideo") == 0 )
341 return "avi";
342 else if( strcmp(mime+6, "mpeg") == 0 )
343 return "mpg";
344 else if( strcmp(mime+6, "mp4") == 0 )
345 return "mp4";
346 else if( strcmp(mime+6, "x-ms-wmv") == 0 )
347 return "wmv";
348 else if( strcmp(mime+6, "x-matroska") == 0 )
349 return "mkv";
350 else if( strcmp(mime+6, "x-mkv") == 0 )
351 return "mkv";
352 else if( strcmp(mime+6, "x-flv") == 0 )
353 return "flv";
354 else if( strcmp(mime+6, "vnd.dlna.mpeg-tts") == 0 )
355 return "mpg";
356 else if( strcmp(mime+6, "quicktime") == 0 )
357 return "mov";
358 else if( strcmp(mime+6, "3gpp") == 0 )
359 return "3gp";
360 else if( strncmp(mime+6, "x-tivo-mpeg", 11) == 0 )
361 return "TiVo";
362 break;
363 case 'i':
364 if( strcmp(mime+6, "jpeg") == 0 )
365 return "jpg";
366 else if( strcmp(mime+6, "png") == 0 )
367 return "png";
368 break;
369 default:
370 break;
372 return "dat";
376 is_video(const char * file)
378 return (ends_with(file, ".mpg") || ends_with(file, ".mpeg") ||
379 ends_with(file, ".avi") || ends_with(file, ".divx") ||
380 ends_with(file, ".asf") || ends_with(file, ".wmv") ||
381 ends_with(file, ".mp4") || ends_with(file, ".m4v") ||
382 ends_with(file, ".mts") || ends_with(file, ".m2ts") ||
383 ends_with(file, ".m2t") || ends_with(file, ".mkv") ||
384 ends_with(file, ".vob") || ends_with(file, ".ts") ||
385 ends_with(file, ".flv") || ends_with(file, ".xvid") ||
386 #ifdef TIVO_SUPPORT
387 ends_with(file, ".TiVo") ||
388 #endif
389 ends_with(file, ".mov") || ends_with(file, ".3gp"));
393 is_audio(const char * file)
395 return (ends_with(file, ".mp3") || ends_with(file, ".flac") ||
396 ends_with(file, ".wma") || ends_with(file, ".asf") ||
397 ends_with(file, ".fla") || ends_with(file, ".flc") ||
398 ends_with(file, ".m4a") || ends_with(file, ".aac") ||
399 ends_with(file, ".mp4") || ends_with(file, ".m4p") ||
400 ends_with(file, ".wav") || ends_with(file, ".ogg") ||
401 ends_with(file, ".pcm") || ends_with(file, ".3gp") ||
402 ends_with(file, ".dsf") || ends_with(file, ".dff"));
406 is_image(const char * file)
408 return (ends_with(file, ".jpg") || ends_with(file, ".jpeg"));
412 is_playlist(const char * file)
414 return (ends_with(file, ".m3u") || ends_with(file, ".pls"));
418 is_caption(const char * file)
420 return (ends_with(file, ".srt") || ends_with(file, ".smi"));
424 is_album_art(const char * name)
426 struct album_art_name_s * album_art_name;
428 /* Check if this file name matches one of the default album art names */
429 for( album_art_name = album_art_names; album_art_name; album_art_name = album_art_name->next )
431 if( album_art_name->wildcard )
433 if( strncmp(album_art_name->name, name, strlen(album_art_name->name)) == 0 )
434 break;
436 else
438 if( strcmp(album_art_name->name, name) == 0 )
439 break;
443 return (album_art_name ? 1 : 0);
447 resolve_unknown_type(const char * path, media_types dir_type)
449 struct stat entry;
450 unsigned char type = TYPE_UNKNOWN;
451 char str_buf[PATH_MAX];
452 ssize_t len;
454 if( lstat(path, &entry) == 0 )
456 if( S_ISLNK(entry.st_mode) )
458 if( (len = readlink(path, str_buf, PATH_MAX-1)) > 0 )
460 str_buf[len] = '\0';
461 //DEBUG DPRINTF(E_DEBUG, L_GENERAL, "Checking for recursive symbolic link: %s (%s)\n", path, str_buf);
462 if( strncmp(path, str_buf, strlen(str_buf)) == 0 )
464 DPRINTF(E_DEBUG, L_GENERAL, "Ignoring recursive symbolic link: %s (%s)\n", path, str_buf);
465 return type;
468 stat(path, &entry);
471 if( S_ISDIR(entry.st_mode) )
473 type = TYPE_DIR;
475 else if( S_ISREG(entry.st_mode) )
477 switch( dir_type )
479 case ALL_MEDIA:
480 if( is_image(path) ||
481 is_audio(path) ||
482 is_video(path) ||
483 is_playlist(path) )
484 type = TYPE_FILE;
485 break;
486 case TYPE_AUDIO:
487 if( is_audio(path) ||
488 is_playlist(path) )
489 type = TYPE_FILE;
490 break;
491 case TYPE_VIDEO:
492 if( is_video(path) )
493 type = TYPE_FILE;
494 break;
495 case TYPE_IMAGES:
496 if( is_image(path) )
497 type = TYPE_FILE;
498 break;
499 default:
500 break;
504 return type;
507 void
508 begin_scan()
510 FILE * flag;
512 #ifdef READYNAS
513 flag = fopen("/ramfs/.upnp-av_scan", "w");
514 if( flag )
515 fclose(flag);
516 #else
517 mkdir("/var/notice", 0755);
518 flag = fopen("/var/notice/dlna", "w");
519 if( flag )
521 fprintf(flag, "Scan in progress");
522 fclose(flag);
524 #endif
527 void
528 end_scan()
530 #ifdef READYNAS
531 if( access("/ramfs/.rescan_done", F_OK) == 0 )
532 system("/bin/sh /ramfs/.rescan_done");
533 unlink("/ramfs/.upnp-av_scan");
534 #else
535 unlink("/var/notice/dlna");
536 #endif