directory.c 31.3 KB
Newer Older
Warren Dukes's avatar
Warren Dukes committed
1
/* the Music Player Daemon (MPD)
2
 * (c)2003-2006 by Warren Dukes (warren.dukes@gmail.com)
Warren Dukes's avatar
Warren Dukes committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 * This project's homepage is: http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "directory.h"

#include "command.h"
#include "conf.h"
23
#include "dbUtils.h"
24
#include "interface.h"
25 26 27 28
#include "list.h"
#include "listen.h"
#include "log.h"
#include "ls.h"
Warren Dukes's avatar
Warren Dukes committed
29
#include "mpd_types.h"
30 31 32
#include "path.h"
#include "player.h"
#include "playlist.h"
Warren Dukes's avatar
Warren Dukes committed
33
#include "sig_handlers.h"
34
#include "stats.h"
35
#include "tagTracker.h"
36 37
#include "utils.h"
#include "volume.h"
Warren Dukes's avatar
Warren Dukes committed
38

39
#include <sys/wait.h>
Warren Dukes's avatar
Warren Dukes committed
40 41
#include <dirent.h>
#include <errno.h>
42
#include <assert.h>
43
#include <libgen.h>
Warren Dukes's avatar
Warren Dukes committed
44 45 46 47 48 49 50 51 52 53

#define DIRECTORY_DIR		"directory: "
#define DIRECTORY_MTIME		"mtime: "
#define DIRECTORY_BEGIN		"begin: "
#define DIRECTORY_END		"end: "
#define DIRECTORY_INFO_BEGIN	"info_begin"
#define DIRECTORY_INFO_END	"info_end"
#define DIRECTORY_MPD_VERSION	"mpd_version: "
#define DIRECTORY_FS_CHARSET	"fs_charset: "

54 55 56 57
#define DIRECTORY_UPDATE_EXIT_NOUPDATE  0
#define DIRECTORY_UPDATE_EXIT_UPDATE    1
#define DIRECTORY_UPDATE_EXIT_ERROR     2

58 59 60 61
#define DIRECTORY_RETURN_NOUPDATE       0
#define DIRECTORY_RETURN_UPDATE         1
#define DIRECTORY_RETURN_ERROR         -1

62
static Directory *mp3rootDirectory;
Warren Dukes's avatar
Warren Dukes committed
63

64
static time_t directory_dbModTime;
Warren Dukes's avatar
Warren Dukes committed
65

66
static volatile int directory_updatePid;
67

68
static volatile int directory_reReadDB;
69

70
static volatile mpd_uint16 directory_updateJobId;
Warren Dukes's avatar
Warren Dukes committed
71

Avuton Olrich's avatar
Avuton Olrich committed
72
static DirectoryList *newDirectoryList();
Warren Dukes's avatar
Warren Dukes committed
73

Avuton Olrich's avatar
Avuton Olrich committed
74
static int addToDirectory(Directory * directory, char *shortname, char *name);
Warren Dukes's avatar
Warren Dukes committed
75

76
static void freeDirectoryList(DirectoryList * list);
Warren Dukes's avatar
Warren Dukes committed
77

78
static void freeDirectory(Directory * directory);
Warren Dukes's avatar
Warren Dukes committed
79

80
static int exploreDirectory(Directory * directory);
Warren Dukes's avatar
Warren Dukes committed
81

82
static int updateDirectory(Directory * directory);
Warren Dukes's avatar
Warren Dukes committed
83

84
static void deleteEmptyDirectoriesInDirectory(Directory * directory);
Warren Dukes's avatar
Warren Dukes committed
85

Avuton Olrich's avatar
Avuton Olrich committed
86
static void removeSongFromDirectory(Directory * directory, char *shortname);
87

Avuton Olrich's avatar
Avuton Olrich committed
88 89
static int addSubDirectoryToDirectory(Directory * directory, char *shortname,
				      char *name, struct stat *st);
90

Avuton Olrich's avatar
Avuton Olrich committed
91
static Directory *getDirectoryDetails(char *name, char **shortname);
Warren Dukes's avatar
Warren Dukes committed
92

Avuton Olrich's avatar
Avuton Olrich committed
93
static Directory *getDirectory(char *name);
94

Avuton Olrich's avatar
Avuton Olrich committed
95 96
static Song *getSongDetails(char *file, char **shortnameRet,
			    Directory ** directoryRet);
97

Avuton Olrich's avatar
Avuton Olrich committed
98
static int updatePath(char *utf8path);
99

100
static void sortDirectory(Directory * directory);
101

102
static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device);
103

104
static int statDirectory(Directory * dir);
105

Eric Wong's avatar
Eric Wong committed
106
static char *getDbFile(void)
Avuton Olrich's avatar
Avuton Olrich committed
107 108
{
	ConfigParam *param = parseConfigFilePath(CONF_DB_FILE, 1);
109 110 111 112 113 114 115

	assert(param);
	assert(param->value);

	return param->value;
}

Eric Wong's avatar
Eric Wong committed
116
static void clearUpdatePid(void)
Avuton Olrich's avatar
Avuton Olrich committed
117
{
118 119 120
	directory_updatePid = 0;
}

Eric Wong's avatar
Eric Wong committed
121
int isUpdatingDB(void)
Avuton Olrich's avatar
Avuton Olrich committed
122 123
{
	if (directory_updatePid > 0 || directory_reReadDB) {
124 125
		return directory_updateJobId;
	}
126 127 128
	return 0;
}

Avuton Olrich's avatar
Avuton Olrich committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
void directory_sigChldHandler(int pid, int status)
{
	if (directory_updatePid == pid) {
		if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) {
			ERROR("update process died from a "
			      "non-TERM signal: %i\n", WTERMSIG(status));
		} else if (!WIFSIGNALED(status)) {
			switch (WEXITSTATUS(status)) {
			case DIRECTORY_UPDATE_EXIT_UPDATE:
				directory_reReadDB = 1;
				DEBUG("directory_sigChldHandler: "
				      "updated db\n");
			case DIRECTORY_UPDATE_EXIT_NOUPDATE:
				DEBUG("directory_sigChldHandler: "
				      "update exited succesffully\n");
				break;
			default:
				ERROR("error updating db\n");
			}
148
		}
149
		clearUpdatePid();
150 151 152
	}
}

Eric Wong's avatar
Eric Wong committed
153
void readDirectoryDBIfUpdateIsFinished(void)
Avuton Olrich's avatar
Avuton Olrich committed
154 155
{
	if (directory_reReadDB && 0 == directory_updatePid) {
156 157
		DEBUG("readDirectoryDB since update finished successfully\n");
		readDirectoryDB();
158
		playlistVersionChange();
159 160 161 162
		directory_reReadDB = 0;
	}
}

163
int updateInit(int fd, List * pathList)
Avuton Olrich's avatar
Avuton Olrich committed
164 165
{
	if (directory_updatePid > 0) {
166
		commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating");
167 168 169
		return -1;
	}

170
	/* need to block CHLD signal, cause it can exit before we
Avuton Olrich's avatar
Avuton Olrich committed
171 172
	   even get a chance to assign directory_updatePID */
	blockSignals();
173
	directory_updatePid = fork();
