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