DJWLindenaar balanced tree and master process patch

git-svn-id: https://svn.musicpd.org/mpd/trunk@3669 09075e82-0dd4-0310-85a5-a0d7c8717e4f
This commit is contained in:
Qball Cow 2005-11-16 14:43:04 +00:00
parent 402c8cd707
commit 32e5f4ca2b
13 changed files with 291 additions and 118 deletions

View File

@ -84,6 +84,10 @@ AC_CHECK_LIB(nsl,gethostbyname,MPD_LIBS="$MPD_LIBS -lnsl",)
AC_CHECK_LIB(m,exp,MPD_LIBS="$MPD_LIBS -lm",)
PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.0, [MPD_LIBS="$MPD_LIBS $GLIB_LIBS" MPD_CFLAGS="$MPD_CFLAGS $GLIB_CFLAGS"],[echo "Unable to find glib-2.0 of version 2.0 or above"])
dnl doesn't work for systems that don't have CODESET like OpenBSD
dnl AC_CHECK_HEADER(langinfo.h,[enable_langinfo=yes;AC_DEFINE(HAVE_LANGINFO,1,[Define if nl_langinfo.h is present])],enable_langinfo=no)
AM_LANGINFO_CODESET

View File

@ -399,7 +399,7 @@ int decoderInit(PlayerControl * pc, OutputBuffer * cb, DecoderControl * dc) {
pc->error = PLAYER_ERROR_SYSTEM;
return -1;
}
DEBUG("decoder PID: %d\n", decode_pid);
getPlayerData()->playerControl.decode_pid = decode_pid;
unblockSignals();

View File