Avuton Olrich's avatar
Avuton Olrich committed
174
	if (directory_updatePid == 0) {
175 176
		/* child */
		int dbUpdated = 0;
177
		clearPlayerPid();
178

179
		unblockSignals();
180

Warren Dukes's avatar
Warren Dukes committed
181
		finishSigHandlers();
182 183 184
		closeAllListenSockets();
		freeAllInterfaces();
		finishPlaylist();
185 186
		finishVolume();

Avuton Olrich's avatar
Avuton Olrich committed
187 188
		if (pathList) {
			ListNode *node = pathList->firstNode;
189

Avuton Olrich's avatar
Avuton Olrich committed
190 191
			while (node) {
				switch (updatePath(node->key)) {
192
				case 1:
Avuton Olrich's avatar
Avuton Olrich committed
193 194 195 196 197 198 199
					dbUpdated = 1;
					break;
				case 0:
					break;
				default:
					exit(DIRECTORY_UPDATE_EXIT_ERROR);
				}
200 201
				node = node->nextNode;
			}
Avuton Olrich's avatar
Avuton Olrich committed
202 203 204 205
		} else {
			if ((dbUpdated = updateDirectory(mp3rootDirectory)) < 0) {
				exit(DIRECTORY_UPDATE_EXIT_ERROR);
			}
206
		}
207

Avuton Olrich's avatar
Avuton Olrich committed
208 209
		if (!dbUpdated)
			exit(DIRECTORY_UPDATE_EXIT_NOUPDATE);
210

Avuton Olrich's avatar
Avuton Olrich committed
211
		/* ignore signals since we don't want them to corrupt the db */
212
		ignoreSignals();
Avuton Olrich's avatar
Avuton Olrich committed
213
		if (writeDirectoryDB() < 0) {
214
			exit(DIRECTORY_UPDATE_EXIT_ERROR);
215
		}
216
		exit(DIRECTORY_UPDATE_EXIT_UPDATE);
Avuton Olrich's avatar
Avuton Olrich committed
217
	} else if (directory_updatePid < 0) {
218
		unblockSignals();
219
		ERROR("updateInit: Problems forking()'ing\n");
220
		commandError(fd, ACK_ERROR_SYSTEM,
221
			     "problems trying to update");
222 223 224
		directory_updatePid = 0;
		return -1;
	}
225
	unblockSignals();
226

Warren Dukes's avatar
Warren Dukes committed
227
	directory_updateJobId++;
Avuton Olrich's avatar
Avuton Olrich committed
228 229
	if (directory_updateJobId > 1 << 15)
		directory_updateJobId = 1;
Warren Dukes's avatar
Warren Dukes committed
230
	DEBUG("updateInit: fork()'d update child for update job id %i\n",
Avuton Olrich's avatar
Avuton Olrich committed
231
	      (int)directory_updateJobId);
232
	fdprintf(fd, "updating_db: %i\n", (int)directory_updateJobId);
233 234 235 236

	return 0;
}

Avuton Olrich's avatar
Avuton Olrich committed
237 238
static DirectoryStat *newDirectoryStat(struct stat *st)
{
239
	DirectoryStat *ret = xmalloc(sizeof(DirectoryStat));
240 241 242 243 244
	ret->inode = st->st_ino;
	ret->device = st->st_dev;
	return ret;
}

Avuton Olrich's avatar
Avuton Olrich committed
245 246 247 248
static void freeDirectoryStatFromDirectory(Directory * dir)
{
	if (dir->stat)
		free(dir->stat);
249 250 251
	dir->stat = NULL;
}

252 253 254 255 256
static DirectoryList *newDirectoryList(void)
{
	return makeList((ListFreeDataFunc *) freeDirectory, 1);
}

Avuton Olrich's avatar
Avuton Olrich committed
257 258 259
static Directory *newDirectory(char *dirname, Directory * parent)
{
	Directory *directory;
Warren Dukes's avatar
Warren Dukes committed
260

261
	directory = xmalloc(sizeof(Directory));
Warren Dukes's avatar
Warren Dukes committed
262

Avuton Olrich's avatar
Avuton Olrich committed
263
	if (dirname && strlen(dirname))
264
		directory->path = xstrdup(dirname);
Avuton Olrich's avatar
Avuton Olrich committed
265 266
	else
		directory->path = NULL;
Warren Dukes's avatar
Warren Dukes committed
267 268
	directory->subDirectories = newDirectoryList();
	directory->songs = newSongList();
269 270
	directory->stat = NULL;
	directory->parent = parent;
Warren Dukes's avatar
Warren Dukes committed
271 272 273 274

	return directory;
}

Avuton Olrich's avatar
Avuton Olrich committed
275 276
static void freeDirectory(Directory * directory)
{
Warren Dukes's avatar
Warren Dukes committed
277 278
	freeDirectoryList(directory->subDirectories);
	freeSongList(directory->songs);
Avuton Olrich's avatar
Avuton Olrich committed
279 280
	if (directory->path)
		free(directory->path);
281
	freeDirectoryStatFromDirectory(directory);
Warren Dukes's avatar
Warren Dukes committed
282
	free(directory);
283
	/* this resets last dir returned */
Avuton Olrich's avatar
Avuton Olrich committed
284
	/*getDirectoryPath(NULL); */
Warren Dukes's avatar
Warren Dukes committed
285 286
}

Avuton Olrich's avatar
Avuton Olrich committed
287 288
static void freeDirectoryList(DirectoryList * directoryList)
{
Warren Dukes's avatar
Warren Dukes committed
289 290 291
	freeList(directoryList);
}

Avuton Olrich's avatar
Avuton Olrich committed
292 293 294 295 296 297
static void removeSongFromDirectory(Directory * directory, char *shortname)
{
	void *song;

	if (findInList(directory->songs, shortname, &song)) {
		LOG("removing: %s\n", getSongUrl((Song *) song));
298
		deleteFromList(directory->songs, shortname);
Warren Dukes's avatar
Warren Dukes committed
299 300 301
	}
}

Avuton Olrich's avatar
Avuton Olrich committed
302 303 304 305 306
static void deleteEmptyDirectoriesInDirectory(Directory * directory)
{
	ListNode *node = directory->subDirectories->firstNode;
	ListNode *nextNode;
	Directory *subDir;
Warren Dukes's avatar
Warren Dukes committed
307

Avuton Olrich's avatar
Avuton Olrich committed
308 309
	while (node) {
		subDir = (Directory *) node->data;
Warren Dukes's avatar
Warren Dukes committed
310 311
		deleteEmptyDirectoriesInDirectory(subDir);
		nextNode = node->nextNode;
Avuton Olrich's avatar
Avuton Olrich committed
312 313 314
		if (subDir->subDirectories->numberOfNodes == 0 &&
		    subDir->songs->numberOfNodes == 0) {
			deleteNodeFromList(directory->subDirectories, node);
Warren Dukes's avatar
Warren Dukes committed
315 316 317 318 319
		}
		node = nextNode;
	}
}

320 321 322 323 324
/* return values:
   -1 -> error
    0 -> no error, but nothing updated
    1 -> no error, and stuff updated
 */
Avuton Olrich's avatar
Avuton Olrich committed
325 326 327 328
static int updateInDirectory(Directory * directory, char *shortname, char *name)
{
	void *song;
	void *subDir;
329
	struct stat st;
Warren Dukes's avatar
Warren Dukes committed
330

Avuton Olrich's avatar
Avuton Olrich committed
331 332
	if (myStat(name, &st))
		return -1;
333

Avuton Olrich's avatar
Avuton Olrich committed
334 335
	if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) {
		if (0 == findInList(directory->songs, shortname, &song)) {
336
			addToDirectory(directory, shortname, name);
Avuton Olrich's avatar
Avuton Olrich committed
337 338 339 340 341
			return DIRECTORY_RETURN_UPDATE;
		} else if (st.st_mtime != ((Song *) song)->mtime) {
			LOG("updating %s\n", name);
			if (updateSongInfo((Song *) song) < 0) {
				removeSongFromDirectory(directory, shortname);
342
			}
Avuton Olrich's avatar
Avuton Olrich committed
343
			return 1;
Warren Dukes's avatar
Warren Dukes committed
344
		}
Avuton Olrich's avatar
Avuton Olrich committed
345 346 347
	} else if (S_ISDIR(st.st_mode)) {
		if (findInList
		    (directory->subDirectories, shortname, (void **)&subDir)) {
348
			freeDirectoryStatFromDirectory(subDir);
Avuton Olrich's avatar
Avuton Olrich committed
349 350 351 352 353
			((Directory *) subDir)->stat = newDirectoryStat(&st);
			return updateDirectory((Directory *) subDir);
		} else {
			return addSubDirectoryToDirectory(directory, shortname,
							  name, &st);
Warren Dukes's avatar
Warren Dukes committed
354 355 356 357 358 359
		}
	}

	return 0;
}

Warren Dukes's avatar
Warren Dukes committed
360 361 362 363 364
/* return values:
   -1 -> error
    0 -> no error, but nothing removed
    1 -> no error, and stuff removed
 */
