admin.c 21.1 KB
Newer Older
1
2
#include <string.h>
#include <stdlib.h>
3
4
#include <stdarg.h>
#include <time.h>
5
6
7
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
8
9
10
11
12
13
14
15
16

#include "config.h"
#include "connection.h"
#include "refbuf.h"
#include "client.h"
#include "source.h"
#include "global.h"
#include "event.h"
#include "stats.h"
17
#include "os.h"
18
19
20
21
22
23
24
25

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

#include "logging.h"

#define CATMODULE "admin"

26
27
28
#define COMMAND_ERROR             (-1)

/* Mount-specific commands */
29
#define COMMAND_RAW_FALLBACK        1
30
#define COMMAND_METADATA_UPDATE     2
31
32
33
34
35
36
#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
37
38

/* Global commands */
39
#define COMMAND_RAW_LIST_MOUNTS   101
40
#define COMMAND_RAW_STATS         102
41
#define COMMAND_RAW_LISTSTREAM    103
42
43
44
#define COMMAND_TRANSFORMED_LIST_MOUNTS   201
#define COMMAND_TRANSFORMED_STATS         202
#define COMMAND_TRANSFORMED_LISTSTREAM    203
45

46
/* Client management commands */
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#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
75
76
int admin_get_command(char *command)
{
77
78
79
80
81
    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))
82
        return COMMAND_METADATA_UPDATE;
83
84
85
86
87
    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))
88
        return COMMAND_RAW_STATS;
89
90
    else if(!strcmp(command, STATS_TRANSFORMED_REQUEST))
        return COMMAND_TRANSFORMED_STATS;
91
92
    else if(!strcmp(command, "stats.xml")) /* The old way */
        return COMMAND_RAW_STATS;
93
94
95
96
97
    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))
98
        return COMMAND_RAW_LISTSTREAM;
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
    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;
115
116
117
118
    else
        return COMMAND_ERROR;
}

119
static void command_fallback(client_t *client, source_t *source, int response);
120
static void command_metadata(client_t *client, source_t *source);
121
122
123
124
125
126
127
128
129
130
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);
131
132
133
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);
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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];
    int len = 0;
    time_t now = time(NULL);

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

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
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
    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));
        snprintf(buf, sizeof(buf)-1, "%d", source->listeners);
        xmlNewChild(srcnode, NULL, "listeners", buf);
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "%d", now - source->con->con_time);
        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);
    }
}
221
222
223
224
225
226
227
228
229
230
231
232
233
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;

234
    DEBUG1("Got command (%s)", command_string);
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
    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 */
250
251
        if(!connection_check_admin_pass(client->parser)) {
            if(!connection_check_source_pass(client->parser, mount)) {
252
                INFO1("Bad or missing password on mount modification admin "
253
254
255
256
                      "request (command: %s)", command_string);
                client_send_401(client);
                return;
            }
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
        }
        
        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)) {
278
            INFO1("Bad or missing password on admin command "
279
280
281
282
283
284
285
286
287
288
289
290
291
                  "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:
292
            command_stats(client, RAW);
293
            break;
294
295
        case COMMAND_RAW_LIST_MOUNTS:
            command_list_mounts(client, RAW);
296
297
            break;
        case COMMAND_RAW_LISTSTREAM:
298
299
300
301
302
303
304
305
306
307
308
309
310
            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);
311
            break;
312
313
314
315
316
317
318
319
320
321
322
        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) {
323
324
        case COMMAND_RAW_FALLBACK:
            command_fallback(client, source, RAW);
325
326
327
328
            break;
        case COMMAND_METADATA_UPDATE:
            command_metadata(client, source);
            break;
329
330
331
332
333
334
335
336
        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);
337
            break;
338
339
        case COMMAND_RAW_KILL_SOURCE:
            command_kill_source(client, source, RAW);
340
            break;
341
342
        case COMMAND_TRANSFORMED_FALLBACK:
            command_fallback(client, source, RAW);
343
            break;
344
345
346
347
348
349
350
351
352
353
354
        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);
355
            break;
356
357
358
        default:
            WARN0("Mount request not recognised");
            client_send_400(client, "Mount request unknown");
359
            break;
360
361
362
363
364
365
366
367
368
369
370
    }
}

#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);
371
372
#define COMMAND_OPTIONAL(client,name,var) \
    (var) = httpp_get_query_param((client)->parser, (name))
373

374
static void html_success(client_t *client, char *message)
375
376
377
378
379
380
381
382
383
384
385
386
{
    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);
}

387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
static void html_head(client_t *client)
{
    int bytes;

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

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;
}

412
413
static void command_move_clients(client_t *client, source_t *source,
    int response)
414
415
416
417
418
{
    char *dest_source;
    source_t *dest;
    avl_node *client_node;
    client_t *current;
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[255];
    int parameters_passed = 0;

    DEBUG0("Doing optional check");
    if (COMMAND_OPTIONAL(client, "destination", dest_source)) {
        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;
    }
437
438
439
440
441
442
443
444
445
446
    
    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;
    }

