mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-25 06:35:57 -05:00
Artwork caching (pull request #61), and also:
- introduced new section in config file for sqlite pragma settings, added config for artwork cache - added setting of pragma synchronous, cache size and journal mode to daap cache
This commit is contained in:
parent
61a4da215c
commit
7578bb1205
@ -19,11 +19,11 @@ general {
|
||||
admin_password = "unused"
|
||||
# Enable/disable IPv6
|
||||
ipv6 = no
|
||||
# Location of DAAP cache
|
||||
# daapcache_path = "/var/cache/forked-daapd/daapcache.db"
|
||||
# Location of cache database
|
||||
# cache_path = "/var/cache/forked-daapd/cache.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
|
||||
# cache_daap_threshold = 1000
|
||||
}
|
||||
|
||||
# Library configuration
|
||||
@ -137,3 +137,25 @@ spotify {
|
||||
# 0: No preference (default), 1: 96kbps, 2: 160kbps, 3: 320kbps
|
||||
# bitrate = 0
|
||||
}
|
||||
|
||||
# SQLite configuration (allows to modify the operation of the SQLite databases)
|
||||
# Make sure to read the SQLite documentation for the corresponding PRAGMA statements as
|
||||
# changing them from the defaults may increase the possibility of database corruptions!
|
||||
# By default the SQLite default values are used.
|
||||
sqlite {
|
||||
# Cache size in number of db pages for the library database
|
||||
# (SQLite default page size is 1024 bytes and cache size is 2000 pages)
|
||||
# pragma_cache_size_library = 2000
|
||||
|
||||
# Cache size in number of db pages for the daap cache database
|
||||
# (SQLite default page size is 1024 bytes and cache size is 2000 pages)
|
||||
# pragma_cache_size_cache = 2000
|
||||
|
||||
# Sets the journal mode for the database
|
||||
# DELETE (default), TRUNCATE, PERSIST, MEMORY, WAL, OFF
|
||||
# pragma_journal_mode = DELETE
|
||||
|
||||
# Change the setting of the "synchronous" flag
|
||||
# 0: OFF, 1: NORMAL, 2: FULL (default)
|
||||
# pragma_synchronous = 2
|
||||
}
|
||||
|
@ -96,7 +96,7 @@ forked_daapd_SOURCES = main.c \
|
||||
db.c db.h \
|
||||
logger.c logger.h \
|
||||
conffile.c conffile.h \
|
||||
daap_cache.h daap_cache.c \
|
||||
cache.h cache.c \
|
||||
filescanner.c filescanner.h \
|
||||
filescanner_ffmpeg.c filescanner_playlist.c filescanner_icy.c $(ITUNES_SRC) \
|
||||
mdns_avahi.c mdns.h \
|
||||
|
407
src/artwork.c
407
src/artwork.c
@ -37,6 +37,7 @@
|
||||
#include "misc.h"
|
||||
#include "logger.h"
|
||||
#include "conffile.h"
|
||||
#include "cache.h"
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 53
|
||||
# include "avio_evbuffer.h"
|
||||
@ -56,7 +57,6 @@ static const char *cover_extension[] =
|
||||
"jpg", "png",
|
||||
};
|
||||
|
||||
|
||||
static int
|
||||
artwork_read(char *filename, struct evbuffer *evbuf)
|
||||
{
|
||||
@ -152,7 +152,7 @@ rescale_needed(AVCodecContext *src, int max_w, int max_h, int *target_w, int *ta
|
||||
}
|
||||
|
||||
static int
|
||||
artwork_rescale(AVFormatContext *src_ctx, int s, int out_w, int out_h, int format, struct evbuffer *evbuf)
|
||||
artwork_rescale(AVFormatContext *src_ctx, int s, int out_w, int out_h, struct evbuffer *evbuf)
|
||||
{
|
||||
uint8_t *buf;
|
||||
uint8_t *outbuf;
|
||||
@ -220,35 +220,29 @@ artwork_rescale(AVFormatContext *src_ctx, int s, int out_w, int out_h, int forma
|
||||
dst_fmt->video_codec = AV_CODEC_ID_NONE;
|
||||
|
||||
/* Try to keep same codec if possible */
|
||||
if ((src->codec_id == AV_CODEC_ID_PNG) && (format & ART_CAN_PNG))
|
||||
if (src->codec_id == AV_CODEC_ID_PNG)
|
||||
dst_fmt->video_codec = AV_CODEC_ID_PNG;
|
||||
else if ((src->codec_id == AV_CODEC_ID_MJPEG) && (format & ART_CAN_JPEG))
|
||||
else if (src->codec_id == AV_CODEC_ID_MJPEG)
|
||||
dst_fmt->video_codec = AV_CODEC_ID_MJPEG;
|
||||
|
||||
/* If not possible, select new codec */
|
||||
if (dst_fmt->video_codec == AV_CODEC_ID_NONE)
|
||||
{
|
||||
if (format & ART_CAN_PNG)
|
||||
dst_fmt->video_codec = AV_CODEC_ID_PNG;
|
||||
else if (format & ART_CAN_JPEG)
|
||||
dst_fmt->video_codec = AV_CODEC_ID_MJPEG;
|
||||
dst_fmt->video_codec = AV_CODEC_ID_PNG;
|
||||
}
|
||||
#else
|
||||
dst_fmt->video_codec = CODEC_ID_NONE;
|
||||
|
||||
/* Try to keep same codec if possible */
|
||||
if ((src->codec_id == CODEC_ID_PNG) && (format & ART_CAN_PNG))
|
||||
if (src->codec_id == CODEC_ID_PNG)
|
||||
dst_fmt->video_codec = CODEC_ID_PNG;
|
||||
else if ((src->codec_id == CODEC_ID_MJPEG) && (format & ART_CAN_JPEG))
|
||||
else if (src->codec_id == CODEC_ID_MJPEG)
|
||||
dst_fmt->video_codec = CODEC_ID_MJPEG;
|
||||
|
||||
/* If not possible, select new codec */
|
||||
if (dst_fmt->video_codec == CODEC_ID_NONE)
|
||||
{
|
||||
if (format & ART_CAN_PNG)
|
||||
dst_fmt->video_codec = CODEC_ID_PNG;
|
||||
else if (format & ART_CAN_JPEG)
|
||||
dst_fmt->video_codec = CODEC_ID_MJPEG;
|
||||
dst_fmt->video_codec = CODEC_ID_PNG;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -624,7 +618,7 @@ artwork_rescale(AVFormatContext *src_ctx, int s, int out_w, int out_h, int forma
|
||||
}
|
||||
|
||||
static int
|
||||
artwork_get(char *filename, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
artwork_get(char *filename, int max_w, int max_h, struct evbuffer *evbuf)
|
||||
{
|
||||
AVFormatContext *src_ctx;
|
||||
int s;
|
||||
@ -675,7 +669,7 @@ artwork_get(char *filename, int max_w, int max_h, int format, struct evbuffer *e
|
||||
if (src_ctx->streams[s]->codec->codec_id == CODEC_ID_PNG)
|
||||
#endif
|
||||
{
|
||||
format_ok = (format & ART_CAN_PNG) ? ART_FMT_PNG : 0;
|
||||
format_ok = ART_FMT_PNG;
|
||||
break;
|
||||
}
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35)
|
||||
@ -684,7 +678,7 @@ artwork_get(char *filename, int max_w, int max_h, int format, struct evbuffer *e
|
||||
else if (src_ctx->streams[s]->codec->codec_id == CODEC_ID_MJPEG)
|
||||
#endif
|
||||
{
|
||||
format_ok = (format & ART_CAN_JPEG) ? ART_FMT_JPEG : 0;
|
||||
format_ok = ART_FMT_JPEG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -711,7 +705,7 @@ artwork_get(char *filename, int max_w, int max_h, int format, struct evbuffer *e
|
||||
ret = format_ok;
|
||||
}
|
||||
else
|
||||
ret = artwork_rescale(src_ctx, s, target_w, target_h, format, evbuf);
|
||||
ret = artwork_rescale(src_ctx, s, target_w, target_h, evbuf);
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 54 || (LIBAVFORMAT_VERSION_MAJOR == 53 && LIBAVFORMAT_VERSION_MINOR >= 21)
|
||||
avformat_close_input(&src_ctx);
|
||||
@ -730,7 +724,7 @@ artwork_get(char *filename, int max_w, int max_h, int format, struct evbuffer *e
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 55 || (LIBAVFORMAT_VERSION_MAJOR == 54 && LIBAVFORMAT_VERSION_MINOR >= 6)
|
||||
static int
|
||||
artwork_get_embedded_image(char *filename, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
artwork_get_embedded_image(char *filename, int max_w, int max_h, struct evbuffer *evbuf)
|
||||
{
|
||||
AVFormatContext *src_ctx;
|
||||
AVStream *src_st;
|
||||
@ -772,7 +766,7 @@ artwork_get_embedded_image(char *filename, int max_w, int max_h, int format, str
|
||||
if (src_ctx->streams[s]->codec->codec_id == CODEC_ID_PNG)
|
||||
#endif
|
||||
{
|
||||
format_ok = (format & ART_CAN_PNG) ? ART_FMT_PNG : 0;
|
||||
format_ok = ART_FMT_PNG;
|
||||
break;
|
||||
}
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 55 || (LIBAVCODEC_VERSION_MAJOR == 54 && LIBAVCODEC_VERSION_MINOR >= 35)
|
||||
@ -781,7 +775,7 @@ artwork_get_embedded_image(char *filename, int max_w, int max_h, int format, str
|
||||
else if (src_ctx->streams[s]->codec->codec_id == CODEC_ID_MJPEG)
|
||||
#endif
|
||||
{
|
||||
format_ok = (format & ART_CAN_JPEG) ? ART_FMT_JPEG : 0;
|
||||
format_ok = ART_FMT_JPEG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -827,7 +821,7 @@ artwork_get_embedded_image(char *filename, int max_w, int max_h, int format, str
|
||||
{
|
||||
DPRINTF(E_DBG, L_ART, "Artwork too large, rescaling image\n");
|
||||
|
||||
ret = artwork_rescale(src_ctx, s, target_w, target_h, format, evbuf);
|
||||
ret = artwork_rescale(src_ctx, s, target_w, target_h, evbuf);
|
||||
}
|
||||
|
||||
avformat_close_input(&src_ctx);
|
||||
@ -843,66 +837,18 @@ artwork_get_embedded_image(char *filename, int max_w, int max_h, int format, str
|
||||
#endif
|
||||
|
||||
static int
|
||||
artwork_get_own_image(char *path, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
artwork_get_dir_image(char *path, int max_w, int max_h, char *filename, struct evbuffer *evbuf)
|
||||
{
|
||||
char artwork[PATH_MAX];
|
||||
char *ptr;
|
||||
int len;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
ret = snprintf(artwork, sizeof(artwork), "%s", path);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork)))
|
||||
{
|
||||
DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
ptr = strrchr(artwork, '.');
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
|
||||
len = strlen(artwork);
|
||||
|
||||
for (i = 0; i < (sizeof(cover_extension) / sizeof(cover_extension[0])); i++)
|
||||
{
|
||||
ret = snprintf(artwork + len, sizeof(artwork) - len, ".%s", cover_extension[i]);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork) - len))
|
||||
{
|
||||
DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX (ext %s)\n", cover_extension[i]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM, L_ART, "Trying own artwork file %s\n", artwork);
|
||||
|
||||
ret = access(artwork, F_OK);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == (sizeof(cover_extension) / sizeof(cover_extension[0])))
|
||||
return -1;
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Found own artwork file %s\n", artwork);
|
||||
|
||||
return artwork_get(artwork, max_w, max_h, format, evbuf);
|
||||
}
|
||||
|
||||
static int
|
||||
artwork_get_dir_image(char *path, int isdir, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
{
|
||||
char artwork[PATH_MAX];
|
||||
char *ptr;
|
||||
char parentdir[PATH_MAX];
|
||||
int i;
|
||||
int j;
|
||||
int len;
|
||||
int ret;
|
||||
cfg_t *lib;
|
||||
int nbasenames;
|
||||
int nextensions;
|
||||
char *ptr;
|
||||
|
||||
ret = snprintf(artwork, sizeof(artwork), "%s", path);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork)))
|
||||
@ -912,13 +858,6 @@ artwork_get_dir_image(char *path, int isdir, int max_w, int max_h, int format, s
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!isdir)
|
||||
{
|
||||
ptr = strrchr(artwork, '/');
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
len = strlen(artwork);
|
||||
|
||||
lib = cfg_getsec(cfg, "library");
|
||||
@ -927,9 +866,11 @@ artwork_get_dir_image(char *path, int isdir, int max_w, int max_h, int format, s
|
||||
if (nbasenames == 0)
|
||||
return -1;
|
||||
|
||||
nextensions = sizeof(cover_extension) / sizeof(cover_extension[0]);
|
||||
|
||||
for (i = 0; i < nbasenames; i++)
|
||||
{
|
||||
for (j = 0; j < (sizeof(cover_extension) / sizeof(cover_extension[0])); j++)
|
||||
for (j = 0; j < nextensions; j++)
|
||||
{
|
||||
ret = snprintf(artwork + len, sizeof(artwork) - len, "/%s.%s", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork) - len))
|
||||
@ -945,82 +886,59 @@ artwork_get_dir_image(char *path, int isdir, int max_w, int max_h, int format, s
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
// If artwork file exists (ret == 0), exit the loop
|
||||
break;
|
||||
}
|
||||
|
||||
if (j < (sizeof(cover_extension) / sizeof(cover_extension[0])))
|
||||
// In case the previous loop exited early, we found an existing artwork file and exit the outer loop
|
||||
if (j < nextensions)
|
||||
break;
|
||||
}
|
||||
|
||||
// If the loop for directory artwork did not exit early, look for parent directory artwork
|
||||
if (i == nbasenames)
|
||||
return -1;
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Found directory artwork file %s\n", artwork);
|
||||
|
||||
return artwork_get(artwork, max_w, max_h, format, evbuf);
|
||||
}
|
||||
|
||||
static int
|
||||
artwork_get_parentdir_image(char *path, int isdir, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
{
|
||||
char artwork[PATH_MAX];
|
||||
char parentdir[PATH_MAX];
|
||||
char *ptr;
|
||||
int len;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
ret = snprintf(artwork, sizeof(artwork), "%s", path);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork)))
|
||||
{
|
||||
DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!isdir)
|
||||
{
|
||||
ptr = strrchr(artwork, '/');
|
||||
if (ptr)
|
||||
*ptr = '\0';
|
||||
}
|
||||
|
||||
ptr = strrchr(artwork, '/');
|
||||
if ((!ptr) || (strlen(ptr) <= 1))
|
||||
return -1;
|
||||
strcpy(parentdir, ptr + 1);
|
||||
ptr = strrchr(artwork, '/');
|
||||
if ((!ptr) || (strlen(ptr) <= 1))
|
||||
return -1;
|
||||
strcpy(parentdir, ptr + 1);
|
||||
|
||||
len = strlen(artwork);
|
||||
len = strlen(artwork);
|
||||
|
||||
for (i = 0; i < (sizeof(cover_extension) / sizeof(cover_extension[0])); i++)
|
||||
{
|
||||
ret = snprintf(artwork + len, sizeof(artwork) - len, "/%s.%s", parentdir, cover_extension[i]);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork) - len))
|
||||
for (i = 0; i < nextensions; i++)
|
||||
{
|
||||
DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX (%s.%s)\n", parentdir, cover_extension[i]);
|
||||
ret = snprintf(artwork + len, sizeof(artwork) - len, "/%s.%s", parentdir, cover_extension[i]);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork) - len))
|
||||
{
|
||||
DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX (%s.%s)\n", parentdir, cover_extension[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
continue;
|
||||
DPRINTF(E_SPAM, L_ART, "Trying parent directory artwork file %s\n", artwork);
|
||||
|
||||
ret = access(artwork, F_OK);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM, L_ART, "Trying parent directory artwork file %s\n", artwork);
|
||||
|
||||
ret = access(artwork, F_OK);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
break;
|
||||
if (i == nextensions)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (i == (sizeof(cover_extension) / sizeof(cover_extension[0])))
|
||||
return -1;
|
||||
DPRINTF(E_DBG, L_ART, "Found directory artwork file %s\n", artwork);
|
||||
strcpy(filename, artwork);
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Found parent directory artwork file %s\n", artwork);
|
||||
|
||||
return artwork_get(artwork, max_w, max_h, format, evbuf);
|
||||
return artwork_get(artwork, max_w, max_h, evbuf);
|
||||
}
|
||||
|
||||
static int
|
||||
artwork_get_item_path(char *path, int artwork, uint32_t data_kind, int nodir, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
artwork_get_item_path(char *path, int artwork, int max_w, int max_h, struct evbuffer *evbuf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -1031,43 +949,16 @@ artwork_get_item_path(char *path, int artwork, uint32_t data_kind, int nodir, in
|
||||
break;
|
||||
#ifdef HAVE_SPOTIFY_H
|
||||
case ARTWORK_SPOTIFY:
|
||||
if (!(format & ART_CAN_JPEG))
|
||||
break;
|
||||
|
||||
ret = spotify_artwork_get(evbuf, path, max_w, max_h);
|
||||
(ret < 0) ? (ret = 0) : (ret = ART_FMT_JPEG);
|
||||
break;
|
||||
#endif
|
||||
#if LIBAVFORMAT_VERSION_MAJOR >= 55 || (LIBAVFORMAT_VERSION_MAJOR == 54 && LIBAVFORMAT_VERSION_MINOR >= 6)
|
||||
case ARTWORK_EMBEDDED:
|
||||
ret = artwork_get_embedded_image(path, max_w, max_h, format, evbuf);
|
||||
ret = artwork_get_embedded_image(path, max_w, max_h, evbuf);
|
||||
|
||||
/* Fall through if embedded artwork not found */
|
||||
if (ret > 0)
|
||||
break;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
/* Not a normal file */
|
||||
if (data_kind != 0)
|
||||
break;
|
||||
|
||||
/* Look for basename(filename).{png,jpg} */
|
||||
ret = artwork_get_own_image(path, max_w, max_h, format, evbuf);
|
||||
if (ret > 0)
|
||||
break;
|
||||
|
||||
if (nodir)
|
||||
break;
|
||||
|
||||
/* Look for basedir(filename)/{artwork,cover}.{png,jpg} */
|
||||
ret = artwork_get_dir_image(path, 0, max_w, max_h, format, evbuf);
|
||||
if (ret > 0)
|
||||
break;
|
||||
|
||||
/* Look for parentdir(filename).{png,jpg} */
|
||||
ret = artwork_get_parentdir_image(path, 0, max_w, max_h, format, evbuf);
|
||||
if (ret > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret > 0)
|
||||
@ -1076,46 +967,62 @@ artwork_get_item_path(char *path, int artwork, uint32_t data_kind, int nodir, in
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
artwork_get_item(int id, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
{
|
||||
struct media_file_info *mfi;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Artwork request for item %d\n", id);
|
||||
|
||||
mfi = db_file_fetch_byid(id);
|
||||
if (!mfi)
|
||||
return -1;
|
||||
|
||||
ret = artwork_get_item_path(mfi->path, mfi->artwork, mfi->data_kind, 0, max_w, max_h, format, evbuf);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_DBG, L_ART, "No artwork found for item id %d (%s)\n", id, mfi->fname);
|
||||
|
||||
free_mfi(mfi, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evbuf)
|
||||
/*
|
||||
* Get the artwork image for the given persistentid and the given maximum width/height
|
||||
*
|
||||
* The function first checks if there is a cache entry, if not it will first look for directory artwork files.
|
||||
* If no directory artwork files are found, it looks for individual artwork (embedded images or images from spotify).
|
||||
*
|
||||
* @param persistentid persistent songalbumid or songartistid
|
||||
* @param max_w maximum image width
|
||||
* @param max_h maximum image height
|
||||
* @param evbuf the event buffer that will contain the (scaled) image
|
||||
*/
|
||||
static int
|
||||
artwork_get_group_persistentid(int64_t persistentid, int max_w, int max_h, struct evbuffer *evbuf)
|
||||
{
|
||||
struct query_params qp;
|
||||
struct db_media_file_info dbmfi;
|
||||
char *dir;
|
||||
int got_art;
|
||||
int cached;
|
||||
int format;
|
||||
char filename[PATH_MAX];
|
||||
int ret;
|
||||
int artwork;
|
||||
int got_spotifyitem;
|
||||
uint32_t data_kind;
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Artwork request for group %d\n", id);
|
||||
DPRINTF(E_DBG, L_ART, "Artwork request for group %" PRIi64 "\n", persistentid);
|
||||
|
||||
/* Try directory artwork first */
|
||||
ret = 0;
|
||||
got_spotifyitem = 0;
|
||||
|
||||
/*
|
||||
* First check if the artwork cache has a cached entry for the given persistent id and requested width/height
|
||||
*/
|
||||
ret = cache_artwork_get(persistentid, max_w, max_h, &cached, &format, evbuf);
|
||||
if (ret == 0 && cached)
|
||||
{
|
||||
if (format > 0)
|
||||
{
|
||||
// Artwork found in cache "ret" contains the format of the image
|
||||
DPRINTF(E_DBG, L_ART, "Artwork found in cache for group %" PRIi64 "\n", persistentid);
|
||||
return format;
|
||||
}
|
||||
else if (format == 0)
|
||||
{
|
||||
// Entry found in cache but there is not artwork available
|
||||
DPRINTF(E_DBG, L_ART, "Artwork found in cache but no image available for group %" PRIi64 "\n", persistentid);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Image is not in the artwork cache. Try directory artwork first */
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
|
||||
qp.type = Q_GROUP_DIRS;
|
||||
qp.id = id;
|
||||
qp.persistentid = persistentid;
|
||||
|
||||
ret = db_query_start(&qp);
|
||||
if (ret < 0)
|
||||
@ -1133,12 +1040,12 @@ artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evb
|
||||
if (strncmp(dir, "http://", strlen("http://")) == 0)
|
||||
continue;
|
||||
|
||||
/* If Spotify item don't look for artwork */
|
||||
/* If Spotify item don't look for files artwork */
|
||||
if (strncmp(dir, "spotify:", strlen("spotify:")) == 0)
|
||||
continue;
|
||||
|
||||
got_art = (artwork_get_dir_image(dir, 1, max_w, max_h, format, evbuf) > 0)
|
||||
|| (artwork_get_parentdir_image(dir, 1, max_w, max_h, format, evbuf) > 0);
|
||||
format = artwork_get_dir_image(dir, max_w, max_h, filename, evbuf);
|
||||
got_art = (format > 0);
|
||||
}
|
||||
|
||||
db_query_end(&qp);
|
||||
@ -1146,7 +1053,10 @@ artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evb
|
||||
if (ret < 0)
|
||||
DPRINTF(E_LOG, L_ART, "Error fetching Q_GROUP_DIRS results\n");
|
||||
else if (got_art > 0)
|
||||
return got_art;
|
||||
{
|
||||
cache_artwork_add(persistentid, max_w, max_h, format, filename, evbuf);
|
||||
return format;
|
||||
}
|
||||
|
||||
|
||||
/* Then try individual files */
|
||||
@ -1154,7 +1064,7 @@ artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evb
|
||||
memset(&qp, 0, sizeof(struct query_params));
|
||||
|
||||
qp.type = Q_GROUP_ITEMS;
|
||||
qp.id = id;
|
||||
qp.persistentid = persistentid;
|
||||
|
||||
ret = db_query_start(&qp);
|
||||
if (ret < 0)
|
||||
@ -1170,7 +1080,14 @@ artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evb
|
||||
if ((safe_atoi32(dbmfi.artwork, &artwork) != 0) && (safe_atou32(dbmfi.data_kind, &data_kind) != 0))
|
||||
continue;
|
||||
|
||||
got_art = (artwork_get_item_path(dbmfi.path, artwork, data_kind, 1, max_w, max_h, format, evbuf) > 0);
|
||||
format = artwork_get_item_path(dbmfi.path, artwork, max_w, max_h, evbuf);
|
||||
got_art = (format > 0);
|
||||
|
||||
if (artwork == ARTWORK_SPOTIFY)
|
||||
got_spotifyitem = 1;
|
||||
|
||||
if (got_art)
|
||||
strcpy(filename, dbmfi.path);
|
||||
}
|
||||
|
||||
db_query_end(&qp);
|
||||
@ -1178,9 +1095,103 @@ artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evb
|
||||
if (ret < 0)
|
||||
DPRINTF(E_LOG, L_ART, "Error fetching Q_GROUP_ITEMS results\n");
|
||||
else if (got_art > 0)
|
||||
return got_art;
|
||||
{
|
||||
cache_artwork_add(persistentid, max_w, max_h, format, filename, evbuf);
|
||||
return format;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "No artwork found for group %d\n", id);
|
||||
/* Add cache entry for no artwork available */
|
||||
if (!got_spotifyitem)
|
||||
cache_artwork_add(persistentid, max_w, max_h, 0, "", evbuf);
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "No artwork found for group %" PRIi64 "\n", persistentid);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
artwork_get_item(int id, int max_w, int max_h, struct evbuffer *evbuf)
|
||||
{
|
||||
struct media_file_info *mfi;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Artwork request for item %d\n", id);
|
||||
|
||||
mfi = db_file_fetch_byid(id);
|
||||
if (!mfi)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Load artwork image for the persistent id
|
||||
*/
|
||||
ret = artwork_get_group_persistentid(mfi->songalbumid, max_w, max_h, evbuf);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_DBG, L_ART, "No artwork found for item id %d (%s)\n", id, mfi->fname);
|
||||
|
||||
free_mfi(mfi, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
artwork_get_group(int id, int max_w, int max_h, struct evbuffer *evbuf)
|
||||
{
|
||||
int64_t persistentid;
|
||||
int ret;
|
||||
|
||||
DPRINTF(E_DBG, L_ART, "Artwork request for group %d\n", id);
|
||||
|
||||
/*
|
||||
* Get the persistent id for the given group id
|
||||
*/
|
||||
ret = db_group_persistentid_byid(id, &persistentid);
|
||||
if (ret < 0) {
|
||||
DPRINTF(E_LOG, L_ART, "Error fetching persistent id for group id %d\n", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load artwork image for the persistent id
|
||||
*/
|
||||
ret = artwork_get_group_persistentid(persistentid, max_w, max_h, evbuf);
|
||||
if (ret < 0)
|
||||
DPRINTF(E_DBG, L_ART, "No artwork found for group id %d\n", id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Checks if the file is an artwork file */
|
||||
int
|
||||
artwork_file_is_artwork(const char *filename)
|
||||
{
|
||||
cfg_t *lib;
|
||||
int n;
|
||||
int i;
|
||||
int j;
|
||||
int ret;
|
||||
char artwork[PATH_MAX];
|
||||
|
||||
lib = cfg_getsec(cfg, "library");
|
||||
n = cfg_size(lib, "artwork_basenames");
|
||||
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
for (j = 0; j < (sizeof(cover_extension) / sizeof(cover_extension[0])); j++)
|
||||
{
|
||||
ret = snprintf(artwork, sizeof(artwork), "%s.%s", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]);
|
||||
if ((ret < 0) || (ret >= sizeof(artwork)))
|
||||
{
|
||||
DPRINTF(E_INFO, L_ART, "Artwork path exceeds PATH_MAX (%s.%s)\n", cfg_getnstr(lib, "artwork_basenames", i), cover_extension[j]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(artwork, filename) == 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (j < (sizeof(cover_extension) / sizeof(cover_extension[0])))
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2,9 +2,6 @@
|
||||
#ifndef __ARTWORK_H__
|
||||
#define __ARTWORK_H__
|
||||
|
||||
#define ART_CAN_PNG (1 << 0)
|
||||
#define ART_CAN_JPEG (1 << 1)
|
||||
|
||||
#define ART_FMT_PNG 1
|
||||
#define ART_FMT_JPEG 2
|
||||
|
||||
@ -16,10 +13,14 @@
|
||||
|
||||
/* Get artwork for individual track */
|
||||
int
|
||||
artwork_get_item(int id, int max_w, int max_h, int format, struct evbuffer *evbuf);
|
||||
artwork_get_item(int id, int max_w, int max_h, struct evbuffer *evbuf);
|
||||
|
||||
/* Get artwork for album or artist */
|
||||
int
|
||||
artwork_get_group(int id, int max_w, int max_h, int format, struct evbuffer *evbuf);
|
||||
artwork_get_group(int id, int max_w, int max_h, struct evbuffer *evbuf);
|
||||
|
||||
/* Checks if the file is an artwork file */
|
||||
int
|
||||
artwork_file_is_artwork(const char *filename);
|
||||
|
||||
#endif /* !__ARTWORK_H__ */
|
||||
|
1692
src/cache.c
Normal file
1692
src/cache.c
Normal file
File diff suppressed because it is too large
Load Diff
54
src/cache.h
Normal file
54
src/cache.h
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
#ifndef __CACHE_H__
|
||||
#define __CACHE_H__
|
||||
|
||||
#ifdef HAVE_LIBEVENT2
|
||||
# include <event2/event.h>
|
||||
# include <event2/buffer.h>
|
||||
#else
|
||||
# include <event.h>
|
||||
#endif
|
||||
|
||||
|
||||
/* ---------------------------- DAAP cache API --------------------------- */
|
||||
|
||||
void
|
||||
cache_daap_trigger(void);
|
||||
|
||||
int
|
||||
cache_daap_get(const char *query, struct evbuffer *evbuf);
|
||||
|
||||
void
|
||||
cache_daap_add(const char *query, const char *ua, int msec);
|
||||
|
||||
int
|
||||
cache_daap_threshold(void);
|
||||
|
||||
|
||||
/* ---------------------------- Artwork cache API --------------------------- */
|
||||
|
||||
int
|
||||
cache_artwork_ping(char *path, time_t mtime);
|
||||
|
||||
int
|
||||
cache_artwork_delete_by_path(char *path);
|
||||
|
||||
int
|
||||
cache_artwork_purge_cruft(time_t ref);
|
||||
|
||||
int
|
||||
cache_artwork_add(int64_t persistentid, int max_w, int max_h, int format, char *filename, struct evbuffer *evbuf);
|
||||
|
||||
int
|
||||
cache_artwork_get(int64_t persistentid, int max_w, int max_h, int *cached, int *format, struct evbuffer *evbuf);
|
||||
|
||||
|
||||
/* ---------------------------- Cache API --------------------------- */
|
||||
|
||||
int
|
||||
cache_init(void);
|
||||
|
||||
void
|
||||
cache_deinit(void);
|
||||
|
||||
#endif /* !__CACHE_H__ */
|
@ -53,8 +53,8 @@ static cfg_opt_t sec_general[] =
|
||||
CFG_INT("db_pragma_synchronous", -1, CFGF_NONE),
|
||||
CFG_INT_CB("loglevel", E_LOG, CFGF_NONE, &cb_loglevel),
|
||||
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_STR("cache_path", STATEDIR "/cache/" PACKAGE "/cache.db", CFGF_NONE),
|
||||
CFG_INT("cache_daap_threshold", 1000, CFGF_NONE),
|
||||
CFG_END()
|
||||
};
|
||||
|
||||
@ -114,6 +114,16 @@ static cfg_opt_t sec_spotify[] =
|
||||
CFG_END()
|
||||
};
|
||||
|
||||
/* SQLite section structure */
|
||||
static cfg_opt_t sec_sqlite[] =
|
||||
{
|
||||
CFG_INT("pragma_cache_size_library", -1, CFGF_NONE),
|
||||
CFG_INT("pragma_cache_size_cache", -1, CFGF_NONE),
|
||||
CFG_STR("pragma_journal_mode", NULL, CFGF_NONE),
|
||||
CFG_INT("pragma_synchronous", -1, CFGF_NONE),
|
||||
CFG_END()
|
||||
};
|
||||
|
||||
/* Config file structure */
|
||||
static cfg_opt_t toplvl_cfg[] =
|
||||
{
|
||||
@ -122,6 +132,7 @@ static cfg_opt_t toplvl_cfg[] =
|
||||
CFG_SEC("audio", sec_audio, CFGF_NONE),
|
||||
CFG_SEC("airplay", sec_airplay, CFGF_MULTI | CFGF_TITLE),
|
||||
CFG_SEC("spotify", sec_spotify, CFGF_NONE),
|
||||
CFG_SEC("sqlite", sec_sqlite, CFGF_NONE),
|
||||
CFG_END()
|
||||
};
|
||||
|
||||
|
895
src/daap_cache.c
895
src/daap_cache.c
@ -1,895 +0,0 @@
|
||||
/*
|
||||
* 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 60 seconds before rebuilding daapcache
|
||||
static struct timeval g_wait = { 60, 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]);
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
|
||||
#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__ */
|
123
src/db.c
123
src/db.c
@ -40,7 +40,7 @@
|
||||
|
||||
#include "conffile.h"
|
||||
#include "logger.h"
|
||||
#include "daap_cache.h"
|
||||
#include "cache.h"
|
||||
#include "misc.h"
|
||||
#include "db.h"
|
||||
|
||||
@ -291,6 +291,9 @@ db_smartpl_count_items(const char *smartpl_query);
|
||||
struct playlist_info *
|
||||
db_pl_fetch_byid(int id);
|
||||
|
||||
static enum group_type
|
||||
db_group_type_bypersistentid(int64_t persistentid);
|
||||
|
||||
|
||||
char *
|
||||
db_escape_string(const char *str)
|
||||
@ -1259,22 +1262,22 @@ db_build_query_group_items(struct query_params *qp, char **q)
|
||||
char *count;
|
||||
enum group_type gt;
|
||||
|
||||
gt = db_group_type_byid(qp->id);
|
||||
gt = db_group_type_bypersistentid(qp->persistentid);
|
||||
|
||||
switch (gt)
|
||||
{
|
||||
case G_ALBUMS:
|
||||
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f JOIN groups g ON f.songalbumid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f"
|
||||
" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
case G_ARTISTS:
|
||||
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f JOIN groups g ON f.songartistid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
count = sqlite3_mprintf("SELECT COUNT(*) FROM files f"
|
||||
" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(E_LOG, L_DB, "Unsupported group type %d for group id %d\n", gt, qp->id);
|
||||
DPRINTF(E_LOG, L_DB, "Unsupported group type %d for group id %" PRIi64 "\n", gt, qp->persistentid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -1294,13 +1297,13 @@ db_build_query_group_items(struct query_params *qp, char **q)
|
||||
switch (gt)
|
||||
{
|
||||
case G_ALBUMS:
|
||||
query = sqlite3_mprintf("SELECT f.* FROM files f JOIN groups g ON f.songalbumid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
query = sqlite3_mprintf("SELECT f.* FROM files f"
|
||||
" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
case G_ARTISTS:
|
||||
query = sqlite3_mprintf("SELECT f.* FROM files f JOIN groups g ON f.songartistid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
query = sqlite3_mprintf("SELECT f.* FROM files f"
|
||||
" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1325,24 +1328,24 @@ db_build_query_group_dirs(struct query_params *qp, char **q)
|
||||
char *count;
|
||||
enum group_type gt;
|
||||
|
||||
gt = db_group_type_byid(qp->id);
|
||||
gt = db_group_type_bypersistentid(qp->persistentid);
|
||||
|
||||
switch (gt)
|
||||
{
|
||||
case G_ALBUMS:
|
||||
count = sqlite3_mprintf("SELECT COUNT(DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1)))"
|
||||
" FROM files f JOIN groups g ON f.songalbumid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
" FROM files f"
|
||||
" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
case G_ARTISTS:
|
||||
count = sqlite3_mprintf("SELECT COUNT(DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1)))"
|
||||
" FROM files f JOIN groups g ON f.songartistid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
" FROM files f"
|
||||
" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
default:
|
||||
DPRINTF(E_LOG, L_DB, "Unsupported group type %d for group id %d\n", gt, qp->id);
|
||||
DPRINTF(E_LOG, L_DB, "Unsupported group type %d for group id %" PRIi64 "\n", gt, qp->persistentid);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -1363,14 +1366,14 @@ db_build_query_group_dirs(struct query_params *qp, char **q)
|
||||
{
|
||||
case G_ALBUMS:
|
||||
query = sqlite3_mprintf("SELECT DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1))"
|
||||
" FROM files f JOIN groups g ON f.songalbumid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
" FROM files f"
|
||||
" WHERE f.songalbumid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
case G_ARTISTS:
|
||||
query = sqlite3_mprintf("SELECT DISTINCT(SUBSTR(f.path, 1, LENGTH(f.path) - LENGTH(f.fname) - 1))"
|
||||
" FROM files f JOIN groups g ON f.songartistid = g.persistentid"
|
||||
" WHERE g.id = %d AND f.disabled = 0;", qp->id);
|
||||
" FROM files f"
|
||||
" WHERE f.songartistid = %" PRIi64 " AND f.disabled = 0;", qp->persistentid);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -1581,7 +1584,7 @@ db_query_run(char *query, int free, int cache_update)
|
||||
sqlite3_free(query);
|
||||
|
||||
if (cache_update)
|
||||
daapcache_trigger();
|
||||
cache_daap_trigger();
|
||||
|
||||
return ((ret != SQLITE_OK) ? -1 : 0);
|
||||
}
|
||||
@ -2430,7 +2433,7 @@ db_file_add(struct media_file_info *mfi)
|
||||
|
||||
sqlite3_free(query);
|
||||
|
||||
daapcache_trigger();
|
||||
cache_daap_trigger();
|
||||
|
||||
return 0;
|
||||
|
||||
@ -2506,7 +2509,7 @@ db_file_update(struct media_file_info *mfi)
|
||||
|
||||
sqlite3_free(query);
|
||||
|
||||
daapcache_trigger();
|
||||
cache_daap_trigger();
|
||||
|
||||
return 0;
|
||||
|
||||
@ -3112,15 +3115,15 @@ db_groups_clear(void)
|
||||
return db_query_run("DELETE FROM groups;", 0, 1);
|
||||
}
|
||||
|
||||
enum group_type
|
||||
db_group_type_byid(int id)
|
||||
static enum group_type
|
||||
db_group_type_bypersistentid(int64_t persistentid)
|
||||
{
|
||||
#define Q_TMPL "SELECT g.type FROM groups g WHERE g.id = '%d';"
|
||||
#define Q_TMPL "SELECT g.type FROM groups g WHERE g.persistentid = '%" PRIi64 "';"
|
||||
char *query;
|
||||
sqlite3_stmt *stmt;
|
||||
int ret;
|
||||
|
||||
query = sqlite3_mprintf(Q_TMPL, id);
|
||||
query = sqlite3_mprintf(Q_TMPL, persistentid);
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||
@ -3167,6 +3170,62 @@ db_group_type_byid(int id)
|
||||
#undef Q_TMPL
|
||||
}
|
||||
|
||||
int
|
||||
db_group_persistentid_byid(int id, int64_t *persistentid)
|
||||
{
|
||||
#define Q_TMPL "SELECT g.persistentid FROM groups g WHERE g.id = %d;"
|
||||
char *query;
|
||||
sqlite3_stmt *stmt;
|
||||
int ret;
|
||||
|
||||
query = sqlite3_mprintf(Q_TMPL, id);
|
||||
if (!query)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Out of memory for query string\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
DPRINTF(E_DBG, L_DB, "Running query '%s'\n", query);
|
||||
|
||||
ret = db_blocking_prepare_v2(query, -1, &stmt, NULL);
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
DPRINTF(E_LOG, L_DB, "Could not prepare statement: %s\n", sqlite3_errmsg(hdl));
|
||||
|
||||
sqlite3_free(query);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = db_blocking_step(stmt);
|
||||
if (ret != SQLITE_ROW)
|
||||
{
|
||||
if (ret == SQLITE_DONE)
|
||||
DPRINTF(E_DBG, L_DB, "No results\n");
|
||||
else
|
||||
DPRINTF(E_LOG, L_DB, "Could not step: %s\n", sqlite3_errmsg(hdl));
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_free(query);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*persistentid = sqlite3_column_int64(stmt, 0);
|
||||
|
||||
#ifdef DB_PROFILE
|
||||
while (db_blocking_step(stmt) == SQLITE_ROW)
|
||||
; /* EMPTY */
|
||||
#endif
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
sqlite3_free(query);
|
||||
|
||||
return 0;
|
||||
|
||||
#undef Q_TMPL
|
||||
}
|
||||
|
||||
|
||||
/* Remotes */
|
||||
static int
|
||||
db_pairing_delete_byremote(char *remote_id)
|
||||
@ -4052,6 +4111,8 @@ db_pragma_set_synchronous(int synchronous)
|
||||
#undef Q_TMPL
|
||||
}
|
||||
|
||||
|
||||
|
||||
int
|
||||
db_perthread_init(void)
|
||||
{
|
||||
@ -4108,7 +4169,7 @@ db_perthread_init(void)
|
||||
sqlite3_profile(hdl, db_xprofile, NULL);
|
||||
#endif
|
||||
|
||||
cache_size = cfg_getint(cfg_getsec(cfg, "general"), "db_pragma_cache_size");
|
||||
cache_size = cfg_getint(cfg_getsec(cfg, "sqlite"), "pragma_cache_size_library");
|
||||
if (cache_size > -1)
|
||||
{
|
||||
db_pragma_set_cache_size(cache_size);
|
||||
@ -4116,14 +4177,14 @@ db_perthread_init(void)
|
||||
DPRINTF(E_DBG, L_DB, "Database cache size in pages: %d\n", cache_size);
|
||||
}
|
||||
|
||||
journal_mode = cfg_getstr(cfg_getsec(cfg, "general"), "db_pragma_journal_mode");
|
||||
journal_mode = cfg_getstr(cfg_getsec(cfg, "sqlite"), "pragma_journal_mode");
|
||||
if (journal_mode)
|
||||
{
|
||||
journal_mode = db_pragma_set_journal_mode(journal_mode);
|
||||
DPRINTF(E_DBG, L_DB, "Database journal mode: %s\n", journal_mode);
|
||||
}
|
||||
|
||||
synchronous = cfg_getint(cfg_getsec(cfg, "general"), "db_pragma_synchronous");
|
||||
synchronous = cfg_getint(cfg_getsec(cfg, "sqlite"), "pragma_synchronous");
|
||||
if (synchronous > -1)
|
||||
{
|
||||
db_pragma_set_synchronous(synchronous);
|
||||
|
5
src/db.h
5
src/db.h
@ -54,6 +54,7 @@ struct query_params {
|
||||
enum index_type idx_type;
|
||||
enum sort_type sort;
|
||||
int id;
|
||||
int64_t persistentid;
|
||||
int offset;
|
||||
int limit;
|
||||
|
||||
@ -461,8 +462,8 @@ db_pl_enable_bycookie(uint32_t cookie, char *path);
|
||||
int
|
||||
db_groups_clear(void);
|
||||
|
||||
enum group_type
|
||||
db_group_type_byid(int id);
|
||||
int
|
||||
db_group_persistentid_byid(int id, int64_t *persistentid);
|
||||
|
||||
/* Remotes */
|
||||
int
|
||||
|
@ -62,6 +62,8 @@
|
||||
#include "misc.h"
|
||||
#include "remote_pairing.h"
|
||||
#include "player.h"
|
||||
#include "cache.h"
|
||||
#include "artwork.h"
|
||||
|
||||
#ifdef LASTFM
|
||||
# include "lastfm.h"
|
||||
@ -208,7 +210,7 @@ file_type_get(const char *path) {
|
||||
if ((strcasecmp(ext, ".m3u") == 0) || (strcasecmp(ext, ".pls") == 0))
|
||||
return FILE_PLAYLIST;
|
||||
|
||||
if ((strcasecmp(ext, ".png") == 0) || (strcasecmp(ext, ".jpg") == 0))
|
||||
if (artwork_file_is_artwork(filename))
|
||||
return FILE_ARTWORK;
|
||||
|
||||
#ifdef ITUNES
|
||||
@ -712,6 +714,9 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
|
||||
case FILE_REGULAR:
|
||||
filescanner_process_media(file, mtime, size, type, NULL);
|
||||
|
||||
cache_artwork_ping(file, mtime);
|
||||
// TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork
|
||||
|
||||
counter++;
|
||||
|
||||
/* When in bulk mode, split transaction in pieces of 200 */
|
||||
@ -731,6 +736,14 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags)
|
||||
process_playlist(file, mtime);
|
||||
break;
|
||||
|
||||
case FILE_ARTWORK:
|
||||
DPRINTF(E_DBG, L_SCAN, "Artwork file: %s\n", file);
|
||||
cache_artwork_ping(file, mtime);
|
||||
|
||||
// TODO [artworkcache] If entry in artwork cache exists for no artwork available for a album with files in the same directory, delete the entry
|
||||
|
||||
break;
|
||||
|
||||
case FILE_CTRL_REMOTE:
|
||||
remote_pairing_read_pin(file);
|
||||
break;
|
||||
@ -1066,6 +1079,7 @@ bulk_scan(int flags)
|
||||
|
||||
DPRINTF(E_DBG, L_SCAN, "Purging old database content\n");
|
||||
db_purge_cruft(start);
|
||||
cache_artwork_purge_cruft(start);
|
||||
|
||||
DPRINTF(E_LOG, L_SCAN, "Bulk library scan completed in %.f sec\n", difftime(end, start));
|
||||
|
||||
@ -1147,6 +1161,7 @@ filescanner(void *arg)
|
||||
DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n");
|
||||
|
||||
db_perthread_deinit();
|
||||
//artworkcache_perthread_deinit();
|
||||
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
@ -1335,6 +1350,7 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
|
||||
|
||||
db_file_delete_bypath(path);
|
||||
db_pl_delete_bypath(path);
|
||||
cache_artwork_delete_by_path(path);
|
||||
}
|
||||
|
||||
if (ie->mask & IN_MOVED_FROM)
|
||||
@ -1357,7 +1373,8 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
|
||||
{
|
||||
DPRINTF(E_LOG, L_SCAN, "File access to '%s' failed: %s\n", path, strerror(errno));
|
||||
|
||||
db_file_delete_bypath(path);;
|
||||
db_file_delete_bypath(path);
|
||||
cache_artwork_delete_by_path(path);
|
||||
}
|
||||
else if ((file_type_get(path) == FILE_REGULAR) && (db_file_id_bypath(path) <= 0)) // TODO Playlists
|
||||
{
|
||||
|
@ -52,7 +52,7 @@
|
||||
#include "httpd_daap.h"
|
||||
#include "daap_query.h"
|
||||
#include "dmap_common.h"
|
||||
#include "daap_cache.h"
|
||||
#include "cache.h"
|
||||
|
||||
#ifdef HAVE_LIBEVENT2
|
||||
# include <event2/http_struct.h>
|
||||
@ -2255,9 +2255,9 @@ daap_reply_extra_data(struct evhttp_request *req, struct evbuffer *evbuf, char *
|
||||
}
|
||||
|
||||
if (strcmp(uri[2], "groups") == 0)
|
||||
ret = artwork_get_group(id, max_w, max_h, ART_CAN_PNG | ART_CAN_JPEG, evbuf);
|
||||
ret = artwork_get_group(id, max_w, max_h, evbuf);
|
||||
else if (strcmp(uri[2], "items") == 0)
|
||||
ret = artwork_get_item(id, max_w, max_h, ART_CAN_PNG | ART_CAN_JPEG, evbuf);
|
||||
ret = artwork_get_item(id, max_w, max_h, evbuf);
|
||||
|
||||
switch (ret)
|
||||
{
|
||||
@ -2668,19 +2668,6 @@ daap_request(struct evhttp_request *req)
|
||||
*/
|
||||
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();
|
||||
if (!evbuf)
|
||||
{
|
||||
@ -2693,6 +2680,19 @@ daap_request(struct evhttp_request *req)
|
||||
return;
|
||||
}
|
||||
|
||||
// Try the cache
|
||||
ret = cache_daap_get(full_uri, evbuf);
|
||||
if (ret == 0)
|
||||
{
|
||||
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
|
||||
evhttp_parse_query(full_uri, &query);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &start);
|
||||
@ -2705,8 +2705,8 @@ daap_request(struct evhttp_request *req)
|
||||
|
||||
DPRINTF(E_DBG, L_DB, "DAAP request handled in %d milliseconds\n", msec);
|
||||
|
||||
if (msec > daapcache_threshold())
|
||||
daapcache_add(full_uri, ua, msec);
|
||||
if (msec > cache_daap_threshold())
|
||||
cache_daap_add(full_uri, ua, msec);
|
||||
|
||||
evhttp_clear_headers(&query);
|
||||
evbuffer_free(evbuf);
|
||||
|
@ -1802,7 +1802,7 @@ dacp_reply_nowplayingartwork(struct evhttp_request *req, struct evbuffer *evbuf,
|
||||
if (ret < 0)
|
||||
goto no_artwork;
|
||||
|
||||
ret = artwork_get_item(id, max_w, max_h, ART_CAN_PNG | ART_CAN_JPEG, evbuf);
|
||||
ret = artwork_get_item(id, max_w, max_h, evbuf);
|
||||
switch (ret)
|
||||
{
|
||||
case ART_FMT_PNG:
|
||||
|
@ -43,7 +43,7 @@ static int threshold;
|
||||
static int console;
|
||||
static char *logfilename;
|
||||
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", "dcache" };
|
||||
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", "cache" };
|
||||
|
||||
|
||||
static int
|
||||
|
@ -28,7 +28,7 @@
|
||||
#define L_DBPERF 19
|
||||
#define L_SPOTIFY 20
|
||||
#define L_LASTFM 21
|
||||
#define L_DCACHE 22
|
||||
#define L_CACHE 22
|
||||
|
||||
#define N_LOGDOMAINS 23
|
||||
|
||||
|
12
src/main.c
12
src/main.c
@ -58,7 +58,7 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
|
||||
#include "db.h"
|
||||
#include "logger.h"
|
||||
#include "misc.h"
|
||||
#include "daap_cache.h"
|
||||
#include "cache.h"
|
||||
#include "filescanner.h"
|
||||
#include "httpd.h"
|
||||
#include "mdns.h"
|
||||
@ -675,11 +675,11 @@ main(int argc, char **argv)
|
||||
goto db_fail;
|
||||
}
|
||||
|
||||
/* Spawn DAAP cache thread */
|
||||
ret = daapcache_init();
|
||||
/* Spawn cache thread */
|
||||
ret = cache_init();
|
||||
if (ret != 0)
|
||||
{
|
||||
DPRINTF(E_FATAL, L_MAIN, "DAAP cache thread failed to start\n");
|
||||
DPRINTF(E_FATAL, L_MAIN, "Cache thread failed to start\n");
|
||||
|
||||
ret = EXIT_FAILURE;
|
||||
goto cache_fail;
|
||||
@ -824,8 +824,8 @@ main(int argc, char **argv)
|
||||
filescanner_deinit();
|
||||
|
||||
filescanner_fail:
|
||||
DPRINTF(E_LOG, L_MAIN, "DAAP cache deinit\n");
|
||||
daapcache_deinit();
|
||||
DPRINTF(E_LOG, L_MAIN, "Cache deinit\n");
|
||||
cache_deinit();
|
||||
|
||||
cache_fail:
|
||||
DPRINTF(E_LOG, L_MAIN, "Database deinit\n");
|
||||
|
@ -851,7 +851,7 @@ raop_metadata_prepare(int id, uint64_t rtptime)
|
||||
goto skip_artwork;
|
||||
}
|
||||
|
||||
ret = artwork_get_item(id, 600, 600, ART_CAN_PNG | ART_CAN_JPEG, rmd->artwork);
|
||||
ret = artwork_get_item(id, 600, 600, rmd->artwork);
|
||||
if (ret < 0)
|
||||
{
|
||||
DPRINTF(E_INFO, L_RAOP, "Failed to retrieve artwork for '%s' (%d); no artwork will be sent\n", dbmfi.title, id);
|
||||
|
Loading…
Reference in New Issue
Block a user