Avuton Olrich's avatar
Avuton Olrich committed
365 366
static int removeDeletedFromDirectory(Directory * directory, DIR * dir)
{
Warren Dukes's avatar
Warren Dukes committed
367
	char cwd[2];
Avuton Olrich's avatar
Avuton Olrich committed
368 369 370 371 372 373 374 375 376
	struct dirent *ent;
	char *dirname = getDirectoryPath(directory);
	List *entList = makeList(free, 1);
	void *name;
	char *s;
	char *utf8;
	ListNode *node;
	ListNode *tmpNode;
	int ret = 0;
Warren Dukes's avatar
Warren Dukes committed
377 378 379

	cwd[0] = '.';
	cwd[1] = '\0';
Avuton Olrich's avatar
Avuton Olrich committed
380 381
	if (dirname == NULL)
		dirname = cwd;
Warren Dukes's avatar
Warren Dukes committed
382

Avuton Olrich's avatar
Avuton Olrich committed
383 384 385 386 387
	while ((ent = readdir(dir))) {
		if (ent->d_name[0] == '.')
			continue;	/* hide hidden stuff */
		if (strchr(ent->d_name, '\n'))
			continue;
Warren Dukes's avatar
Warren Dukes committed
388

389 390
		utf8 = fsCharsetToUtf8(ent->d_name);

Avuton Olrich's avatar
Avuton Olrich committed
391 392
		if (!utf8)
			continue;
393

Avuton Olrich's avatar
Avuton Olrich committed
394
		if (directory->path) {
395
			s = xmalloc(strlen(getDirectoryPath(directory))
Avuton Olrich's avatar
Avuton Olrich committed
396 397 398
				   + strlen(utf8) + 2);
			sprintf(s, "%s/%s", getDirectoryPath(directory), utf8);
		} else
399
			s = xstrdup(utf8);
400
		insertInList(entList, utf8, s);
Warren Dukes's avatar
Warren Dukes committed
401 402 403
	}

	node = directory->subDirectories->firstNode;
Avuton Olrich's avatar
Avuton Olrich committed
404
	while (node) {
Warren Dukes's avatar
Warren Dukes committed
405
		tmpNode = node->nextNode;
Avuton Olrich's avatar
Avuton Olrich committed
406 407 408
		if (findInList(entList, node->key, &name)) {
			if (!isDir((char *)name)) {
				LOG("removing directory: %s\n", (char *)name);
409
				deleteFromList(directory->subDirectories,
Avuton Olrich's avatar
Avuton Olrich committed
410 411
					       node->key);
				ret = 1;
Warren Dukes's avatar
Warren Dukes committed
412
			}
Avuton Olrich's avatar
Avuton Olrich committed
413
		} else {
414
			LOG("removing directory: %s/%s\n",
Avuton Olrich's avatar
Avuton Olrich committed
415
			    getDirectoryPath(directory), node->key);
416
			deleteFromList(directory->subDirectories, node->key);
Avuton Olrich's avatar
Avuton Olrich committed
417
			ret = 1;
Warren Dukes's avatar
Warren Dukes committed
418 419 420 421 422
		}
		node = tmpNode;
	}

	node = directory->songs->firstNode;
Avuton Olrich's avatar
Avuton Olrich committed
423
	while (node) {
Warren Dukes's avatar
Warren Dukes committed
424
		tmpNode = node->nextNode;
Avuton Olrich's avatar
Avuton Olrich committed
425 426 427 428
		if (findInList(entList, node->key, (void **)&name)) {
			if (!isMusic(name, NULL, 0)) {
				removeSongFromDirectory(directory, node->key);
				ret = 1;
Warren Dukes's avatar
Warren Dukes committed
429
			}
Avuton Olrich's avatar
Avuton Olrich committed
430 431 432
		} else {
			removeSongFromDirectory(directory, node->key);
			ret = 1;
Warren Dukes's avatar
Warren Dukes committed
433 434 435 436 437 438
		}
		node = tmpNode;
	}

	freeList(entList);

439
	return ret;
Warren Dukes's avatar
Warren Dukes committed
440 441
}

Avuton Olrich's avatar
Avuton Olrich committed
442 443 444 445 446
static Directory *addDirectoryPathToDB(char *utf8path, char **shortname)
{
	char *parent;
	Directory *parentDirectory;
	void *directory;
447

448
	parent = xstrdup(parentPath(utf8path));
449

Avuton Olrich's avatar
Avuton Olrich committed
450 451 452 453
	if (strlen(parent) == 0)
		parentDirectory = (void *)mp3rootDirectory;
	else
		parentDirectory = addDirectoryPathToDB(parent, shortname);
454

Avuton Olrich's avatar
Avuton Olrich committed
455
	if (!parentDirectory) {
456 457 458 459
		free(parent);
		return NULL;
	}

Avuton Olrich's avatar
Avuton Olrich committed
460 461 462
	*shortname = utf8path + strlen(parent);
	while (*(*shortname) && *(*shortname) == '/')
		(*shortname)++;
463

Avuton Olrich's avatar
Avuton Olrich committed
464 465
	if (!findInList
	    (parentDirectory->subDirectories, *shortname, &directory)) {
466
		struct stat st;
Avuton Olrich's avatar
Avuton Olrich committed
467 468
		if (myStat(utf8path, &st) < 0 ||
		    inodeFoundInParent(parentDirectory, st.st_ino, st.st_dev)) {
469 470
			free(parent);
			return NULL;
Avuton Olrich's avatar
Avuton Olrich committed
471 472 473 474
		} else {
			directory = newDirectory(utf8path, parentDirectory);
			insertInList(parentDirectory->subDirectories,
				     *shortname, directory);
475
		}
476 477 478
	}

	/* if we're adding directory paths, make sure to delete filenames
Avuton Olrich's avatar
Avuton Olrich committed
479 480
	   with potentially the same name */
	removeSongFromDirectory(parentDirectory, *shortname);
481 482 483

	free(parent);

Avuton Olrich's avatar
Avuton Olrich committed
484
	return (Directory *) directory;
485 486
}

Avuton Olrich's avatar
Avuton Olrich committed
487 488 489 490
static Directory *addParentPathToDB(char *utf8path, char **shortname)
{
	char *parent;
	Directory *parentDirectory;
491

492
	parent = xstrdup(parentPath(utf8path));
493

Avuton Olrich's avatar
Avuton Olrich committed
494 495 496 497
	if (strlen(parent) == 0)
		parentDirectory = (void *)mp3rootDirectory;
	else
		parentDirectory = addDirectoryPathToDB(parent, shortname);
498

Avuton Olrich's avatar
Avuton Olrich committed
499
	if (!parentDirectory) {
500 501 502 503
		free(parent);
		return NULL;
	}

Avuton Olrich's avatar
Avuton Olrich committed
504 505 506
	*shortname = utf8path + strlen(parent);
	while (*(*shortname) && *(*shortname) == '/')
		(*shortname)++;
507 508 509

	free(parent);

Avuton Olrich's avatar
Avuton Olrich committed
510
	return (Directory *) parentDirectory;
511 512
}

513 514 515 516 517
/* return values:
   -1 -> error
    0 -> no error, but nothing updated
    1 -> no error, and stuff updated
 */