@ -178,7 +178,7 @@ int updateInit(FILE * fp, List * pathList) {
if(directory_updatePid==0) {
/* child */
int dbUpdated = 0;
clearPlayerPid();
//clearPlayerPid();
unblockSignals();
@ -784,6 +784,7 @@ int addToDirectory(Directory * directory, char * shortname, char * name) {
void closeMp3Directory() {
freeDirectory(mp3rootDirectory);
destroyTagTracker();
}
Directory * findSubDirectory(Directory * directory,char * name) {

View File

@ -213,7 +213,10 @@ void closeAllListenSockets() {
DEBUG("closing listen socket %i\n", i);
while(close(listenSockets[i]) < 0 && errno==EINTR);
}
freeAllListenSockets();
}
void freeAllListenSockets() {
numberOfListenSockets = 0;
free(listenSockets);
listenSockets = NULL;

View File

@ -33,6 +33,7 @@ void getConnections(fd_set * fds);
int isAListenSocket(int sock);
void closeAllListenSockets();
void freeAllListenSockets();
/* fdmax should be initialized to something */
void addListenSocketsToFdSet(fd_set * fds, int * fdmax);

View File

@ -54,6 +54,9 @@
#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf"
#define USER_CONFIG_FILE_LOCATION "/.mpdconf"
volatile int masterPid = 0;
volatile int mainPid = 0;
typedef struct _Options {
int kill;
int daemon;
@ -288,6 +291,45 @@ void openDB(Options * options, char * argv0) {
}
}
void startMainProcess() {
int pid;
fflush(0);
pid = fork();
if(pid>0) {
initInputStream();
initReplayGainState();
/* qball crappy code */
readAudioDevicesState();
/* free stuff we don't need */
freeAllListenSockets();
mainPid = pid;
masterInitSigHandlers();
while (masterHandlePendingSignals()!=COMMAND_RETURN_KILL)
waitOnSignals();
/* we're killed */
playerKill();
finishAudioConfig();
finishAudioDriver();
/* qball crappy code */
saveAudioDevicesState();
finishPaths();
kill(mainPid, SIGTERM);
exit(EXIT_SUCCESS);
} else if(pid<0) {
ERROR("problems fork'ing main process!\n");
exit(EXIT_FAILURE);
}
DEBUG("main process started!\n");
}
void daemonize(Options * options) {
FILE * fp;
ConfigParam * pidFileParam = parseConfigFilePath(CONF_PID_FILE, 1);
@ -338,6 +380,7 @@ void daemonize(Options * options) {
DEBUG("writing pid file\n");
fprintf(fp, "%lu\n", (unsigned long)getpid());
fclose(fp);
masterPid = getpid();
}
void setupLogOutput(Options * options, FILE * out, FILE * err) {
@ -451,6 +494,7 @@ int main(int argc, char * argv[]) {
parseOptions(argc, argv, &options);
if(options.kill) killFromPidFile(argv[0], options.kill);
initStats();
initTagConfig();
@ -459,31 +503,39 @@ int main(int argc, char * argv[]) {
if(options.createDB <= 0 && !options.updateDB) listenOnPort();
changeToUser();
openLogFiles(&options, &out, &err);
initPlayerData();
daemonize(&options);
initInputPlugins();
initPaths();
initAudioConfig();
initAudioDriver();
startMainProcess();
/* This is the main process which has
* been forked from the master process.
*/
initPermissions();
initReplayGainState();
initPlaylist();
initInputPlugins();
openDB(&options, argv[0]);
initCommands();
initPlayerData();
initAudioConfig();
initAudioDriver();
initVolume();
initInterfaces();
initInputStream();
printMemorySavedByTagTracker();
printSavedMemoryFromFilenames();
/*printSavedMemoryFromDirectoryNames();*/
daemonize(&options);
setupLogOutput(&options, out, err);
@ -491,8 +543,6 @@ int main(int argc, char * argv[]) {
initSigHandlers();
readPlaylistState();
/* qball crappy code */
readAudioDevicesState();
while(COMMAND_RETURN_KILL!=doIOForInterfaces()) {
if(COMMAND_RETURN_KILL==handlePendingSignals()) break;
@ -502,12 +552,8 @@ int main(int argc, char * argv[]) {
}
savePlaylistState();
/* qball crappy code */
saveAudioDevicesState();
playerKill();
freeAllInterfaces();
closeAllListenSockets();
closeMp3Directory();

View File

@ -43,11 +43,7 @@
#include <errno.h>
#include <fcntl.h>
volatile int player_pid = 0;
void clearPlayerPid() {
player_pid = 0;
}
extern int masterPid;
static void resetPlayerMetadata() {
PlayerControl * pc = &(getPlayerData()->playerControl);
@ -60,7 +56,7 @@ static void resetPlayerMetadata() {
void resetPlayer() {
int pid;
clearPlayerPid();
setPlayerPid(0);
getPlayerData()->playerControl.stop = 0;
getPlayerData()->playerControl.play = 0;
getPlayerData()->playerControl.pause = 0;
@ -77,7 +73,7 @@ void resetPlayer() {
}
void player_sigChldHandler(int pid, int status) {
if(player_pid==pid) {
if(getPlayerPid()==pid) {
DEBUG("SIGCHLD caused by player process\n");
if(WIFSIGNALED(status) && WTERMSIG(status)!=SIGTERM &&
WTERMSIG(status)!=SIGINT)
@ -87,7 +83,7 @@ void player_sigChldHandler(int pid, int status) {
}
resetPlayer();
}
else if(pid==getPlayerData()->playerControl.decode_pid && player_pid<=0)
else if(pid==getPlayerData()->playerControl.decode_pid && getPlayerPid()<=0)
{
if(WIFSIGNALED(status) && WTERMSIG(status)!=SIGTERM) {
ERROR("(caught by master parent) "
@ -100,24 +96,31 @@ void player_sigChldHandler(int pid, int status) {
}
int playerInit() {
kill(masterPid, SIGUSR2);
while (getPlayerPid()==0) my_usleep(10000); //we need to wait for the signal to take effect
return 0;
}
int playerInitReal() {
int player_pid;
blockSignals();
player_pid = fork();
if(player_pid==0) {
PlayerControl * pc = &(getPlayerData()->playerControl);
clearUpdatePid();
//clearUpdatePid();
unblockSignals();
setSigHandlersForDecoder();
closeAllListenSockets();
freeAllInterfaces();
closeMp3Directory();
finishPlaylist();
finishPermissions();
finishCommands();
finishVolume();
//closeAllListenSockets();
//freeAllInterfaces();
//closeMp3Directory();
//finishPlaylist();
//finishPermissions();
//finishCommands();
//finishVolume();
while(1) {
if(pc->play) decode();
@ -148,10 +151,13 @@ int playerInit() {
else if(player_pid<0) {
unblockSignals();
ERROR("player Problems fork()'ing\n");
setPlayerPid(0);
player_pid = 0;
return -1;
}
else
setPlayerPid(player_pid);
unblockSignals();
return 0;
@ -183,13 +189,13 @@ int playerPlay(FILE * fp, Song * song) {
pc->utf8url[MAXPATHLEN] = '\0';
pc->play = 1;
if(player_pid==0 && playerInit()<0) {
if(getPlayerPid()==0 && playerInit()<0) {
pc->play = 0;
return -1;
}
resetPlayerMetadata();
while(player_pid>0 && pc->play) my_usleep(1000);
while(getPlayerPid()>0 && pc->play) my_usleep(1000);
return 0;
}
@ -197,9 +203,9 @@ int playerPlay(FILE * fp, Song * song) {
int playerStop(FILE * fp) {
PlayerControl * pc = &(getPlayerData()->playerControl);
if(player_pid>0 && pc->state!=PLAYER_STATE_STOP) {
if(getPlayerPid()>0 && pc->state!=PLAYER_STATE_STOP) {
pc->stop = 1;
while(player_pid>0 && pc->stop) my_usleep(1000);
while(getPlayerPid()>0 && pc->stop) my_usleep(1000);
}
pc->queueState = PLAYER_QUEUE_BLANK;
@ -216,16 +222,16 @@ void playerKill() {
playerCloseAudio(stderr);
if(player_pid>0 && pc->closeAudio) sleep(1);*/
pid = player_pid;
pid = getPlayerPid();
if(pid>0) kill(pid,SIGTERM);
}
int playerPause(FILE * fp) {
PlayerControl * pc = &(getPlayerData()->playerControl);
if(player_pid>0 && pc->state!=PLAYER_STATE_STOP) {
if(getPlayerPid()>0 && pc->state!=PLAYER_STATE_STOP) {
pc->pause = 1;
while(player_pid>0 && pc->pause) my_usleep(1000);
while(getPlayerPid()>0 && pc->pause) my_usleep(1000);
}
return 0;
@ -234,7 +240,7 @@ int playerPause(FILE * fp) {
int playerSetPause(FILE * fp, int pause) {
PlayerControl * pc = &(getPlayerData()->playerControl);
if(player_pid<=0) return 0;
if(getPlayerPid()<=0) return 0;
switch(pc->state) {
case PLAYER_STATE_PLAY:
@ -314,7 +320,7 @@ char * getPlayerErrorStr() {
void playerCloseAudio() {
PlayerControl * pc = &(getPlayerData()->playerControl);
if(player_pid>0) {
if(getPlayerPid()>0) {
if(playerStop(stderr)<0) return;
pc->closeAudio = 1;
}
@ -354,20 +360,20 @@ void setQueueState(int queueState) {
void playerQueueLock() {
PlayerControl * pc = &(getPlayerData()->playerControl);
if(player_pid>0 && pc->queueLockState==PLAYER_QUEUE_UNLOCKED)
if(getPlayerPid()>0 && pc->queueLockState==PLAYER_QUEUE_UNLOCKED)
{
pc->lockQueue = 1;
while(player_pid>0 && pc->lockQueue) my_usleep(1000);
while(getPlayerPid()>0 && pc->lockQueue) my_usleep(1000);
}
}
void playerQueueUnlock() {
PlayerControl * pc = &(getPlayerData()->playerControl);
if(player_pid>0 && pc->queueLockState==PLAYER_QUEUE_LOCKED)
if(getPlayerPid()>0 && pc->queueLockState==PLAYER_QUEUE_LOCKED)
{
pc->unlockQueue = 1;
while(player_pid>0 && pc->unlockQueue) my_usleep(1000);
while(getPlayerPid()>0 && pc->unlockQueue) my_usleep(1000);
}
}
@ -394,7 +400,7 @@ int playerSeek(FILE * fp, Song * song, float time) {
resetPlayerMetadata();
pc->seekWhere = time;
pc->seek = 1;
while(player_pid>0 && pc->seek) my_usleep(1000);
while(getPlayerPid()>0 && pc->seek) my_usleep(1000);
}
return 0;

View File

@ -34,6 +34,7 @@ int buffered_chunks;
#define DEFAULT_BUFFER_BEFORE_PLAY 0
PlayerData * playerData_pd;
int * player_pid;
void initPlayerData() {
float perc = DEFAULT_BUFFER_BEFORE_PLAY;
@ -102,6 +103,22 @@ void initPlayerData() {
ERROR("problems shmctl'ing\n");
exit(EXIT_FAILURE);
}
/* maybe the following should be put in the same shm block as the previous
* or maybe even made a part of the playerData struct
*/
allocationSize = sizeof(int);
if((shmid = shmget(IPC_PRIVATE,allocationSize,IPC_CREAT|0600))<0) {
ERROR("problems shmget'ing\n");
exit(EXIT_FAILURE);
}
if((player_pid = shmat(shmid,NULL,0))<0) {
ERROR("problems shmat'ing\n");
exit(EXIT_FAILURE);
}
if (shmctl(shmid, IPC_RMID, 0)<0) {
ERROR("problems shmctl'ing\n");
exit(EXIT_FAILURE);
}
buffer = &(playerData_pd->buffer);
@ -147,6 +164,15 @@ PlayerData * getPlayerData() {
return playerData_pd;
}
int getPlayerPid() {
return *player_pid;
}
void setPlayerPid(int pid) {
*player_pid = pid;
}
void freePlayerData() {
shmdt(playerData_pd);
shmdt(player_pid);
}

View File

@ -43,6 +43,8 @@ typedef struct _PlayerData {
void initPlayerData();
PlayerData * getPlayerData();
int getPlayerPid();
void setPlayerPid(int pid);
void freePlayerData();

View File

@ -19,6 +19,7 @@
#include "sig_handlers.h"
#include "player.h"
#include "playerData.h"
#include "playlist.h"
#include "directory.h"
#include "command.h"
@ -33,10 +34,30 @@
#include <sys/resource.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
extern volatile int masterPid;
extern volatile int mainPid;
int masterHandlePendingSignals() {
if(signal_is_pending(SIGINT) || signal_is_pending(SIGTERM)) {
DEBUG("master process got SIGINT or SIGTERM, exiting\n");
return COMMAND_RETURN_KILL;
}
if(signal_is_pending(SIGHUP)) {
signal_clear(SIGHUP);
/* Forward it to the main process, which will update the DB */
kill(mainPid, SIGHUP);
}
return 0;
}
int handlePendingSignals() {
if(signal_is_pending(SIGINT) || signal_is_pending(SIGTERM)) {
DEBUG("got SIGINT or SIGTERM, exiting\n");
DEBUG("main process got SIGINT or SIGTERM, exiting\n");
return COMMAND_RETURN_KILL;
}
@ -57,17 +78,55 @@ int handlePendingSignals() {
void chldSigHandler(int signal) {
int status;
int pid;
DEBUG("got SIGCHLD\n");
DEBUG("main process got SIGCHLD\n");
while(0 != (pid = wait3(&status,WNOHANG,NULL))) {
if(pid<0) {
if(errno==EINTR) continue;
else break;
}
player_sigChldHandler(pid,status);
directory_sigChldHandler(pid,status);
}
}
void masterChldSigHandler(int signal) {
int status;
int pid;
DEBUG("master process got SIGCHLD\n");
while(0 != (pid = wait3(&status,WNOHANG,NULL))) {
if(pid<0) {
if(errno==EINTR) continue;
else break;
}
DEBUG("PID: %d\n",pid);
if (pid == mainPid) kill(getpid(), SIGTERM);
player_sigChldHandler(pid,status);
}
}
int playerInitReal();
void masterSigUsr2Handler(int signal) {
DEBUG("Master process got SIGUSR2 starting a new player process\n");
if (getPlayerPid() <= 0)
playerInitReal();
}
void masterInitSigHandlers() {
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = SIG_IGN;
while(sigaction(SIGPIPE,&sa,NULL)<0 && errno==EINTR);
sa.sa_handler = masterChldSigHandler;
while(sigaction(SIGCHLD,&sa,NULL)<0 && errno==EINTR);
sa.sa_handler = masterSigUsr2Handler;
while(sigaction(SIGUSR2,&sa,NULL)<0 && errno==EINTR);
signal_handle(SIGUSR1);
signal_handle(SIGINT);
signal_handle(SIGTERM);
signal_handle(SIGHUP);
}
void initSigHandlers() {
struct sigaction sa;
@ -114,17 +173,32 @@ void ignoreSignals() {
while(sigaction(SIGPIPE,&sa,NULL)<0 && errno==EINTR);
while(sigaction(SIGCHLD,&sa,NULL)<0 && errno==EINTR);
while(sigaction(SIGUSR1,&sa,NULL)<0 && errno==EINTR);
while(sigaction(SIGUSR2,&sa,NULL)<0 && errno==EINTR);
while(sigaction(SIGINT,&sa,NULL)<0 && errno==EINTR);
while(sigaction(SIGTERM,&sa,NULL)<0 && errno==EINTR);
while(sigaction(SIGHUP,&sa,NULL)<0 && errno==EINTR);
}
void waitOnSignals() {
sigset_t sset;
sigfillset(&sset);
sigdelset(&sset,SIGCHLD);
sigdelset(&sset,SIGUSR1);
sigdelset(&sset,SIGUSR2);
sigdelset(&sset,SIGHUP);
sigdelset(&sset,SIGINT);
sigdelset(&sset,SIGTERM);
sigsuspend(&sset);
}
void blockSignals() {
sigset_t sset;
sigemptyset(&sset);
sigaddset(&sset,SIGCHLD);
sigaddset(&sset,SIGUSR1);
sigaddset(&sset,SIGUSR2);
sigaddset(&sset,SIGHUP);
sigaddset(&sset,SIGINT);
sigaddset(&sset,SIGTERM);
@ -137,6 +211,7 @@ void unblockSignals() {
sigemptyset(&sset);
sigaddset(&sset,SIGCHLD);
sigaddset(&sset,SIGUSR1);
sigaddset(&sset,SIGUSR2);
sigaddset(&sset,SIGHUP);
sigaddset(&sset,SIGINT);
sigaddset(&sset,SIGTERM);

View File

@ -22,8 +22,10 @@
#include "../config.h"
int handlePendingSignals();
int masterHandlePendingSignals();
void initSigHandlers();
void masterInitSigHandlers();
void finishSigHandlers();
@ -31,6 +33,8 @@ void setSigHandlersForDecoder();
void ignoreSignals();
void waitOnSignals();
void blockSignals();
void unblockSignals();

View File

@ -1,11 +1,12 @@
#include "tagTracker.h"
#include "list.h"
#include "log.h"
#include <glib/gtree.h>
#include <assert.h>
#include <stdlib.h>
static List * tagLists[TAG_NUM_OF_ITEM_TYPES] =
static GTree * tagLists[TAG_NUM_OF_ITEM_TYPES] =
{
NULL,
NULL,
@ -21,89 +22,96 @@ typedef struct tagTrackerItem {
mpd_sint8 visited;
} TagTrackerItem;
int keyCompare(const void *a, const void *b, void *data) {
return strcmp(a,b);
}
char * getTagItemString(int type, char * string) {
ListNode * node;
int pos;
TagTrackerItem * item;
TagTrackerItem ** itemPointer = &item;
char *key;
char **keyPointer = &key;
/*if(type == TAG_ITEM_TITLE) return strdup(string);*/
if(tagLists[type] == NULL) {
tagLists[type] = makeList(free, 1);
sortList(tagLists[type]);
tagLists[type] = g_tree_new_full(keyCompare, NULL, free, free);
}
if(findNodeInList(tagLists[type], string, &node, &pos)) {
((TagTrackerItem *)node->data)->count++;
if((TagTrackerItem *)g_tree_lookup_extended(tagLists[type], string, (void**)keyPointer, (void**)itemPointer )) {
item->count++;
}
else {
TagTrackerItem * item = malloc(sizeof(TagTrackerItem));
item = malloc(sizeof(TagTrackerItem));
item->count = 1;
item->visited = 0;
node = insertInListBeforeNode(tagLists[type], node, pos,
string, item);
key = strdup(string);
g_tree_insert(tagLists[type], key, item);
}
return node->key;
return key;
}
void removeTagItemString(int type, char * string) {
ListNode * node;
int pos;
TagTrackerItem *item;
assert(string);
assert(tagLists[type]);
if(tagLists[type] == NULL) return;
/*if(!node) {
free(string);
return;
}*/
if(findNodeInList(tagLists[type], string, &node, &pos)) {
TagTrackerItem * item = node->data;
if((item = g_tree_lookup(tagLists[type], string))) {
item->count--;
if(item->count <= 0) deleteNodeFromList(tagLists[type], node);
if(item->count <= 0) g_tree_remove(tagLists[type], string);
}
if(tagLists[type]->numberOfNodes == 0) {
/* why would this be done??? free it when mpd quits...
if(tagLists[type]->numberOfNodes == 0) {
freeList(tagLists[type]);
tagLists[type] = NULL;
}
*/
}
void destroyTagTracker() {
int type;
for (type=0; type < TAG_NUM_OF_ITEM_TYPES; type ++)
if (tagLists[type])
g_tree_destroy(tagLists[type]);
}
int getNumberOfTagItems(int type) {
if(tagLists[type] == NULL) return 0;
return tagLists[type]->numberOfNodes;
return g_tree_nnodes(tagLists[type]);
}
int calcSavedMemory(char *key, TagTrackerItem* value, int* sum) {
*sum -= sizeof(int) + 4*sizeof(void*); //sizeof(_GTreeNode)
*sum -= sizeof(TagTrackerItem);
*sum += (strlen(key)+1)*value->count;
return FALSE;
}
void printMemorySavedByTagTracker() {
int i;
ListNode * node;
size_t sum = 0;
for(i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
if(!tagLists[i]) continue;
sum -= sizeof(List);
node = tagLists[i]->firstNode;
while(node != NULL) {
sum -= sizeof(ListNode);
sum -= sizeof(TagTrackerItem);
sum -= sizeof(node->key);
sum += (strlen(node->key)+1)*(*((int *)node->data));
node = node->nextNode;
}
sum -= 5*sizeof(void*);//sizeof(_GTree)
g_tree_foreach(tagLists[i], (GTraverseFunc)calcSavedMemory, &sum);
}
DEBUG("saved memory from tags: %li\n", (long)sum);
}
void sortTagTrackerInfo() {
/* implicit sorting
int i;
for(i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) {
@ -112,56 +120,52 @@ void sortTagTrackerInfo() {
DEBUG("sorting %s info\n", mpdTagItemKeys[i]);
sortList(tagLists[i]);
}
}*/
}
int resetVisitedFlag(char *key, TagTrackerItem *value, void *data) {
value->visited = 0;
return FALSE;
}
void resetVisitedFlagsInTagTracker(int type) {
ListNode * node;
if(!tagLists[type]) return;
node = tagLists[type]->firstNode;
while(node) {
((TagTrackerItem *)node->data)->visited = 0;
node = node->nextNode;
}
g_tree_foreach(tagLists[type], (GTraverseFunc)resetVisitedFlag, NULL);
}
int wasVisitedInTagTracker(int type, char * str) {
void * item;
TagTrackerItem * item;
if(!tagLists[type]) return 0;
if(!findInList(tagLists[type], str, &item)) return 0;
if(!(item = g_tree_lookup(tagLists[type], str))) return 0;
return ((TagTrackerItem *)item)->visited;
return item->visited;
}
void visitInTagTracker(int type, char * str) {
void * item;
if(!tagLists[type]) return;
if(!findInList(tagLists[type], str, &item)) return;
((TagTrackerItem *)item)->visited = 1;
}
void printVisitedInTagTracker(FILE * fp, int type) {
ListNode * node;
TagTrackerItem * item;
if(!tagLists[type]) return;
node = tagLists[type]->firstNode;
if(!(item = g_tree_lookup(tagLists[type], str))) return;
while(node) {
item = node->data;
if(item->visited) {
myfprintf(fp, "%s: %s\n", mpdTagItemKeys[type],
node->key);
}
node = node->nextNode;
}
item->visited = 1;
}
struct _PrintVisitedUserdata {
FILE *fp;
char *type;
};
int printVisitedFlag(char *key, TagTrackerItem* value, struct _PrintVisitedUserdata *data) {
if(value->visited) myfprintf(data->fp, "%s: %s\n", data->type, key);
return FALSE;
}
void printVisitedInTagTracker(FILE * fp, int type) {
struct _PrintVisitedUserdata data = {fp, mpdTagItemKeys[type]};
if(!tagLists[type]) return;
g_tree_foreach( tagLists[type], (GTraverseFunc)printVisitedFlag, (void*)&data);
}

View File

@ -6,6 +6,7 @@
char * getTagItemString(int type, char * string);
void removeTagItemString(int type, char * string);
void destroyTagTracker();
int getNumberOfTagItems(int type);