admin.c 21 KB
Newer Older
1
2
3
4
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

5
6
#include <string.h>
#include <stdlib.h>
7
8
#include <stdarg.h>
#include <time.h>
9
10
11
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
12

13
#include "cfgfile.h"
14
15
16
17
18
19
20
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "event.h"
#include "stats.h"
21
#include "os.h"
22
#include "xslt.h"
23
24
25
26
27

#include "format.h"
#include "format_mp3.h"

#include "logging.h"
Ed "oddsock" Zaleski's avatar
Ed "oddsock" Zaleski committed
28
29
30
#ifdef _WIN32
#define snprintf _snprintf
#endif
31
32
33

#define CATMODULE "admin"

34
35
36
#define COMMAND_ERROR             (-1)

/* Mount-specific commands */
37
#define COMMAND_RAW_FALLBACK        1
38
#define COMMAND_METADATA_UPDATE     2
39
40
41
42
43
44
#define COMMAND_RAW_SHOW_LISTENERS  3
#define COMMAND_RAW_MOVE_CLIENTS    4

#define COMMAND_TRANSFORMED_FALLBACK        50
#define COMMAND_TRANSFORMED_SHOW_LISTENERS  53
#define COMMAND_TRANSFORMED_MOVE_CLIENTS    54
45
46

/* Global commands */
47
#define COMMAND_RAW_LIST_MOUNTS   101
48
#define COMMAND_RAW_STATS         102
49
#define COMMAND_RAW_LISTSTREAM    103
50
51
52
#define COMMAND_TRANSFORMED_LIST_MOUNTS   201
#define COMMAND_TRANSFORMED_STATS         202
#define COMMAND_TRANSFORMED_LISTSTREAM    203
53

54
/* Client management commands */
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#define COMMAND_RAW_KILL_CLIENT   301
#define COMMAND_RAW_KILL_SOURCE   302
#define COMMAND_TRANSFORMED_KILL_CLIENT   401
#define COMMAND_TRANSFORMED_KILL_SOURCE   402

#define FALLBACK_RAW_REQUEST "fallbacks"
#define FALLBACK_TRANSFORMED_REQUEST "fallbacks.xsl"
#define METADATA_REQUEST "metadata"
#define LISTCLIENTS_RAW_REQUEST "listclients"
#define LISTCLIENTS_TRANSFORMED_REQUEST "listclients.xsl"
#define STATS_RAW_REQUEST "stats"
#define STATS_TRANSFORMED_REQUEST "stats.xsl"
#define LISTMOUNTS_RAW_REQUEST "listmounts"
#define LISTMOUNTS_TRANSFORMED_REQUEST "listmounts.xsl"
#define STREAMLIST_RAW_REQUEST "streamlist"
#define STREAMLIST_TRANSFORMED_REQUEST "streamlist.xsl"
#define MOVECLIENTS_RAW_REQUEST "moveclients"
#define MOVECLIENTS_TRANSFORMED_REQUEST "moveclients.xsl"
#define KILLCLIENT_RAW_REQUEST "killclient"
#define KILLCLIENT_TRANSFORMED_REQUEST "killclient.xsl"
#define KILLSOURCE_RAW_REQUEST "killsource"
#define KILLSOURCE_TRANSFORMED_REQUEST "killsource.xsl"
#define ADMIN_XSL_RESPONSE "response.xsl"
#define DEFAULT_RAW_REQUEST ""
#define DEFAULT_TRANSFORMED_REQUEST ""