Avuton Olrich's avatar
Avuton Olrich committed
518 519 520 521 522 523 524 525 526 527 528 529
static int updatePath(char *utf8path)
{
	Directory *directory;
	Directory *parentDirectory;
	Song *song;
	char *shortname;
	char *path = sanitizePathDup(utf8path);
	time_t mtime;
	int ret = 0;

	if (NULL == path)
		return -1;
530

531
	/* if path is in the DB try to update it, or else delete it */
Avuton Olrich's avatar
Avuton Olrich committed
532
	if ((directory = getDirectoryDetails(path, &shortname))) {
533 534
		parentDirectory = directory->parent;

535
		/* if this update directory is successfull, we are done */
Avuton Olrich's avatar
Avuton Olrich committed
536
		if ((ret = updateDirectory(directory)) >= 0) {
537 538
			free(path);
			sortDirectory(directory);
539
			return ret;
540 541
		}
		/* we don't want to delete the root directory */
Avuton Olrich's avatar
Avuton Olrich committed
542
		else if (directory == mp3rootDirectory) {
543
			free(path);
544
			return 0;
545 546 547
		}
		/* if updateDirectory fials, means we should delete it */
		else {
Avuton Olrich's avatar
Avuton Olrich committed
548
			LOG("removing directory: %s\n", path);
549
			deleteFromList(parentDirectory->subDirectories,
Avuton Olrich's avatar
Avuton Olrich committed
550 551 552
				       shortname);
			ret = 1;
			/* don't return, path maybe a song now */
553
		}
Avuton Olrich's avatar
Avuton Olrich committed
554 555 556
	} else if ((song = getSongDetails(path, &shortname, &parentDirectory))) {
		if (!parentDirectory->stat
		    && statDirectory(parentDirectory) < 0) {
557 558 559
			free(path);
			return 0;
		}
560
		/* if this song update is successfull, we are done */
Avuton Olrich's avatar
Avuton Olrich committed
561 562 563 564
		else if (0 == inodeFoundInParent(parentDirectory->parent,
						 parentDirectory->stat->inode,
						 parentDirectory->stat->device)
			 && song && isMusic(getSongUrl(song), &mtime, 0)) {
565
			free(path);
Avuton Olrich's avatar
Avuton Olrich committed
566 567 568 569 570 571 572 573 574
			if (song->mtime == mtime)
				return 0;
			else if (updateSongInfo(song) == 0)
				return 1;
			else {
				removeSongFromDirectory(parentDirectory,
							shortname);
				return 1;
			}
575
		}
576
		/* if updateDirectory fials, means we should delete it */
577
		else {
Avuton Olrich's avatar
Avuton Olrich committed
578 579 580 581
			removeSongFromDirectory(parentDirectory, shortname);
			ret = 1;
			/* don't return, path maybe a directory now */
		}
582
	}
583 584 585

	/* path not found in the db, see if it actually exists on the fs.
	 * Also, if by chance a directory was replaced by a file of the same
Avuton Olrich's avatar
Avuton Olrich committed
586 587 588 589 590 591 592 593 594 595 596 597
	 * name or vice versa, we need to add it to the db
	 */
	if (isDir(path) || isMusic(path, NULL, 0)) {
		parentDirectory = addParentPathToDB(path, &shortname);
		if (!parentDirectory || (!parentDirectory->stat &&
					 statDirectory(parentDirectory) < 0)) {
		} else if (0 == inodeFoundInParent(parentDirectory->parent,
						   parentDirectory->stat->inode,
						   parentDirectory->stat->
						   device)
			   && addToDirectory(parentDirectory, shortname, path)
			   > 0) {
598 599
			ret = 1;
		}
600
	}
601 602

	free(path);
603

Avuton Olrich's avatar
Avuton Olrich committed
604
	return ret;
605 606
}

607 608 609 610 611
/* return values:
   -1 -> error
    0 -> no error, but nothing updated
    1 -> no error, and stuff updated
 */
Avuton Olrich's avatar
Avuton Olrich committed
612 613 614
static int updateDirectory(Directory * directory)
{
	DIR *dir;
Warren Dukes's avatar
Warren Dukes committed
615
	char cwd[2];
Avuton Olrich's avatar
Avuton Olrich committed
616 617 618 619 620
	struct dirent *ent;
	char *s;
	char *utf8;
	char *dirname = getDirectoryPath(directory);
	int ret = 0;
Warren Dukes's avatar
Warren Dukes committed
621

622
	{
Avuton Olrich's avatar
Avuton Olrich committed
623
		if (!directory->stat && statDirectory(directory) < 0) {
624
			return -1;
Avuton Olrich's avatar
Avuton Olrich committed
625 626 627
		} else if (inodeFoundInParent(directory->parent,
					      directory->stat->inode,
					      directory->stat->device)) {
628 629 630 631
			return -1;
		}
	}

Warren Dukes's avatar
Warren Dukes committed
632 633
	cwd[0] = '.';
	cwd[1] = '\0';
Avuton Olrich's avatar
Avuton Olrich committed
634 635
	if (dirname == NULL)
		dirname = cwd;
Warren Dukes's avatar
Warren Dukes committed
636

Avuton Olrich's avatar
Avuton Olrich committed
637 638
	if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL)
		return -1;
Warren Dukes's avatar
Warren Dukes committed
639

Avuton Olrich's avatar
Avuton Olrich committed
640 641
	if (removeDeletedFromDirectory(directory, dir) > 0)
		ret = 1;
642 643 644

	rewinddir(dir);

Avuton Olrich's avatar
Avuton Olrich committed
645 646 647 648 649
	while ((ent = readdir(dir))) {
		if (ent->d_name[0] == '.')
			continue;	/* hide hidden stuff */
		if (strchr(ent->d_name, '\n'))
			continue;
Warren Dukes's avatar
Warren Dukes committed
650

651 652
		utf8 = fsCharsetToUtf8(ent->d_name);

Avuton Olrich's avatar
Avuton Olrich committed
653 654
		if (!utf8)
			continue;
655

656
		utf8 = xstrdup(utf8);
Warren Dukes's avatar
Warren Dukes committed
657

Avuton Olrich's avatar
Avuton Olrich committed
658
		if (directory->path) {
659
			s = xmalloc(strlen(getDirectoryPath(directory)) +
Avuton Olrich's avatar
Avuton Olrich committed
660 661 662
				   strlen(utf8) + 2);
			sprintf(s, "%s/%s", getDirectoryPath(directory), utf8);
		} else
663
			s = xstrdup(utf8);
Avuton Olrich's avatar
Avuton Olrich committed
664 665
		if (updateInDirectory(directory, utf8, s) > 0)
			ret = 1;
Warren Dukes's avatar
Warren Dukes committed
666 667 668
		free(utf8);
		free(s);
	}
Avuton Olrich's avatar
Avuton Olrich committed
669

Warren Dukes's avatar
Warren Dukes committed
670 671
	closedir(dir);

672
	return ret;
Warren Dukes's avatar
Warren Dukes committed
673 674
}

675 676 677 678 679
/* return values:
   -1 -> error
    0 -> no error, but nothing found
    1 -> no error, and stuff found
 */
Avuton Olrich's avatar
Avuton Olrich committed
680 681 682
static int exploreDirectory(Directory * directory)
{
	DIR *dir;
Warren Dukes's avatar
Warren Dukes committed
683
	char cwd[2];
Avuton Olrich's avatar
Avuton Olrich committed
684 685 686 687 688
	struct dirent *ent;
	char *s;
	char *utf8;
	char *dirname = getDirectoryPath(directory);
	int ret = 0;
Warren Dukes's avatar
Warren Dukes committed
689 690 691

	cwd[0] = '.';
	cwd[1] = '\0';
Avuton Olrich's avatar
Avuton Olrich committed
692 693
	if (dirname == NULL)
		dirname = cwd;
Warren Dukes's avatar
Warren Dukes committed
694

Avuton Olrich's avatar
Avuton Olrich committed
695 696 697
	DEBUG("explore: attempting to opendir: %s\n", dirname);
	if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL)
		return -1;
Warren Dukes's avatar
Warren Dukes committed
698

Avuton Olrich's avatar
Avuton Olrich committed
699 700 701 702 703 704
	DEBUG("explore: %s\n", dirname);
	while ((ent = readdir(dir))) {
		if (ent->d_name[0] == '.')
			continue;	/* hide hidden stuff */
		if (strchr(ent->d_name, '\n'))
			continue;
Warren Dukes's avatar
Warren Dukes committed
705

706 707
		utf8 = fsCharsetToUtf8(ent->d_name);

Avuton Olrich's avatar
Avuton Olrich committed
708 709
		if (!utf8)
			continue;
710

711
		utf8 = xstrdup(utf8);
Warren Dukes's avatar
Warren Dukes committed
712

Avuton Olrich's avatar
Avuton Olrich committed
713
		DEBUG("explore: found: %s (%s)\n", ent->d_name, utf8);
Warren Dukes's avatar
Warren Dukes committed
714

Avuton Olrich's avatar
Avuton Olrich committed
715
		if (directory->path) {
716
			s = xmalloc(strlen(getDirectoryPath(directory)) +
Avuton Olrich's avatar
Avuton Olrich committed
717 718 719
				   strlen(utf8) + 2);
			sprintf(s, "%s/%s", getDirectoryPath(directory), utf8);
		} else
720
			s = xstrdup(utf8);
Avuton Olrich's avatar
Avuton Olrich committed
721 722
		if (addToDirectory(directory, utf8, s) > 0)
			ret = 1;
Warren Dukes's avatar
Warren Dukes committed
723 724 725
		free(utf8);
		free(s);
	}
