mirror of
https://github.com/owntone/owntone-server.git
synced 2025-05-03 08:20:30 -04:00
Adds a DAAP cache to improve performance and fix timeouts (issue #50)
This commit is contained in:
commit
16df2c591b
@ -1,3 +1,11 @@
|
|||||||
|
# A quick guide to configuring forked-daapd:
|
||||||
|
#
|
||||||
|
# For regular use, the most important setting to configure is "directories",
|
||||||
|
# which should be the location of your media. Whatever user you have set as
|
||||||
|
# "uid" must have read access to this location. If the location is a network
|
||||||
|
# mount, please see the README.
|
||||||
|
#
|
||||||
|
# In all likelihood, that's all you need to do!
|
||||||
|
|
||||||
general {
|
general {
|
||||||
# Username
|
# Username
|
||||||
@ -11,6 +19,11 @@ general {
|
|||||||
admin_password = "unused"
|
admin_password = "unused"
|
||||||
# Enable/disable IPv6
|
# Enable/disable IPv6
|
||||||
ipv6 = no
|
ipv6 = no
|
||||||
|
# Location of DAAP cache
|
||||||
|
# daapcache_path = "/var/cache/forked-daapd/daapcache.db"
|
||||||
|
# DAAP requests that take longer than this threshold (in msec) get their
|
||||||
|
# replies cached for next time. Set to 0 to disable caching.
|
||||||
|
# daapcache_threshold = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
# Library configuration
|
# Library configuration
|
||||||
|
@ -96,6 +96,7 @@ forked_daapd_SOURCES = main.c \
|
|||||||
db.c db.h \
|
db.c db.h \
|
||||||
logger.c logger.h \
|
logger.c logger.h \
|
||||||
conffile.c conffile.h \
|
conffile.c conffile.h \
|
||||||
|
daap_cache.h daap_cache.c \
|
||||||
filescanner.c filescanner.h \
|
filescanner.c filescanner.h \
|
||||||
filescanner_ffmpeg.c filescanner_m3u.c filescanner_icy.c $(ITUNES_SRC) \
|
filescanner_ffmpeg.c filescanner_m3u.c filescanner_icy.c $(ITUNES_SRC) \
|
||||||
mdns_avahi.c mdns.h \
|
mdns_avahi.c mdns.h \
|
||||||
|
@ -53,6 +53,8 @@ static cfg_opt_t sec_general[] =
|
|||||||
CFG_INT("db_pragma_synchronous", -1, CFGF_NONE),
|
CFG_INT("db_pragma_synchronous", -1, CFGF_NONE),
|
||||||
CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel),
|
CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel),
|
||||||
CFG_BOOL("ipv6", cfg_false, CFGF_NONE),
|
CFG_BOOL("ipv6", cfg_false, CFGF_NONE),
|
||||||
|
CFG_STR("daapcache_path", STATEDIR "/cache/" PACKAGE "/daapcache.db", CFGF_NONE),
|
||||||
|
CFG_INT("daapcache_threshold", 1000, CFGF_NONE),
|
||||||
CFG_END()
|
CFG_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
895
src/daap_cache.c
Normal file
895
src/daap_cache.c
Normal file
@ -0,0 +1,895 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Espen Jürgensen <espenjurgensen@gmail.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
# include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include "conffile.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "httpd_daap.h"
|
||||||
|
#include "db.h"
|
||||||
|
#include "daap_cache.h"
|
||||||
|
|
||||||
|
/* The DAAP cache will cache raw daap replies for queries added with
|
||||||
|
* daapcache_add(). Only some query types are supported.
|
||||||
|
* You can't add queries where the canonical reply is not HTTP_OK, because
|
||||||
|
* daap_request will use that as default for cache replies.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct daapcache_command;
|
||||||
|
|
||||||
|
typedef int (*cmd_func)(struct daapcache_command *cmd);
|
||||||
|
|
||||||
|
struct daapcache_command
|
||||||
|
{
|
||||||
|
pthread_mutex_t lck;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
|
||||||
|
cmd_func func;
|
||||||
|
|
||||||
|
int nonblock;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
char *query;
|
||||||
|
char *ua;
|
||||||
|
int msec;
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
} arg;
|
||||||
|
|
||||||
|
int ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* --- Globals --- */
|
||||||
|
// daapcache thread
|
||||||
|
static pthread_t tid_daapcache;
|
||||||
|
|
||||||
|
// Event base, pipes and events
|
||||||
|
struct event_base *evbase_daapcache;
|
||||||
|
static int g_exit_pipe[2];
|
||||||
|
static int g_cmd_pipe[2];
|
||||||
|
static struct event *g_exitev;
|
||||||
|
static struct event *g_cmdev;
|
||||||
|
static struct event *g_cacheev;
|
||||||
|
|
||||||
|
static int g_initialized;
|
||||||
|
|
||||||
|
// Global cache database handle
|
||||||
|
static sqlite3 *g_db_hdl;
|
||||||
|
static char *g_db_path;
|
||||||
|
|
||||||
|
// After being triggered wait 5 seconds before rebuilding daapcache
|
||||||
|
static struct timeval g_wait = { 5, 0 };
|
||||||
|
|
||||||
|
// The user may configure a threshold (in msec), and queries slower than
|
||||||
|
// that will have their reply cached
|
||||||
|
static int g_cfg_threshold;
|
||||||
|
|
||||||
|
/* --------------------------------- HELPERS ------------------------------- */
|
||||||
|
|
||||||
|
/* The purpose of this function is to remove transient tags from a request
|
||||||
|
* url (query), eg remove session-id=xxx
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
remove_tag(char *in, const char *tag)
|
||||||
|
{
|
||||||
|
char *s;
|
||||||
|
char *e;
|
||||||
|
|
||||||
|
s = strstr(in, tag);
|
||||||
|
if (!s)
|
||||||
|
return;
|
||||||
|
|
||||||
|
e = strchr(s, '&');
|
||||||
|
if (e)
|
||||||
|
memmove(s, (e + 1), strlen(e + 1) + 1);
|
||||||
|
else if (s > in)
|
||||||
|
*(s - 1) = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------- COMMAND EXECUTION -------------------------- */
|
||||||
|
|
||||||
|
static void
|
||||||
|
command_init(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
memset(cmd, 0, sizeof(struct daapcache_command));
|
||||||
|
|
||||||
|
pthread_mutex_init(&cmd->lck, NULL);
|
||||||
|
pthread_cond_init(&cmd->cond, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
command_deinit(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
pthread_cond_destroy(&cmd->cond);
|
||||||
|
pthread_mutex_destroy(&cmd->lck);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
send_command(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!cmd->func)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "BUG: cmd->func is NULL!\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = write(g_cmd_pipe[1], &cmd, sizeof(cmd));
|
||||||
|
if (ret != sizeof(cmd))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not send command: %s\n", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sync_command(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&cmd->lck);
|
||||||
|
|
||||||
|
ret = send_command(cmd);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
pthread_mutex_unlock(&cmd->lck);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_cond_wait(&cmd->cond, &cmd->lck);
|
||||||
|
pthread_mutex_unlock(&cmd->lck);
|
||||||
|
|
||||||
|
ret = cmd->ret;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
nonblock_command(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = send_command(cmd);
|
||||||
|
if (ret < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
thread_exit(void)
|
||||||
|
{
|
||||||
|
int dummy = 42;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_DCACHE, "Killing daapcache thread\n");
|
||||||
|
|
||||||
|
if (write(g_exit_pipe[1], &dummy, sizeof(dummy)) != sizeof(dummy))
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not write to exit fd: %s\n", strerror(errno));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* --------------------------------- MAIN --------------------------------- */
|
||||||
|
/* Thread: daapcache */
|
||||||
|
|
||||||
|
static int
|
||||||
|
daapcache_create(void)
|
||||||
|
{
|
||||||
|
#define T_REPLIES \
|
||||||
|
"CREATE TABLE IF NOT EXISTS replies (" \
|
||||||
|
" id INTEGER PRIMARY KEY NOT NULL," \
|
||||||
|
" query VARCHAR(4096) NOT NULL," \
|
||||||
|
" reply BLOB" \
|
||||||
|
");"
|
||||||
|
#define T_QUERIES \
|
||||||
|
"CREATE TABLE IF NOT EXISTS queries (" \
|
||||||
|
" id INTEGER PRIMARY KEY NOT NULL," \
|
||||||
|
" query VARCHAR(4096) UNIQUE NOT NULL," \
|
||||||
|
" user_agent VARCHAR(1024)," \
|
||||||
|
" msec INTEGER DEFAULT 0," \
|
||||||
|
" timestamp INTEGER DEFAULT 0" \
|
||||||
|
");"
|
||||||
|
#define I_QUERY \
|
||||||
|
"CREATE INDEX IF NOT EXISTS idx_query ON replies (query);"
|
||||||
|
char *errmsg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// A fresh start
|
||||||
|
unlink(g_db_path);
|
||||||
|
|
||||||
|
// Create db
|
||||||
|
ret = sqlite3_open(g_db_path, &g_db_hdl);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_DCACHE, "Could not create database: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
|
||||||
|
sqlite3_close(g_db_hdl);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create reply cache table
|
||||||
|
ret = sqlite3_exec(g_db_hdl, T_REPLIES, NULL, NULL, &errmsg);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_DCACHE, "Error creating reply cache table: %s\n", errmsg);
|
||||||
|
|
||||||
|
sqlite3_free(errmsg);
|
||||||
|
sqlite3_close(g_db_hdl);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create query table (the queries for which we will generate and cache replies)
|
||||||
|
ret = sqlite3_exec(g_db_hdl, T_QUERIES, NULL, NULL, &errmsg);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_DCACHE, "Error creating query table: %s\n", errmsg);
|
||||||
|
|
||||||
|
sqlite3_free(errmsg);
|
||||||
|
sqlite3_close(g_db_hdl);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create index
|
||||||
|
ret = sqlite3_exec(g_db_hdl, I_QUERY, NULL, NULL, &errmsg);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_DCACHE, "Error creating query index: %s\n", errmsg);
|
||||||
|
|
||||||
|
sqlite3_free(errmsg);
|
||||||
|
sqlite3_close(g_db_hdl);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_DCACHE, "Cache created\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
#undef T_CACHE
|
||||||
|
#undef I_QUERY
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
daapcache_destroy(void)
|
||||||
|
{
|
||||||
|
sqlite3_stmt *stmt;
|
||||||
|
|
||||||
|
if (!g_db_hdl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Tear down anything that's in flight */
|
||||||
|
while ((stmt = sqlite3_next_stmt(g_db_hdl, 0)))
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
sqlite3_close(g_db_hdl);
|
||||||
|
|
||||||
|
unlink(g_db_path);
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_DCACHE, "Cache destroyed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adds the reply (stored in evbuf) to the cache */
|
||||||
|
static int
|
||||||
|
daapcache_reply_add(const char *query, struct evbuffer *evbuf)
|
||||||
|
{
|
||||||
|
#define Q_TMPL "INSERT INTO replies (query, reply) VALUES (?, ?);"
|
||||||
|
sqlite3_stmt *stmt;
|
||||||
|
unsigned char *data;
|
||||||
|
size_t datlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBEVENT2
|
||||||
|
datlen = evbuffer_get_length(evbuf);
|
||||||
|
data = evbuffer_pullup(evbuf, -1);
|
||||||
|
#else
|
||||||
|
datlen = EVBUFFER_LENGTH(evbuf);
|
||||||
|
data = EVBUFFER_DATA(evbuf);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ret = sqlite3_prepare_v2(g_db_hdl, Q_TMPL, -1, &stmt, 0);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error preparing query for cache update: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, query, -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_blob(stmt, 2, data, datlen, SQLITE_STATIC);
|
||||||
|
|
||||||
|
ret = sqlite3_step(stmt);
|
||||||
|
if (ret != SQLITE_DONE)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error stepping query for cache update: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sqlite3_finalize(stmt);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error finalizing query for cache update: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_DCACHE, "Wrote cache reply, size %d\n", datlen);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
#undef Q_TMPL
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adds the query to the list of queries for which we will build and cache a reply */
|
||||||
|
static int
|
||||||
|
daapcache_query_add(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
#define Q_TMPL "INSERT OR REPLACE INTO queries (user_agent, query, msec, timestamp) VALUES ('%q', '%q', %d, %" PRIi64 ");"
|
||||||
|
#define Q_CLEANUP "DELETE FROM queries WHERE id NOT IN (SELECT id FROM queries ORDER BY timestamp DESC LIMIT 20);"
|
||||||
|
char *query;
|
||||||
|
char *errmsg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!cmd->arg.ua)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Couldn't add slow query to cache, unknown user-agent\n");
|
||||||
|
|
||||||
|
goto error_add;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Currently we are only able to pre-build and cache these reply types
|
||||||
|
if ( (strncmp(cmd->arg.query, "/databases/1/containers/", strlen("/databases/1/containers/")) != 0) &&
|
||||||
|
(strncmp(cmd->arg.query, "/databases/1/groups?", strlen("/databases/1/groups?")) != 0) &&
|
||||||
|
(strncmp(cmd->arg.query, "/databases/1/items?", strlen("/databases/1/items?")) != 0) &&
|
||||||
|
(strncmp(cmd->arg.query, "/databases/1/browse/", strlen("/databases/1/browse/")) != 0) )
|
||||||
|
goto error_add;
|
||||||
|
|
||||||
|
remove_tag(cmd->arg.query, "session-id");
|
||||||
|
remove_tag(cmd->arg.query, "revision-number");
|
||||||
|
|
||||||
|
query = sqlite3_mprintf(Q_TMPL, cmd->arg.ua, cmd->arg.query, cmd->arg.msec, (int64_t)time(NULL));
|
||||||
|
if (!query)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Out of memory making query string.\n");
|
||||||
|
|
||||||
|
goto error_add;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sqlite3_exec(g_db_hdl, query, NULL, NULL, &errmsg);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error adding query to query list: %s\n", errmsg);
|
||||||
|
|
||||||
|
sqlite3_free(query);
|
||||||
|
sqlite3_free(errmsg);
|
||||||
|
goto error_add;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_free(query);
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_DCACHE, "Slow query (%d ms) added to cache: '%s' (user-agent: '%s')\n", cmd->arg.msec, cmd->arg.query, cmd->arg.ua);
|
||||||
|
|
||||||
|
free(cmd->arg.ua);
|
||||||
|
free(cmd->arg.query);
|
||||||
|
|
||||||
|
// Limits the size of the cache to only contain replies for 20 most recent queries
|
||||||
|
ret = sqlite3_exec(g_db_hdl, Q_CLEANUP, NULL, NULL, &errmsg);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error cleaning up query list before update: %s\n", errmsg);
|
||||||
|
sqlite3_free(errmsg);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
daapcache_trigger();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error_add:
|
||||||
|
if (cmd->arg.ua)
|
||||||
|
free(cmd->arg.ua);
|
||||||
|
|
||||||
|
if (cmd->arg.query)
|
||||||
|
free(cmd->arg.query);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
#undef Q_CLEANUP
|
||||||
|
#undef Q_TMPL
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gets a reply from the cache */
|
||||||
|
static int
|
||||||
|
daapcache_query_get(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
#define Q_TMPL "SELECT reply FROM replies WHERE query = ?;"
|
||||||
|
sqlite3_stmt *stmt;
|
||||||
|
char *query;
|
||||||
|
int datlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
cmd->arg.evbuf = NULL;
|
||||||
|
|
||||||
|
query = cmd->arg.query;
|
||||||
|
remove_tag(query, "session-id");
|
||||||
|
remove_tag(query, "revision-number");
|
||||||
|
|
||||||
|
// Look in the DB
|
||||||
|
ret = sqlite3_prepare_v2(g_db_hdl, Q_TMPL, -1, &stmt, 0);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error preparing query for cache update: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
free(query);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, query, -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
ret = sqlite3_step(stmt);
|
||||||
|
if (ret != SQLITE_ROW)
|
||||||
|
{
|
||||||
|
if (ret != SQLITE_DONE)
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error stepping query for cache update: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
goto error_get;
|
||||||
|
}
|
||||||
|
|
||||||
|
datlen = sqlite3_column_bytes(stmt, 0);
|
||||||
|
|
||||||
|
cmd->arg.evbuf = evbuffer_new();
|
||||||
|
if (!cmd->arg.evbuf)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create reply evbuffer\n");
|
||||||
|
goto error_get;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = evbuffer_add(cmd->arg.evbuf, sqlite3_column_blob(stmt, 0), datlen);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Out of memory for reply evbuffer\n");
|
||||||
|
evbuffer_free(cmd->arg.evbuf);
|
||||||
|
cmd->arg.evbuf = NULL;
|
||||||
|
goto error_get;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sqlite3_finalize(stmt);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error finalizing query for getting cache: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_DCACHE, "Cache hit: %s\n", query);
|
||||||
|
|
||||||
|
free(query);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
error_get:
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
free(query);
|
||||||
|
return -1;
|
||||||
|
#undef Q_TMPL
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Here we actually update the cache by asking httpd_daap for responses
|
||||||
|
* to the queries set for caching
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
daapcache_update_cb(int fd, short what, void *arg)
|
||||||
|
{
|
||||||
|
sqlite3_stmt *stmt;
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
char *errmsg;
|
||||||
|
char *query;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_DCACHE, "Timeout reached, time to update DAAP cache\n");
|
||||||
|
|
||||||
|
ret = sqlite3_exec(g_db_hdl, "DELETE FROM replies;", NULL, NULL, &errmsg);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error clearing reply cache before update: %s\n", errmsg);
|
||||||
|
sqlite3_free(errmsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = sqlite3_prepare_v2(g_db_hdl, "SELECT user_agent, query FROM queries;", -1, &stmt, 0);
|
||||||
|
if (ret != SQLITE_OK)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error preparing for cache update: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((ret = sqlite3_step(stmt)) == SQLITE_ROW)
|
||||||
|
{
|
||||||
|
query = strdup((char *)sqlite3_column_text(stmt, 1));
|
||||||
|
|
||||||
|
evbuf = daap_reply_build(query, (char *)sqlite3_column_text(stmt, 0));
|
||||||
|
if (!evbuf)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error building DAAP reply for query: %s\n", query);
|
||||||
|
free(query);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
daapcache_reply_add(query, evbuf);
|
||||||
|
|
||||||
|
free(query);
|
||||||
|
evbuffer_free(evbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != SQLITE_DONE)
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not step: %s\n", sqlite3_errmsg(g_db_hdl));
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_DCACHE, "DAAP cache updated\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This function will just set a timer, which when it times out will trigger
|
||||||
|
* the actual daapcache update. The purpose is to avoid avoid daapcache updates when
|
||||||
|
* the database is busy, eg during a library scan.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
daapcache_update_timer(struct daapcache_command *cmd)
|
||||||
|
{
|
||||||
|
if (!g_cacheev)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
evtimer_add(g_cacheev, &g_wait);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
daapcache(void *arg)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = daapcache_create();
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error: Cache create failed\n");
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = db_perthread_init();
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error: DB init failed\n");
|
||||||
|
daapcache_destroy();
|
||||||
|
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_initialized = 1;
|
||||||
|
|
||||||
|
event_base_dispatch(evbase_daapcache);
|
||||||
|
|
||||||
|
if (g_initialized)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "daapcache event loop terminated ahead of time!\n");
|
||||||
|
g_initialized = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
db_perthread_deinit();
|
||||||
|
|
||||||
|
daapcache_destroy();
|
||||||
|
|
||||||
|
pthread_exit(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
exit_cb(int fd, short what, void *arg)
|
||||||
|
{
|
||||||
|
int dummy;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = read(g_exit_pipe[0], &dummy, sizeof(dummy));
|
||||||
|
if (ret != sizeof(dummy))
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Error reading from exit pipe\n");
|
||||||
|
|
||||||
|
event_base_loopbreak(evbase_daapcache);
|
||||||
|
|
||||||
|
g_initialized = 0;
|
||||||
|
|
||||||
|
event_add(g_exitev, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
command_cb(int fd, short what, void *arg)
|
||||||
|
{
|
||||||
|
struct daapcache_command *cmd;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = read(g_cmd_pipe[0], &cmd, sizeof(cmd));
|
||||||
|
if (ret != sizeof(cmd))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not read command! (read %d): %s\n", ret, (ret < 0) ? strerror(errno) : "-no error-");
|
||||||
|
goto readd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd->nonblock)
|
||||||
|
{
|
||||||
|
cmd->func(cmd);
|
||||||
|
|
||||||
|
free(cmd);
|
||||||
|
goto readd;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&cmd->lck);
|
||||||
|
|
||||||
|
ret = cmd->func(cmd);
|
||||||
|
cmd->ret = ret;
|
||||||
|
|
||||||
|
pthread_cond_signal(&cmd->cond);
|
||||||
|
pthread_mutex_unlock(&cmd->lck);
|
||||||
|
|
||||||
|
readd:
|
||||||
|
event_add(g_cmdev, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------------------------- Our daapcache API --------------------------- */
|
||||||
|
|
||||||
|
void
|
||||||
|
daapcache_trigger(void)
|
||||||
|
{
|
||||||
|
struct daapcache_command *cmd;
|
||||||
|
|
||||||
|
if (!g_initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cmd = (struct daapcache_command *)malloc(sizeof(struct daapcache_command));
|
||||||
|
if (!cmd)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not allocate daapcache_command\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(cmd, 0, sizeof(struct daapcache_command));
|
||||||
|
|
||||||
|
cmd->nonblock = 1;
|
||||||
|
|
||||||
|
cmd->func = daapcache_update_timer;
|
||||||
|
|
||||||
|
nonblock_command(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct evbuffer *
|
||||||
|
daapcache_get(const char *query)
|
||||||
|
{
|
||||||
|
struct daapcache_command cmd;
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!g_initialized)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
command_init(&cmd);
|
||||||
|
|
||||||
|
cmd.func = daapcache_query_get;
|
||||||
|
cmd.arg.query = strdup(query);
|
||||||
|
|
||||||
|
ret = sync_command(&cmd);
|
||||||
|
|
||||||
|
evbuf = cmd.arg.evbuf;
|
||||||
|
|
||||||
|
command_deinit(&cmd);
|
||||||
|
|
||||||
|
return ((ret < 0) ? NULL : evbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
daapcache_add(const char *query, const char *ua, int msec)
|
||||||
|
{
|
||||||
|
struct daapcache_command *cmd;
|
||||||
|
|
||||||
|
if (!g_initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cmd = (struct daapcache_command *)malloc(sizeof(struct daapcache_command));
|
||||||
|
if (!cmd)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not allocate daapcache_command\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(cmd, 0, sizeof(struct daapcache_command));
|
||||||
|
|
||||||
|
cmd->nonblock = 1;
|
||||||
|
|
||||||
|
cmd->func = daapcache_query_add;
|
||||||
|
cmd->arg.query = strdup(query);
|
||||||
|
cmd->arg.ua = strdup(ua);
|
||||||
|
cmd->arg.msec = msec;
|
||||||
|
|
||||||
|
nonblock_command(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
daapcache_threshold(void)
|
||||||
|
{
|
||||||
|
return g_cfg_threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
daapcache_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
g_db_path = cfg_getstr(cfg_getsec(cfg, "general"), "daapcache_path");
|
||||||
|
if (!g_db_path || (strlen(g_db_path) == 0))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Cache path invalid, disabling cache\n");
|
||||||
|
g_initialized = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cfg_threshold = cfg_getint(cfg_getsec(cfg, "general"), "daapcache_threshold");
|
||||||
|
if (g_cfg_threshold == 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Cache threshold set to 0, disabling cache\n");
|
||||||
|
g_initialized = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
# if defined(__linux__)
|
||||||
|
ret = pipe2(g_exit_pipe, O_CLOEXEC);
|
||||||
|
# else
|
||||||
|
ret = pipe(g_exit_pipe);
|
||||||
|
# endif
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create pipe: %s\n", strerror(errno));
|
||||||
|
goto exit_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
# if defined(__linux__)
|
||||||
|
ret = pipe2(g_cmd_pipe, O_CLOEXEC);
|
||||||
|
# else
|
||||||
|
ret = pipe(g_cmd_pipe);
|
||||||
|
# endif
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create command pipe: %s\n", strerror(errno));
|
||||||
|
goto cmd_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
evbase_daapcache = event_base_new();
|
||||||
|
if (!evbase_daapcache)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create an event base\n");
|
||||||
|
goto evbase_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBEVENT2
|
||||||
|
g_exitev = event_new(evbase_daapcache, g_exit_pipe[0], EV_READ, exit_cb, NULL);
|
||||||
|
if (!g_exitev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create exit event\n");
|
||||||
|
goto evnew_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cmdev = event_new(evbase_daapcache, g_cmd_pipe[0], EV_READ, command_cb, NULL);
|
||||||
|
if (!g_cmdev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create cmd event\n");
|
||||||
|
goto evnew_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cacheev = evtimer_new(evbase_daapcache, daapcache_update_cb, NULL);
|
||||||
|
if (!g_cmdev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create daapcache event\n");
|
||||||
|
goto evnew_fail;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
g_exitev = (struct event *)malloc(sizeof(struct event));
|
||||||
|
if (!g_exitev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create exit event\n");
|
||||||
|
goto evnew_fail;
|
||||||
|
}
|
||||||
|
event_set(g_exitev, g_exit_pipe[0], EV_READ, exit_cb, NULL);
|
||||||
|
event_base_set(evbase_daapcache, g_exitev);
|
||||||
|
|
||||||
|
g_cmdev = (struct event *)malloc(sizeof(struct event));
|
||||||
|
if (!g_cmdev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create cmd event\n");
|
||||||
|
goto evnew_fail;
|
||||||
|
}
|
||||||
|
event_set(g_cmdev, g_cmd_pipe[0], EV_READ, command_cb, NULL);
|
||||||
|
event_base_set(evbase_daapcache, g_cmdev);
|
||||||
|
|
||||||
|
g_cacheev = (struct event *)malloc(sizeof(struct event));
|
||||||
|
if (!g_cacheev)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not create daapcache event\n");
|
||||||
|
goto evnew_fail;
|
||||||
|
}
|
||||||
|
event_set(g_cacheev, -1, EV_TIMEOUT, daapcache_update_cb, NULL);
|
||||||
|
event_base_set(evbase_daapcache, g_cacheev);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
event_add(g_exitev, NULL);
|
||||||
|
event_add(g_cmdev, NULL);
|
||||||
|
|
||||||
|
DPRINTF(E_INFO, L_DCACHE, "daapcache thread init\n");
|
||||||
|
|
||||||
|
ret = pthread_create(&tid_daapcache, NULL, daapcache, NULL);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DCACHE, "Could not spawn daapcache thread: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
goto thread_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
thread_fail:
|
||||||
|
evnew_fail:
|
||||||
|
event_base_free(evbase_daapcache);
|
||||||
|
evbase_daapcache = NULL;
|
||||||
|
|
||||||
|
evbase_fail:
|
||||||
|
close(g_cmd_pipe[0]);
|
||||||
|
close(g_cmd_pipe[1]);
|
||||||
|
|
||||||
|
cmd_fail:
|
||||||
|
close(g_exit_pipe[0]);
|
||||||
|
close(g_exit_pipe[1]);
|
||||||
|
|
||||||
|
exit_fail:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
daapcache_deinit(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!g_initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
thread_exit();
|
||||||
|
|
||||||
|
ret = pthread_join(tid_daapcache, NULL);
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_DCACHE, "Could not join daapcache thread: %s\n", strerror(errno));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free event base (should free events too)
|
||||||
|
event_base_free(evbase_daapcache);
|
||||||
|
|
||||||
|
// Close pipes
|
||||||
|
close(g_cmd_pipe[0]);
|
||||||
|
close(g_cmd_pipe[1]);
|
||||||
|
close(g_exit_pipe[0]);
|
||||||
|
close(g_exit_pipe[1]);
|
||||||
|
}
|
30
src/daap_cache.h
Normal file
30
src/daap_cache.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
#ifndef __DAAP_CACHE_H__
|
||||||
|
#define __DAAP_CACHE_H__
|
||||||
|
|
||||||
|
#ifdef HAVE_LIBEVENT2
|
||||||
|
# include <event2/event.h>
|
||||||
|
# include <event2/buffer.h>
|
||||||
|
#else
|
||||||
|
# include <event.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
daapcache_trigger(void);
|
||||||
|
|
||||||
|
struct evbuffer *
|
||||||
|
daapcache_get(const char *query);
|
||||||
|
|
||||||
|
void
|
||||||
|
daapcache_add(const char *query, const char *ua, int msec);
|
||||||
|
|
||||||
|
int
|
||||||
|
daapcache_threshold(void);
|
||||||
|
|
||||||
|
int
|
||||||
|
daapcache_init(void);
|
||||||
|
|
||||||
|
void
|
||||||
|
daapcache_deinit(void);
|
||||||
|
|
||||||
|
#endif /* !__DAAP_CACHE_H__ */
|
@ -43,7 +43,7 @@ dmap_get_fields_table(int *nfields)
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_container(struct evbuffer *evbuf, char *tag, int len)
|
dmap_add_container(struct evbuffer *evbuf, const char *tag, int len)
|
||||||
{
|
{
|
||||||
unsigned char buf[4];
|
unsigned char buf[4];
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ dmap_add_container(struct evbuffer *evbuf, char *tag, int len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_long(struct evbuffer *evbuf, char *tag, int64_t val)
|
dmap_add_long(struct evbuffer *evbuf, const char *tag, int64_t val)
|
||||||
{
|
{
|
||||||
unsigned char buf[12];
|
unsigned char buf[12];
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ dmap_add_long(struct evbuffer *evbuf, char *tag, int64_t val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_int(struct evbuffer *evbuf, char *tag, int val)
|
dmap_add_int(struct evbuffer *evbuf, const char *tag, int val)
|
||||||
{
|
{
|
||||||
unsigned char buf[8];
|
unsigned char buf[8];
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ dmap_add_int(struct evbuffer *evbuf, char *tag, int val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_short(struct evbuffer *evbuf, char *tag, short val)
|
dmap_add_short(struct evbuffer *evbuf, const char *tag, short val)
|
||||||
{
|
{
|
||||||
unsigned char buf[6];
|
unsigned char buf[6];
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ dmap_add_short(struct evbuffer *evbuf, char *tag, short val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_char(struct evbuffer *evbuf, char *tag, char val)
|
dmap_add_char(struct evbuffer *evbuf, const char *tag, char val)
|
||||||
{
|
{
|
||||||
unsigned char buf[5];
|
unsigned char buf[5];
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ dmap_add_char(struct evbuffer *evbuf, char *tag, char val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_literal(struct evbuffer *evbuf, char *tag, char *str, int len)
|
dmap_add_literal(struct evbuffer *evbuf, const char *tag, char *str, int len)
|
||||||
{
|
{
|
||||||
char buf[4];
|
char buf[4];
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ dmap_add_raw_uint32(struct evbuffer *evbuf, uint32_t val)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_string(struct evbuffer *evbuf, char *tag, const char *str)
|
dmap_add_string(struct evbuffer *evbuf, const char *tag, const char *str)
|
||||||
{
|
{
|
||||||
unsigned char buf[4];
|
unsigned char buf[4];
|
||||||
int len;
|
int len;
|
||||||
@ -341,12 +341,15 @@ dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_send_error(struct evhttp_request *req, char *container, char *errmsg)
|
dmap_send_error(struct evhttp_request *req, const char *container, const char *errmsg)
|
||||||
{
|
{
|
||||||
struct evbuffer *evbuf;
|
struct evbuffer *evbuf;
|
||||||
int len;
|
int len;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (!req)
|
||||||
|
return;
|
||||||
|
|
||||||
evbuf = evbuffer_new();
|
evbuf = evbuffer_new();
|
||||||
if (!evbuf)
|
if (!evbuf)
|
||||||
{
|
{
|
||||||
|
@ -54,35 +54,35 @@ dmap_find_field (register const char *str, register unsigned int len);
|
|||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_container(struct evbuffer *evbuf, char *tag, int len);
|
dmap_add_container(struct evbuffer *evbuf, const char *tag, int len);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_long(struct evbuffer *evbuf, char *tag, int64_t val);
|
dmap_add_long(struct evbuffer *evbuf, const char *tag, int64_t val);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_int(struct evbuffer *evbuf, char *tag, int val);
|
dmap_add_int(struct evbuffer *evbuf, const char *tag, int val);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_short(struct evbuffer *evbuf, char *tag, short val);
|
dmap_add_short(struct evbuffer *evbuf, const char *tag, short val);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_char(struct evbuffer *evbuf, char *tag, char val);
|
dmap_add_char(struct evbuffer *evbuf, const char *tag, char val);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_literal(struct evbuffer *evbuf, char *tag, char *str, int len);
|
dmap_add_literal(struct evbuffer *evbuf, const char *tag, char *str, int len);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_raw_uint32(struct evbuffer *evbuf, uint32_t val);
|
dmap_add_raw_uint32(struct evbuffer *evbuf, uint32_t val);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_string(struct evbuffer *evbuf, char *tag, const char *str);
|
dmap_add_string(struct evbuffer *evbuf, const char *tag, const char *str);
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval, int32_t intval);
|
dmap_add_field(struct evbuffer *evbuf, const struct dmap_field *df, char *strval, int32_t intval);
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
dmap_send_error(struct evhttp_request *req, char *container, char *errmsg);
|
dmap_send_error(struct evhttp_request *req, const char *container, const char *errmsg);
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
|
10
src/httpd.c
10
src/httpd.c
@ -357,6 +357,8 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
struct evkeyvalq *output_headers;
|
struct evkeyvalq *output_headers;
|
||||||
const char *param;
|
const char *param;
|
||||||
const char *param_end;
|
const char *param_end;
|
||||||
|
const char *ua;
|
||||||
|
const char *client_codecs;
|
||||||
char buf[64];
|
char buf[64];
|
||||||
int64_t offset;
|
int64_t offset;
|
||||||
int64_t end_offset;
|
int64_t end_offset;
|
||||||
@ -431,7 +433,10 @@ httpd_stream_file(struct evhttp_request *req, int id)
|
|||||||
memset(st, 0, sizeof(struct stream_ctx));
|
memset(st, 0, sizeof(struct stream_ctx));
|
||||||
st->fd = -1;
|
st->fd = -1;
|
||||||
|
|
||||||
transcode = transcode_needed(input_headers, mfi->codectype);
|
ua = evhttp_find_header(input_headers, "User-Agent");
|
||||||
|
client_codecs = evhttp_find_header(input_headers, "Accept-Codecs");
|
||||||
|
|
||||||
|
transcode = transcode_needed(ua, client_codecs, mfi->codectype);
|
||||||
|
|
||||||
output_headers = evhttp_request_get_output_headers(req);
|
output_headers = evhttp_request_get_output_headers(req);
|
||||||
|
|
||||||
@ -665,6 +670,9 @@ httpd_send_reply(struct evhttp_request *req, int code, const char *reason, struc
|
|||||||
int zret;
|
int zret;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (!req)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!evbuf || (EVBUFFER_LENGTH(evbuf) == 0))
|
if (!evbuf || (EVBUFFER_LENGTH(evbuf) == 0))
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_HTTPD, "Not gzipping body-less reply\n");
|
DPRINTF(E_DBG, L_HTTPD, "Not gzipping body-less reply\n");
|
||||||
|
390
src/httpd_daap.c
390
src/httpd_daap.c
@ -34,9 +34,11 @@
|
|||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <time.h>
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
|
||||||
#include <uninorm.h>
|
#include <uninorm.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include <avl.h>
|
#include <avl.h>
|
||||||
|
|
||||||
@ -50,6 +52,7 @@
|
|||||||
#include "httpd_daap.h"
|
#include "httpd_daap.h"
|
||||||
#include "daap_query.h"
|
#include "daap_query.h"
|
||||||
#include "dmap_common.h"
|
#include "dmap_common.h"
|
||||||
|
#include "daap_cache.h"
|
||||||
|
|
||||||
#ifdef HAVE_LIBEVENT2
|
#ifdef HAVE_LIBEVENT2
|
||||||
# include <event2/http_struct.h>
|
# include <event2/http_struct.h>
|
||||||
@ -71,7 +74,7 @@ extern struct event_base *evbase_httpd;
|
|||||||
struct uri_map {
|
struct uri_map {
|
||||||
regex_t preg;
|
regex_t preg;
|
||||||
char *regexp;
|
char *regexp;
|
||||||
void (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query);
|
int (*handler)(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct daap_session {
|
struct daap_session {
|
||||||
@ -241,6 +244,9 @@ daap_session_find(struct evhttp_request *req, struct evkeyvalq *query, struct ev
|
|||||||
const char *param;
|
const char *param;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (!req)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
param = evhttp_find_header(query, "session-id");
|
param = evhttp_find_header(query, "session-id");
|
||||||
if (!param)
|
if (!param)
|
||||||
{
|
{
|
||||||
@ -545,6 +551,32 @@ user_agent_filter(const char *user_agent, struct query_params *qp)
|
|||||||
free(filter);
|
free(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns eg /databases/1/containers from /databases/1/containers?meta=dmap.item... */
|
||||||
|
static char *
|
||||||
|
extract_uri(char *full_uri)
|
||||||
|
{
|
||||||
|
char *uri;
|
||||||
|
char *ptr;
|
||||||
|
|
||||||
|
ptr = strchr(full_uri, '?');
|
||||||
|
if (ptr)
|
||||||
|
*ptr = '\0';
|
||||||
|
|
||||||
|
uri = strdup(full_uri);
|
||||||
|
|
||||||
|
if (ptr)
|
||||||
|
*ptr = '?';
|
||||||
|
|
||||||
|
if (!uri)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
ptr = uri;
|
||||||
|
uri = evhttp_decode_uri(uri);
|
||||||
|
free(ptr);
|
||||||
|
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params *qp)
|
get_query_params(struct evkeyvalq *query, int *sort_headers, struct query_params *qp)
|
||||||
{
|
{
|
||||||
@ -729,8 +761,8 @@ parse_meta(struct evhttp_request *req, char *tag, const char *param, const struc
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct evbuffer *content;
|
struct evbuffer *content;
|
||||||
struct evkeyvalq *headers;
|
struct evkeyvalq *headers;
|
||||||
@ -751,7 +783,7 @@ daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP server-info reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP server-info reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, "msrv", "Out of memory");
|
dmap_send_error(req, "msrv", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
mpro = 2 << 16 | 10;
|
mpro = 2 << 16 | 10;
|
||||||
@ -822,10 +854,12 @@ daap_reply_server_info(struct evhttp_request *req, struct evbuffer *evbuf, char
|
|||||||
evbuffer_free(content);
|
evbuffer_free(content);
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_content_codes(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_content_codes(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
const struct dmap_field *dmap_fields;
|
const struct dmap_field *dmap_fields;
|
||||||
int nfields;
|
int nfields;
|
||||||
@ -845,7 +879,7 @@ daap_reply_content_codes(struct evhttp_request *req, struct evbuffer *evbuf, cha
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP content-codes reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP content-codes reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, "mccr", "Out of memory");
|
dmap_send_error(req, "mccr", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
dmap_add_container(evbuf, "mccr", len);
|
dmap_add_container(evbuf, "mccr", len);
|
||||||
@ -862,15 +896,15 @@ daap_reply_content_codes(struct evhttp_request *req, struct evbuffer *evbuf, cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct pairing_info pi;
|
struct pairing_info pi;
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
struct evkeyvalq *headers;
|
|
||||||
const char *ua;
|
|
||||||
const char *param;
|
const char *param;
|
||||||
int request_session_id;
|
int request_session_id;
|
||||||
int ret;
|
int ret;
|
||||||
@ -881,11 +915,9 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP login reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP login reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, "mlog", "Out of memory");
|
dmap_send_error(req, "mlog", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
headers = evhttp_request_get_input_headers(req);
|
|
||||||
ua = evhttp_find_header(headers, "User-Agent");
|
|
||||||
if (ua && (strncmp(ua, "Remote", strlen("Remote")) == 0))
|
if (ua && (strncmp(ua, "Remote", strlen("Remote")) == 0))
|
||||||
{
|
{
|
||||||
param = evhttp_find_header(query, "pairing-guid");
|
param = evhttp_find_header(query, "pairing-guid");
|
||||||
@ -894,7 +926,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Login attempt with U-A: Remote and no pairing-guid\n");
|
DPRINTF(E_LOG, L_DAAP, "Login attempt with U-A: Remote and no pairing-guid\n");
|
||||||
|
|
||||||
evhttp_send_error(req, 403, "Forbidden");
|
evhttp_send_error(req, 403, "Forbidden");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&pi, 0, sizeof(struct pairing_info));
|
memset(&pi, 0, sizeof(struct pairing_info));
|
||||||
@ -907,7 +939,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
|
|||||||
|
|
||||||
free_pi(&pi, 1);
|
free_pi(&pi, 1);
|
||||||
evhttp_send_error(req, 403, "Forbidden");
|
evhttp_send_error(req, 403, "Forbidden");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_INFO, L_DAAP, "Remote '%s' logging in with GUID %s\n", pi.name, pi.guid);
|
DPRINTF(E_INFO, L_DAAP, "Remote '%s' logging in with GUID %s\n", pi.name, pi.guid);
|
||||||
@ -931,7 +963,7 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
|
|||||||
if (!s)
|
if (!s)
|
||||||
{
|
{
|
||||||
dmap_send_error(req, "mlog", "Could not start session");
|
dmap_send_error(req, "mlog", "Could not start session");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
dmap_add_container(evbuf, "mlog", 24);
|
dmap_add_container(evbuf, "mlog", 24);
|
||||||
@ -939,24 +971,28 @@ daap_reply_login(struct evhttp_request *req, struct evbuffer *evbuf, char **uri,
|
|||||||
dmap_add_int(evbuf, "mlid", s->id); /* 12 */
|
dmap_add_int(evbuf, "mlid", s->id); /* 12 */
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_logout(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_logout(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
daap_session_kill(s);
|
daap_session_kill(s);
|
||||||
|
|
||||||
httpd_send_reply(req, 204, "Logout Successful", evbuf);
|
httpd_send_reply(req, 204, "Logout Successful", evbuf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
@ -968,7 +1004,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
param = evhttp_find_header(query, "revision-number");
|
param = evhttp_find_header(query, "revision-number");
|
||||||
if (!param)
|
if (!param)
|
||||||
@ -985,7 +1021,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Parameter revision-number not an integer\n");
|
DPRINTF(E_LOG, L_DAAP, "Parameter revision-number not an integer\n");
|
||||||
|
|
||||||
dmap_send_error(req, "mupd", "Invalid request");
|
dmap_send_error(req, "mupd", "Invalid request");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reqd_rev == 1) /* Or revision is not valid */
|
if (reqd_rev == 1) /* Or revision is not valid */
|
||||||
@ -996,7 +1032,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP update reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP update reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, "mupd", "Out of memory");
|
dmap_send_error(req, "mupd", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Send back current revision */
|
/* Send back current revision */
|
||||||
@ -1006,7 +1042,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Else, just let the request hang until we have changes to push back */
|
/* Else, just let the request hang until we have changes to push back */
|
||||||
@ -1016,7 +1052,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Out of memory for update request\n");
|
DPRINTF(E_LOG, L_DAAP, "Out of memory for update request\n");
|
||||||
|
|
||||||
dmap_send_error(req, "mupd", "Out of memory");
|
dmap_send_error(req, "mupd", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
memset(ur, 0, sizeof(struct daap_update_request));
|
memset(ur, 0, sizeof(struct daap_update_request));
|
||||||
|
|
||||||
@ -1035,7 +1071,7 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
dmap_send_error(req, "mupd", "Could not register timer");
|
dmap_send_error(req, "mupd", "Could not register timer");
|
||||||
|
|
||||||
update_free(ur);
|
update_free(ur);
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1051,17 +1087,21 @@ daap_reply_update(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
evcon = evhttp_request_get_connection(req);
|
evcon = evhttp_request_get_connection(req);
|
||||||
if (evcon)
|
if (evcon)
|
||||||
evhttp_connection_set_closecb(evcon, update_fail_cb, ur);
|
evhttp_connection_set_closecb(evcon, update_fail_cb, ur);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_activity(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_activity(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
/* That's so nice, thanks for letting us know */
|
/* That's so nice, thanks for letting us know */
|
||||||
evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
|
evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct evbuffer *content;
|
struct evbuffer *content;
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
@ -1071,7 +1111,7 @@ daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
lib = cfg_getsec(cfg, "library");
|
lib = cfg_getsec(cfg, "library");
|
||||||
name = cfg_getstr(lib, "name");
|
name = cfg_getstr(lib, "name");
|
||||||
@ -1082,7 +1122,7 @@ daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP dblist reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DAAP dblist reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, "avdb", "Out of memory");
|
dmap_send_error(req, "avdb", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
dmap_add_int(content, "miid", 1);
|
dmap_add_int(content, "miid", 1);
|
||||||
@ -1112,10 +1152,12 @@ daap_reply_dblist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
evbuffer_free(content);
|
evbuffer_free(content);
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, int playlist, struct evkeyvalq *query)
|
daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf, int playlist, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
@ -1126,6 +1168,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
const struct dmap_field **meta;
|
const struct dmap_field **meta;
|
||||||
struct sort_ctx *sctx;
|
struct sort_ctx *sctx;
|
||||||
const char *param;
|
const char *param;
|
||||||
|
const char *client_codecs;
|
||||||
char *tag;
|
char *tag;
|
||||||
int nmeta;
|
int nmeta;
|
||||||
int sort_headers;
|
int sort_headers;
|
||||||
@ -1134,8 +1177,8 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s && req)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist);
|
DPRINTF(E_DBG, L_DAAP, "Fetching song list for playlist %d\n", playlist);
|
||||||
|
|
||||||
@ -1150,7 +1193,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP song list reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP song list reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, tag, "Out of memory");
|
dmap_send_error(req, tag, "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
songlist = evbuffer_new();
|
songlist = evbuffer_new();
|
||||||
@ -1159,7 +1202,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP song list\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP song list\n");
|
||||||
|
|
||||||
dmap_send_error(req, tag, "Out of memory");
|
dmap_send_error(req, tag, "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start with a big enough evbuffer - it'll expand as needed */
|
/* Start with a big enough evbuffer - it'll expand as needed */
|
||||||
@ -1218,8 +1261,9 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
|
|
||||||
memset(&qp, 0, sizeof(struct query_params));
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
get_query_params(query, &sort_headers, &qp);
|
get_query_params(query, &sort_headers, &qp);
|
||||||
|
|
||||||
if (playlist == -1)
|
if (playlist == -1)
|
||||||
user_agent_filter(s->user_agent, &qp);
|
user_agent_filter(ua, &qp);
|
||||||
|
|
||||||
sctx = NULL;
|
sctx = NULL;
|
||||||
if (sort_headers)
|
if (sort_headers)
|
||||||
@ -1260,8 +1304,14 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
{
|
{
|
||||||
nsongs++;
|
nsongs++;
|
||||||
|
|
||||||
headers = evhttp_request_get_input_headers(req);
|
client_codecs = NULL;
|
||||||
transcode = transcode_needed(headers, dbmfi.codectype);
|
if (req)
|
||||||
|
{
|
||||||
|
headers = evhttp_request_get_input_headers(req);
|
||||||
|
client_codecs = evhttp_find_header(headers, "Accept-Codecs");
|
||||||
|
}
|
||||||
|
|
||||||
|
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
|
||||||
|
|
||||||
ret = dmap_encode_file_metadata(songlist, song, &dbmfi, meta, nmeta, sort_headers, transcode);
|
ret = dmap_encode_file_metadata(songlist, song, &dbmfi, meta, nmeta, sort_headers, transcode);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@ -1284,7 +1334,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_DAAP, "Done with song\n");
|
DPRINTF(E_SPAM, L_DAAP, "Done with song\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_DAAP, "Done with song list, %d songs\n", nsongs);
|
DPRINTF(E_DBG, L_DAAP, "Done with song list, %d songs\n", nsongs);
|
||||||
@ -1342,7 +1392,7 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
if (sort_headers)
|
if (sort_headers)
|
||||||
daap_sort_context_free(sctx);
|
daap_sort_context_free(sctx);
|
||||||
|
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sort_headers)
|
if (sort_headers)
|
||||||
@ -1356,13 +1406,13 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP song list reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP song list reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, tag, "Out of memory");
|
dmap_send_error(req, tag, "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
out_query_free:
|
out_query_free:
|
||||||
if (nmeta > 0)
|
if (nmeta > 0)
|
||||||
@ -1376,16 +1426,18 @@ daap_reply_songlist_generic(struct evhttp_request *req, struct evbuffer *evbuf,
|
|||||||
|
|
||||||
out_list_free:
|
out_list_free:
|
||||||
evbuffer_free(songlist);
|
evbuffer_free(songlist);
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_dbsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_dbsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
daap_reply_songlist_generic(req, evbuf, -1, query);
|
return daap_reply_songlist_generic(req, evbuf, -1, query, ua);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_plsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_plsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
int playlist;
|
int playlist;
|
||||||
int ret;
|
int ret;
|
||||||
@ -1395,14 +1447,14 @@ daap_reply_plsonglist(struct evhttp_request *req, struct evbuffer *evbuf, char *
|
|||||||
{
|
{
|
||||||
dmap_send_error(req, "apso", "Invalid playlist ID");
|
dmap_send_error(req, "apso", "Invalid playlist ID");
|
||||||
|
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
daap_reply_songlist_generic(req, evbuf, playlist, query);
|
return daap_reply_songlist_generic(req, evbuf, playlist, query, ua);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
struct db_playlist_info dbpli;
|
struct db_playlist_info dbpli;
|
||||||
@ -1424,7 +1476,7 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
|||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
ret = evbuffer_expand(evbuf, 61);
|
ret = evbuffer_expand(evbuf, 61);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
@ -1432,7 +1484,7 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP playlists reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not expand evbuffer for DAAP playlists reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, "aply", "Out of memory");
|
dmap_send_error(req, "aply", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
playlistlist = evbuffer_new();
|
playlistlist = evbuffer_new();
|
||||||
@ -1441,7 +1493,7 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP playlist list\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP playlist list\n");
|
||||||
|
|
||||||
dmap_send_error(req, "aply", "Out of memory");
|
dmap_send_error(req, "aply", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Start with a big enough evbuffer - it'll expand as needed */
|
/* Start with a big enough evbuffer - it'll expand as needed */
|
||||||
@ -1626,12 +1678,12 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not add playlist list to DAAP playlists reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not add playlist list to DAAP playlists reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, "aply", "Out of memory");
|
dmap_send_error(req, "aply", "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
out_query_free:
|
out_query_free:
|
||||||
free(meta);
|
free(meta);
|
||||||
@ -1643,10 +1695,12 @@ daap_reply_playlists(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
|||||||
|
|
||||||
out_list_free:
|
out_list_free:
|
||||||
evbuffer_free(playlistlist);
|
evbuffer_free(playlistlist);
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
struct db_group_info dbgri;
|
struct db_group_info dbgri;
|
||||||
@ -1668,13 +1722,14 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
char *tag;
|
char *tag;
|
||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s && req)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
memset(&qp, 0, sizeof(struct query_params));
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
|
|
||||||
get_query_params(query, &sort_headers, &qp);
|
get_query_params(query, &sort_headers, &qp);
|
||||||
user_agent_filter(s->user_agent, &qp);
|
|
||||||
|
user_agent_filter(ua, &qp);
|
||||||
|
|
||||||
param = evhttp_find_header(query, "group-type");
|
param = evhttp_find_header(query, "group-type");
|
||||||
if (strcmp(param, "artists") == 0)
|
if (strcmp(param, "artists") == 0)
|
||||||
@ -1906,7 +1961,7 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
if (sort_headers)
|
if (sort_headers)
|
||||||
daap_sort_context_free(sctx);
|
daap_sort_context_free(sctx);
|
||||||
|
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sort_headers)
|
if (sort_headers)
|
||||||
@ -1920,13 +1975,13 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP groups reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not add sort headers to DAAP groups reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, tag, "Out of memory");
|
dmap_send_error(req, tag, "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
out_query_free:
|
out_query_free:
|
||||||
free(meta);
|
free(meta);
|
||||||
@ -1940,10 +1995,12 @@ daap_reply_groups(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
out_qfilter_free:
|
out_qfilter_free:
|
||||||
if (qp.filter)
|
if (qp.filter)
|
||||||
free(qp.filter);
|
free(qp.filter);
|
||||||
|
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct query_params qp;
|
struct query_params qp;
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
@ -1957,13 +2014,14 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s && req)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
memset(&qp, 0, sizeof(struct query_params));
|
memset(&qp, 0, sizeof(struct query_params));
|
||||||
|
|
||||||
get_query_params(query, &sort_headers, &qp);
|
get_query_params(query, &sort_headers, &qp);
|
||||||
user_agent_filter(s->user_agent, &qp);
|
|
||||||
|
user_agent_filter(ua, &qp);
|
||||||
|
|
||||||
if (strcmp(uri[3], "artists") == 0)
|
if (strcmp(uri[3], "artists") == 0)
|
||||||
{
|
{
|
||||||
@ -1992,6 +2050,8 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", uri[3]);
|
DPRINTF(E_LOG, L_DAAP, "Invalid DAAP browse request type '%s'\n", uri[3]);
|
||||||
|
|
||||||
dmap_send_error(req, "abro", "Invalid browse type");
|
dmap_send_error(req, "abro", "Invalid browse type");
|
||||||
|
ret = -1;
|
||||||
|
|
||||||
goto out_qfilter_free;
|
goto out_qfilter_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2010,6 +2070,8 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP browse item list\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP browse item list\n");
|
||||||
|
|
||||||
dmap_send_error(req, "abro", "Out of memory");
|
dmap_send_error(req, "abro", "Out of memory");
|
||||||
|
ret = -1;
|
||||||
|
|
||||||
goto out_qfilter_free;
|
goto out_qfilter_free;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2033,6 +2095,7 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not create sort context\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not create sort context\n");
|
||||||
|
|
||||||
dmap_send_error(req, "abro", "Out of memory");
|
dmap_send_error(req, "abro", "Out of memory");
|
||||||
|
ret = -1;
|
||||||
|
|
||||||
goto out_itemlist_free;
|
goto out_itemlist_free;
|
||||||
}
|
}
|
||||||
@ -2120,6 +2183,8 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
|
|
||||||
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
out_sort_headers_free:
|
out_sort_headers_free:
|
||||||
if (sort_headers)
|
if (sort_headers)
|
||||||
daap_sort_context_free(sctx);
|
daap_sort_context_free(sctx);
|
||||||
@ -2130,11 +2195,13 @@ daap_reply_browse(struct evhttp_request *req, struct evbuffer *evbuf, char **uri
|
|||||||
out_qfilter_free:
|
out_qfilter_free:
|
||||||
if (qp.filter)
|
if (qp.filter)
|
||||||
free(qp.filter);
|
free(qp.filter);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NOTE: We only handle artwork at the moment */
|
/* NOTE: We only handle artwork at the moment */
|
||||||
static void
|
static int
|
||||||
daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
char clen[32];
|
char clen[32];
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
@ -2148,13 +2215,13 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
|
|||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
ret = safe_atoi32(uri[3], &id);
|
ret = safe_atoi32(uri[3], &id);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
{
|
{
|
||||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (evhttp_find_header(query, "mw") && evhttp_find_header(query, "mh"))
|
if (evhttp_find_header(query, "mw") && evhttp_find_header(query, "mh"))
|
||||||
@ -2166,7 +2233,7 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not convert mw parameter to integer\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not convert mw parameter to integer\n");
|
||||||
|
|
||||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
param = evhttp_find_header(query, "mh");
|
param = evhttp_find_header(query, "mh");
|
||||||
@ -2176,7 +2243,7 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not convert mh parameter to integer\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not convert mh parameter to integer\n");
|
||||||
|
|
||||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -2217,14 +2284,15 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
|
|||||||
|
|
||||||
/* No gzip compression for artwork */
|
/* No gzip compression for artwork */
|
||||||
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
|
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
no_artwork:
|
no_artwork:
|
||||||
evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
|
evhttp_send_reply(req, HTTP_NOCONTENT, "No Content", evbuf);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_stream(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_stream(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
struct daap_session *s;
|
struct daap_session *s;
|
||||||
int id;
|
int id;
|
||||||
@ -2232,13 +2300,15 @@ daap_stream(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, stru
|
|||||||
|
|
||||||
s = daap_session_find(req, query, evbuf);
|
s = daap_session_find(req, query, evbuf);
|
||||||
if (!s)
|
if (!s)
|
||||||
return;
|
return -1;
|
||||||
|
|
||||||
ret = safe_atoi32(uri[3], &id);
|
ret = safe_atoi32(uri[3], &id);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
evhttp_send_error(req, HTTP_BADREQUEST, "Bad Request");
|
||||||
else
|
else
|
||||||
httpd_stream_file(req, id);
|
httpd_stream_file(req, id);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2283,8 +2353,8 @@ static const struct dmap_field dmap_TST7 = { "test.ulong", "TST7", NULL, DMA
|
|||||||
static const struct dmap_field dmap_TST8 = { "test.long", "TST8", NULL, DMAP_TYPE_LONG };
|
static const struct dmap_field dmap_TST8 = { "test.long", "TST8", NULL, DMAP_TYPE_LONG };
|
||||||
static const struct dmap_field dmap_TST9 = { "test.string", "TST9", NULL, DMAP_TYPE_STRING };
|
static const struct dmap_field dmap_TST9 = { "test.string", "TST9", NULL, DMAP_TYPE_STRING };
|
||||||
|
|
||||||
static void
|
static int
|
||||||
daap_reply_dmap_test(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query)
|
daap_reply_dmap_test(struct evhttp_request *req, struct evbuffer *evbuf, char **uri, struct evkeyvalq *query, const char *ua)
|
||||||
{
|
{
|
||||||
char buf[64];
|
char buf[64];
|
||||||
struct evbuffer *test;
|
struct evbuffer *test;
|
||||||
@ -2296,7 +2366,7 @@ daap_reply_dmap_test(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP test\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not create evbuffer for DMAP test\n");
|
||||||
|
|
||||||
dmap_send_error(req, dmap_TEST.tag, "Out of memory");
|
dmap_send_error(req, dmap_TEST.tag, "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* UBYTE */
|
/* UBYTE */
|
||||||
@ -2361,10 +2431,12 @@ daap_reply_dmap_test(struct evhttp_request *req, struct evbuffer *evbuf, char **
|
|||||||
DPRINTF(E_LOG, L_DAAP, "Could not add test results to DMAP test reply\n");
|
DPRINTF(E_LOG, L_DAAP, "Could not add test results to DMAP test reply\n");
|
||||||
|
|
||||||
dmap_send_error(req, dmap_TEST.tag, "Out of memory");
|
dmap_send_error(req, dmap_TEST.tag, "Out of memory");
|
||||||
return;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
|
evhttp_send_reply(req, HTTP_OK, "OK", evbuf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
#endif /* DMAP_TEST */
|
#endif /* DMAP_TEST */
|
||||||
|
|
||||||
@ -2455,10 +2527,13 @@ daap_request(struct evhttp_request *req)
|
|||||||
struct evbuffer *evbuf;
|
struct evbuffer *evbuf;
|
||||||
struct evkeyvalq query;
|
struct evkeyvalq query;
|
||||||
struct evkeyvalq *headers;
|
struct evkeyvalq *headers;
|
||||||
|
struct timespec start;
|
||||||
|
struct timespec end;
|
||||||
const char *ua;
|
const char *ua;
|
||||||
cfg_t *lib;
|
cfg_t *lib;
|
||||||
char *libname;
|
char *libname;
|
||||||
char *passwd;
|
char *passwd;
|
||||||
|
int msec;
|
||||||
int handler;
|
int handler;
|
||||||
int ret;
|
int ret;
|
||||||
int i;
|
int i;
|
||||||
@ -2494,11 +2569,7 @@ daap_request(struct evhttp_request *req)
|
|||||||
full_uri = uri;
|
full_uri = uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr = strchr(full_uri, '?');
|
uri = extract_uri(full_uri);
|
||||||
if (ptr)
|
|
||||||
*ptr = '\0';
|
|
||||||
|
|
||||||
uri = strdup(full_uri);
|
|
||||||
if (!uri)
|
if (!uri)
|
||||||
{
|
{
|
||||||
free(full_uri);
|
free(full_uri);
|
||||||
@ -2506,13 +2577,6 @@ daap_request(struct evhttp_request *req)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ptr)
|
|
||||||
*ptr = '?';
|
|
||||||
|
|
||||||
ptr = uri;
|
|
||||||
uri = evhttp_decode_uri(uri);
|
|
||||||
free(ptr);
|
|
||||||
|
|
||||||
DPRINTF(E_DBG, L_DAAP, "DAAP request: %s\n", full_uri);
|
DPRINTF(E_DBG, L_DAAP, "DAAP request: %s\n", full_uri);
|
||||||
|
|
||||||
handler = -1;
|
handler = -1;
|
||||||
@ -2594,6 +2658,29 @@ daap_request(struct evhttp_request *req)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set reply headers
|
||||||
|
headers = evhttp_request_get_output_headers(req);
|
||||||
|
evhttp_add_header(headers, "Accept-Ranges", "bytes");
|
||||||
|
evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION);
|
||||||
|
/* Content-Type for all replies, even the actual audio streaming. Note that
|
||||||
|
* video streaming will override this Content-Type with a more appropriate
|
||||||
|
* video/<type> Content-Type as expected by clients like Front Row.
|
||||||
|
*/
|
||||||
|
evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged");
|
||||||
|
|
||||||
|
// Try the cache
|
||||||
|
evbuf = daapcache_get(full_uri);
|
||||||
|
if (evbuf)
|
||||||
|
{
|
||||||
|
httpd_send_reply(req, HTTP_OK, "OK", evbuf); // TODO not all want this reply
|
||||||
|
|
||||||
|
evbuffer_free(evbuf);
|
||||||
|
free(uri);
|
||||||
|
free(full_uri);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No cache, so prepare handler arguments and send to the handler
|
||||||
evbuf = evbuffer_new();
|
evbuf = evbuffer_new();
|
||||||
if (!evbuf)
|
if (!evbuf)
|
||||||
{
|
{
|
||||||
@ -2608,19 +2695,21 @@ daap_request(struct evhttp_request *req)
|
|||||||
|
|
||||||
evhttp_parse_query(full_uri, &query);
|
evhttp_parse_query(full_uri, &query);
|
||||||
|
|
||||||
headers = evhttp_request_get_output_headers(req);
|
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||||
evhttp_add_header(headers, "Accept-Ranges", "bytes");
|
|
||||||
evhttp_add_header(headers, "DAAP-Server", "forked-daapd/" VERSION);
|
|
||||||
/* Content-Type for all replies, even the actual audio streaming. Note that
|
|
||||||
* video streaming will override this Content-Type with a more appropriate
|
|
||||||
* video/<type> Content-Type as expected by clients like Front Row.
|
|
||||||
*/
|
|
||||||
evhttp_add_header(headers, "Content-Type", "application/x-dmap-tagged");
|
|
||||||
|
|
||||||
daap_handlers[handler].handler(req, evbuf, uri_parts, &query);
|
daap_handlers[handler].handler(req, evbuf, uri_parts, &query, ua);
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &end);
|
||||||
|
|
||||||
|
msec = (end.tv_sec * 1000 + end.tv_nsec / 1000000) - (start.tv_sec * 1000 + start.tv_nsec / 1000000);
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_DB, "DAAP request handled in %d milliseconds\n", msec);
|
||||||
|
|
||||||
|
if (msec > daapcache_threshold())
|
||||||
|
daapcache_add(full_uri, ua, msec);
|
||||||
|
|
||||||
evbuffer_free(evbuf);
|
|
||||||
evhttp_clear_headers(&query);
|
evhttp_clear_headers(&query);
|
||||||
|
evbuffer_free(evbuf);
|
||||||
free(uri);
|
free(uri);
|
||||||
free(full_uri);
|
free(full_uri);
|
||||||
}
|
}
|
||||||
@ -2657,6 +2746,87 @@ daap_is_request(struct evhttp_request *req, char *uri)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct evbuffer *
|
||||||
|
daap_reply_build(char *full_uri, const char *ua)
|
||||||
|
{
|
||||||
|
char *uri;
|
||||||
|
char *ptr;
|
||||||
|
char *uri_parts[7];
|
||||||
|
struct evbuffer *evbuf;
|
||||||
|
struct evkeyvalq query;
|
||||||
|
int handler;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
DPRINTF(E_DBG, L_DAAP, "Building reply for DAAP request: %s\n", full_uri);
|
||||||
|
|
||||||
|
uri = extract_uri(full_uri);
|
||||||
|
if (!uri)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DAAP, "Error extracting DAAP request: %s\n", full_uri);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = -1;
|
||||||
|
for (i = 0; daap_handlers[i].handler; i++)
|
||||||
|
{
|
||||||
|
ret = regexec(&daap_handlers[i].preg, uri, 0, NULL, 0);
|
||||||
|
if (ret == 0)
|
||||||
|
{
|
||||||
|
handler = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler < 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DAAP, "Unrecognized DAAP request: %s\n", full_uri);
|
||||||
|
|
||||||
|
free(uri);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(uri_parts, 0, sizeof(uri_parts));
|
||||||
|
|
||||||
|
uri_parts[0] = strtok_r(uri, "/", &ptr);
|
||||||
|
for (i = 1; (i < sizeof(uri_parts) / sizeof(uri_parts[0])) && uri_parts[i - 1]; i++)
|
||||||
|
{
|
||||||
|
uri_parts[i] = strtok_r(NULL, "/", &ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uri_parts[0] || uri_parts[i - 1] || (i < 2))
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DAAP, "DAAP URI has too many/few components (%d)\n", (uri_parts[0]) ? i : 0);
|
||||||
|
|
||||||
|
free(uri);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
evbuf = evbuffer_new();
|
||||||
|
if (!evbuf)
|
||||||
|
{
|
||||||
|
DPRINTF(E_LOG, L_DAAP, "Could not allocate evbuffer for building DAAP reply\n");
|
||||||
|
|
||||||
|
free(uri);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
evhttp_parse_query(full_uri, &query);
|
||||||
|
|
||||||
|
ret = daap_handlers[handler].handler(NULL, evbuf, uri_parts, &query, ua);
|
||||||
|
if (ret < 0)
|
||||||
|
{
|
||||||
|
evbuffer_free(evbuf);
|
||||||
|
evbuf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
evhttp_clear_headers(&query);
|
||||||
|
free(uri);
|
||||||
|
|
||||||
|
return evbuf;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
daap_init(void)
|
daap_init(void)
|
||||||
{
|
{
|
||||||
|
@ -21,4 +21,7 @@ daap_request(struct evhttp_request *req);
|
|||||||
int
|
int
|
||||||
daap_is_request(struct evhttp_request *req, char *uri);
|
daap_is_request(struct evhttp_request *req, char *uri);
|
||||||
|
|
||||||
|
struct evbuffer *
|
||||||
|
daap_reply_build(char *full_uri, const char *ua);
|
||||||
|
|
||||||
#endif /* !__HTTPD_DAAP_H__ */
|
#endif /* !__HTTPD_DAAP_H__ */
|
||||||
|
@ -439,6 +439,8 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
|||||||
struct db_media_file_info dbmfi;
|
struct db_media_file_info dbmfi;
|
||||||
struct evkeyvalq *headers;
|
struct evkeyvalq *headers;
|
||||||
const char *param;
|
const char *param;
|
||||||
|
const char *ua;
|
||||||
|
const char *client_codecs;
|
||||||
char **strval;
|
char **strval;
|
||||||
mxml_node_t *reply;
|
mxml_node_t *reply;
|
||||||
mxml_node_t *status;
|
mxml_node_t *status;
|
||||||
@ -531,7 +533,11 @@ rsp_reply_playlist(struct evhttp_request *req, char **uri, struct evkeyvalq *que
|
|||||||
while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
|
while (((ret = db_query_fetch_file(&qp, &dbmfi)) == 0) && (dbmfi.id))
|
||||||
{
|
{
|
||||||
headers = evhttp_request_get_input_headers(req);
|
headers = evhttp_request_get_input_headers(req);
|
||||||
transcode = transcode_needed(headers, dbmfi.codectype);
|
|
||||||
|
ua = evhttp_find_header(headers, "User-Agent");
|
||||||
|
client_codecs = evhttp_find_header(headers, "Accept-Codecs");
|
||||||
|
|
||||||
|
transcode = transcode_needed(ua, client_codecs, dbmfi.codectype);
|
||||||
|
|
||||||
/* Item block (one item) */
|
/* Item block (one item) */
|
||||||
item = mxmlNewElement(items, "item");
|
item = mxmlNewElement(items, "item");
|
||||||
|
@ -43,7 +43,7 @@ static int threshold;
|
|||||||
static int console;
|
static int console;
|
||||||
static char *logfilename;
|
static char *logfilename;
|
||||||
static FILE *logfile;
|
static FILE *logfile;
|
||||||
static char *labels[] = { "config", "daap", "db", "httpd", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm" };
|
static char *labels[] = { "config", "daap", "db", "httpd", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "dcache" };
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
@ -28,8 +28,9 @@
|
|||||||
#define L_DBPERF 19
|
#define L_DBPERF 19
|
||||||
#define L_SPOTIFY 20
|
#define L_SPOTIFY 20
|
||||||
#define L_LASTFM 21
|
#define L_LASTFM 21
|
||||||
|
#define L_DCACHE 22
|
||||||
|
|
||||||
#define N_LOGDOMAINS 22
|
#define N_LOGDOMAINS 23
|
||||||
|
|
||||||
/* Severities */
|
/* Severities */
|
||||||
#define E_FATAL 0
|
#define E_FATAL 0
|
||||||
|
16
src/main.c
16
src/main.c
@ -58,6 +58,7 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
|||||||
#include "db.h"
|
#include "db.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
#include "daap_cache.h"
|
||||||
#include "filescanner.h"
|
#include "filescanner.h"
|
||||||
#include "httpd.h"
|
#include "httpd.h"
|
||||||
#include "mdns.h"
|
#include "mdns.h"
|
||||||
@ -674,6 +675,16 @@ main(int argc, char **argv)
|
|||||||
goto db_fail;
|
goto db_fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Spawn DAAP cache thread */
|
||||||
|
ret = daapcache_init();
|
||||||
|
if (ret != 0)
|
||||||
|
{
|
||||||
|
DPRINTF(E_FATAL, L_MAIN, "DAAP cache thread failed to start\n");
|
||||||
|
|
||||||
|
ret = EXIT_FAILURE;
|
||||||
|
goto cache_fail;
|
||||||
|
}
|
||||||
|
|
||||||
/* Spawn file scanner thread */
|
/* Spawn file scanner thread */
|
||||||
ret = filescanner_init();
|
ret = filescanner_init();
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
@ -813,9 +824,14 @@ main(int argc, char **argv)
|
|||||||
filescanner_deinit();
|
filescanner_deinit();
|
||||||
|
|
||||||
filescanner_fail:
|
filescanner_fail:
|
||||||
|
DPRINTF(E_LOG, L_MAIN, "DAAP cache deinit\n");
|
||||||
|
daapcache_deinit();
|
||||||
|
|
||||||
|
cache_fail:
|
||||||
DPRINTF(E_LOG, L_MAIN, "Database deinit\n");
|
DPRINTF(E_LOG, L_MAIN, "Database deinit\n");
|
||||||
db_perthread_deinit();
|
db_perthread_deinit();
|
||||||
db_deinit();
|
db_deinit();
|
||||||
|
|
||||||
db_fail:
|
db_fail:
|
||||||
if (ret == EXIT_FAILURE)
|
if (ret == EXIT_FAILURE)
|
||||||
{
|
{
|
||||||
|
@ -712,15 +712,17 @@ transcode_cleanup(struct transcode_ctx *ctx)
|
|||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
transcode_needed(struct evkeyvalq *headers, char *file_codectype)
|
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype)
|
||||||
{
|
{
|
||||||
const char *client_codecs;
|
|
||||||
const char *user_agent;
|
|
||||||
char *codectype;
|
char *codectype;
|
||||||
cfg_t *lib;
|
cfg_t *lib;
|
||||||
int size;
|
int size;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
// If client is a Remote we will AirPlay, which means we will transcode to PCM
|
||||||
|
if (user_agent && strcasestr(user_agent, "remote"))
|
||||||
|
return 1;
|
||||||
|
|
||||||
if (!file_codectype)
|
if (!file_codectype)
|
||||||
{
|
{
|
||||||
DPRINTF(E_LOG, L_XCODE, "Can't proceed, codectype is unknown (null)\n");
|
DPRINTF(E_LOG, L_XCODE, "Can't proceed, codectype is unknown (null)\n");
|
||||||
@ -763,10 +765,8 @@ transcode_needed(struct evkeyvalq *headers, char *file_codectype)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
client_codecs = evhttp_find_header(headers, "Accept-Codecs");
|
|
||||||
if (!client_codecs)
|
if (!client_codecs)
|
||||||
{
|
{
|
||||||
user_agent = evhttp_find_header(headers, "User-Agent");
|
|
||||||
if (user_agent)
|
if (user_agent)
|
||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_XCODE, "User-Agent: %s\n", user_agent);
|
DPRINTF(E_DBG, L_XCODE, "User-Agent: %s\n", user_agent);
|
||||||
@ -787,12 +787,6 @@ transcode_needed(struct evkeyvalq *headers, char *file_codectype)
|
|||||||
{
|
{
|
||||||
DPRINTF(E_DBG, L_XCODE, "Client is Front Row, using iTunes codecs\n");
|
DPRINTF(E_DBG, L_XCODE, "Client is Front Row, using iTunes codecs\n");
|
||||||
|
|
||||||
client_codecs = itunes_codecs;
|
|
||||||
}
|
|
||||||
else if (strncmp(user_agent, "Remote", strlen("Remote")) == 0)
|
|
||||||
{
|
|
||||||
DPRINTF(E_DBG, L_XCODE, "Client is Remote, using iTunes codecs\n");
|
|
||||||
|
|
||||||
client_codecs = itunes_codecs;
|
client_codecs = itunes_codecs;
|
||||||
}
|
}
|
||||||
else if (strncmp(user_agent, "AppleCoreMedia", strlen("AppleCoreMedia")) == 0)
|
else if (strncmp(user_agent, "AppleCoreMedia", strlen("AppleCoreMedia")) == 0)
|
||||||
|
@ -3,11 +3,6 @@
|
|||||||
#define __TRANSCODE_H__
|
#define __TRANSCODE_H__
|
||||||
|
|
||||||
#include <event.h>
|
#include <event.h>
|
||||||
#ifdef HAVE_LIBEVENT2
|
|
||||||
# include <event2/http.h>
|
|
||||||
#else
|
|
||||||
# include "evhttp/evhttp.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct transcode_ctx;
|
struct transcode_ctx;
|
||||||
|
|
||||||
@ -24,6 +19,6 @@ void
|
|||||||
transcode_cleanup(struct transcode_ctx *ctx);
|
transcode_cleanup(struct transcode_ctx *ctx);
|
||||||
|
|
||||||
int
|
int
|
||||||
transcode_needed(struct evkeyvalq *headers, char *file_codectype);
|
transcode_needed(const char *user_agent, const char *client_codecs, char *file_codectype);
|
||||||
|
|
||||||
#endif /* !__TRANSCODE_H__ */
|
#endif /* !__TRANSCODE_H__ */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user