#define RAW         1
#define TRANSFORMED 2
83
84
int admin_get_command(char *command)
{
85
86
87
88
89
    if(!strcmp(command, FALLBACK_RAW_REQUEST))
        return COMMAND_RAW_FALLBACK;
    else if(!strcmp(command, FALLBACK_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_FALLBACK;
    else if(!strcmp(command, METADATA_REQUEST))
90
        return COMMAND_METADATA_UPDATE;
91
92
93
94
95
    else if(!strcmp(command, LISTCLIENTS_RAW_REQUEST))
        return COMMAND_RAW_SHOW_LISTENERS;
    else if(!strcmp(command, LISTCLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_SHOW_LISTENERS;
    else if(!strcmp(command, STATS_RAW_REQUEST))
96
        return COMMAND_RAW_STATS;
97
98
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
99
100
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
101
102
103
104
105
    else if(!strcmp(command, LISTMOUNTS_RAW_REQUEST))
        return COMMAND_RAW_LIST_MOUNTS;
    else if(!strcmp(command, LISTMOUNTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_LIST_MOUNTS;
    else if(!strcmp(command, STREAMLIST_RAW_REQUEST))
106
        return COMMAND_RAW_LISTSTREAM;
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    else if(!strcmp(command, MOVECLIENTS_RAW_REQUEST))
        return COMMAND_RAW_MOVE_CLIENTS;
    else if(!strcmp(command, MOVECLIENTS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_MOVE_CLIENTS;
    else if(!strcmp(command, KILLCLIENT_RAW_REQUEST))
        return COMMAND_RAW_KILL_CLIENT;
    else if(!strcmp(command, KILLCLIENT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_CLIENT;
    else if(!strcmp(command, KILLSOURCE_RAW_REQUEST))
        return COMMAND_RAW_KILL_SOURCE;
    else if(!strcmp(command, KILLSOURCE_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_KILL_SOURCE;
    else if(!strcmp(command, DEFAULT_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
    else if(!strcmp(command, DEFAULT_RAW_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
123
124
125
126
    else
        return COMMAND_ERROR;
}

127
static void command_fallback(client_t *client, source_t *source, int response);
128
static void command_metadata(client_t *client, source_t *source);
129
130
131
132
133
134
135
136
137
138
static void command_show_listeners(client_t *client, source_t *source,
        int response);
static void command_move_clients(client_t *client, source_t *source,
        int response);
static void command_stats(client_t *client, int response);
static void command_list_mounts(client_t *client, int response);
static void command_kill_client(client_t *client, source_t *source,
        int response);
static void command_kill_source(client_t *client, source_t *source,
        int response);
139
140
141
static void admin_handle_mount_request(client_t *client, source_t *source,
        int command);
static void admin_handle_general_request(client_t *client, int command);
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
static void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template);
static void html_write(client_t *client, char *fmt, ...);

xmlDocPtr admin_build_sourcelist(char *current_source)
{
    avl_node *node;
    source_t *source;
    xmlNodePtr xmlnode, srcnode;
    xmlDocPtr doc;
    char buf[22];
    time_t now = time(NULL);

    doc = xmlNewDoc("1.0");
    xmlnode = xmlNewDocNode(doc, NULL, "icestats", NULL);
    xmlDocSetRootElement(doc, xmlnode);
158

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    if (current_source) {
        xmlNewChild(xmlnode, NULL, "current_source", current_source);
    }

    avl_tree_rlock(global.source_tree);

    node = avl_get_first(global.source_tree);
    while(node) {
        source = (source_t *)node->key;
        srcnode = xmlNewChild(xmlnode, NULL, "source", NULL);
        xmlSetProp(srcnode, "mount", source->mount);

        xmlNewChild(srcnode, NULL, "fallback", 
                    (source->fallback_mount != NULL)?
                    source->fallback_mount:"");
        memset(buf, '\000', sizeof(buf));
175
        snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
176
177
        xmlNewChild(srcnode, NULL, "listeners", buf);
        memset(buf, '\000', sizeof(buf));
178
        snprintf(buf, sizeof(buf)-1, "%ld", now - source->con->con_time);
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
        xmlNewChild(srcnode, NULL, "Connected", buf);
        xmlNewChild(srcnode, NULL, "Format", 
            source->format->format_description);
        node = avl_get_next(node);
    }
    avl_tree_unlock(global.source_tree);
    return(doc);
}

void admin_send_response(xmlDocPtr doc, client_t *client, 
        int response, char *xslt_template)
{
    char *buff = NULL;
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
        xmlDocDumpMemory(doc, (xmlChar **)&buff, &len);
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Length: %d\r\n"
               "Content-Type: text/xml\r\n"
               "\r\n", len);
        html_write(client, buff);
    }
    if (response == TRANSFORMED) {
        config = config_get_config();
        adminwebroot = config->adminroot_dir;
        config_release_config();
        fullpath_xslt_template_len = strlen(adminwebroot) + 
            strlen(xslt_template) + 2;
        fullpath_xslt_template = malloc(fullpath_xslt_template_len);
        memset(fullpath_xslt_template, '\000', fullpath_xslt_template_len);
        snprintf(fullpath_xslt_template, fullpath_xslt_template_len, "%s%s%s",
            adminwebroot, PATH_SEPARATOR, xslt_template);
        html_write(client, "HTTP/1.0 200 OK\r\n"
               "Content-Type: text/html\r\n"
               "\r\n");
        DEBUG1("Sending XSLT (%s)", fullpath_xslt_template);
        xslt_transform(doc, fullpath_xslt_template, client);
        free(fullpath_xslt_template);
    }
    if (buff) {
        xmlFree(buff);
    }
}
228
229
230
231
232
233
234
235
236
237
238
239
240
void admin_handle_request(client_t *client, char *uri)
{
    char *mount, *command_string;
    int command;

    if(strncmp("/admin/", uri, 7)) {
        ERROR0("Internal error: admin request isn't");
        client_send_401(client);
        return;
    }

    command_string = uri + 7;

241
    DEBUG1("Got command (%s)", command_string);
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
    command = admin_get_command(command_string);

    if(command < 0) {
        ERROR1("Error parsing command string or unrecognised command: %s",
                command_string);
        client_send_400(client, "Unrecognised command");
        return;
    }

    mount = httpp_get_query_param(client->parser, "mount");

    if(mount != NULL) {
        source_t *source;

        /* This is a mount request, handle it as such */
257
258
        if(!connection_check_admin_pass(client->parser)) {
            if(!connection_check_source_pass(client->parser, mount)) {
259
                INFO1("Bad or missing password on mount modification admin "
260
261
262
263
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
        }
        
        avl_tree_rlock(global.source_tree);
        source = source_find_mount(mount);
        avl_tree_unlock(global.source_tree);

        if(source == NULL) {
            WARN2("Admin command %s on non-existent source %s", 
                    command_string, mount);
            client_send_400(client, "Source does not exist");
            return;
        }

        INFO2("Received admin command %s on mount \"%s\"", 
                command_string, mount);

        admin_handle_mount_request(client, source, command);
    }
    else {

        if(!connection_check_admin_pass(client->parser)) {
285
            INFO1("Bad or missing password on admin command "
286
287
288
289
290
291
292
293
294
295
296
297
298
                  "request (command: %s)", command_string);
            client_send_401(client);
            return;
        }
        
        admin_handle_general_request(client, command);
    }
}

static void admin_handle_general_request(client_t *client, int command)
{
    switch(command) {
        case COMMAND_RAW_STATS:
299
            command_stats(client, RAW);
300
            break;
301
302
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
303
304
            break;
        case COMMAND_RAW_LISTSTREAM:
305
306
307
308
309
310
311
312
313
314
315
316
317
            command_list_mounts(client, RAW);
            break;
        case COMMAND_TRANSFORMED_STATS:
            command_stats(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_LIST_MOUNTS:
            command_list_mounts(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_LISTSTREAM:
            command_list_mounts(client, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_MOVE_CLIENTS:
            command_list_mounts(client, TRANSFORMED);
318
            break;
319
320
321
322
323
324
325
326
327
328
329
        default:
            WARN0("General admin request not recognised");
            client_send_400(client, "Unknown admin request");
            return;
    }
}

static void admin_handle_mount_request(client_t *client, source_t *source, 
        int command)
{
    switch(command) {
330
331
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
332
333
334
335
            break;
        case COMMAND_METADATA_UPDATE:
            command_metadata(client, source);
            break;
336
337
338
339
340
341
342
343
        case COMMAND_RAW_SHOW_LISTENERS:
            command_show_listeners(client, source, RAW);
            break;
        case COMMAND_RAW_MOVE_CLIENTS:
            command_move_clients(client, source, RAW);
            break;
        case COMMAND_RAW_KILL_CLIENT:
            command_kill_client(client, source, RAW);
344
            break;
345
346
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
347
            break;
348
349
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
350
            break;
351
352
353
354
355
356
357
358
359
360
361
        case COMMAND_TRANSFORMED_SHOW_LISTENERS:
            command_show_listeners(client, source, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_MOVE_CLIENTS:
            command_move_clients(client, source, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_KILL_CLIENT:
            command_kill_client(client, source, TRANSFORMED);
            break;
        case COMMAND_TRANSFORMED_KILL_SOURCE:
            command_kill_source(client, source, TRANSFORMED);
362
            break;
363
364
365
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
366
            break;
367
368
369
370
371
372
373
374
375
376
377
    }
}

#define COMMAND_REQUIRE(client,name,var) \
    do { \
        (var) = httpp_get_query_param((client)->parser, (name)); \
        if((var) == NULL) { \
            client_send_400((client), "Missing parameter"); \
            return; \
        } \
    } while(0);
378
379
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
380

381
static void html_success(client_t *client, char *message)
382
383
384
385
386
387
388
389
390
391
392
393
{
    int bytes;

    client->respcode = 200;
    bytes = sock_write(client->con->sock, 
            "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n" 
            "<html><head><title>Admin request successful</title></head>"
            "<body><p>%s</p></body></html>", message);
    if(bytes > 0) client->con->sent_bytes = bytes;
    client_destroy(client);
}

394
395
396
397
398
399
400
401
402
403
404
static void html_write(client_t *client, char *fmt, ...)
{
    int bytes;
    va_list ap;

    va_start(ap, fmt);
    bytes = sock_write_fmt(client->con->sock, fmt, ap);
    va_end(ap);
    if(bytes > 0) client->con->sent_bytes = bytes;
}

405
406
static void command_move_clients(client_t *client, source_t *source,
    int response)
407
408
409
410
411
{
    char *dest_source;
    source_t *dest;
    avl_node *client_node;
    client_t *current;
412
413
414
415
416
417
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
418
    if((COMMAND_OPTIONAL(client, "destination", dest_source))) {
419
420
421
422
423
424
425
426
427
428
429
        parameters_passed = 1;
    }
    DEBUG1("Done optional check (%d)", parameters_passed);
    if (!parameters_passed) {
        doc = admin_build_sourcelist(source->mount);
        admin_send_response(doc, client, response, 
             MOVECLIENTS_TRANSFORMED_REQUEST);
        xmlFreeDoc(doc);
        client_destroy(client);
        return;
    }
430
431
432
433
434
435
436
437
438
439
    
    avl_tree_rlock(global.source_tree);
    dest = source_find_mount(dest_source);
    avl_tree_unlock(global.source_tree);

    if(dest == NULL) {
        client_send_400(client, "No such source");
        return;
    }

440
441
442
443
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

444
445
446
447
448
449
450
451
452
453
454
455
    avl_tree_wlock(source->client_tree);
    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;

        avl_tree_wlock(dest->pending_tree);
        avl_insert(dest->pending_tree, current);
        avl_tree_unlock(dest->pending_tree);

        client_node = avl_get_next(client_node);

        avl_delete(source->client_tree, current, source_remove_client);
456
        source->listeners--;
457
458
459
460
    }

    avl_tree_unlock(source->client_tree);
        
461
462
463
464
465
466
467
468
469
470
    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf)-1, "Clients moved from %s to %s", dest_source, 
        source->mount);
    xmlNewChild(node, NULL, "message", buf);
    xmlNewChild(node, NULL, "return", "1");

    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
471
472
}

473
474
static void command_show_listeners(client_t *client, source_t *source,
    int response)
475
{
476
477
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
478
479
    avl_node *client_node;
    client_t *current;
480
481
    char buf[22];
    char *userAgent = NULL;
482
483
    time_t now = time(NULL);

484
485
486
487
488
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
489

490
    memset(buf, '\000', sizeof(buf));
491
    snprintf(buf, sizeof(buf)-1, "%ld", source->listeners);
492
    xmlNewChild(srcnode, NULL, "Listeners", buf);
493
494
495
496
497
498

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
499
500
501
502
503
504
505
506
507
508
        listenernode = xmlNewChild(srcnode, NULL, "listener", NULL);
        xmlNewChild(listenernode, NULL, "IP", current->con->ip);
        userAgent = httpp_getvar(current->parser, "user-agent");
        if (userAgent) {
            xmlNewChild(listenernode, NULL, "UserAgent", userAgent);
        }
        else {
            xmlNewChild(listenernode, NULL, "UserAgent", "Unknown");
        }
        memset(buf, '\000', sizeof(buf));
509
        snprintf(buf, sizeof(buf)-1, "%ld", now - current->con->con_time);
510
511
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
512
        snprintf(buf, sizeof(buf)-1, "%lu", current->con->id);
513
        xmlNewChild(listenernode, NULL, "ID", buf);
514
515
516
517
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
518
519
520
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
521
522
523
    client_destroy(client);
}

524
525
static void command_kill_source(client_t *client, source_t *source,
    int response)
526
{
527
528
529
530
531
532
533
534
535
    xmlDocPtr doc;
    xmlNodePtr node;

    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlNewChild(node, NULL, "message", "Source Removed");
    xmlNewChild(node, NULL, "return", "1");
    xmlDocSetRootElement(doc, node);

536
537
    source->running = 0;

538
539
540
541
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
542
543
}

544
545
static void command_kill_client(client_t *client, source_t *source,
    int response)
546
547
548
549
{
    char *idtext;
    int id;
    client_t *listener;
550
551
552
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
553
554
555
556
557
558
559

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

560
561
562
563
564
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

565
566
567
568
569
570
571
    if(listener != NULL) {
        INFO1("Admin request: client %d removed", id);

        /* This tags it for removal on the next iteration of the main source
         * loop
         */
        listener->con->error = 1;
572
573
574
575
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
576
577
    }
    else {
578
579
580
581
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d not found", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "0");
582
    }
583
584
585
586
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
587
588
}

589
590
static void command_fallback(client_t *client, source_t *source,
    int response)
591
592
593
594
595
596
597
598
599
600
601
602
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

    old = source->fallback_mount;
    source->fallback_mount = strdup(fallback);
    free(old);

603
    html_success(client, "Fallback configured");
604
605
606
607
608
609
610
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
    mp3_state *state;
611
612
613
614
#ifdef USE_YP
    int i;
    time_t current_time;
#endif
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639

    DEBUG0("Got metadata update request");

    COMMAND_REQUIRE(client, "mode", action);
    COMMAND_REQUIRE(client, "song", value);

    if(source->format->type != FORMAT_TYPE_MP3) {
        client_send_400(client, "Not mp3, cannot update metadata");
        return;
    }

    if(strcmp(action, "updinfo") != 0) {
        client_send_400(client, "No such action");
        return;
    }

    state = source->format->_state;

    thread_mutex_lock(&(state->lock));
    free(state->metadata);
    state->metadata = strdup(value);
    state->metadata_age++;
    state->metadata_raw = 0;
    thread_mutex_unlock(&(state->lock));

640
641
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
642
    stats_event(source->mount, "title", value);
643
644
645
646
647
648
649
650
651
652
#ifdef USE_YP
    /* If we get an update on the mountpoint, force a
       yp touch */
    current_time = time(NULL);
    for (i=0; i<source->num_yp_directories; i++) {
        source->ypdata[i]->yp_last_touch = current_time - 
            source->ypdata[i]->yp_touch_interval + 2;
    }
#endif

653

654
    html_success(client, "Metadata update successful");
655
656
}

657
658
659
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

660
661
    DEBUG0("Stats request, sending xml stats");

662
663
664
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
665
666
667
668
    client_destroy(client);
    return;
}

669
670
static void command_list_mounts(client_t *client, int response) {
    xmlDocPtr doc;
671
672
673

    DEBUG0("List mounts request");

674
    doc = admin_build_sourcelist(NULL);
675

676
677
    admin_send_response(doc, client, response, LISTMOUNTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
678
679
680
681
    client_destroy(client);
    return;
}