Avuton Olrich's avatar
Avuton Olrich committed
726

Warren Dukes's avatar
Warren Dukes committed
727 728
	closedir(dir);

729
	return ret;
Warren Dukes's avatar
Warren Dukes committed
730 731
}

Avuton Olrich's avatar
Avuton Olrich committed
732 733
static int statDirectory(Directory * dir)
{
734 735
	struct stat st;

736 737
	if (myStat(getDirectoryPath(dir) ? getDirectoryPath(dir) : "", &st) < 0)
	{
738 739
		return -1;
	}
740 741 742 743 744 745

	dir->stat = newDirectoryStat(&st);

	return 0;
}

Avuton Olrich's avatar
Avuton Olrich committed
746 747 748 749 750 751
static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device)
{
	while (parent) {
		if (!parent->stat) {
			if (statDirectory(parent) < 0)
				return -1;
752
		}
Avuton Olrich's avatar
Avuton Olrich committed
753 754
		if (parent->stat->inode == inode &&
		    parent->stat->device == device) {
755
			DEBUG("recursive directory found\n");
756 757 758 759 760 761 762 763
			return 1;
		}
		parent = parent->parent;
	}

	return 0;
}

Avuton Olrich's avatar
Avuton Olrich committed
764 765
static int addSubDirectoryToDirectory(Directory * directory, char *shortname,
				      char *name, struct stat *st)
Warren Dukes's avatar
Warren Dukes committed
766
{
Avuton Olrich's avatar
Avuton Olrich committed
767
	Directory *subDirectory;
768

Avuton Olrich's avatar
Avuton Olrich committed
769 770
	if (inodeFoundInParent(directory, st->st_ino, st->st_dev))
		return 0;
771

772
	subDirectory = newDirectory(name, directory);
773
	subDirectory->stat = newDirectoryStat(st);
774

Avuton Olrich's avatar
Avuton Olrich committed
775 776 777 778 779 780
	if (exploreDirectory(subDirectory) < 1) {
		freeDirectory(subDirectory);
		return 0;
	}

	insertInList(directory->subDirectories, shortname, subDirectory);
Warren Dukes's avatar
Warren Dukes committed
781

782
	return 1;
Warren Dukes's avatar
Warren Dukes committed
783 784
}

Avuton Olrich's avatar
Avuton Olrich committed
785 786
static int addToDirectory(Directory * directory, char *shortname, char *name)
{
787 788
	struct stat st;

Avuton Olrich's avatar
Avuton Olrich committed
789
	if (myStat(name, &st)) {
790 791 792
		DEBUG("failed to stat %s: %s\n", name, strerror(errno));
		return -1;
	}
793

Avuton Olrich's avatar
Avuton Olrich committed
794 795
	if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) {
		Song *song;
796
		song = addSongToList(directory->songs, shortname, name,
Avuton Olrich's avatar
Avuton Olrich committed
797 798 799
				     SONG_TYPE_FILE, directory);
		if (!song)
			return -1;
800
		LOG("added %s\n", name);
801
		return 1;
Avuton Olrich's avatar
Avuton Olrich committed
802
	} else if (S_ISDIR(st.st_mode)) {
803
		return addSubDirectoryToDirectory(directory, shortname, name,
Avuton Olrich's avatar
Avuton Olrich committed
804
						  &st);
805
	}
Warren Dukes's avatar
Warren Dukes committed
806

Avuton Olrich's avatar
Avuton Olrich committed
807
	DEBUG("addToDirectory: %s is not a directory or music\n", name);
Warren Dukes's avatar
Warren Dukes committed
808 809 810 811

	return -1;
}

Eric Wong's avatar
Eric Wong committed
812
void closeMp3Directory(void)
Avuton Olrich's avatar
Avuton Olrich committed
813
{
Warren Dukes's avatar
Warren Dukes committed
814 815 816
	freeDirectory(mp3rootDirectory);
}

Avuton Olrich's avatar
Avuton Olrich committed
817 818 819
static Directory *findSubDirectory(Directory * directory, char *name)
{
	void *subDirectory;
820
	char *dup = xstrdup(name);
Avuton Olrich's avatar
Avuton Olrich committed
821
	char *key;
Warren Dukes's avatar
Warren Dukes committed
822

Avuton Olrich's avatar
Avuton Olrich committed
823 824
	key = strtok(dup, "/");
	if (!key) {
Warren Dukes's avatar
Warren Dukes committed
825 826 827
		free(dup);
		return NULL;
	}
Avuton Olrich's avatar
Avuton Olrich committed
828 829

	if (findInList(directory->subDirectories, key, &subDirectory)) {
Warren Dukes's avatar
Warren Dukes committed
830
		free(dup);
Avuton Olrich's avatar
Avuton Olrich committed
831
		return (Directory *) subDirectory;
Warren Dukes's avatar
Warren Dukes committed
832 833 834 835 836 837
	}

	free(dup);
	return NULL;
}

Avuton Olrich's avatar
Avuton Olrich committed
838 839
static Directory *getSubDirectory(Directory * directory, char *name,
				  char **shortname)
840
{
Avuton Olrich's avatar
Avuton Olrich committed
841
	Directory *subDirectory;
Warren Dukes's avatar
Warren Dukes committed
842 843
	int len;

Avuton Olrich's avatar
Avuton Olrich committed
844
	if (name == NULL || name[0] == '\0' || strcmp(name, "/") == 0) {
Warren Dukes's avatar
Warren Dukes committed
845 846 847
		return directory;
	}

Avuton Olrich's avatar
Avuton Olrich committed
848 849
	if ((subDirectory = findSubDirectory(directory, name)) == NULL)
		return NULL;
Warren Dukes's avatar
Warren Dukes committed
850

851 852
	*shortname = name;

Warren Dukes's avatar
Warren Dukes committed
853
	len = 0;
Avuton Olrich's avatar
Avuton Olrich committed
854 855 856 857
	while (name[len] != '/' && name[len] != '\0')
		len++;
	while (name[len] == '/')
		len++;
Warren Dukes's avatar
Warren Dukes committed
858

Avuton Olrich's avatar
Avuton Olrich committed
859
	return getSubDirectory(subDirectory, &(name[len]), shortname);
860 861
}

Avuton Olrich's avatar
Avuton Olrich committed
862 863
static Directory *getDirectoryDetails(char *name, char **shortname)
{
864 865
	*shortname = NULL;

Avuton Olrich's avatar
Avuton Olrich committed
866
	return getSubDirectory(mp3rootDirectory, name, shortname);
Warren Dukes's avatar
Warren Dukes committed
867 868
}

Avuton Olrich's avatar
Avuton Olrich committed
869 870 871
static Directory *getDirectory(char *name)
{
	char *shortname;
872

Avuton Olrich's avatar
Avuton Olrich committed
873
	return getSubDirectory(mp3rootDirectory, name, &shortname);
Warren Dukes's avatar
Warren Dukes committed
874 875
}

876
static int printDirectoryList(int fd, DirectoryList * directoryList)
Avuton Olrich's avatar
Avuton Olrich committed
877 878 879
{
	ListNode *node = directoryList->firstNode;
	Directory *directory;
Warren Dukes's avatar
Warren Dukes committed
880

Avuton Olrich's avatar
Avuton Olrich committed
881 882
	while (node != NULL) {
		directory = (Directory *) node->data;
883 884
		fdprintf(fd, "%s%s\n", DIRECTORY_DIR,
			 getDirectoryPath(directory));
Warren Dukes's avatar
Warren Dukes committed
885 886 887 888 889 890
		node = node->nextNode;
	}

	return 0;
}

