1/*  MiniDLNA media server
2 *  Copyright (C) 2008-2009  Justin Maggard
3 *
4 *  This program is free software; you can redistribute it and/or modify
5 *  it under the terms of the GNU General Public License as published by
6 *  the Free Software Foundation; either version 2 of the License, or
7 *  (at your option) any later version.
8 *
9 *  This program is distributed in the hope that it will be useful,
10 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 *  GNU General Public License for more details.
13 *
14 *  You should have received a copy of the GNU General Public License
15 *  along with this program; if not, write to the Free Software
16 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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 <sys/stat.h>
26#include <sys/time.h>
27#include <sys/resource.h>
28
29#include "config.h"
30
31#ifdef ENABLE_NLS
32#include <libintl.h>
33#endif
34#include <sqlite3.h>
35
36#include "upnpglobalvars.h"
37#include "metadata.h"
38#include "playlist.h"
39#include "utils.h"
40#include "sql.h"
41#include "scanner.h"
42#include "log.h"
43
44int valid_cache = 0;
45
46struct virtual_item
47{
48	int  objectID;
49	char parentID[64];
50	char name[256];
51};
52
53sqlite_int64
54get_next_available_id(const char * table, const char * parentID)
55{
56		char * sql;
57		char **result;
58		int ret, rows;
59		sqlite_int64 objectID = 0;
60
61		asprintf(&sql, "SELECT OBJECT_ID from %s where ID = "
62		               "(SELECT max(ID) from %s where PARENT_ID = '%s')", table, table, parentID);
63		ret = sql_get_table(db, sql, &result, &rows, NULL);
64		if( rows )
65		{
66			objectID = strtoll(strrchr(result[1], '$')+1, NULL, 16) + 1;
67		}
68		sqlite3_free_table(result);
69		free(sql);
70
71		return objectID;
72}
73
74long long int
75insert_container(const char * item, const char * rootParent, const char * refID, const char *class,
76                 const char *artist, const char *genre, const char *album_art)
77{
78	char **result;
79	char **result2;
80	char *sql;
81	int cols, rows, ret;
82	int parentID = 0, objectID = 0;
83	sqlite_int64 detailID = 0;
84
85	sql = sqlite3_mprintf("SELECT OBJECT_ID from OBJECTS"
86	                      " where PARENT_ID = '%s'"
87			      " and NAME = '%q'"
88	                      " and CLASS = 'container.%s' limit 1",
89	                      rootParent, item, class);
90	ret = sql_get_table(db, sql, &result, &rows, &cols);
91	sqlite3_free(sql);
92	if( cols )
93	{
94		parentID = strtol(rindex(result[1], '$')+1, NULL, 16);
95		objectID = get_next_available_id("OBJECTS", result[1]);
96	}
97	else
98	{
99		parentID = get_next_available_id("OBJECTS", rootParent);
100		if( refID )
101		{
102			sql = sqlite3_mprintf("SELECT DETAIL_ID from OBJECTS where OBJECT_ID = %Q", refID);
103			ret = sql_get_table(db, sql, &result2, &rows, NULL);
104			if( ret == SQLITE_OK )
105			{
106				if( rows )
107				{
108					detailID = strtoll(result2[1], NULL, 10);
109				}
110				sqlite3_free_table(result2);
111			}
112			sqlite3_free(sql);
113		}
114		if( !detailID )
115		{
116			detailID = GetFolderMetadata(item, NULL, artist, genre, album_art);
117		}
118		ret = sql_exec(db, "INSERT into OBJECTS"
119		                   " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) "
120		                   "VALUES"
121		                   " ('%s$%X', '%s', %Q, %lld, 'container.%s', '%q')",
122		                   rootParent, parentID, rootParent, refID, detailID, class, item);
123	}
124	sqlite3_free_table(result);
125
126	return (long long)parentID<<32|objectID;
127}
128
129void
130insert_containers(const char * name, const char *path, const char * refID, const char * class, long unsigned int detailID)
131{
132	char *sql;
133	char **result;
134	int ret;
135	int cols, row;
136	sqlite_int64 container;
137
138	if( strstr(class, "imageItem") )
139	{
140		char *date = NULL, *cam = NULL;
141		char date_taken[13], camera[64];
142		static struct virtual_item last_date;
143		static struct virtual_item last_cam;
144		static struct virtual_item last_camdate;
145		static sqlite_int64 last_all_objectID = 0;
146
147		asprintf(&sql, "SELECT DATE, CREATOR from DETAILS where ID = %lu", detailID);
148		ret = sql_get_table(db, sql, &result, &row, &cols);
149		free(sql);
150		if( ret == SQLITE_OK )
151		{
152			date = result[2];
153			cam = result[3];
154		}
155
156		if( date )
157		{
158			strncpy(date_taken, date, 10);
159			date_taken[10] = '\0';
160		}
161		else
162		{
163			strcpy(date_taken, _("Unknown Date"));
164		}
165		if( valid_cache && strcmp(last_date.name, date_taken) == 0 )
166		{
167			last_date.objectID++;
168			//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
169		}
170		else
171		{
172			container = insert_container(date_taken, "3$12", NULL, "album.photoAlbum", NULL, NULL, NULL);
173			sprintf(last_date.parentID, "3$12$%llX", container>>32);
174			last_date.objectID = (int)container;
175			strcpy(last_date.name, date_taken);
176			//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached date item: %s/%s/%X\n", last_date.name, last_date.parentID, last_date.objectID);
177		}
178		sql_exec(db, "INSERT into OBJECTS"
179		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
180		             "VALUES"
181		             " ('%s$%X', '%s', '%s', '%s', %lu, %Q)",
182		             last_date.parentID, last_date.objectID, last_date.parentID, refID, class, detailID, name);
183
184		if( cam )
185		{
186			strncpy(camera, cam, 63);
187			camera[63] = '\0';
188		}
189		else
190		{
191			strcpy(camera, _("Unknown Camera"));
192		}
193		if( !valid_cache || strcmp(camera, last_cam.name) != 0 )
194		{
195			container = insert_container(camera, "3$13", NULL, "storageFolder", NULL, NULL, NULL);
196			sprintf(last_cam.parentID, "3$13$%llX", container>>32);
197			strncpy(last_cam.name, camera, 255);
198			last_camdate.name[0] = '\0';
199		}
200		if( valid_cache && strcmp(last_camdate.name, date_taken) == 0 )
201		{
202			last_camdate.objectID++;
203			//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);
204		}
205		else
206		{
207			container = insert_container(date_taken, last_cam.parentID, NULL, "album.photoAlbum", NULL, NULL, NULL);
208			sprintf(last_camdate.parentID, "%s$%llX", last_cam.parentID, container>>32);
209			last_camdate.objectID = (int)container;
210			strcpy(last_camdate.name, date_taken);
211			//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);
212		}
213		sql_exec(db, "INSERT into OBJECTS"
214		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
215		             "VALUES"
216		             " ('%s$%X', '%s', '%s', '%s', %lu, %Q)",
217		             last_camdate.parentID, last_camdate.objectID, last_camdate.parentID, refID, class, detailID, name);
218		/* All Images */
219		if( !last_all_objectID )
220		{
221			last_all_objectID = get_next_available_id("OBJECTS", "3$11");
222		}
223		sql_exec(db, "INSERT into OBJECTS"
224		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
225		             "VALUES"
226		             " ('3$11$%llX', '3$11', '%s', '%s', %lu, %Q)",
227		             last_all_objectID++, refID, class, detailID, name);
228	}
229	else if( strstr(class, "audioItem") )
230	{
231		asprintf(&sql, "SELECT ALBUM, CREATOR, GENRE, ALBUM_ART from DETAILS where ID = %lu", detailID);
232		ret = sql_get_table(db, sql, &result, &row, &cols);
233		free(sql);
234		if( ret != SQLITE_OK )
235			return;
236		if( !row )
237		{
238			sqlite3_free_table(result);
239			return;
240		}
241		char *album = result[4], *artist = result[5], *genre = result[6];
242		char *album_art = result[7];
243		static struct virtual_item last_album;
244		static struct virtual_item last_artist;
245		static struct virtual_item last_artistAlbum;
246		static struct virtual_item last_artistAlbumAll;
247		static struct virtual_item last_genre;
248		static struct virtual_item last_genreArtist;
249		static struct virtual_item last_genreArtistAll;
250		static sqlite_int64 last_all_objectID = 0;
251
252		if( album )
253		{
254			if( valid_cache && strcmp(album, last_album.name) == 0 )
255			{
256				last_album.objectID++;
257				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
258			}
259			else
260			{
261				strcpy(last_album.name, album);
262				container = insert_container(album, "1$7", NULL, "album.musicAlbum", artist, genre, album_art);
263				sprintf(last_album.parentID, "1$7$%llX", container>>32);
264				last_album.objectID = (int)container;
265				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached album item: %s/%s/%X\n", last_album.name, last_album.parentID, last_album.objectID);
266			}
267			sql_exec(db, "INSERT into OBJECTS"
268			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
269			             "VALUES"
270			             " ('%s$%X', '%s', '%s', '%s', %lu, %Q)",
271			             last_album.parentID, last_album.objectID, last_album.parentID, refID, class, detailID, name);
272		}
273		if( artist )
274		{
275			if( !valid_cache || strcmp(artist, last_artist.name) != 0 )
276			{
277				container = insert_container(artist, "1$6", NULL, "person.musicArtist", NULL, genre, NULL);
278				sprintf(last_artist.parentID, "1$6$%llX", container>>32);
279				strcpy(last_artist.name, artist);
280				last_artistAlbum.name[0] = '\0';
281				/* Add this file to the "- All Albums -" container as well */
282				container = insert_container(_("- All Albums -"), last_artist.parentID, NULL, "storageFolder", artist, genre, NULL);
283				sprintf(last_artistAlbumAll.parentID, "%s$%llX", last_artist.parentID, container>>32);
284				last_artistAlbumAll.objectID = (int)container;
285			}
286			else
287			{
288				last_artistAlbumAll.objectID++;
289			}
290			if( valid_cache && strcmp(album?album:_("Unknown Album"), last_artistAlbum.name) == 0 )
291			{
292				last_artistAlbum.objectID++;
293				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Using last artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
294			}
295			else
296			{
297				container = insert_container(album?album:_("Unknown Album"), last_artist.parentID, album?last_album.parentID:NULL, "album.musicAlbum", artist, genre, album_art);
298				sprintf(last_artistAlbum.parentID, "%s$%llX", last_artist.parentID, container>>32);
299				last_artistAlbum.objectID = (int)container;
300				strcpy(last_artistAlbum.name, album?album:_("Unknown Album"));
301				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached artist/album item: %s/%s/%X\n", last_artist.name, last_artist.parentID, last_artist.objectID);
302			}
303			sql_exec(db, "INSERT into OBJECTS"
304			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
305			             "VALUES"
306			             " ('%s$%X', '%s', '%s', '%s', %lu, %Q)",
307			             last_artistAlbum.parentID, last_artistAlbum.objectID, last_artistAlbum.parentID, refID, class, detailID, name);
308			sql_exec(db, "INSERT into OBJECTS"
309			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
310			             "VALUES"
311			             " ('%s$%X', '%s', '%s', '%s', %lu, %Q)",
312			             last_artistAlbumAll.parentID, last_artistAlbumAll.objectID, last_artistAlbumAll.parentID, refID, class, detailID, name);
313		}
314		if( genre )
315		{
316			if( !valid_cache || strcmp(genre, last_genre.name) != 0 )
317			{
318				container = insert_container(genre, "1$5", NULL, "genre.musicGenre", NULL, NULL, NULL);
319				sprintf(last_genre.parentID, "1$5$%llX", container>>32);
320				strcpy(last_genre.name, genre);
321				last_genreArtist.name[0] = '\0';
322				/* Add this file to the "- All Artists -" container as well */
323				container = insert_container(_("- All Artists -"), last_genre.parentID, NULL, "storageFolder", NULL, genre, NULL);
324				sprintf(last_genreArtistAll.parentID, "%s$%llX", last_genre.parentID, container>>32);
325				last_genreArtistAll.objectID = (int)container;
326			}
327			else
328			{
329				last_genreArtistAll.objectID++;
330			}
331			if( valid_cache && strcmp(artist?artist:_("Unknown Artist"), last_genreArtist.name) == 0 )
332			{
333				last_genreArtist.objectID++;
334			}
335			else
336			{
337				container = insert_container(artist?artist:_("Unknown Artist"), last_genre.parentID, artist?last_artist.parentID:NULL, "person.musicArtist", NULL, genre, NULL);
338				sprintf(last_genreArtist.parentID, "%s$%llX", last_genre.parentID, container>>32);
339				last_genreArtist.objectID = (int)container;
340				strcpy(last_genreArtist.name, artist?artist:_("Unknown Artist"));
341				//DEBUG DPRINTF(E_DEBUG, L_SCANNER, "Creating cached genre/artist item: %s/%s/%X\n", last_genreArtist.name, last_genreArtist.parentID, last_genreArtist.objectID);
342			}
343			sql_exec(db, "INSERT into OBJECTS"
344			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
345			             "VALUES"
346			             " ('%s$%X', '%s', '%s', '%s', %lu, %Q)",
347			             last_genreArtist.parentID, last_genreArtist.objectID, last_genreArtist.parentID, refID, class, detailID, name);
348			sql_exec(db, "INSERT into OBJECTS"
349			             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
350			             "VALUES"
351			             " ('%s$%X', '%s', '%s', '%s', %lu, %Q)",
352			             last_genreArtistAll.parentID, last_genreArtistAll.objectID, last_genreArtistAll.parentID, refID, class, detailID, name);
353		}
354		/* All Music */
355		if( !last_all_objectID )
356		{
357			last_all_objectID = get_next_available_id("OBJECTS", "1$4");
358		}
359		sql_exec(db, "INSERT into OBJECTS"
360		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
361		             "VALUES"
362		             " ('1$4$%llX', '1$4', '%s', '%s', %lu, %Q)",
363		             last_all_objectID++, refID, class, detailID, name);
364	}
365	else if( strstr(class, "videoItem") )
366	{
367		static sqlite_int64 last_all_objectID = 0;
368
369		/* All Videos */
370		if( !last_all_objectID )
371		{
372			last_all_objectID = get_next_available_id("OBJECTS", "2$8");
373		}
374		sql_exec(db, "INSERT into OBJECTS"
375		             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
376		             "VALUES"
377		             " ('2$8$%llX', '2$8', '%s', '%s', %lu, %Q)",
378		             last_all_objectID++, refID, class, detailID, name);
379		return;
380	}
381	else
382	{
383		return;
384	}
385	sqlite3_free_table(result);
386	valid_cache = 1;
387}
388
389int
390insert_directory(const char * name, const char * path, const char * base, const char * parentID, int objectID)
391{
392	char * sql;
393	int rows, found = 0;
394	sqlite_int64 detailID = 0;
395	char * refID = NULL;
396	char class[] = "container.storageFolder";
397	char * id_buf = NULL;
398	char * parent_buf = NULL;
399	char **result;
400	char *dir = NULL;
401	static char last_found[256] = "-1";
402
403	if( strcmp(base, BROWSEDIR_ID) != 0 )
404		asprintf(&refID, "%s%s$%X", BROWSEDIR_ID, parentID, objectID);
405
406	if( refID )
407	{
408 		dir = strdup(path);
409		dir = dirname(dir);
410		asprintf(&id_buf, "%s%s$%X", base, parentID, objectID);
411		asprintf(&parent_buf, "%s%s", base, parentID);
412		while( !found )
413		{
414			if( strcmp(id_buf, last_found) == 0 )
415				break;
416			if( sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", id_buf) > 0 )
417			{
418				strcpy(last_found, id_buf);
419				break;
420			}
421			/* Does not exist.  Need to create, and may need to create parents also */
422			sql = sqlite3_mprintf("SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%s'", refID);
423			if( (sql_get_table(db, sql, &result, &rows, NULL) == SQLITE_OK) && rows )
424			{
425				detailID = atoi(result[1]);
426			}
427			sqlite3_free_table(result);
428			sqlite3_free(sql);
429			sql_exec(db, "INSERT into OBJECTS"
430			             " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) "
431			             "VALUES"
432			             " ('%s', '%s', %Q, '%lld', '%s', '%q')",
433			             id_buf, parent_buf, refID, detailID, class, rindex(dir, '/')+1);
434			if( rindex(id_buf, '$') )
435				*rindex(id_buf, '$') = '\0';
436			if( rindex(parent_buf, '$') )
437				*rindex(parent_buf, '$') = '\0';
438			if( rindex(refID, '$') )
439				*rindex(refID, '$') = '\0';
440			dir = dirname(dir);
441		}
442		free(refID);
443		free(parent_buf);
444		free(id_buf);
445		free(dir);
446		return 1;
447	}
448
449	detailID = GetFolderMetadata(name, path, NULL, NULL, NULL);
450	sql_exec(db, "INSERT into OBJECTS"
451	             " (OBJECT_ID, PARENT_ID, REF_ID, DETAIL_ID, CLASS, NAME) "
452	             "VALUES"
453	             " ('%s%s$%X', '%s%s', %Q, '%lld', '%s', '%q')",
454	             base, parentID, objectID, base, parentID, refID, detailID, class, name);
455	if( refID )
456		free(refID);
457
458	return -1;
459}
460
461int
462insert_file(char * name, const char * path, const char * parentID, int object)
463{
464	char class[32];
465	char objectID[64];
466	unsigned long int detailID = 0;
467	char base[8];
468	char * typedir_parentID;
469	int typedir_objectID;
470	char * baseid;
471	char * orig_name = NULL;
472
473	if( is_image(name) )
474	{
475		strcpy(base, IMAGE_DIR_ID);
476		strcpy(class, "item.imageItem.photo");
477		detailID = GetImageMetadata(path, name);
478	}
479	else if( is_video(name) )
480	{
481 		orig_name = strdup(name);
482		strcpy(base, VIDEO_DIR_ID);
483		strcpy(class, "item.videoItem");
484		detailID = GetVideoMetadata(path, name);
485		if( !detailID )
486			strcpy(name, orig_name);
487	}
488	else if( is_playlist(name) )
489	{
490		if( insert_playlist(path, name) == 0 )
491			return 1;
492	}
493	if( !detailID && is_audio(name) )
494	{
495		strcpy(base, MUSIC_DIR_ID);
496		strcpy(class, "item.audioItem.musicTrack");
497		detailID = GetAudioMetadata(path, name);
498	}
499	if( orig_name )
500		free(orig_name);
501	if( !detailID )
502	{
503		DPRINTF(E_WARN, L_SCANNER, "Unsuccessful getting details for %s!\n", path);
504		return -1;
505	}
506
507	sprintf(objectID, "%s%s$%X", BROWSEDIR_ID, parentID, object);
508
509	sql_exec(db, "INSERT into OBJECTS"
510	             " (OBJECT_ID, PARENT_ID, CLASS, DETAIL_ID, NAME) "
511	             "VALUES"
512	             " ('%s', '%s%s', '%s', %lu, '%q')",
513	             objectID, BROWSEDIR_ID, parentID, class, detailID, name);
514
515	if( *parentID )
516	{
517		typedir_objectID = 0;
518		typedir_parentID = strdup(parentID);
519		baseid = rindex(typedir_parentID, '$');
520		if( baseid )
521		{
522			sscanf(baseid+1, "%X", &typedir_objectID);
523			*baseid = '\0';
524		}
525		insert_directory(name, path, base, typedir_parentID, typedir_objectID);
526		free(typedir_parentID);
527	}
528	sql_exec(db, "INSERT into OBJECTS"
529	             " (OBJECT_ID, PARENT_ID, REF_ID, CLASS, DETAIL_ID, NAME) "
530	             "VALUES"
531	             " ('%s%s$%X', '%s%s', '%s', '%s', %lu, '%q')",
532	             base, parentID, object, base, parentID, objectID, class, detailID, name);
533
534	insert_containers(name, path, objectID, class, detailID);
535	return 0;
536}
537
538int
539CreateDatabase(void)
540{
541	int ret, i;
542	const char * containers[] = {      "0","-1", "root",
543					   "1", "0", _("Music"),
544					 "1$4", "1", _("All Music"),
545					 "1$5", "1", _("Genre"),
546					 "1$6", "1", _("Artist"),
547					 "1$7", "1", _("Album"),
548				  MUSIC_DIR_ID, "1", _("Folders"),
549				MUSIC_PLIST_ID, "1", _("Playlists"),
550					   "2", "0", _("Video"),
551					 "2$8", "2", _("All Video"),
552				  VIDEO_DIR_ID, "2", _("Folders"),
553					   "3", "0", _("Pictures"),
554					"3$11", "3", _("All Pictures"),
555					"3$12", "3", _("Date Taken"),
556					"3$13", "3", _("Camera"),
557				  IMAGE_DIR_ID, "3", _("Folders"),
558					  "64", "0", _("Browse Folders"),
559					0 };
560
561	ret = sql_exec(db, "CREATE TABLE OBJECTS ( "
562					"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
563					"OBJECT_ID TEXT UNIQUE NOT NULL, "
564					"PARENT_ID TEXT NOT NULL, "
565					"REF_ID TEXT DEFAULT NULL, "
566					"CLASS TEXT NOT NULL, "
567					"DETAIL_ID INTEGER DEFAULT NULL, "
568					"NAME TEXT DEFAULT NULL"
569					");");
570	if( ret != SQLITE_OK )
571		goto sql_failed;
572	ret = sql_exec(db, "CREATE TABLE DETAILS ( "
573					"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
574					"PATH TEXT DEFAULT NULL, "
575					"SIZE INTEGER, "
576					"TITLE TEXT COLLATE NOCASE, "
577					"DURATION TEXT, "
578					"BITRATE INTEGER, "
579					"SAMPLERATE INTEGER, "
580					"ARTIST TEXT COLLATE NOCASE, "
581					"ALBUM TEXT COLLATE NOCASE, "
582					"GENRE TEXT COLLATE NOCASE, "
583					"COMMENT TEXT, "
584					"CHANNELS INTEGER, "
585					"TRACK INTEGER, "
586					"DATE DATE, "
587					"RESOLUTION TEXT, "
588					"THUMBNAIL BOOL DEFAULT 0, "
589					"CREATOR TEXT COLLATE NOCASE, "
590					"DLNA_PN TEXT, "
591					"MIME TEXT, "
592					"ALBUM_ART INTEGER DEFAULT 0, "
593					"DISC INTEGER, "
594					"TIMESTAMP INTEGER"
595					")");
596	if( ret != SQLITE_OK )
597		goto sql_failed;
598	ret = sql_exec(db, "CREATE TABLE ALBUM_ART ( "
599					"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
600					"PATH TEXT NOT NULL"
601					")");
602	if( ret != SQLITE_OK )
603		goto sql_failed;
604	ret = sql_exec(db, "CREATE TABLE CAPTIONS ("
605					"ID INTEGER PRIMARY KEY, "
606					"PATH TEXT NOT NULL"
607					")");
608	if( ret != SQLITE_OK )
609		goto sql_failed;
610	ret = sql_exec(db, "CREATE TABLE PLAYLISTS ("
611					"ID INTEGER PRIMARY KEY AUTOINCREMENT, "
612					"NAME TEXT NOT NULL, "
613					"PATH TEXT NOT NULL, "
614					"ITEMS INTEGER DEFAULT 0, "
615					"FOUND INTEGER DEFAULT 0"
616					")");
617	if( ret != SQLITE_OK )
618		goto sql_failed;
619	ret = sql_exec(db, "CREATE TABLE SETTINGS ("
620					"UPDATE_ID INTEGER PRIMARY KEY DEFAULT 0, "
621					"FLAGS INTEGER DEFAULT 0"
622					")");
623	if( ret != SQLITE_OK )
624		goto sql_failed;
625	ret = sql_exec(db, "INSERT into SETTINGS values (0, 0)");
626	if( ret != SQLITE_OK )
627		goto sql_failed;
628	for( i=0; containers[i]; i=i+3 )
629	{
630		ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)"
631		                   " values "
632		                   "('%s', '%s', %lld, 'container.storageFolder', '%s')",
633		                   containers[i], containers[i+1], GetFolderMetadata(containers[i+2], NULL, NULL, NULL, NULL), containers[i+2]);
634		if( ret != SQLITE_OK )
635			goto sql_failed;
636	}
637	sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);");
638	sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);");
639	sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);");
640	sql_exec(db, "create INDEX IDX_OBJECTS_CLASS ON OBJECTS(CLASS);");
641	sql_exec(db, "create INDEX IDX_DETAILS_PATH ON DETAILS(PATH);");
642	sql_exec(db, "create INDEX IDX_DETAILS_ID ON DETAILS(ID);");
643	sql_exec(db, "create INDEX IDX_ALBUM_ART ON ALBUM_ART(ID);");
644	sql_exec(db, "create INDEX IDX_SCANNER_OPT ON OBJECTS(PARENT_ID, NAME, OBJECT_ID);");
645
646sql_failed:
647	if( ret != SQLITE_OK )
648		fprintf(stderr, "Error creating SQLite3 database!\n");
649	return (ret != SQLITE_OK);
650}
651
652int
653filter_audio(const struct dirent *d)
654{
655	return ( (*d->d_name != '.') &&
656	         ((d->d_type == DT_DIR) ||
657	          (d->d_type == DT_LNK) ||
658	          (d->d_type == DT_UNKNOWN) ||
659		  ((d->d_type == DT_REG) &&
660		   (is_audio(d->d_name) ||
661	            is_playlist(d->d_name)
662		   )
663	       ) ));
664}
665
666int
667filter_video(const struct dirent *d)
668{
669	return ( (*d->d_name != '.') &&
670	         ((d->d_type == DT_DIR) ||
671	          (d->d_type == DT_LNK) ||
672	          (d->d_type == DT_UNKNOWN) ||
673		  ((d->d_type == DT_REG) &&
674		   is_video(d->d_name) )
675	       ) );
676}
677
678int
679filter_images(const struct dirent *d)
680{
681	return ( (*d->d_name != '.') &&
682	         ((d->d_type == DT_DIR) ||
683	          (d->d_type == DT_LNK) ||
684	          (d->d_type == DT_UNKNOWN) ||
685		  ((d->d_type == DT_REG) &&
686		   is_image(d->d_name) )
687	       ) );
688}
689
690int
691filter_media(const struct dirent *d)
692{
693	return ( (*d->d_name != '.') &&
694	         ((d->d_type == DT_DIR) ||
695	          (d->d_type == DT_LNK) ||
696	          (d->d_type == DT_UNKNOWN) ||
697	          ((d->d_type == DT_REG) &&
698	           (is_image(d->d_name) ||
699	            is_audio(d->d_name) ||
700	            is_video(d->d_name) ||
701	            is_playlist(d->d_name)
702	           )
703	       ) ));
704}
705
706/* Foxconn, Michael */
707//for restrict the max number of media files.
708#define MAX_FILE_NUMBER 25000
709
710
711void
712ScanDirectory(const char * dir, const char * parent, enum media_types dir_type)
713{
714	struct dirent **namelist;
715	int i, n, startID=0;
716	char parent_id[PATH_MAX];
717	char full_path[PATH_MAX];
718	char * name = NULL;
719	static long long unsigned int fileno = 0;
720	enum file_types type;
721
722	setlocale(LC_COLLATE, "");
723	if( chdir(dir) != 0 )
724		return;
725    /* Foxconn, Michael,
726     * should we restrict the scanned file number with limits?
727     */
728    //printf("%s: %d\n", __FUNCTION__, fileno);
729    if(fileno >= MAX_FILE_NUMBER) // stop scanner
730		return;
731
732	DPRINTF(parent?E_INFO:E_WARN, L_SCANNER, _("Scanning %s\n"), dir);
733	switch( dir_type )
734	{
735		case ALL_MEDIA:
736			n = scandir(".", &namelist, filter_media, alphasort);
737			break;
738		case AUDIO_ONLY:
739			n = scandir(".", &namelist, filter_audio, alphasort);
740			break;
741		case VIDEO_ONLY:
742			n = scandir(".", &namelist, filter_video, alphasort);
743			break;
744		case IMAGES_ONLY:
745			n = scandir(".", &namelist, filter_images, alphasort);
746			break;
747		default:
748			n = -1;
749			break;
750	}
751	if (n < 0) {
752		fprintf(stderr, "Error scanning %s [scandir]\n", dir);
753		return;
754	}
755
756	if( !parent )
757	{
758		startID = get_next_available_id("OBJECTS", BROWSEDIR_ID);
759	}
760
761	for (i=0; i < n; i++)
762	{
763		type = TYPE_UNKNOWN;
764		sprintf(full_path, "%s/%s", dir, namelist[i]->d_name);
765		name = escape_tag(namelist[i]->d_name);
766		if( namelist[i]->d_type == DT_DIR )
767		{
768			type = TYPE_DIR;
769		}
770		else if( namelist[i]->d_type == DT_REG )
771		{
772			type = TYPE_FILE;
773		}
774		else
775		{
776			type = resolve_unknown_type(full_path, dir_type);
777		}
778		if( type == TYPE_DIR )
779		{
780			insert_directory(name?name:namelist[i]->d_name, full_path, BROWSEDIR_ID, (parent ? parent:""), i+startID);
781			sprintf(parent_id, "%s$%X", (parent ? parent:""), i+startID);
782			ScanDirectory(full_path, parent_id, dir_type);
783		}
784		else if( type == TYPE_FILE )
785		{
786			if( insert_file(name?name:namelist[i]->d_name, full_path, (parent ? parent:""), i+startID) == 0 )
787            {
788				fileno++;
789                /* Foxconn, Michael, should we restrict the scanned file number with limits? */
790                if(fileno >= MAX_FILE_NUMBER){
791                    /*stop scanner*/
792                    n = 0;
793                }
794
795            }
796		}
797		if( name )
798			free(name);
799		free(namelist[i]);
800	}
801	free(namelist);
802	if( parent ) /*Foxconn, Michael, recursively called.*/
803	{
804		chdir(dirname((char*)dir));
805	}
806	else
807	{
808		DPRINTF(E_WARN, L_SCANNER, _("Scanning %s finished (%llu files)!\n"), dir, fileno);
809        printf("Scanning %s finished (%llu files)!\n",  dir, fileno);
810	}
811}
812
813void
814start_scanner()
815{
816	struct media_dir_s * media_path = media_dirs;
817
818	if (setpriority(PRIO_PROCESS, 0, 15) == -1)
819		DPRINTF(E_WARN, L_INOTIFY,  "Failed to reduce scanner thread priority\n");
820
821#ifdef READYNAS
822	FILE * flag = fopen("/ramfs/.upnp-av_scan", "w");
823	if( flag )
824		fclose(flag);
825#endif
826	freopen("/dev/null", "a", stderr);
827	while( media_path )
828	{
829		ScanDirectory(media_path->path, NULL, media_path->type);
830		media_path = media_path->next;
831	}
832	freopen("/proc/self/fd/2", "a", stderr);
833#ifdef READYNAS
834	if( access("/ramfs/.rescan_done", F_OK) == 0 )
835		system("/bin/sh /ramfs/.rescan_done");
836	unlink("/ramfs/.upnp-av_scan");
837#endif
838	/* Create this index after scanning, so it doesn't slow down the scanning process.
839	 * This index is very useful for large libraries used with an XBox360 (or any
840	 * client that uses UPnPSearch on large containers). */
841	sql_exec(db, "create INDEX IDX_SEARCH_OPT ON OBJECTS(OBJECT_ID, CLASS, DETAIL_ID);");
842
843	fill_playlists();
844
845	//JM: Set up a db version number, so we know if we need to rebuild due to a new structure.
846	sql_exec(db, "pragma user_version = %d;", DB_VERSION);
847}
848