diff --git a/CREDITS b/CREDITS index a597af26..e9f15cc1 100644 --- a/CREDITS +++ b/CREDITS @@ -58,3 +58,7 @@ Gavin Shelley Stephen Rubner * fixes for ulong ino_t + +Ciamac Moallemi + * gzip content-encoding + diff --git a/src/configfile.c b/src/configfile.c index 56ae3004..2ab3a51e 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -112,6 +112,7 @@ CONFIGELEMENT config_elements[] = { { 1,0,0,CONFIG_TYPE_INT,"always_scan",(void*)&config.always_scan,config_emit_int }, { 1,0,0,CONFIG_TYPE_INT,"process_m3u",(void*)&config.process_m3u,config_emit_int }, { 1,0,0,CONFIG_TYPE_INT,"scan_type",(void*)&config.scan_type,config_emit_int }, + { 1,0,0,CONFIG_TYPE_INT,"compress",(void*)&config.compress,config_emit_int }, { 1,0,0,CONFIG_TYPE_STRING,"playlist",(void*)&config.playlist,config_emit_string }, { 1,0,0,CONFIG_TYPE_STRING,"extensions",(void*)&config.extensions,config_emit_string }, { 1,0,0,CONFIG_TYPE_STRING,"password",(void*)&config.readpassword, config_emit_string }, @@ -255,6 +256,7 @@ int config_read(char *file) { config.rescan_interval=0; config.process_m3u=0; config.scan_type=0; + config.compress=0; /* DWB: use alloced space so it can be freed without errors */ config.extensions=strdup(".mp3"); diff --git a/src/daap-proto.c b/src/daap-proto.c index 7c152ea5..29e7edbb 100644 --- a/src/daap-proto.c +++ b/src/daap-proto.c @@ -37,6 +37,103 @@ DAAP_BLOCK *daap_get_new(void); DAAP_BLOCK *daap_add_formatted(DAAP_BLOCK *parent, char *tag, int len, char *value); + + +GZIP_STREAM *gzip_alloc(void) { + GZIP_STREAM *gz = malloc(sizeof(GZIP_STREAM)); + gz->in_size = GZIP_CHUNK; + gz->in = malloc(gz->in_size); + gz->bytes_in = 0; + gz->out = NULL; + gz->bytes_out = 0; + return gz; +} + +ssize_t gzip_write(GZIP_STREAM *gz, void *buf, size_t size) { + int next_size; + char *in2; + int new_size; + + if (gz->in == NULL) + return -1; + + /* make sure input buffer is big enough */ + while (gz->in_size <= gz->bytes_in + size) { + new_size = 2*gz->in_size; + in2 = malloc(new_size); + if (in2 == NULL) { + DPRINTF(E_LOG,L_WS|L_DAAP,"out of memory for input buffer\n"); + free(gz->in); + gz->in = NULL; + gz->bytes_in = gz->in_size = 0; + return -1; + } + memcpy(in2, gz->in, gz->in_size); + free(gz->in); + gz->in = in2; + gz->in_size = new_size; + } + memcpy(gz->in + gz->bytes_in, buf, size); + gz->bytes_in += size; + return size; +} + +int gzip_compress(GZIP_STREAM *gz) { + int out_size; + int status; + z_stream strm; + + if (gz->in == NULL) + return -1; + + out_size = (int)(1.05*gz->in_size) + 20; + gz->out = malloc(out_size); + if (gz->out == NULL) { + DPRINTF(E_INF,L_WS|L_DAAP,"out of memory for output buffer\n"); + gz->bytes_out = 0; + return -1; + } + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = gz->in; + strm.avail_in = gz->bytes_in; + strm.next_out = gz->out; + strm.avail_out = out_size; + deflateInit2(&strm,GZIP_COMPRESSION_LEVEL,Z_DEFLATED,31,8,Z_DEFAULT_STRATEGY); + while ((status = deflate(&strm,Z_FINISH)) == Z_OK) + ; + if (status != Z_STREAM_END) { + DPRINTF(E_LOG,L_WS|L_DAAP,"unable to compress data\n"); + gz->bytes_out = 0; + return -1; + } + gz->bytes_out = strm.total_out; + deflateEnd(&strm); + + return gz->bytes_out; +} + +int gzip_close(GZIP_STREAM *gz, int fd) { + int bytes_written = gz->bytes_out; + if (r_write(fd,gz->out,gz->bytes_out) != gz->bytes_out) { + DPRINTF(E_LOG,L_WS|L_DAAP,"unable to write gzipped data\n"); + return -1; + } + if (gz->in != NULL) + free(gz->in); + if (gz->out != NULL) + free(gz->out); + free(gz); + return bytes_written; +} + + + + + + /* * daap_get_new * @@ -237,32 +334,50 @@ DAAP_BLOCK *daap_add_empty(DAAP_BLOCK *parent, char *tag) { * * Serialize a daap tree to a fd; */ -int daap_serialize(DAAP_BLOCK *root, int fd, int gzip) { +int daap_serialize(DAAP_BLOCK *root, int fd, GZIP_STREAM *gz) { char size[4]; while(root) { + if (gz == NULL) r_write(fd,root->tag,4); + else + gzip_write(gz,root->tag,4); - size[0] = (root->reported_size >> 24) & 0xFF; - size[1] = (root->reported_size >> 16) & 0xFF; - size[2] = (root->reported_size >> 8 ) & 0xFF; - size[3] = (root->reported_size) & 0xFF; - + size[0] = (root->reported_size >> 24) & 0xFF; + size[1] = (root->reported_size >> 16) & 0xFF; + size[2] = (root->reported_size >> 8 ) & 0xFF; + size[3] = (root->reported_size) & 0xFF; + + if (gz == NULL) r_write(fd,&size,4); + else + gzip_write(gz,&size,4); - if(root->size) { - if(root->free) - r_write(fd,root->value,root->size); - else - r_write(fd,root->svalue,root->size); + if(root->size) { + if(root->free) { + if (gz == NULL) { + r_write(fd,root->value,root->size); + } + else { + gzip_write(gz,root->value,root->size); + } } + else { + if (gz == NULL) { + r_write(fd,root->svalue,root->size); + } + else { + gzip_write(gz,root->svalue,root->size); + } + } + } - if(root->children) { - if(daap_serialize(root->children,fd,gzip)) - return -1; - } + if(root->children) { + if(daap_serialize(root->children,fd,gz)) + return -1; + } - root=root->next; + root=root->next; } return 0; diff --git a/src/daap-proto.h b/src/daap-proto.h index 516f5916..1f6757a5 100644 --- a/src/daap-proto.h +++ b/src/daap-proto.h @@ -23,6 +23,24 @@ #define _DAAP_PROTO_H_ +#include "zlib.h" + +#define GZIP_CHUNK 16384 +#define GZIP_COMPRESSION_LEVEL Z_DEFAULT_COMPRESSION + +typedef struct gzip_stream_tag { + int bytes_in; + int bytes_out; + int in_size; + char *in; + char *out; +} GZIP_STREAM; + +GZIP_STREAM *gzip_alloc(void); +ssize_t gzip_write(GZIP_STREAM *gz, void *buf, size_t size); +int gzip_compress(GZIP_STREAM *gz); +int gzip_close(GZIP_STREAM *gz, int fd); + typedef struct daap_block_tag { char tag[4]; int reported_size; @@ -42,7 +60,7 @@ DAAP_BLOCK *daap_add_empty(DAAP_BLOCK *parent, char *tag); DAAP_BLOCK *daap_add_char(DAAP_BLOCK *parent, char *tag, char value); DAAP_BLOCK *daap_add_short(DAAP_BLOCK *parent, char *tag, short int value); DAAP_BLOCK *daap_add_long(DAAP_BLOCK *parent, char *tag, int v1, int v2); -int daap_serialize(DAAP_BLOCK *root, int fd, int gzip); +int daap_serialize(DAAP_BLOCK *root, int fd, GZIP_STREAM *gz); void daap_free(DAAP_BLOCK *root); // remove a block from it's parent (and free it) diff --git a/src/daapd.h b/src/daapd.h index 3628275e..a6dc898b 100644 --- a/src/daapd.h +++ b/src/daapd.h @@ -54,6 +54,7 @@ typedef struct tag_config { int always_scan; /**< 0 to minimize disk usage (embedded devices) */ int process_m3u; /**< Should we process m3u files? */ int scan_type; /**< How hard to search mp3 files. see scan_get_mp3fileinfo() */ + int compress; /**< Should we compress? */ char *adminpassword; /**< Password to web management pages */ char *readpassword; /**< iTunes password */ char *mp3dir; /**< root directory of the mp3 files */ diff --git a/src/main.c b/src/main.c index 56e2dff6..92231ea2 100644 --- a/src/main.c +++ b/src/main.c @@ -171,6 +171,10 @@ void daap_handler(WS_CONNINFO *pwsc) { char *first, *last; char* index = 0; int streaming=0; + int compress =0; + int start_time; + int end_time; + int bytes_written; MP3FILE *pmp3; int file_fd; @@ -325,21 +329,39 @@ void daap_handler(WS_CONNINFO *pwsc) { if(!streaming) { DPRINTF(E_DBG,L_WS,"Satisfying request\n"); - ws_addresponseheader(pwsc,"Content-Length","%d",root->reported_size + 8); - ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); - DPRINTF(E_DBG,L_WS,"Emitting headers\n"); - ws_emitheaders(pwsc); - - /* - if(ws_testrequestheader(pwsc,"Accept-Encoding","gzip")) { - ws_addresponseheader(pwsc,"Content-Encoding","gzip"); + if((config.compress) && ws_testrequestheader(pwsc,"Accept-Encoding","gzip") && root->reported_size >= 1000) { compress=1; - } - */ + } DPRINTF(E_DBG,L_WS|L_DAAP,"Serializing\n"); - daap_serialize(root,pwsc->fd,0); + start_time = time(NULL); + if (compress) { + DPRINTF(E_DBG,L_WS|L_DAAP,"Using compression: %s\n", pwsc->uri); + GZIP_STREAM *gz = gzip_alloc(); + daap_serialize(root,pwsc->fd,gz); + gzip_compress(gz); + bytes_written = gz->bytes_out; + ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); + ws_addresponseheader(pwsc,"Content-Length","%d",bytes_written); + ws_addresponseheader(pwsc,"Content-Encoding","gzip"); + DPRINTF(E_DBG,L_WS,"Emitting headers\n"); + ws_emitheaders(pwsc); + if (gzip_close(gz,pwsc->fd) != bytes_written) { + DPRINTF(E_LOG,L_WS|L_DAAP,"Error compressing data\n"); + } + DPRINTF(E_DBG,L_WS|L_DAAP,"Compression ratio: %f\n",((double) bytes_written)/(8.0 + root->reported_size)) + } + else { + bytes_written = root->reported_size + 8; + ws_addresponseheader(pwsc,"Content-Length","%d",bytes_written); + ws_writefd(pwsc,"HTTP/1.1 200 OK\r\n"); + DPRINTF(E_DBG,L_WS,"Emitting headers\n"); + ws_emitheaders(pwsc); + daap_serialize(root,pwsc->fd,NULL); + } + end_time = time(NULL); + DPRINTF(E_DBG,L_WS|L_DAAP,"Sent %d bytes in %d seconds\n",bytes_written,end_time-start_time); DPRINTF(E_DBG,L_WS|L_DAAP,"Done, freeing\n"); daap_free(root); } else {