891
int printDirectoryInfo(int fd, char *name)
Avuton Olrich's avatar
Avuton Olrich committed
892 893 894 895
{
	Directory *directory;

	if ((directory = getDirectory(name)) == NULL) {
896
		commandError(fd, ACK_ERROR_NO_EXIST, "directory not found");
Warren Dukes's avatar
Warren Dukes committed
897 898 899
		return -1;
	}

900 901
	printDirectoryList(fd, directory->subDirectories);
	printSongInfoFromList(fd, directory->songs);
Warren Dukes's avatar
Warren Dukes committed
902 903 904 905

	return 0;
}

Avuton Olrich's avatar
Avuton Olrich committed
906 907 908 909
static void writeDirectoryInfo(FILE * fp, Directory * directory)
{
	ListNode *node = (directory->subDirectories)->firstNode;
	Directory *subDirectory;
Warren Dukes's avatar
Warren Dukes committed
910

Avuton Olrich's avatar
Avuton Olrich committed
911
	if (directory->path) {
912
		fprintf(fp, "%s%s\n", DIRECTORY_BEGIN,
Avuton Olrich's avatar
Avuton Olrich committed
913
			  getDirectoryPath(directory));
Warren Dukes's avatar
Warren Dukes committed
914
	}
Avuton Olrich's avatar
Avuton Olrich committed
915 916 917

	while (node != NULL) {
		subDirectory = (Directory *) node->data;
918
		fprintf(fp, "%s%s\n", DIRECTORY_DIR, node->key);
Avuton Olrich's avatar
Avuton Olrich committed
919
		writeDirectoryInfo(fp, subDirectory);
Warren Dukes's avatar
Warren Dukes committed
920 921 922
		node = node->nextNode;
	}

Avuton Olrich's avatar
Avuton Olrich committed
923
	writeSongInfoFromList(fp, directory->songs);
Warren Dukes's avatar
Warren Dukes committed
924

Avuton Olrich's avatar
Avuton Olrich committed
925
	if (directory->path) {
926
		fprintf(fp, "%s%s\n", DIRECTORY_END,
Avuton Olrich's avatar
Avuton Olrich committed
927
			  getDirectoryPath(directory));
Warren Dukes's avatar
Warren Dukes committed
928 929 930
	}
}

Avuton Olrich's avatar
Avuton Olrich committed
931 932 933 934 935 936
static void readDirectoryInfo(FILE * fp, Directory * directory)
{
	char buffer[MAXPATHLEN * 2];
	int bufferSize = MAXPATHLEN * 2;
	char *key;
	Directory *subDirectory;
937
	int strcmpRet;
Avuton Olrich's avatar
Avuton Olrich committed
938 939 940
	char *name;
	ListNode *nextDirNode = directory->subDirectories->firstNode;
	ListNode *nodeTemp;
Warren Dukes's avatar
Warren Dukes committed
941

Avuton Olrich's avatar
Avuton Olrich committed
942 943 944
	while (myFgets(buffer, bufferSize, fp)
	       && 0 != strncmp(DIRECTORY_END, buffer, strlen(DIRECTORY_END))) {
		if (0 == strncmp(DIRECTORY_DIR, buffer, strlen(DIRECTORY_DIR))) {
945
			key = xstrdup(&(buffer[strlen(DIRECTORY_DIR)]));
946
			if (!myFgets(buffer, bufferSize, fp)) {
Avuton Olrich's avatar
Avuton Olrich committed
947
				ERROR("Error reading db, fgets\n");
948
				exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
949
			}
Avuton Olrich's avatar
Avuton Olrich committed
950 951 952
			/* for compatibility with db's prior to 0.11 */
			if (0 == strncmp(DIRECTORY_MTIME, buffer,
					 strlen(DIRECTORY_MTIME))) {
953
				if (!myFgets(buffer, bufferSize, fp)) {
Avuton Olrich's avatar
Avuton Olrich committed
954 955 956 957 958 959 960 961
					ERROR("Error reading db, fgets\n");
					exit(EXIT_FAILURE);
				}
			}
			if (strncmp
			    (DIRECTORY_BEGIN, buffer,
			     strlen(DIRECTORY_BEGIN))) {
				ERROR("Error reading db at line: %s\n", buffer);
962
				exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
963
			}
964
			name = xstrdup(&(buffer[strlen(DIRECTORY_BEGIN)]));
965

Avuton Olrich's avatar
Avuton Olrich committed
966 967 968
			while (nextDirNode && (strcmpRet =
					       strcmp(key,
						      nextDirNode->key)) > 0) {
969 970
				nodeTemp = nextDirNode->nextNode;
				deleteNodeFromList(directory->subDirectories,
Avuton Olrich's avatar
Avuton Olrich committed
971
						   nextDirNode);
972 973 974
				nextDirNode = nodeTemp;
			}

Avuton Olrich's avatar
Avuton Olrich committed
975
			if (NULL == nextDirNode) {
976
				subDirectory = newDirectory(name, directory);
977
				insertInList(directory->subDirectories,
Avuton Olrich's avatar
Avuton Olrich committed
978 979 980
					     key, (void *)subDirectory);
			} else if (strcmpRet == 0) {
				subDirectory = (Directory *) nextDirNode->data;
981
				nextDirNode = nextDirNode->nextNode;
Avuton Olrich's avatar
Avuton Olrich committed
982
			} else {
983
				subDirectory = newDirectory(name, directory);
Avuton Olrich's avatar
Avuton Olrich committed
984 985 986 987
				insertInListBeforeNode(directory->
						       subDirectories,
						       nextDirNode, -1, key,
						       (void *)subDirectory);
988 989
			}

990
			free(name);
Warren Dukes's avatar
Warren Dukes committed
991
			free(key);
Avuton Olrich's avatar
Avuton Olrich committed
992 993
			readDirectoryInfo(fp, subDirectory);
		} else if (0 == strncmp(SONG_BEGIN, buffer, strlen(SONG_BEGIN))) {
994
			readSongInfoIntoList(fp, directory->songs, directory);
Avuton Olrich's avatar
Avuton Olrich committed
995 996
		} else {
			ERROR("Unknown line in db: %s\n", buffer);
997
			exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
998 999
		}
	}
1000

Avuton Olrich's avatar
Avuton Olrich committed
1001
	while (nextDirNode) {
1002
		nodeTemp = nextDirNode->nextNode;
Avuton Olrich's avatar
Avuton Olrich committed
1003
		deleteNodeFromList(directory->subDirectories, nextDirNode);
1004 1005
		nextDirNode = nodeTemp;
	}
Warren Dukes's avatar
Warren Dukes committed
1006 1007
}

Avuton Olrich's avatar
Avuton Olrich committed
1008 1009 1010 1011 1012
static void sortDirectory(Directory * directory)
{
	ListNode *node = directory->subDirectories->firstNode;
	Directory *subDir;

Warren Dukes's avatar
Warren Dukes committed
1013 1014 1015
	sortList(directory->subDirectories);
	sortList(directory->songs);

Avuton Olrich's avatar
Avuton Olrich committed
1016 1017
	while (node != NULL) {
		subDir = (Directory *) node->data;
Warren Dukes's avatar
Warren Dukes committed
1018 1019 1020 1021 1022
		sortDirectory(subDir);
		node = node->nextNode;
	}
}

