Audacious  $Id:Doxyfile42802007-03-2104:39:00Znenolod$
util.c
Go to the documentation of this file.
1 /*
2  * util.c
3  * Copyright 2009-2012 John Lindgren and MichaƂ Lipski
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice,
9  * this list of conditions, and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  * this list of conditions, and the following disclaimer in the documentation
13  * provided with the distribution.
14  *
15  * This software is provided "as is" and without any warranty, express or
16  * implied. In no event shall the authors be liable for any damages arising from
17  * the use of this software.
18  */
19 
20 #include <dirent.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 
24 #ifdef _WIN32
25 #include <windows.h>
26 #endif
27 
28 #ifdef __APPLE__
29 #include <mach-o/dyld.h>
30 #endif
31 
32 #include <glib.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <ctype.h>
36 
37 #include <errno.h>
38 
39 #include <libaudcore/audstrings.h>
40 
41 #include "config.h"
42 #include "debug.h"
43 #include "i18n.h"
44 #include "misc.h"
45 #include "plugins.h"
46 #include "util.h"
47 
48 bool_t dir_foreach (const char * path, DirForeachFunc func, void * user)
49 {
50  DIR * dir = opendir (path);
51  if (! dir)
52  return FALSE;
53 
54  struct dirent * entry;
55  while ((entry = readdir (dir)))
56  {
57  if (entry->d_name[0] == '.')
58  continue;
59 
60  char * full = g_strdup_printf ("%s" G_DIR_SEPARATOR_S "%s", path, entry->d_name);
61  bool_t stop = func (full, entry->d_name, user);
62  g_free (full);
63 
64  if (stop)
65  break;
66  }
67 
68  closedir (dir);
69  return TRUE;
70 }
71 
72 char * construct_uri (const char * string, const char * playlist_name)
73 {
74  /* URI */
75  if (strstr (string, "://"))
76  return strdup (string);
77 
78  /* absolute filename (assumed UTF-8) */
79 #ifdef _WIN32
80  if (string[0] && string[1] == ':' && string[2] == '\\')
81 #else
82  if (string[0] == '/')
83 #endif
84  return filename_to_uri (string);
85 
86  /* relative filename (assumed UTF-8) */
87  const char * slash = strrchr (playlist_name, '/');
88  if (! slash)
89  return NULL;
90 
91  int pathlen = slash + 1 - playlist_name;
92  int rellen = strlen (string);
93 
94  char buf[pathlen + 3 * rellen + 1];
95  memcpy (buf, playlist_name, pathlen);
96 
97  if (get_bool (NULL, "convert_backslash"))
98  {
99  char tmp[rellen + 1];
100  strcpy (tmp, string);
101  string_replace_char (tmp, '\\', '/');
102  str_encode_percent (tmp, -1, buf + pathlen);
103  }
104  else
105  str_encode_percent (string, -1, buf + pathlen);
106 
107  return strdup (buf);
108 }
109 
110 /* local files -- not URI's */
111 int file_get_mtime (const char * filename)
112 {
113  struct stat info;
114 
115  if (stat (filename, & info))
116  return -1;
117 
118  return info.st_mtime;
119 }
120 
121 void
122 make_directory(const char * path, mode_t mode)
123 {
124  if (g_mkdir_with_parents(path, mode) == 0)
125  return;
126 
127  g_printerr(_("Could not create directory (%s): %s\n"), path,
128  g_strerror(errno));
129 }
130 
131 char * write_temp_file (void * data, int64_t len)
132 {
133  char * name = g_strdup_printf ("%s/audacious-temp-XXXXXX", g_get_tmp_dir ());
134 
135  int handle = g_mkstemp (name);
136  if (handle < 0)
137  {
138  fprintf (stderr, "Error creating temporary file: %s\n", strerror (errno));
139  g_free (name);
140  return NULL;
141  }
142 
143  while (len)
144  {
145  int64_t written = write (handle, data, len);
146  if (written < 0)
147  {
148  fprintf (stderr, "Error writing %s: %s\n", name, strerror (errno));
149  close (handle);
150  g_free (name);
151  return NULL;
152  }
153 
154  data = (char *) data + written;
155  len -= written;
156  }
157 
158  if (close (handle) < 0)
159  {
160  fprintf (stderr, "Error closing %s: %s\n", name, strerror (errno));
161  g_free (name);
162  return NULL;
163  }
164 
165  return name;
166 }
167 
168 char * get_path_to_self (void)
169 {
170 #if defined _WIN32 || defined HAVE_PROC_SELF_EXE
171  int size = 256;
172  char * buf = g_malloc (size);
173 
174  while (1)
175  {
176  int len;
177 
178 #ifdef _WIN32
179  if (! (len = GetModuleFileName (NULL, buf, size)))
180  {
181  fprintf (stderr, "GetModuleFileName failed.\n");
182  g_free (buf);
183  return NULL;
184  }
185 #else
186  if ((len = readlink ("/proc/self/exe", buf, size)) < 0)
187  {
188  fprintf (stderr, "Cannot access /proc/self/exe: %s.\n", strerror (errno));
189  g_free (buf);
190  return NULL;
191  }
192 #endif
193 
194  if (len < size)
195  {
196  buf[len] = 0;
197  return buf;
198  }
199 
200  size += size;
201  buf = g_realloc (buf, size);
202  }
203 #elif defined __APPLE__
204  unsigned int size = 256;
205  char * buf = g_malloc (size);
206 
207  while (1)
208  {
209  int res;
210 
211  if (! (res = _NSGetExecutablePath (buf, &size)))
212  return buf;
213 
214  if (res == -1)
215  buf = g_realloc (buf, size);
216  else
217  {
218  g_free (buf);
219  return NULL;
220  }
221  }
222 #else
223  return NULL;
224 #endif
225 }
226 
227 /* Strips various common top-level folders from a URI. The string passed will
228  * not be modified, but the string returned will share the same memory.
229  * Examples:
230  * "file:///home/john/folder/file.mp3" -> "folder/file.mp3"
231  * "file:///folder/file.mp3" -> "folder/file.mp3" */
232 
233 static char * skip_top_folders (char * name)
234 {
235  static char * home;
236  static int len;
237 
238  if (! home)
239  {
240  home = filename_to_uri (g_get_home_dir ());
241  len = strlen (home);
242 
243  if (len > 0 && home[len - 1] == '/')
244  len --;
245  }
246 
247 #ifdef _WIN32
248  if (! g_ascii_strncasecmp (name, home, len) && name[len] == '/')
249 #else
250  if (! strncmp (name, home, len) && name[len] == '/')
251 #endif
252  return name + len + 1;
253 
254  if (! strncmp (name, "file:///", 8))
255  return name + 8;
256 
257  return name;
258 }
259 
260 /* Divides a URI into the base name, the lowest folder, and the
261  * second lowest folder. The string passed will be modified, and the strings
262  * returned will use the same memory. May return NULL for <first> and <second>.
263  * Examples:
264  * "a/b/c/d/e.mp3" -> "e", "d", "c"
265  * "d/e.mp3" -> "e", "d", NULL
266  * "e.mp3" -> "e", NULL, NULL */
267 
268 static void split_filename (char * name, char * * base, char * * first,
269  char * * second)
270 {
271  * first = * second = NULL;
272 
273  char * c;
274 
275  if ((c = strrchr (name, '/')))
276  {
277  * base = c + 1;
278  * c = 0;
279  }
280  else
281  {
282  * base = name;
283  goto DONE;
284  }
285 
286  if ((c = strrchr (name, '/')))
287  {
288  * first = c + 1;
289  * c = 0;
290  }
291  else
292  {
293  * first = name;
294  goto DONE;
295  }
296 
297  if ((c = strrchr (name, '/')))
298  * second = c + 1;
299  else
300  * second = name;
301 
302 DONE:
303  if ((c = strrchr (* base, '.')))
304  * c = 0;
305 }
306 
307 /* Separates the domain name from an internet URI. The string passed will be
308  * modified, and the string returned will share the same memory. May return
309  * NULL. Examples:
310  * "http://some.domain.org/folder/file.mp3" -> "some.domain.org"
311  * "http://some.stream.fm:8000" -> "some.stream.fm" */
312 
313 static char * stream_name (char * name)
314 {
315  if (! strncmp (name, "http://", 7))
316  name += 7;
317  else if (! strncmp (name, "https://", 8))
318  name += 8;
319  else if (! strncmp (name, "mms://", 6))
320  name += 6;
321  else
322  return NULL;
323 
324  char * c;
325 
326  if ((c = strchr (name, '/')))
327  * c = 0;
328  if ((c = strchr (name, ':')))
329  * c = 0;
330  if ((c = strchr (name, '?')))
331  * c = 0;
332 
333  return name;
334 }
335 
336 static char * get_nonblank_field (const Tuple * tuple, int field)
337 {
338  char * str = tuple ? tuple_get_str (tuple, field, NULL) : NULL;
339 
340  if (str && ! str[0])
341  {
342  str_unref (str);
343  str = NULL;
344  }
345 
346  return str;
347 }
348 
349 static char * str_get_decoded (char * str)
350 {
351  if (! str)
352  return NULL;
353 
354  str_decode_percent (str, -1, str);
355  return str_get (str);
356 }
357 
358 /* Derives best guesses of title, artist, and album from a file name (URI) and
359  * tuple (which may be NULL). The returned strings are stringpooled or NULL. */
360 
361 void describe_song (const char * name, const Tuple * tuple, char * * _title,
362  char * * _artist, char * * _album)
363 {
364  /* Common folder names to skip */
365  static const char * const skip[] = {"music"};
366 
367  char * title = get_nonblank_field (tuple, FIELD_TITLE);
368  char * artist = get_nonblank_field (tuple, FIELD_ARTIST);
369  char * album = get_nonblank_field (tuple, FIELD_ALBUM);
370 
371  if (title && artist && album)
372  {
373 DONE:
374  * _title = title;
375  * _artist = artist;
376  * _album = album;
377  return;
378  }
379 
380  char buf[strlen (name) + 1];
381  memcpy (buf, name, sizeof buf);
382 
383  if (! strncmp (buf, "file:///", 8))
384  {
385  char * base, * first, * second;
386  split_filename (skip_top_folders (buf), & base, & first, & second);
387 
388  if (! title)
389  title = str_get_decoded (base);
390 
391  for (int i = 0; i < G_N_ELEMENTS (skip); i ++)
392  {
393  if (first && ! g_ascii_strcasecmp (first, skip[i]))
394  first = NULL;
395  if (second && ! g_ascii_strcasecmp (second, skip[i]))
396  second = NULL;
397  }
398 
399  if (first)
400  {
401  if (second && ! artist && ! album)
402  {
403  artist = str_get_decoded (second);
404  album = str_get_decoded (first);
405  }
406  else if (! artist)
407  artist = str_get_decoded (first);
408  else if (! album)
409  album = str_get_decoded (first);
410  }
411  }
412  else
413  {
414  if (! title)
415  {
416  title = str_get_decoded (stream_name (buf));
417 
418  if (! title)
419  title = str_get_decoded (buf);
420  }
421  else if (! artist)
422  artist = str_get_decoded (stream_name (buf));
423  else if (! album)
424  album = str_get_decoded (stream_name (buf));
425  }
426 
427  goto DONE;
428 }