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