Eric Wong's avatar
Eric Wong committed
1023
int checkDirectoryDB(void)
Avuton Olrich's avatar
Avuton Olrich committed
1024
{
1025
	struct stat st;
1026 1027 1028 1029 1030 1031 1032
	char *dbFile;
	char *dirPath;
	char *dbPath;

	dbFile = getDbFile();

	/* Check if the file exists */
Avuton Olrich's avatar
Avuton Olrich committed
1033
	if (access(dbFile, F_OK)) {
1034 1035 1036
		/* If the file doesn't exist, we can't check if we can write
		 * it, so we are going to try to get the directory path, and
		 * see if we can write a file in that */
1037
		dbPath = xstrdup(dbFile);
1038
		dirPath = dirname(dbPath);
Avuton Olrich's avatar
Avuton Olrich committed
1039

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054
		/* Check that the parent part of the path is a directory */
		if (stat(dirPath, &st) < 0) {
			ERROR("Couldn't stat parent directory of db file "
			      "\"%s\": %s\n", dbFile, strerror(errno));
			free(dbPath);
			return -1;
		}

		if (!S_ISDIR(st.st_mode)) {
			ERROR("Couldn't create db file \"%s\" because the "
			      "parent path is not a directory\n", dbFile);
			free(dbPath);
			return -1;
		}

1055
		/* Check if we can write to the directory */
Avuton Olrich's avatar
Avuton Olrich committed
1056
		if (access(dirPath, R_OK | W_OK)) {
1057
			ERROR("Can't create db file in \"%s\": %s\n", dirPath,
Avuton Olrich's avatar
Avuton Olrich committed
1058
			      strerror(errno));
1059 1060
			free(dbPath);
			return -1;
1061

1062
		}
1063

1064 1065 1066
		free(dbPath);
		return 0;
	}
1067

1068 1069
	/* Path exists, now check if it's a regular file */
	if (stat(dbFile, &st) < 0) {
1070
		ERROR("Couldn't stat db file \"%s\": %s\n", dbFile,
1071 1072 1073 1074 1075 1076 1077 1078 1079 1080
		      strerror(errno));
		return -1;
	}

	if (!S_ISREG(st.st_mode)) {
		ERROR("db file \"%s\" is not a regular file\n", dbFile);
		return -1;
	}

	/* And check that we can write to it */
Avuton Olrich's avatar
Avuton Olrich committed
1081
	if (access(dbFile, R_OK | W_OK)) {
1082
		ERROR("Can't open db file \"%s\" for reading/writing: %s\n",
Avuton Olrich's avatar
Avuton Olrich committed
1083
		      dbFile, strerror(errno));
1084 1085
		return -1;
	}
Avuton Olrich's avatar
Avuton Olrich committed
1086

1087 1088 1089
	return 0;
}

Eric Wong's avatar
Eric Wong committed
1090
int writeDirectoryDB(void)
Avuton Olrich's avatar
Avuton Olrich committed
1091 1092 1093
{
	FILE *fp;
	char *dbFile = getDbFile();
Warren Dukes's avatar
Warren Dukes committed
1094

Warren Dukes's avatar
Warren Dukes committed
1095
	DEBUG("removing empty directories from DB\n");
Warren Dukes's avatar
Warren Dukes committed
1096
	deleteEmptyDirectoriesInDirectory(mp3rootDirectory);
Warren Dukes's avatar
Warren Dukes committed
1097 1098 1099

	DEBUG("sorting DB\n");

Warren Dukes's avatar
Warren Dukes committed
1100 1101
	sortDirectory(mp3rootDirectory);

Warren Dukes's avatar
Warren Dukes committed
1102 1103
	DEBUG("writing DB\n");

Avuton Olrich's avatar
Avuton Olrich committed
1104 1105
	while (!(fp = fopen(dbFile, "w")) && errno == EINTR) ;
	if (!fp) {
1106
		ERROR("unable to write to db file \"%s\": %s\n",
Avuton Olrich's avatar
Avuton Olrich committed
1107
		      dbFile, strerror(errno));
1108 1109
		return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
1110

Avuton Olrich's avatar
Avuton Olrich committed
1111
	/* block signals when writing the db so we don't get a corrupted db */
1112 1113 1114 1115
	fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
	fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
	fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, getFsCharset());
	fprintf(fp, "%s\n", DIRECTORY_INFO_END);
Warren Dukes's avatar
Warren Dukes committed
1116

Avuton Olrich's avatar
Avuton Olrich committed
1117
	writeDirectoryInfo(fp, mp3rootDirectory);
1118

Avuton Olrich's avatar
Avuton Olrich committed
1119
	while (fclose(fp) && errno == EINTR) ;
Warren Dukes's avatar
Warren Dukes committed
1120 1121 1122 1123

	return 0;
}

Eric Wong's avatar
Eric Wong committed
1124
int readDirectoryDB(void)
Avuton Olrich's avatar
Avuton Olrich committed
1125 1126 1127 1128
{
	FILE *fp = NULL;
	char *dbFile = getDbFile();
	struct stat st;
Warren Dukes's avatar
Warren Dukes committed
1129

Avuton Olrich's avatar
Avuton Olrich committed
1130 1131 1132 1133 1134 1135
	if (!mp3rootDirectory)
		mp3rootDirectory = newDirectory(NULL, NULL);
	while (!(fp = fopen(dbFile, "r")) && errno == EINTR) ;
	if (fp == NULL) {
		ERROR("unable open db file \"%s\": %s\n",
		      dbFile, strerror(errno));
1136 1137
		return -1;
	}
Warren Dukes's avatar
Warren Dukes committed
1138 1139 1140 1141 1142 1143 1144 1145

	/* get initial info */
	{
		char buffer[100];
		int bufferSize = 100;
		int foundFsCharset = 0;
		int foundVersion = 0;

1146
		if (!myFgets(buffer, bufferSize, fp)) {
1147
			ERROR("Error reading db, fgets\n");
1148
			exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
1149
		}
Avuton Olrich's avatar
Avuton Olrich committed
1150 1151 1152 1153 1154
		if (0 == strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
			while (myFgets(buffer, bufferSize, fp) &&
			       0 != strcmp(DIRECTORY_INFO_END, buffer)) {
				if (0 == strncmp(DIRECTORY_MPD_VERSION, buffer,
						 strlen(DIRECTORY_MPD_VERSION)))
Warren Dukes's avatar
Warren Dukes committed
1155
				{
Avuton Olrich's avatar
Avuton Olrich committed
1156
					if (foundVersion) {
Warren Dukes's avatar
Warren Dukes committed
1157
						ERROR("already found "
Avuton Olrich's avatar
Avuton Olrich committed
1158
						      "version in db\n");
1159
						exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
1160 1161
					}
					foundVersion = 1;
Avuton Olrich's avatar
Avuton Olrich committed
1162 1163 1164 1165 1166 1167 1168 1169
				} else if (0 ==
					   strncmp(DIRECTORY_FS_CHARSET, buffer,
						   strlen
						   (DIRECTORY_FS_CHARSET))) {
					char *fsCharset;
					char *tempCharset;

					if (foundFsCharset) {
1170
						WARNING("already found "
Warren Dukes's avatar
Warren Dukes committed
1171
							"fs charset in db\n");
1172
						exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
1173 1174 1175 1176
					}

					foundFsCharset = 1;

Avuton Olrich's avatar
Avuton Olrich committed
1177 1178 1179 1180 1181 1182 1183
					fsCharset =
					    &(buffer
					      [strlen(DIRECTORY_FS_CHARSET)]);
					if ((tempCharset =
					     getConfigParamValue
					     (CONF_FS_CHARSET))
					    && strcmp(fsCharset, tempCharset)) {
1184
						WARNING("Using \"%s\" for the "
Warren Dukes's avatar
Warren Dukes committed
1185 1186
							"filesystem charset "
							"instead of \"%s\"\n",
Avuton Olrich's avatar
Avuton Olrich committed
1187
							fsCharset, tempCharset);
1188
						WARNING("maybe you need to "
Warren Dukes's avatar
Warren Dukes committed
1189 1190 1191
							"recreate the db?\n");
						setFsCharset(fsCharset);
					}
Avuton Olrich's avatar
Avuton Olrich committed
1192 1193 1194 1195
				} else {
					ERROR
					    ("directory: unknown line in db info: %s\n",
					     buffer);
1196
					exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
1197 1198
				}
			}
Avuton Olrich's avatar
Avuton Olrich committed
1199
		} else {
Warren Dukes's avatar
Warren Dukes committed
1200 1201
			ERROR("db info not found in db file\n");
			ERROR("you should recreate the db using --create-db\n");
Avuton Olrich's avatar
Avuton Olrich committed
1202
			fseek(fp, 0, SEEK_SET);
1203
			return -1;
Warren Dukes's avatar
Warren Dukes committed
1204 1205 1206
		}
	}

Warren Dukes's avatar
Warren Dukes committed
1207 1208
	DEBUG("reading DB\n");

Avuton Olrich's avatar
Avuton Olrich committed
1209 1210
	readDirectoryInfo(fp, mp3rootDirectory);
	while (fclose(fp) && errno == EINTR) ;
Warren Dukes's avatar
Warren Dukes committed
1211

1212 1213
	stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL);
	stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL);
