1/* MiniDLNA media server
2 * Copyright (C) 2008-2009  Justin Maggard
3 *
4 * This file is part of MiniDLNA.
5 *
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.
9 *
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.
14 *
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/>.
17 */
18#include <stdio.h>
19#include <string.h>
20#include <stdlib.h>
21#include <unistd.h>
22#include <dirent.h>
23#include <locale.h>
24#include <libgen.h>
25#include <inttypes.h>
26#include <sys/param.h>
27#include <sys/stat.h>
28#include <sys/time.h>
29#include <sys/resource.h>
30
31#include "config.h"
32
33#ifdef ENABLE_NLS
34#include <libintl.h>
35#endif
36#include <sqlite3.h>
37#include "libav.h"
38
39#include "scanner_sqlite.h"
40#include "upnpglobalvars.h"
41#include "metadata.h"
42#include "playlist.h"
43#include "utils.h"
44#include "sql.h"
45#include "scanner.h"
46#include "albumart.h"
47#include "containers.h"
48#include "log.h"
49#include "inotify.h"
50
51#if SCANDIR_CONST
52typedef const struct dirent scan_filter;
53#else
54typedef struct dirent scan_filter;
55#endif
56#ifndef AV_LOG_PANIC
57#define AV_LOG_PANIC AV_LOG_FATAL
58#endif
59
60int valid_cache = 0;
61
62struct virtual_item
63{
64	int64_t objectID;
65	char parentID[64];
66	char name[256];
67};
68
69int64_t
70get_next_available_id(const char *table, const char *parentID)
71{
72		char *ret, *base;
73		int64_t objectID = 0;
74
75		ret = sql_get_text_field(db, "SELECT OBJECT_ID from %s where ID = "
76		                             "(SELECT max(ID) from %s where PARENT_ID = '%s')",
77		                             table, table, parentID);
78		if( ret )
79		{
80			base = strrchr(ret, '$');
81			if( base )
82				objectID = strtoll(base+1, NULL, 16) + 1;
83			sqlite3_free(ret);
84		}
85
86		return objectID;
87}
88
89int
90insert_container(const char *item, const char *rootParent, const char *refID, const char *class,
91                 const char *artist, const char *genre, const char *album_art, int64_t *objectID, int64_t *parentID)
92{
93	char *result;
94	char *base;
95	int ret = 0;
96
97	result = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS o "
98	                                "left join DETAILS d on (o.DETAIL_ID = d.ID)"
99	                                " where o.PARENT_ID = '%s'"
100			                " and o.NAME like '%q'"
101			                " and d.ARTIST %s %Q"
102	                                " and o.CLASS = 'container.%s' limit 1",
103	                                rootParent, item, artist?"like":"is", artist, class);
104	if( result )
105	{
106		base = strrchr(result, '$');
107		if( base )
108			*parentID = strtoll(base+1, NULL, 16);
109		else
110			*parentID = 0;
111		*objectID = get_next_available_id("OBJECTS", result);
112	}
113	else
114	{
115		int64_t detailID = 0;
116		*objectID = 0;
117		*parentID = get_next_available_id("OBJECTS", rootParent);
118		if( refID )
119		{
120			result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = %Q", refID);
121			if( result )
122				detailID = strtoll(result, NULL, 10);
123		}
124		if( !detailID )
125		{
126			detailID = GetFolderMetadata(item, NULL, artist, genre, (album_art ? strtoll(album_art, NULL, 10) : 0));
127		}
128		ret = sql_exec(db, "INSERT into OBJECTS"
129		                   " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) "
130		                   "VALUES"
131		                   " ('%s$%llX', '%s', %Q, %lld, 'container.%s', '%q')",
132		                   rootParent, (long long)*parentID, rootParent,
133		                   refID, (long long)detailID, class, item);
134	}
135	sqlite3_free(result);
136
137	return ret;
138}
139
140static void
141insert_containers(const char *name, const char *path, const char *refID, const char *class, int64_t detailID)
142{
143	char sql[128];
144	char **result;
145	int ret;
146	int cols, row;
147	int64_t objectID, parentID;
148
149	if( strstr(class, "imageItem") )
150	{
151		char *date_taken = NULL, *camera = NULL;
152		static struct virtual_item last_date;
153		static struct virtual_item last_cam;
154		static struct virtual_item last_camdate;
155		static long long last_all_objectID = 0;
156
157		snprintf(sql, sizeof(sql), "SELECT DATE, CREATOR from DETAILS where ID = %lld", (long long)detailID);
158		ret = sql_get_table(db, sql, &result, &row, &cols);
159		if( ret == SQLITE_OK )
160		{
161			date_taken = result[2];
162			camera = result[3];
163		}
164		if( date_taken )
165			date_taken[10] = '\0';
166		else
167			date_taken = _("Unknown Date");
168		if( !camera )
169			camera = _("Unknown Camera");
170
171		if( valid_cache && strcmp(last_date.name, date_taken) == 0 )
172		{
173			last_date.objectID++;
174			//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
175		}
176		else
177		{
178			insert_container(date_taken, IMAGE_DATE_ID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID);
179			sprintf(last_date.parentID, IMAGE_DATE_ID"$%llX", (unsigned long long)parentID);
180			last_date.objectID = objectID;
181			strncpyt(last_date.name, date_taken, sizeof(last_date.name));
182			//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
183		}
184		sql_exec(db, "INSERT into OBJECTS"
185		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
186		             "VALUES"
187		             " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)",
188		             last_date.parentID, (long long)last_date.objectID, last_date.parentID, refID, class, (long long)detailID, name);
189
190		if( !valid_cache || strcmp(camera, last_cam.name) != 0 )
191		{
192			insert_container(camera, IMAGE_CAMERA_ID, NULL, "storageFolder", NULL, NULL, NULL, &objectID, &parentID);
193			sprintf(last_cam.parentID, IMAGE_CAMERA_ID"$%llX", (long long)parentID);
194			strncpyt(last_cam.name, camera, sizeof(last_cam.name));
195			/* Invalidate last_camdate cache */
196			last_camdate.name[0] = '\0';
197		}
198		if( valid_cache && strcmp(last_camdate.name, date_taken) == 0 )
199		{
200			last_camdate.objectID++;
201			//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID);
202		}
203		else
204		{
205			insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL, &objectID, &parentID);
206			sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, (long long)parentID);
207			last_camdate.objectID = objectID;
208			strncpyt(last_camdate.name, date_taken, sizeof(last_camdate.name));
209			//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached camdate item: %s/%s/%s/%X\n", camera, last_camdate.name, last_camdate.parentID, last_camdate.objectID);
210		}
211		sql_exec(db, "INSERT into OBJECTS"
212		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
213		             "VALUES"
214		             " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)",
215		             last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, (long long)detailID, name);
216		/* All Images */
217		if( !last_all_objectID )
218		{
219			last_all_objectID = get_next_available_id("OBJECTS", IMAGE_ALL_ID);
220		}
221		sql_exec(db, "INSERT into OBJECTS"
222		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
223		             "VALUES"
224		             " ('"IMAGE_ALL_ID"$%llX', '"IMAGE_ALL_ID"', '%s', '%s', %lld, %Q)",
225		             last_all_objectID++, refID, class, (long long)detailID, name);
226	}
227	else if( strstr(class, "audioItem") )
228	{
229		snprintf(sql, sizeof(sql), "SELECT ALBUM, ARTIST, GENRE, ALBUM_ART from DETAILS where ID = %lld", (long long)detailID);
230		ret = sql_get_table(db, sql, &result, &row, &cols);
231		if( ret != SQLITE_OK )
232			return;
233		if( !row )
234		{
235			sqlite3_free_table(result);
236			return;
237		}
238		char *album = result[4], *artist = result[5], *genre = result[6];
239		char *album_art = result[7];
240		static struct virtual_item last_album;
241		static struct virtual_item last_artist;
242		static struct virtual_item last_artistAlbum;
243		static struct virtual_item last_artistAlbumAll;
244		static struct virtual_item last_genre;
245		static struct virtual_item last_genreArtist;
246		static struct virtual_item last_genreArtistAll;
247		static long long last_all_objectID = 0;
248
249		if( album )
250		{
251			if( valid_cache && strcmp(album, last_album.name) == 0 )
252			{
253				last_album.objectID++;
254				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
255			}
256			else
257			{
258				strncpyt(last_album.name, album, sizeof(last_album.name));
259				insert_container(album, MUSIC_ALBUM_ID, NULL, "album.musicAlbum", artist, genre, album_art, &objectID, &parentID);
260				sprintf(last_album.parentID, MUSIC_ALBUM_ID"$%llX", (long long)parentID);
261				last_album.objectID = objectID;
262				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
263			}
264			sql_exec(db, "INSERT into OBJECTS"
265			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
266			             "VALUES"
267			             " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)",
268			             last_album.parentID, last_album.objectID, last_album.parentID, refID, class, (long long)detailID, name);
269		}
270		if( artist )
271		{
272			if( !valid_cache || strcmp(artist, last_artist.name) != 0 )
273			{
274				insert_container(artist, MUSIC_ARTIST_ID, NULL, "person.musicArtist", NULL, genre, NULL, &objectID, &parentID);
275				sprintf(last_artist.parentID, MUSIC_ARTIST_ID"$%llX", (long long)parentID);
276				strncpyt(last_artist.name, artist, sizeof(last_artist.name));
277				last_artistAlbum.name[0] = '\0';
278				/* Add this file to the "- All Albums -" container as well */
279				insert_container(_("- All Albums -"), last_artist.parentID, NULL, "album", artist, genre, NULL, &objectID, &parentID);
280				sprintf(last_artistAlbumAll.parentID, "%s$%llX", last_artist.parentID, (long long)parentID);
281				last_artistAlbumAll.objectID = objectID;
282			}
283			else
284			{
285				last_artistAlbumAll.objectID++;
286			}
287			if( valid_cache && strcmp(album?album:_("Unknown Album"), last_artistAlbum.name) == 0 )
288			{
289				last_artistAlbum.objectID++;
290				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
291			}
292			else
293			{
294				insert_container(album?album:_("Unknown Album"), last_artist.parentID, album?last_album.parentID:NULL,
295				                 "album.musicAlbum", artist, genre, album_art, &objectID, &parentID);
296				sprintf(last_artistAlbum.parentID, "%s$%llX", last_artist.parentID, (long long)parentID);
297				last_artistAlbum.objectID = objectID;
298				strncpyt(last_artistAlbum.name, album ? album : _("Unknown Album"), sizeof(last_artistAlbum.name));
299				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
300			}
301			sql_exec(db, "INSERT into OBJECTS"
302			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
303			             "VALUES"
304			             " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)",
305			             last_artistAlbum.parentID, last_artistAlbum.objectID, last_artistAlbum.parentID, refID, class, (long long)detailID, name);
306			sql_exec(db, "INSERT into OBJECTS"
307			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
308			             "VALUES"
309			             " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)",
310			             last_artistAlbumAll.parentID, last_artistAlbumAll.objectID, last_artistAlbumAll.parentID, refID, class, (long long)detailID, name);
311		}
312		if( genre )
313		{
314			if( !valid_cache || strcmp(genre, last_genre.name) != 0 )
315			{
316				insert_container(genre, MUSIC_GENRE_ID, NULL, "genre.musicGenre", NULL, NULL, NULL, &objectID, &parentID);
317				sprintf(last_genre.parentID, MUSIC_GENRE_ID"$%llX", (long long)parentID);
318				strncpyt(last_genre.name, genre, sizeof(last_genre.name));
319				/* Add this file to the "- All Artists -" container as well */
320				insert_container(_("- All Artists -"), last_genre.parentID, NULL, "person", NULL, genre, NULL, &objectID, &parentID);
321				sprintf(last_genreArtistAll.parentID, "%s$%llX", last_genre.parentID, (long long)parentID);
322				last_genreArtistAll.objectID = objectID;
323			}
324			else
325			{
326				last_genreArtistAll.objectID++;
327			}
328			if( valid_cache && strcmp(artist?artist:_("Unknown Artist"), last_genreArtist.name) == 0 )
329			{
330				last_genreArtist.objectID++;
331			}
332			else
333			{
334				insert_container(artist?artist:_("Unknown Artist"), last_genre.parentID, artist?last_artist.parentID:NULL,
335				                 "person.musicArtist", NULL, genre, NULL, &objectID, &parentID);
336				sprintf(last_genreArtist.parentID, "%s$%llX", last_genre.parentID, (long long)parentID);
337				last_genreArtist.objectID = objectID;
338				strncpyt(last_genreArtist.name, artist ? artist : _("Unknown Artist"), sizeof(last_genreArtist.name));
339				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre/artist item: %s/%s/%X\n", last_genreArtist.name, last_genreArtist.parentID, last_genreArtist.objectID);
340			}
341			sql_exec(db, "INSERT into OBJECTS"
342			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
343			             "VALUES"
344			             " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)",
345			             last_genreArtist.parentID, last_genreArtist.objectID, last_genreArtist.parentID, refID, class, (long long)detailID, name);
346			sql_exec(db, "INSERT into OBJECTS"
347			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
348			             "VALUES"
349			             " ('%s$%llX', '%s', '%s', '%s', %lld, %Q)",
350			             last_genreArtistAll.parentID, last_genreArtistAll.objectID, last_genreArtistAll.parentID, refID, class, (long long)detailID, name);
351		}
352		/* All Music */
353		if( !last_all_objectID )
354		{
355			last_all_objectID = get_next_available_id("OBJECTS", MUSIC_ALL_ID);
356		}
357		sql_exec(db, "INSERT into OBJECTS"
358		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
359		             "VALUES"
360		             " ('"MUSIC_ALL_ID"$%llX', '"MUSIC_ALL_ID"', '%s', '%s', %lld, %Q)",
361		             last_all_objectID++, refID, class, (long long)detailID, name);
362	}
363	else if( strstr(class, "videoItem") )
364	{
365		static long long last_all_objectID = 0;
366
367		/* All Videos */
368		if( !last_all_objectID )
369		{
370			last_all_objectID = get_next_available_id("OBJECTS", VIDEO_ALL_ID);
371		}
372		sql_exec(db, "INSERT into OBJECTS"
373		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
374		             "VALUES"
375		             " ('"VIDEO_ALL_ID"$%llX', '"VIDEO_ALL_ID"', '%s', '%s', %lld, %Q)",
376		             last_all_objectID++, refID, class, (long long)detailID, name);
377		return;
378	}
379	else
380	{
381		return;
382	}
383	sqlite3_free_table(result);
384	valid_cache = 1;
385}
386
387int64_t
388insert_directory(const char *name, const char *path, const char *base, const char *parentID, int objectID)
389{
390	int64_t detailID = 0;
391	char class[] = "container.storageFolder";
392	char *result, *p;
393	static char last_found[256] = "-1";
394
395	if( strcmp(base, BROWSEDIR_ID) != 0 )
396	{
397		int found = 0;
398		char id_buf[64], parent_buf[64], refID[64];
399		char *dir_buf, *dir;
400
401 		dir_buf = strdup(path);
402		dir = dirname(dir_buf);
403		snprintf(refID, sizeof(refID), "%s%s$%X", BROWSEDIR_ID, parentID, objectID);
404		snprintf(id_buf, sizeof(id_buf), "%s%s$%X", base, parentID, objectID);
405		snprintf(parent_buf, sizeof(parent_buf), "%s%s", base, parentID);
406		while( !found )
407		{
408			if( valid_cache && strcmp(id_buf, last_found) == 0 )
409				break;
410			if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", id_buf) > 0 )
411			{
412				strcpy(last_found, id_buf);
413				break;
414			}
415			/* Does not exist.  Need to create, and may need to create parents also */
416			result = sql_get_text_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s'", refID);
417			if( result )
418			{
419				detailID = strtoll(result, NULL, 10);
420				sqlite3_free(result);
421			}
422			sql_exec(db, "INSERT into OBJECTS"
423			             " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) "
424			             "VALUES"
425			             " ('%s', '%s', %Q, %lld, '%s', '%q')",
426			             id_buf, parent_buf, refID, detailID, class, strrchr(dir, '/')+1);
427			if( (p = strrchr(id_buf, '$')) )
428				*p = '\0';
429			if( (p = strrchr(parent_buf, '$')) )
430				*p = '\0';
431			if( (p = strrchr(refID, '$')) )
432				*p = '\0';
433			dir = dirname(dir);
434		}
435		free(dir_buf);
436		return 0;
437	}
438
439	detailID = GetFolderMetadata(name, path, NULL, NULL, find_album_art(path, NULL, 0));
440	sql_exec(db, "INSERT into OBJECTS"
441	             " (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME) "
442	             "VALUES"
443	             " ('%s%s$%X', '%s%s', %lld, '%s', '%q')",
444	             base, parentID, objectID, base, parentID, detailID, class, name);
445
446	return detailID;
447}
448
449int
450insert_file(char *name, const char *path, const char *parentID, int object, media_types types)
451{
452	char class[32];
453	char objectID[64];
454	int64_t detailID = 0;
455	char base[8];
456	char *typedir_parentID;
457	char *baseid;
458	char *orig_name = NULL;
459
460	if( (types & TYPE_IMAGES) && is_image(name) )
461	{
462		if( is_album_art(name) )
463			return -1;
464		strcpy(base, IMAGE_DIR_ID);
465		strcpy(class, "item.imageItem.photo");
466		detailID = GetImageMetadata(path, name);
467	}
468	else if( (types & TYPE_VIDEO) && is_video(name) )
469	{
470 		orig_name = strdup(name);
471		strcpy(base, VIDEO_DIR_ID);
472		strcpy(class, "item.videoItem");
473		detailID = GetVideoMetadata(path, name);
474		if( !detailID )
475			strcpy(name, orig_name);
476	}
477	else if( is_playlist(name) )
478	{
479		if( insert_playlist(path, name) == 0 )
480			return 1;
481	}
482	if( !detailID && (types & TYPE_AUDIO) && is_audio(name) )
483	{
484		strcpy(base, MUSIC_DIR_ID);
485		strcpy(class, "item.audioItem.musicTrack");
486		detailID = GetAudioMetadata(path, name);
487	}
488	free(orig_name);
489	if( !detailID )
490	{
491		DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path);
492		return -1;
493	}
494
495	sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object);
496
497	sql_exec(db, "INSERT into OBJECTS"
498	             " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) "
499	             "VALUES"
500	             " ('%s', '%s%s', '%s', %lld, '%q')",
501	             objectID, BROWSEDIR_ID, parentID, class, detailID, name);
502
503	if( *parentID )
504	{
505		int typedir_objectID = 0;
506		typedir_parentID = strdup(parentID);
507		baseid = strrchr(typedir_parentID, '$');
508		if( baseid )
509		{
510			typedir_objectID = strtol(baseid+1, NULL, 16);
511			*baseid = '\0';
512		}
513		insert_directory(name, path, base, typedir_parentID, typedir_objectID);
514		free(typedir_parentID);
515	}
516	sql_exec(db, "INSERT into OBJECTS"
517	             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
518	             "VALUES"
519	             " ('%s%s$%X', '%s%s', '%s', '%s', %lld, '%q')",
520	             base, parentID, object, base, parentID, objectID, class, detailID, name);
521
522	insert_containers(name, path, objectID, class, detailID);
523	return 0;
524}
525
526int
527CreateDatabase(void)
528{
529	int ret, i;
530	const char *containers[] = { "0","-1",   "root",
531	                        MUSIC_ID, "0", _("Music"),
532	                    MUSIC_ALL_ID, MUSIC_ID, _("All Music"),
533	                  MUSIC_GENRE_ID, MUSIC_ID, _("Genre"),
534	                 MUSIC_ARTIST_ID, MUSIC_ID, _("Artist"),
535	                  MUSIC_ALBUM_ID, MUSIC_ID, _("Album"),
536	                    MUSIC_DIR_ID, MUSIC_ID, _("Folders"),
537	                  MUSIC_PLIST_ID, MUSIC_ID, _("Playlists"),
538
539	                        VIDEO_ID, "0", _("Video"),
540	                    VIDEO_ALL_ID, VIDEO_ID, _("All Video"),
541	                    VIDEO_DIR_ID, VIDEO_ID, _("Folders"),
542
543	                        IMAGE_ID, "0", _("Pictures"),
544	                    IMAGE_ALL_ID, IMAGE_ID, _("All Pictures"),
545	                   IMAGE_DATE_ID, IMAGE_ID, _("Date Taken"),
546	                 IMAGE_CAMERA_ID, IMAGE_ID, _("Camera"),
547	                    IMAGE_DIR_ID, IMAGE_ID, _("Folders"),
548
549	                    BROWSEDIR_ID, "0", _("Browse Folders"),
550			0 };
551
552	ret = sql_exec(db, create_objectTable_sqlite);
553	if( ret != SQLITE_OK )
554		goto sql_failed;
555	ret = sql_exec(db, create_detailTable_sqlite);
556	if( ret != SQLITE_OK )
557		goto sql_failed;
558	ret = sql_exec(db, create_albumArtTable_sqlite);
559	if( ret != SQLITE_OK )
560		goto sql_failed;
561	ret = sql_exec(db, create_captionTable_sqlite);
562	if( ret != SQLITE_OK )
563		goto sql_failed;
564	ret = sql_exec(db, create_bookmarkTable_sqlite);
565	if( ret != SQLITE_OK )
566		goto sql_failed;
567	ret = sql_exec(db, create_playlistTable_sqlite);
568	if( ret != SQLITE_OK )
569		goto sql_failed;
570	ret = sql_exec(db, create_settingsTable_sqlite);
571	if( ret != SQLITE_OK )
572		goto sql_failed;
573	ret = sql_exec(db, "INSERT into SETTINGS values ('UPDATE_ID', '0')");
574	if( ret != SQLITE_OK )
575		goto sql_failed;
576	for( i=0; containers[i]; i=i+3 )
577	{
578		ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)"
579		                   " values "
580		                   "('%s', '%s', %lld, 'container.storageFolder', '%q')",
581		                   containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL, NULL, NULL, 0), containers[i+2]);
582		if( ret != SQLITE_OK )
583			goto sql_failed;
584	}
585	for( i=0; magic_containers[i].objectid_match; i++ )
586	{
587		struct magic_container_s *magic = &magic_containers[i];
588		if (!magic->name)
589			continue;
590		if( sql_get_int_field(db, "SELECT 1 from OBJECTS where OBJECT_ID = '%s'", magic->objectid_match) == 0 )
591		{
592			char *parent = strdup(magic->objectid_match);
593			if (strrchr(parent, '$'))
594				*strrchr(parent, '$') = '\0';
595			ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)"
596			                   " values "
597					   "('%s', '%s', %lld, 'container.storageFolder', '%q')",
598					   magic->objectid_match, parent, GetFolderMetadata(magic->name, NULL, NULL, NULL, 0), magic->name);
599			free(parent);
600			if( ret != SQLITE_OK )
601				goto sql_failed;
602		}
603	}
604	sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);");
605	sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);");
606	sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);");
607	sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);");
608	sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);");
609	sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);");
610	sql_exec(db, "create INDEX IDX_ALBUM_ART ON ALBUM_ART(ID);");
611	sql_exec(db, "create INDEX IDX_SCANNER_OPT ON OBJECTS(PARENT_ID, NAME, OBJECT_ID);");
612
613sql_failed:
614	if( ret != SQLITE_OK )
615		DPRINTF(E_ERROR, L_DB_SQL, "Error creating SQLite3 database!\n");
616	return (ret != SQLITE_OK);
617}
618
619static inline int
620filter_hidden(scan_filter *d)
621{
622	return (d->d_name[0] != '.');
623}
624
625static int
626filter_type(scan_filter *d)
627{
628#if HAVE_STRUCT_DIRENT_D_TYPE
629	return ( (d->d_type == DT_DIR) ||
630	         (d->d_type == DT_LNK) ||
631	         (d->d_type == DT_UNKNOWN)
632	       );
633#else
634	return 1;
635#endif
636}
637
638static int
639filter_a(scan_filter *d)
640{
641	return ( filter_hidden(d) &&
642	         (filter_type(d) ||
643		  (is_reg(d) &&
644		   (is_audio(d->d_name) ||
645	            is_playlist(d->d_name))))
646	       );
647}
648
649static int
650filter_av(scan_filter *d)
651{
652	return ( filter_hidden(d) &&
653	         (filter_type(d) ||
654		  (is_reg(d) &&
655		   (is_audio(d->d_name) ||
656		    is_video(d->d_name) ||
657	            is_playlist(d->d_name))))
658	       );
659}
660
661static int
662filter_ap(scan_filter *d)
663{
664	return ( filter_hidden(d) &&
665	         (filter_type(d) ||
666		  (is_reg(d) &&
667		   (is_audio(d->d_name) ||
668		    is_image(d->d_name) ||
669	            is_playlist(d->d_name))))
670	       );
671}
672
673static int
674filter_v(scan_filter *d)
675{
676	return ( filter_hidden(d) &&
677	         (filter_type(d) ||
678		  (is_reg(d) &&
679	           is_video(d->d_name)))
680	       );
681}
682
683static int
684filter_vp(scan_filter *d)
685{
686	return ( filter_hidden(d) &&
687	         (filter_type(d) ||
688		  (is_reg(d) &&
689		   (is_video(d->d_name) ||
690	            is_image(d->d_name))))
691	       );
692}
693
694static int
695filter_p(scan_filter *d)
696{
697	return ( filter_hidden(d) &&
698	         (filter_type(d) ||
699		  (is_reg(d) &&
700		   is_image(d->d_name)))
701	       );
702}
703
704static int
705filter_avp(scan_filter *d)
706{
707	return ( filter_hidden(d) &&
708	         (filter_type(d) ||
709		  (is_reg(d) &&
710		   (is_audio(d->d_name) ||
711		    is_image(d->d_name) ||
712		    is_video(d->d_name) ||
713	            is_playlist(d->d_name))))
714	       );
715}
716
717static int
718path_is_dir(const char *path)
719{
720	struct stat stat_buf;
721
722	if (!stat(path, &stat_buf))
723		return S_ISDIR(stat_buf.st_mode);
724	else
725		return 0;
726}
727
728static int
729is_sys_dir(const char *dirname)
730{
731	char *MS_System_folder[] = {"SYSTEM VOLUME INFORMATION", "RECYCLER", "RECYCLED", "$RECYCLE.BIN", NULL};
732	char *Linux_System_folder[] = {"lost+found", NULL};
733	int i;
734
735	for (i = 0; MS_System_folder[i] != NULL; ++i) {
736		if (!strcasecmp(dirname, MS_System_folder[i]))
737			return 1;
738	}
739
740	for (i = 0; Linux_System_folder[i] != NULL; ++i) {
741		if (!strcasecmp(dirname, Linux_System_folder[i]))
742		return 1;
743	}
744
745	return 0;
746}
747
748static void
749ScanDirectory(const char *dir, const char *parent, media_types dir_types)
750{
751	struct dirent **namelist;
752	int i, n, startID = 0;
753	char *full_path;
754	char *name = NULL;
755	static long long unsigned int fileno = 0;
756	enum file_types type;
757
758	DPRINTF(parent?E_INFO:E_WARN, L_SCANNER, _("Scanning %s\n"), dir);
759	switch( dir_types )
760	{
761		case ALL_MEDIA:
762			n = scandir(dir, &namelist, filter_avp, alphasort);
763			break;
764		case TYPE_AUDIO:
765			n = scandir(dir, &namelist, filter_a, alphasort);
766			break;
767		case TYPE_AUDIO|TYPE_VIDEO:
768			n = scandir(dir, &namelist, filter_av, alphasort);
769			break;
770		case TYPE_AUDIO|TYPE_IMAGES:
771			n = scandir(dir, &namelist, filter_ap, alphasort);
772			break;
773		case TYPE_VIDEO:
774			n = scandir(dir, &namelist, filter_v, alphasort);
775			break;
776		case TYPE_VIDEO|TYPE_IMAGES:
777			n = scandir(dir, &namelist, filter_vp, alphasort);
778			break;
779		case TYPE_IMAGES:
780			n = scandir(dir, &namelist, filter_p, alphasort);
781			break;
782		default:
783			n = -1;
784			errno = EINVAL;
785			break;
786	}
787	if( n < 0 )
788	{
789		DPRINTF(E_WARN, L_SCANNER, "Error scanning %s [%s]\n",
790			dir, strerror(errno));
791		return;
792	}
793
794	full_path = malloc(PATH_MAX);
795	if (!full_path)
796	{
797		DPRINTF(E_ERROR, L_SCANNER, "Memory allocation failed scanning %s\n", dir);
798		return;
799	}
800
801	if( !parent )
802	{
803		startID = get_next_available_id("OBJECTS", BROWSEDIR_ID);
804	}
805
806	for (i=0; i < n; i++)
807	{
808#if !USE_FORK
809		if( quitting )
810			break;
811#endif
812		type = TYPE_UNKNOWN;
813		snprintf(full_path, PATH_MAX, "%s/%s", dir, namelist[i]->d_name);
814		name = escape_tag(namelist[i]->d_name, 1);
815
816		if (strstr(full_path,"/Download2/InComplete") || strstr(full_path,"/Download2/Seeds") || strstr(full_path,"/Download2/config") || strstr(full_path,"/Download2/action"))
817			continue;
818		if ((strncmp(name,"asusware",8) == 0))//eric added for have no need to scan asusware folder
819			continue;
820		if ((strncmp(name,".minidlna",9) == 0))//sungmin added for have no need to scan minidlna folder
821		    continue;
822		if (path_is_dir(full_path) && is_sys_dir(name))
823                        continue;
824
825		if( is_dir(namelist[i]) == 1 )
826		{
827			type = TYPE_DIR;
828		}
829		else if( is_reg(namelist[i]) == 1 )
830		{
831			type = TYPE_FILE;
832		}
833		else
834		{
835			type = resolve_unknown_type(full_path, dir_types);
836		}
837		if( (type == TYPE_DIR) && (access(full_path, R_OK|X_OK) == 0) )
838		{
839			char *parent_id;
840			insert_directory(name, full_path, BROWSEDIR_ID, THISORNUL(parent), i+startID);
841			xasprintf(&parent_id, "%s$%X", THISORNUL(parent), i+startID);
842			ScanDirectory(full_path, parent_id, dir_types);
843			free(parent_id);
844		}
845		else if( type == TYPE_FILE && (access(full_path, R_OK) == 0) )
846		{
847			if( insert_file(name, full_path, THISORNUL(parent), i+startID, dir_types) == 0 )
848				fileno++;
849		}
850		free(name);
851		free(namelist[i]);
852	}
853	free(namelist);
854	free(full_path);
855	if( !parent )
856	{
857		DPRINTF(E_WARN, L_SCANNER, _("Scanning %s finished (%llu files)!\n"), dir, fileno);
858	}
859}
860
861extern void create_scantag(void);
862extern void remove_scantag(void);
863
864static void
865_notify_start(void)
866{
867#ifdef READYNAS
868	FILE *flag = fopen("/ramfs/.upnp-av_scan", "w");
869	if( flag )
870		fclose(flag);
871#else
872	create_scantag();
873#endif
874}
875
876static void
877_notify_stop(void)
878{
879#ifdef READYNAS
880	if( access("/ramfs/.rescan_done", F_OK) == 0 )
881		system("/bin/sh /ramfs/.rescan_done");
882	unlink("/ramfs/.upnp-av_scan");
883#else
884	remove_scantag();
885#endif
886}
887
888/* rescan functions added by shrimpkin@sourceforge.net */
889static int
890cb_orphans(void *args, int argc, char **argv, char **azColName)
891{
892	struct stat file;
893	char *path = argv[0], *mime = argv[1];
894
895	/* If we can't stat path, remove it */
896	if (stat(path, &file) != 0)
897	{
898		DPRINTF(E_DEBUG, L_SCANNER, "Removing %s [%s]!\n", path, (mime) ? "file" : "dir");
899		if (mime)
900		{
901			inotify_remove_file(path);
902		}
903		else
904		{
905			inotify_remove_directory(0, path);
906		}
907	}
908	return 0;
909}
910
911void
912start_rescan()
913{
914	struct media_dir_s *media_path;
915	char path[MAXPATHLEN], buf[MAXPATHLEN], *esc_name = NULL, *zErrMsg;
916	char *sql_files = "SELECT path, mime FROM details WHERE path NOT NULL AND mime IS NOT NULL;", *sql_dir = "SELECT path, mime FROM details WHERE path NOT NULL AND mime IS NULL;";
917	int ret;
918
919	DPRINTF(E_INFO, L_SCANNER, "Starting rescan\n");
920
921	/* Find and remove any dead directory links */
922	ret = sqlite3_exec(db, sql_dir, cb_orphans, NULL, &zErrMsg);
923	if (ret != SQLITE_OK)
924	{
925		DPRINTF(E_MAXDEBUG, L_SCANNER, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql_dir);
926		sqlite3_free(zErrMsg);
927	}
928
929	/* Find and remove any dead file links */
930	ret = sqlite3_exec(db, sql_files, cb_orphans, NULL, &zErrMsg);
931	if (ret != SQLITE_OK)
932	{
933		DPRINTF(E_MAXDEBUG, L_SCANNER, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, sql_files);
934		sqlite3_free(zErrMsg);
935	}
936
937	/* Rescan media_paths for new and/or modified files */
938	for (media_path = media_dirs; media_path != NULL; media_path = media_path->next)
939	{
940		strncpyt(path, media_path->path, sizeof(path));
941		strncpyt(buf, media_path->path, sizeof(buf));
942		esc_name = escape_tag(basename(buf), 1);
943		inotify_insert_directory(0, esc_name, path);
944		free(esc_name);
945	}
946	DPRINTF(E_INFO, L_SCANNER, "Rescan completed\n");
947}
948/* end rescan functions */
949
950void
951start_scanner()
952{
953	struct media_dir_s *media_path;
954	char path[MAXPATHLEN];
955
956	if (setpriority(PRIO_PROCESS, 0, 15) == -1)
957		DPRINTF(E_WARN, L_INOTIFY,  "Failed to reduce scanner thread priority\n");
958
959	setlocale(LC_COLLATE, "");
960
961	av_register_all();
962	av_log_set_level(AV_LOG_PANIC);
963	if (rescan_db)
964	{
965		start_rescan();
966		return;
967	}
968	_notify_start();
969	for( media_path = media_dirs; media_path != NULL; media_path = media_path->next )
970	{
971		int64_t id;
972		char *bname, *parent = NULL;
973		char buf[8];
974		strncpyt(path, media_path->path, sizeof(path));
975		bname = basename(path);
976		/* If there are multiple media locations, add a level to the ContentDirectory */
977		if( !GETFLAG(MERGE_MEDIA_DIRS_MASK) && media_dirs->next )
978		{
979			int startID = get_next_available_id("OBJECTS", BROWSEDIR_ID);
980			id = insert_directory(bname, path, BROWSEDIR_ID, "", startID);
981			sprintf(buf, "$%X", startID);
982			parent = buf;
983		}
984		else
985			id = GetFolderMetadata(bname, media_path->path, NULL, NULL, 0);
986		/* Use TIMESTAMP to store the media type */
987		sql_exec(db, "UPDATE DETAILS set TIMESTAMP = %d where ID = %lld", media_path->types, (long long)id);
988		ScanDirectory(media_path->path, parent, media_path->types);
989		sql_exec(db, "INSERT into SETTINGS values (%Q, %Q)", "media_dir", media_path->path);
990	}
991	_notify_stop();
992	/* Create this index after scanning, so it doesn't slow down the scanning process.
993	 * This index is very useful for large libraries used with an XBox360 (or any
994	 * client that uses UPnPSearch on large containers). */
995	sql_exec(db, "create INDEX IDX_SEARCH_OPT ON OBJECTS(OBJECT_ID, CLASS, DETAIL_ID);");
996
997	if( GETFLAG(NO_PLAYLIST_MASK) )
998	{
999		DPRINTF(E_WARN, L_SCANNER, "Playlist creation disabled\n");
1000	}
1001	else
1002	{
1003		fill_playlists();
1004	}
1005
1006	DPRINTF(E_DEBUG, L_SCANNER, "Initial file scan completed\n");
1007	//JM: Set up a db version number, so we know if we need to rebuild due to a new structure.
1008	sql_exec(db, "pragma user_version = %d;", DB_VERSION);
1009}
1010