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
        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)
{
191
    xmlChar *buff = NULL;
192
193
194
195
196
197
198
199
    int len = 0;
    ice_config_t *config;
    char *fullpath_xslt_template;
    int fullpath_xslt_template_len;
    char *adminwebroot;

    client->respcode = 200;
    if (response == RAW) {
200
        xmlDocDumpMemory(doc, &buff, &len);
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
        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;
}