Warren Dukes's avatar
Warren Dukes committed
1214

Avuton Olrich's avatar
Avuton Olrich committed
1215 1216
	if (stat(dbFile, &st) == 0)
		directory_dbModTime = st.st_mtime;
1217

Warren Dukes's avatar
Warren Dukes committed
1218 1219 1220
	return 0;
}

Eric Wong's avatar
Eric Wong committed
1221
void updateMp3Directory(void)
Avuton Olrich's avatar
Avuton Olrich committed
1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233
{
	switch (updateDirectory(mp3rootDirectory)) {
	case 0:
		/* nothing updated */
		return;
	case 1:
		if (writeDirectoryDB() < 0) {
			exit(EXIT_FAILURE);
		}
		break;
	default:
		/* something was updated and db should be written */
Warren Dukes's avatar
Warren Dukes committed
1234
		ERROR("problems updating music db\n");
Avuton Olrich's avatar
Avuton Olrich committed
1235
		exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
1236 1237
	}

Warren Dukes's avatar
Warren Dukes committed
1238
	return;
Warren Dukes's avatar
Warren Dukes committed
1239 1240
}

1241 1242
static int traverseAllInSubDirectory(int fd, Directory * directory,
				     int (*forEachSong) (int, Song *,
Avuton Olrich's avatar
Avuton Olrich committed
1243
							 void *),
1244
				     int (*forEachDir) (int, Directory *,
Avuton Olrich's avatar
Avuton Olrich committed
1245
							void *), void *data)
Warren Dukes's avatar
Warren Dukes committed
1246
{
Avuton Olrich's avatar
Avuton Olrich committed
1247 1248 1249 1250 1251 1252
	ListNode *node = directory->songs->firstNode;
	Song *song;
	Directory *dir;
	int errFlag = 0;

	if (forEachDir) {
1253
		errFlag = forEachDir(fd, directory, data);
Avuton Olrich's avatar
Avuton Olrich committed
1254 1255 1256 1257 1258 1259 1260
		if (errFlag)
			return errFlag;
	}

	if (forEachSong) {
		while (node != NULL && !errFlag) {
			song = (Song *) node->data;
1261
			errFlag = forEachSong(fd, song, data);
Avuton Olrich's avatar
Avuton Olrich committed
1262 1263 1264 1265 1266 1267 1268 1269 1270 1271
			node = node->nextNode;
		}
		if (errFlag)
			return errFlag;
	}

	node = directory->subDirectories->firstNode;

	while (node != NULL && !errFlag) {
		dir = (Directory *) node->data;
1272
		errFlag = traverseAllInSubDirectory(fd, dir, forEachSong,
Avuton Olrich's avatar
Avuton Olrich committed
1273 1274 1275 1276 1277
						    forEachDir, data);
		node = node->nextNode;
	}

	return errFlag;
Warren Dukes's avatar
Warren Dukes committed
1278 1279
}

1280 1281 1282
int traverseAllIn(int fd, char *name,
		  int (*forEachSong) (int, Song *, void *),
		  int (*forEachDir) (int, Directory *, void *), void *data)
Avuton Olrich's avatar
Avuton Olrich committed
1283 1284
{
	Directory *directory;
Warren Dukes's avatar
Warren Dukes committed
1285

Avuton Olrich's avatar
Avuton Olrich committed
1286 1287 1288
	if ((directory = getDirectory(name)) == NULL) {
		Song *song;
		if ((song = getSongFromDB(name)) && forEachSong) {
1289
			return forEachSong(fd, song, data);
Warren Dukes's avatar
Warren Dukes committed
1290
		}
1291
		commandError(fd, ACK_ERROR_NO_EXIST,
1292
			     "directory or file not found");
Warren Dukes's avatar
Warren Dukes committed
1293 1294 1295
		return -1;
	}

1296
	return traverseAllInSubDirectory(fd, directory, forEachSong, forEachDir,
Avuton Olrich's avatar
Avuton Olrich committed
1297
					 data);
Warren Dukes's avatar
Warren Dukes committed
1298 1299
}

Avuton Olrich's avatar
Avuton Olrich committed
1300 1301 1302
static void freeAllDirectoryStats(Directory * directory)
{
	ListNode *node = directory->subDirectories->firstNode;
1303

Avuton Olrich's avatar
Avuton Olrich committed
1304 1305
	while (node != NULL) {
		freeAllDirectoryStats((Directory *) node->data);
1306 1307 1308 1309 1310 1311
		node = node->nextNode;
	}

	freeDirectoryStatFromDirectory(directory);
}

Eric Wong's avatar
Eric Wong committed
1312
void initMp3Directory(void)
Avuton Olrich's avatar
Avuton Olrich committed
1313
{
1314 1315
	struct stat st;

1316
	mp3rootDirectory = newDirectory(NULL, NULL);
Warren Dukes's avatar
Warren Dukes committed
1317
	exploreDirectory(mp3rootDirectory);
1318
	freeAllDirectoryStats(mp3rootDirectory);
1319 1320
	stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL);
	stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL);
1321

Avuton Olrich's avatar
Avuton Olrich committed
1322 1323
	if (stat(getDbFile(), &st) == 0)
		directory_dbModTime = st.st_mtime;
Warren Dukes's avatar
Warren Dukes committed
1324 1325
}

Avuton Olrich's avatar
Avuton Olrich committed
1326 1327
static Song *getSongDetails(char *file, char **shortnameRet,
			    Directory ** directoryRet)
1328
{
Avuton Olrich's avatar
Avuton Olrich committed
1329 1330 1331
	void *song = NULL;
	Directory *directory;
	char *dir = NULL;
1332
	char *dup = xstrdup(file);
Avuton Olrich's avatar
Avuton Olrich committed
1333 1334
	char *shortname = dup;
	char *c = strtok(dup, "/");
Warren Dukes's avatar
Warren Dukes committed
1335

Avuton Olrich's avatar
Avuton Olrich committed
1336
	DEBUG("get song: %s\n", file);
Warren Dukes's avatar
Warren Dukes committed
1337

Avuton Olrich's avatar
Avuton Olrich committed
1338
	while (c) {
Warren Dukes's avatar
Warren Dukes committed
1339
		shortname = c;
Avuton Olrich's avatar
Avuton Olrich committed
1340
		c = strtok(NULL, "/");
Warren Dukes's avatar
Warren Dukes committed
1341 1342
	}

Avuton Olrich's avatar
Avuton Olrich committed
1343 1344 1345 1346
	if (shortname != dup) {
		for (c = dup; c < shortname - 1; c++) {
			if (*c == '\0')
				*c = '/';
Warren Dukes's avatar
Warren Dukes committed
1347 1348 1349 1350
		}
		dir = dup;
	}

Avuton Olrich's avatar
Avuton Olrich committed
1351
	if (!(directory = getDirectory(dir))) {
Warren Dukes's avatar
Warren Dukes committed
1352 1353 1354 1355
		free(dup);
		return NULL;
	}

Avuton Olrich's avatar
Avuton Olrich committed
1356
	if (!findInList(directory->songs, shortname, &song)) {
Warren Dukes's avatar
Warren Dukes committed
1357 1358 1359 1360 1361
		free(dup);
		return NULL;
	}

	free(dup);
Avuton Olrich's avatar
Avuton Olrich committed
1362 1363 1364 1365 1366
	if (shortnameRet)
		*shortnameRet = shortname;
	if (directoryRet)
		*directoryRet = directory;
	return (Song *) song;
Warren Dukes's avatar
Warren Dukes committed
1367 1368
}

Avuton Olrich's avatar
Avuton Olrich committed
1369 1370 1371
Song *getSongFromDB(char *file)
{
	return getSongDetails(file, NULL, NULL);
1372 1373
}

Eric Wong's avatar
Eric Wong committed
1374
time_t getDbModTime(void)
Avuton Olrich's avatar
Avuton Olrich committed
1375
{
1376
	return directory_dbModTime;
Warren Dukes's avatar
Warren Dukes committed
1377
}