447
448
449
450
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);

451
452
453
454
455
456
457
458
459
460
461
462
    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);
463
        source->listeners--;
464
465
466
467
    }

    avl_tree_unlock(source->client_tree);
        
468
469
470
471
472
473
474
475
476
477
    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);
478
479
}

480
481
static void command_show_listeners(client_t *client, source_t *source,
    int response)
482
{
483
484
    xmlDocPtr doc;
    xmlNodePtr node, srcnode, listenernode;
485
486
    avl_node *client_node;
    client_t *current;
487
488
489
    char buf[22];
    int len = 0;
    char *userAgent = NULL;
490
491
    time_t now = time(NULL);

492
493
494
495
496
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "icestats", NULL);
    srcnode = xmlNewChild(node, NULL, "source", NULL);
    xmlSetProp(srcnode, "mount", source->mount);
    xmlDocSetRootElement(doc, node);
497

498
499
500
    memset(buf, '\000', sizeof(buf));
    snprintf(buf, sizeof(buf)-1, "%d", source->listeners);
    xmlNewChild(srcnode, NULL, "Listeners", buf);
501
502
503
504
505
506

    avl_tree_rlock(source->client_tree);

    client_node = avl_get_first(source->client_tree);
    while(client_node) {
        current = (client_t *)client_node->key;
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
        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));
        snprintf(buf, sizeof(buf)-1, "%d", now - current->con->con_time);
        xmlNewChild(listenernode, NULL, "Connected", buf);
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "%d", current->con->id);
        xmlNewChild(listenernode, NULL, "ID", buf);
522
523
524
525
        client_node = avl_get_next(client_node);
    }

    avl_tree_unlock(source->client_tree);
526
527
528
    admin_send_response(doc, client, response, 
        LISTCLIENTS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
529
530
531
    client_destroy(client);
}

532
533
static void command_kill_source(client_t *client, source_t *source,
    int response)
534
{
535
536
537
538
539
540
541
542
543
    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);

544
545
    source->running = 0;

546
547
548
549
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
550
551
}

552
553
static void command_kill_client(client_t *client, source_t *source,
    int response)
554
555
556
557
{
    char *idtext;
    int id;
    client_t *listener;
558
559
560
    xmlDocPtr doc;
    xmlNodePtr node;
    char buf[50] = "";
561
562
563
564
565
566
567

    COMMAND_REQUIRE(client, "id", idtext);

    id = atoi(idtext);

    listener = source_find_client(source, id);

568
569
570
571
572
    doc = xmlNewDoc("1.0");
    node = xmlNewDocNode(doc, NULL, "iceresponse", NULL);
    xmlDocSetRootElement(doc, node);
    DEBUG1("Response is %d", response);

573
574
575
576
577
578
579
    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;
580
581
582
583
        memset(buf, '\000', sizeof(buf));
        snprintf(buf, sizeof(buf)-1, "Client %d removed", id);
        xmlNewChild(node, NULL, "message", buf);
        xmlNewChild(node, NULL, "return", "1");
584
585
    }
    else {
586
587
588
589
        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");
590
    }
591
592
593
594
    admin_send_response(doc, client, response, 
        ADMIN_XSL_RESPONSE);
    xmlFreeDoc(doc);
    client_destroy(client);
595
596
}

597
598
static void command_fallback(client_t *client, source_t *source,
    int response)
599
600
601
602
603
604
605
606
607
608
609
610
{
    char *fallback;
    char *old;

    DEBUG0("Got fallback request");

    COMMAND_REQUIRE(client, "fallback", fallback);

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

611
    html_success(client, "Fallback configured");
612
613
614
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
640
641
642
643
}

static void command_metadata(client_t *client, source_t *source)
{
    char *action;
    char *value;
    mp3_state *state;

    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));

644
645
    DEBUG2("Metadata on mountpoint %s changed to \"%s\"", 
        source->mount, value);
646
647
    stats_event(source->mount, "title", value);

648
    html_success(client, "Metadata update successful");
649
650
}

651
652
653
static void command_stats(client_t *client, int response) {
    xmlDocPtr doc;

654
655
    DEBUG0("Stats request, sending xml stats");

656
657
658
    stats_get_xml(&doc);
    admin_send_response(doc, client, response, STATS_TRANSFORMED_REQUEST);
    xmlFreeDoc(doc);
659
660
661
662
    client_destroy(client);
    return;
}

663
static void command_list_mounts(client_t *client, int response) {
664
665
    avl_node *node;
    source_t *source;
666
    int bytes;
667
668
669
670
671
    xmlDocPtr doc;
    xmlNodePtr xmlnode, srcnode, datanode;
    char buf[22];
    int len = 0;
    time_t now = time(NULL);
672
673
674

    DEBUG0("List mounts request");

675
    doc = admin_build_sourcelist(NULL);
676

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