[library/filescanner/spotify] Add library source abstraction and new

"library" thread

- Implement filescanner as a library_source
- Add library source implementation for spotify
- Move process_media_info to library with small adjustments:
  - Remove dependency to F_SCAN_TYPE defines
  - Pass data_kind as parameter
  - Pass media_kind and compilation as parameter if a source forces a
specific value for these
- Move declaration of scan_ffmpeg to library; add
library_add_playlist_info to library
This commit is contained in:
chme 2016-12-31 07:28:18 +01:00
parent ca6836f638
commit 56ce3f9cba
14 changed files with 1018 additions and 674 deletions

View File

@ -82,6 +82,7 @@ forked_daapd_SOURCES = main.c \
filescanner.c filescanner.h \ filescanner.c filescanner.h \
filescanner_ffmpeg.c filescanner_playlist.c \ filescanner_ffmpeg.c filescanner_playlist.c \
filescanner_smartpl.c $(ITUNES_SRC) \ filescanner_smartpl.c $(ITUNES_SRC) \
library.c library.h \
mdns_avahi.c mdns.h \ mdns_avahi.c mdns.h \
remote_pairing.c remote_pairing.h \ remote_pairing.c remote_pairing.h \
avio_evbuffer.c avio_evbuffer.h \ avio_evbuffer.c avio_evbuffer.h \

View File

@ -2084,7 +2084,7 @@ db_file_id_by_virtualpath_match(char *path)
} }
void void
db_file_stamp_bypath(char *path, time_t *stamp, int *id) db_file_stamp_bypath(const char *path, time_t *stamp, int *id)
{ {
#define Q_TMPL "SELECT f.id, f.db_timestamp FROM files f WHERE f.path = '%q';" #define Q_TMPL "SELECT f.id, f.db_timestamp FROM files f WHERE f.path = '%q';"
char *query; char *query;
@ -2847,7 +2847,7 @@ db_pl_fetch_byquery(char *query)
} }
struct playlist_info * struct playlist_info *
db_pl_fetch_bypath(char *path) db_pl_fetch_bypath(const char *path)
{ {
#define Q_TMPL "SELECT p.* FROM playlists p WHERE p.path = '%q';" #define Q_TMPL "SELECT p.* FROM playlists p WHERE p.path = '%q';"
struct playlist_info *pli; struct playlist_info *pli;
@ -3011,7 +3011,7 @@ db_pl_add(struct playlist_info *pli, int *id)
} }
int int
db_pl_add_item_bypath(int plid, char *path) db_pl_add_item_bypath(int plid, const char *path)
{ {
#define Q_TMPL "INSERT INTO playlistitems (playlistid, filepath) VALUES (%d, '%q');" #define Q_TMPL "INSERT INTO playlistitems (playlistid, filepath) VALUES (%d, '%q');"
char *query; char *query;

View File

@ -527,7 +527,7 @@ int
db_file_id_by_virtualpath_match(char *path); db_file_id_by_virtualpath_match(char *path);
void void
db_file_stamp_bypath(char *path, time_t *stamp, int *id); db_file_stamp_bypath(const char *path, time_t *stamp, int *id);
struct media_file_info * struct media_file_info *
db_file_fetch_byid(int id); db_file_fetch_byid(int id);
@ -570,7 +570,7 @@ void
db_pl_ping_bymatch(char *path, int isdir); db_pl_ping_bymatch(char *path, int isdir);
struct playlist_info * struct playlist_info *
db_pl_fetch_bypath(char *path); db_pl_fetch_bypath(const char *path);
struct playlist_info * struct playlist_info *
db_pl_fetch_byvirtualpath(char *virtual_path); db_pl_fetch_byvirtualpath(char *virtual_path);
@ -582,7 +582,7 @@ int
db_pl_add(struct playlist_info *pli, int *id); db_pl_add(struct playlist_info *pli, int *id);
int int
db_pl_add_item_bypath(int plid, char *path); db_pl_add_item_bypath(int plid, const char *path);
int int
db_pl_add_item_byid(int plid, int fileid); db_pl_add_item_byid(int plid, int fileid);

View File

@ -62,6 +62,7 @@
#include "cache.h" #include "cache.h"
#include "artwork.h" #include "artwork.h"
#include "commands.h" #include "commands.h"
#include "library.h"
#ifdef LASTFM #ifdef LASTFM
# include "lastfm.h" # include "lastfm.h"
@ -104,14 +105,14 @@ struct stacked_dir {
struct stacked_dir *next; struct stacked_dir *next;
}; };
static int scan_exit;
static int inofd; static int inofd;
static struct event_base *evbase_scan;
static struct event *inoev; static struct event *inoev;
static pthread_t tid_scan;
static struct deferred_pl *playlists; static struct deferred_pl *playlists;
static struct stacked_dir *dirstack; static struct stacked_dir *dirstack;
static struct commands_base *cmdbase;
/* From library.c */
extern struct event_base *evbase_lib;
#ifndef __linux__ #ifndef __linux__
struct deferred_file struct deferred_file
@ -131,9 +132,6 @@ static struct event *deferred_inoev;
/* Count of files scanned during a bulk scan */ /* Count of files scanned during a bulk scan */
static int counter; static int counter;
/* Flag for scan in progress */
static int scanning;
/* When copying into the lib (eg. if a file is moved to the lib by copying into /* When copying into the lib (eg. if a file is moved to the lib by copying into
* a Samba network share) inotify might give us IN_CREATE -> n x IN_ATTRIB -> * a Samba network share) inotify might give us IN_CREATE -> n x IN_ATTRIB ->
* IN_CLOSE_WRITE, but we don't want to do any scanning before the * IN_CLOSE_WRITE, but we don't want to do any scanning before the
@ -151,10 +149,12 @@ static int
inofd_event_set(void); inofd_event_set(void);
static void static void
inofd_event_unset(void); inofd_event_unset(void);
static enum command_state static int
filescanner_initscan(void *arg, int *retval); filescanner_initscan();
static enum command_state static int
filescanner_fullrescan(void *arg, int *retval); filescanner_rescan();
static int
filescanner_fullrescan();
static int static int
@ -323,416 +323,6 @@ file_type_get(const char *path) {
return FILE_REGULAR; return FILE_REGULAR;
} }
static void
sort_tag_create(char **sort_tag, char *src_tag)
{
const uint8_t *i_ptr;
const uint8_t *n_ptr;
const uint8_t *number;
uint8_t out[1024];
uint8_t *o_ptr;
int append_number;
ucs4_t puc;
int numlen;
size_t len;
int charlen;
/* Note: include terminating NUL in string length for u8_normalize */
if (*sort_tag)
{
DPRINTF(E_DBG, L_SCAN, "Existing sort tag will be normalized: %s\n", *sort_tag);
o_ptr = u8_normalize(UNINORM_NFD, (uint8_t *)*sort_tag, strlen(*sort_tag) + 1, NULL, &len);
free(*sort_tag);
*sort_tag = (char *)o_ptr;
return;
}
if (!src_tag || ((len = strlen(src_tag)) == 0))
{
*sort_tag = NULL;
return;
}
// Set input pointer past article if present
if ((strncasecmp(src_tag, "a ", 2) == 0) && (len > 2))
i_ptr = (uint8_t *)(src_tag + 2);
else if ((strncasecmp(src_tag, "an ", 3) == 0) && (len > 3))
i_ptr = (uint8_t *)(src_tag + 3);
else if ((strncasecmp(src_tag, "the ", 4) == 0) && (len > 4))
i_ptr = (uint8_t *)(src_tag + 4);
else
i_ptr = (uint8_t *)src_tag;
// Poor man's natural sort. Makes sure we sort like this: a1, a2, a10, a11, a21, a111
// We do this by padding zeroes to (short) numbers. As an alternative we could have
// made a proper natural sort algorithm in sqlext.c, but we don't, since we don't
// want any risk of hurting response times
memset(&out, 0, sizeof(out));
o_ptr = (uint8_t *)&out;
number = NULL;
append_number = 0;
do
{
n_ptr = u8_next(&puc, i_ptr);
if (uc_is_digit(puc))
{
if (!number) // We have encountered the beginning of a number
number = i_ptr;
append_number = (n_ptr == NULL); // If last char in string append number now
}
else
{
if (number)
append_number = 1; // A number has ended so time to append it
else
{
charlen = u8_strmblen(i_ptr);
if (charlen >= 0)
o_ptr = u8_stpncpy(o_ptr, i_ptr, charlen); // No numbers in sight, just append char
}
}
// Break if less than 100 bytes remain (prevent buffer overflow)
if (sizeof(out) - u8_strlen(out) < 100)
break;
// Break if number is very large (prevent buffer overflow)
if (number && (i_ptr - number > 50))
break;
if (append_number)
{
numlen = i_ptr - number;
if (numlen < 5) // Max pad width
{
u8_strcpy(o_ptr, (uint8_t *)"00000");
o_ptr += (5 - numlen);
}
o_ptr = u8_stpncpy(o_ptr, number, numlen + u8_strmblen(i_ptr));
number = NULL;
append_number = 0;
}
i_ptr = n_ptr;
}
while (n_ptr);
*sort_tag = (char *)u8_normalize(UNINORM_NFD, (uint8_t *)&out, u8_strlen(out) + 1, NULL, &len);
}
static void
fixup_tags(struct media_file_info *mfi)
{
cfg_t *lib;
size_t len;
char *tag;
char *sep = " - ";
char *ca;
if (mfi->genre && (strlen(mfi->genre) == 0))
{
free(mfi->genre);
mfi->genre = NULL;
}
if (mfi->artist && (strlen(mfi->artist) == 0))
{
free(mfi->artist);
mfi->artist = NULL;
}
if (mfi->title && (strlen(mfi->title) == 0))
{
free(mfi->title);
mfi->title = NULL;
}
/*
* Default to mpeg4 video/audio for unknown file types
* in an attempt to allow streaming of DRM-afflicted files
*/
if (mfi->codectype && strcmp(mfi->codectype, "unkn") == 0)
{
if (mfi->has_video)
{
strcpy(mfi->codectype, "mp4v");
strcpy(mfi->type, "m4v");
}
else
{
strcpy(mfi->codectype, "mp4a");
strcpy(mfi->type, "m4a");
}
}
if (!mfi->artist)
{
if (mfi->orchestra && mfi->conductor)
{
len = strlen(mfi->orchestra) + strlen(sep) + strlen(mfi->conductor);
tag = (char *)malloc(len + 1);
if (tag)
{
sprintf(tag,"%s%s%s", mfi->orchestra, sep, mfi->conductor);
mfi->artist = tag;
}
}
else if (mfi->orchestra)
{
mfi->artist = strdup(mfi->orchestra);
}
else if (mfi->conductor)
{
mfi->artist = strdup(mfi->conductor);
}
}
/* Handle TV shows, try to present prettier metadata */
if (mfi->tv_series_name && strlen(mfi->tv_series_name) != 0)
{
mfi->media_kind = MEDIA_KIND_TVSHOW; /* tv show */
/* Default to artist = series_name */
if (mfi->artist && strlen(mfi->artist) == 0)
{
free(mfi->artist);
mfi->artist = NULL;
}
if (!mfi->artist)
mfi->artist = strdup(mfi->tv_series_name);
/* Default to album = "<series_name>, Season <season_num>" */
if (mfi->album && strlen(mfi->album) == 0)
{
free(mfi->album);
mfi->album = NULL;
}
if (!mfi->album)
{
len = snprintf(NULL, 0, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
mfi->album = (char *)malloc(len + 1);
if (mfi->album)
sprintf(mfi->album, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
}
}
/* Check the 4 top-tags are filled */
if (!mfi->artist)
mfi->artist = strdup("Unknown artist");
if (!mfi->album)
mfi->album = strdup("Unknown album");
if (!mfi->genre)
mfi->genre = strdup("Unknown genre");
if (!mfi->title)
{
/* fname is left untouched by unicode_fixup_mfi() for
* obvious reasons, so ensure it is proper UTF-8
*/
mfi->title = unicode_fixup_string(mfi->fname, "ascii");
if (mfi->title == mfi->fname)
mfi->title = strdup(mfi->fname);
}
/* Ensure sort tags are filled, manipulated and normalized */
sort_tag_create(&mfi->artist_sort, mfi->artist);
sort_tag_create(&mfi->album_sort, mfi->album);
sort_tag_create(&mfi->title_sort, mfi->title);
/* We need to set album_artist according to media type and config */
if (mfi->compilation) /* Compilation */
{
lib = cfg_getsec(cfg, "library");
ca = cfg_getstr(lib, "compilation_artist");
if (ca && mfi->album_artist)
{
free(mfi->album_artist);
mfi->album_artist = strdup(ca);
}
else if (ca && !mfi->album_artist)
{
mfi->album_artist = strdup(ca);
}
else if (!ca && !mfi->album_artist)
{
mfi->album_artist = strdup("");
mfi->album_artist_sort = strdup("");
}
}
else if (mfi->media_kind == MEDIA_KIND_PODCAST) /* Podcast */
{
if (mfi->album_artist)
free(mfi->album_artist);
mfi->album_artist = strdup("");
mfi->album_artist_sort = strdup("");
}
else if (!mfi->album_artist) /* Regular media without album_artist */
{
mfi->album_artist = strdup(mfi->artist);
}
if (!mfi->album_artist_sort && (strcmp(mfi->album_artist, mfi->artist) == 0))
mfi->album_artist_sort = strdup(mfi->artist_sort);
else
sort_tag_create(&mfi->album_artist_sort, mfi->album_artist);
/* Composer is not one of our mandatory tags, so take extra care */
if (mfi->composer_sort || mfi->composer)
sort_tag_create(&mfi->composer_sort, mfi->composer);
}
void
filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi, int dir_id)
{
struct media_file_info *mfi;
char *filename;
time_t stamp;
int id;
char virtual_path[PATH_MAX];
int ret;
filename = strrchr(path, '/');
if ((!filename) || (strlen(filename) == 1))
filename = path;
else
filename++;
db_file_stamp_bypath(path, &stamp, &id);
if (stamp && (stamp >= mtime))
{
db_file_ping(id);
return;
}
if (!external_mfi)
{
mfi = (struct media_file_info*)malloc(sizeof(struct media_file_info));
if (!mfi)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for mfi\n");
return;
}
memset(mfi, 0, sizeof(struct media_file_info));
}
else
mfi = external_mfi;
if (stamp)
mfi->id = id;
mfi->fname = strdup(filename);
if (!mfi->fname)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for fname\n");
goto out;
}
mfi->path = strdup(path);
if (!mfi->path)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for path\n");
goto out;
}
mfi->time_modified = mtime;
mfi->file_size = size;
if (type & F_SCAN_TYPE_COMPILATION)
mfi->compilation = 1;
if (type & F_SCAN_TYPE_PODCAST)
mfi->media_kind = MEDIA_KIND_PODCAST; /* podcast */
if (type & F_SCAN_TYPE_AUDIOBOOK)
mfi->media_kind = MEDIA_KIND_AUDIOBOOK; /* audiobook */
if (type & F_SCAN_TYPE_FILE)
{
mfi->data_kind = DATA_KIND_FILE;
ret = scan_metadata_ffmpeg(path, mfi);
}
else if (type & F_SCAN_TYPE_URL)
{
mfi->data_kind = DATA_KIND_HTTP;
ret = scan_metadata_ffmpeg(path, mfi);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path);
mfi->type = strdup("mp3");
mfi->codectype = strdup("mpeg");
mfi->description = strdup("MPEG audio file");
ret = 1;
}
}
else if (type & F_SCAN_TYPE_SPOTIFY)
{
mfi->data_kind = DATA_KIND_SPOTIFY;
ret = mfi->artist && mfi->album && mfi->title;
}
else if (type & F_SCAN_TYPE_PIPE)
{
mfi->data_kind = DATA_KIND_PIPE;
mfi->type = strdup("wav");
mfi->codectype = strdup("wav");
mfi->description = strdup("PCM16 pipe");
ret = 1;
}
else
{
DPRINTF(E_LOG, L_SCAN, "Unknown scan type for '%s', this error should not occur\n", path);
ret = -1;
}
if (ret < 0)
{
DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for '%s'\n", path);
goto out;
}
if (!mfi->item_kind)
mfi->item_kind = 2; /* music */
if (!mfi->media_kind)
mfi->media_kind = MEDIA_KIND_MUSIC; /* music */
unicode_fixup_mfi(mfi);
fixup_tags(mfi);
if (type & F_SCAN_TYPE_URL)
{
snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi->title);
mfi->virtual_path = strdup(virtual_path);
}
else if (type & F_SCAN_TYPE_SPOTIFY)
{
snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi->album_artist, mfi->album, mfi->title);
mfi->virtual_path = strdup(virtual_path);
}
else
{
snprintf(virtual_path, PATH_MAX, "/file:%s", mfi->path);
mfi->virtual_path = strdup(virtual_path);
}
mfi->directory_id = dir_id;
if (mfi->id == 0)
db_file_add(mfi);
else
db_file_update(mfi);
out:
if (!external_mfi)
free_mfi(mfi, 0);
}
static void static void
process_playlist(char *file, time_t mtime, int dir_id) process_playlist(char *file, time_t mtime, int dir_id)
{ {
@ -795,7 +385,7 @@ process_deferred_playlists(void)
free(pl->path); free(pl->path);
free(pl); free(pl);
if (scan_exit) if (library_is_exiting())
return; return;
} }
} }
@ -804,15 +394,24 @@ process_deferred_playlists(void)
static void static void
process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_id) process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_id)
{ {
int is_bulkscan; bool is_bulkscan;
int ret; bool is_type_compilation;
enum media_kind media_kind;
is_bulkscan = (flags & F_SCAN_BULK); is_bulkscan = (flags & F_SCAN_BULK);
switch (file_type_get(file)) switch (file_type_get(file))
{ {
case FILE_REGULAR: case FILE_REGULAR:
filescanner_process_media(file, mtime, size, type, NULL, dir_id); is_type_compilation = type & F_SCAN_TYPE_COMPILATION;
if (type & F_SCAN_TYPE_AUDIOBOOK)
media_kind = MEDIA_KIND_AUDIOBOOK;
else if (type & F_SCAN_TYPE_PODCAST)
media_kind = MEDIA_KIND_PODCAST;
else
media_kind = 0;
library_process_media(file, mtime, size, DATA_KIND_FILE, media_kind, is_type_compilation, NULL, dir_id);
cache_artwork_ping(file, mtime, !is_bulkscan); cache_artwork_ping(file, mtime, !is_bulkscan);
// TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork // TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork
@ -884,7 +483,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file); DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered, found init-rescan file: %s\n", file);
filescanner_initscan(NULL, &ret); filescanner_rescan();
break; break;
case FILE_CTRL_FULLSCAN: case FILE_CTRL_FULLSCAN:
@ -893,7 +492,7 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file); DPRINTF(E_LOG, L_SCAN, "Full rescan triggered, found full-rescan file: %s\n", file);
filescanner_fullrescan(NULL, &ret); filescanner_fullrescan();
break; break;
default: default:
@ -982,7 +581,7 @@ process_directory(char *path, int parent_id, int flags)
for (;;) for (;;)
{ {
if (scan_exit) if (library_is_exiting())
break; break;
errno = 0; errno = 0;
@ -1139,7 +738,7 @@ process_directories(char *root, int parent_id, int flags)
process_directory(root, parent_id, flags); process_directory(root, parent_id, flags);
if (scan_exit) if (library_is_exiting())
return; return;
while ((dir = pop_dir(&dirstack))) while ((dir = pop_dir(&dirstack)))
@ -1149,7 +748,7 @@ process_directories(char *root, int parent_id, int flags)
free(dir->path); free(dir->path);
free(dir); free(dir);
if (scan_exit) if (library_is_exiting())
return; return;
} }
} }
@ -1168,9 +767,6 @@ bulk_scan(int flags)
int parent_id; int parent_id;
int i; int i;
// Set global flag to avoid queued scan requests
scanning = 1;
start = time(NULL); start = time(NULL);
playlists = NULL; playlists = NULL;
@ -1210,14 +806,14 @@ bulk_scan(int flags)
free(deref); free(deref);
if (scan_exit) if (library_is_exiting())
return; return;
} }
if (!(flags & F_SCAN_FAST) && playlists) if (!(flags & F_SCAN_FAST) && playlists)
process_deferred_playlists(); process_deferred_playlists();
if (scan_exit) if (library_is_exiting())
return; return;
if (dirstack) if (dirstack)
@ -1249,85 +845,6 @@ bulk_scan(int flags)
DPRINTF(E_DBG, L_SCAN, "Running post library scan jobs\n"); DPRINTF(E_DBG, L_SCAN, "Running post library scan jobs\n");
db_hook_post_scan(); db_hook_post_scan();
} }
// Set scan in progress flag to FALSE
scanning = 0;
}
/* Thread: scan */
static void *
filescanner(void *arg)
{
int clear_queue_on_stop_disabled;
int ret;
#ifdef __linux__
struct sched_param param;
/* Lower the priority of the thread so forked-daapd may still respond
* during file scan on low power devices. Param must be 0 for the SCHED_BATCH
* policy.
*/
memset(&param, 0, sizeof(struct sched_param));
ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, &param);
if (ret != 0)
{
DPRINTF(E_LOG, L_SCAN, "Warning: Could not set thread priority to SCHED_BATCH\n");
}
#endif
ret = db_perthread_init();
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Error: DB init failed\n");
pthread_exit(NULL);
}
ret = db_watch_clear();
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Error: could not clear old watches from DB\n");
pthread_exit(NULL);
}
// Only clear the queue if enabled (default) in config
clear_queue_on_stop_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable");
if (!clear_queue_on_stop_disabled)
{
ret = db_queue_clear();
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Error: could not clear queue from DB\n");
pthread_exit(NULL);
}
}
if (cfg_getbool(cfg_getsec(cfg, "library"), "filescan_disable"))
bulk_scan(F_SCAN_BULK | F_SCAN_FAST);
else
bulk_scan(F_SCAN_BULK);
if (!scan_exit)
{
#ifdef HAVE_SPOTIFY_H
spotify_login(NULL);
#endif
/* Enable inotify */
event_add(inoev, NULL);
event_base_dispatch(evbase_scan);
}
if (!scan_exit)
DPRINTF(E_FATAL, L_SCAN, "Scan event loop terminated ahead of time!\n");
db_perthread_deinit();
pthread_exit(NULL);
} }
static int static int
@ -1904,10 +1421,10 @@ inofd_event_set(void)
return -1; return -1;
} }
inoev = event_new(evbase_scan, inofd, EV_READ, inotify_cb, NULL); inoev = event_new(evbase_lib, inofd, EV_READ, inotify_cb, NULL);
#ifndef __linux__ #ifndef __linux__
deferred_inoev = evtimer_new(evbase_scan, inotify_deferred_cb, NULL); deferred_inoev = evtimer_new(evbase_lib, inotify_deferred_cb, NULL);
if (!deferred_inoev) if (!deferred_inoev)
{ {
DPRINTF(E_LOG, L_SCAN, "Could not create deferred inotify event\n"); DPRINTF(E_LOG, L_SCAN, "Could not create deferred inotify event\n");
@ -1931,9 +1448,33 @@ inofd_event_unset(void)
} }
/* Thread: scan */ /* Thread: scan */
static int
filescanner_initscan()
{
int ret;
static enum command_state ret = db_watch_clear();
filescanner_initscan(void *arg, int *retval) if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Error: could not clear old watches from DB\n");
return -1;
}
if (cfg_getbool(cfg_getsec(cfg, "library"), "filescan_disable"))
bulk_scan(F_SCAN_BULK | F_SCAN_FAST);
else
bulk_scan(F_SCAN_BULK);
if (!library_is_exiting())
{
/* Enable inotify */
event_add(inoev, NULL);
}
return 0;
}
static int
filescanner_rescan()
{ {
DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered\n"); DPRINTF(E_LOG, L_SCAN, "Startup rescan triggered\n");
@ -1943,12 +1484,11 @@ filescanner_initscan(void *arg, int *retval)
inofd_event_set(); inofd_event_set();
bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN); bulk_scan(F_SCAN_BULK | F_SCAN_RESCAN);
*retval = 0; return 0;
return COMMAND_END;
} }
static enum command_state static int
filescanner_fullrescan(void *arg, int *retval) filescanner_fullrescan()
{ {
DPRINTF(E_LOG, L_SCAN, "Full rescan triggered\n"); DPRINTF(E_LOG, L_SCAN, "Full rescan triggered\n");
@ -1960,112 +1500,35 @@ filescanner_fullrescan(void *arg, int *retval)
inofd_event_set(); inofd_event_set();
bulk_scan(F_SCAN_BULK); bulk_scan(F_SCAN_BULK);
*retval = 0; return 0;
return COMMAND_END;
}
void
filescanner_trigger_initscan(void)
{
if (scanning)
{
DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
return;
}
commands_exec_async(cmdbase, filescanner_initscan, NULL);
}
void
filescanner_trigger_fullrescan(void)
{
if (scanning)
{
DPRINTF(E_INFO, L_SCAN, "Scan already running, ignoring request to trigger a new init scan\n");
return;
}
commands_exec_async(cmdbase, filescanner_fullrescan, NULL);
}
/*
* Query the status of the filescanner
* @return 1 if scan is running, otherwise 0
*/
int
filescanner_scanning(void)
{
return scanning;
} }
/* Thread: main */ /* Thread: main */
int static int
filescanner_init(void) filescanner_init(void)
{ {
int ret; int ret;
scan_exit = 0;
scanning = 0;
evbase_scan = event_base_new();
if (!evbase_scan)
{
DPRINTF(E_FATAL, L_SCAN, "Could not create an event base\n");
return -1;
}
ret = inofd_event_set(); ret = inofd_event_set();
if (ret < 0)
{
goto ino_fail;
}
cmdbase = commands_base_new(evbase_scan, NULL); return ret;
ret = pthread_create(&tid_scan, NULL, filescanner, NULL);
if (ret != 0)
{
DPRINTF(E_FATAL, L_SCAN, "Could not spawn filescanner thread: %s\n", strerror(errno));
goto thread_fail;
}
#if defined(HAVE_PTHREAD_SETNAME_NP)
pthread_setname_np(tid_scan, "filescanner");
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
pthread_set_name_np(tid_scan, "filescanner");
#endif
return 0;
thread_fail:
commands_base_free(cmdbase);
close(inofd);
ino_fail:
event_base_free(evbase_scan);
return -1;
} }
/* Thread: main */ /* Thread: main */
void static void
filescanner_deinit(void) filescanner_deinit(void)
{ {
int ret;
scan_exit = 1;
commands_base_destroy(cmdbase);
ret = pthread_join(tid_scan, NULL);
if (ret != 0)
{
DPRINTF(E_FATAL, L_SCAN, "Could not join filescanner thread: %s\n", strerror(errno));
return;
}
inofd_event_unset(); inofd_event_unset();
event_base_free(evbase_scan);
} }
struct library_source filescanner =
{
.name = "filescanner",
.disabled = 0,
.init = filescanner_init,
.deinit = filescanner_deinit,
.initscan = filescanner_initscan,
.rescan = filescanner_rescan,
.fullrescan = filescanner_fullrescan,
};

View File

@ -12,19 +12,8 @@
#define F_SCAN_TYPE_SPOTIFY (1 << 5) #define F_SCAN_TYPE_SPOTIFY (1 << 5)
#define F_SCAN_TYPE_PIPE (1 << 6) #define F_SCAN_TYPE_PIPE (1 << 6)
int
filescanner_init(void);
void
filescanner_deinit(void);
void
filescanner_process_media(char *path, time_t mtime, off_t size, int type, struct media_file_info *external_mfi, int dir_id);
/* Actual scanners */ /* Actual scanners */
int
scan_metadata_ffmpeg(char *file, struct media_file_info *mfi);
int int
scan_metadata_icy(char *url, struct media_file_info *mfi); scan_metadata_icy(char *url, struct media_file_info *mfi);
@ -39,13 +28,4 @@ void
scan_itunes_itml(char *file); scan_itunes_itml(char *file);
#endif #endif
void
filescanner_trigger_initscan(void);
void
filescanner_trigger_fullrescan(void);
int
filescanner_scanning(void);
#endif /* !__FILESCANNER_H__ */ #endif /* !__FILESCANNER_H__ */

View File

@ -351,7 +351,7 @@ extract_metadata(struct media_file_info *mfi, AVFormatContext *ctx, AVStream *au
} }
int int
scan_metadata_ffmpeg(char *file, struct media_file_info *mfi) scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi)
{ {
AVFormatContext *ctx; AVFormatContext *ctx;
AVDictionary *options; AVDictionary *options;

View File

@ -38,6 +38,7 @@
#include "db.h" #include "db.h"
#include "filescanner.h" #include "filescanner.h"
#include "misc.h" #include "misc.h"
#include "library.h"
/* Formats we can read so far */ /* Formats we can read so far */
#define PLAYLIST_PLS 1 #define PLAYLIST_PLS 1
@ -238,7 +239,7 @@ scan_playlist(char *file, time_t mtime, int dir_id)
if (extinf) if (extinf)
DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title); DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title);
filescanner_process_media(filename, mtime, 0, F_SCAN_TYPE_URL, &mfi, DIR_HTTP); library_process_media(filename, mtime, 0, DATA_KIND_HTTP, 0, false, &mfi, DIR_HTTP);
} }
/* Regular file, should already be in library */ /* Regular file, should already be in library */
else else

782
src/library.c Normal file
View File

@ -0,0 +1,782 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.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 "library.h"
#include <errno.h>
#include <event2/event.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#ifdef HAVE_PTHREAD_NP_H
# include <pthread_np.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <unictype.h>
#include <uninorm.h>
#include <unistr.h>
#include "commands.h"
#include "conffile.h"
#include "db.h"
#include "logger.h"
#include "misc.h"
static struct commands_base *cmdbase;
static pthread_t tid_library;
struct event_base *evbase_lib;
/* Flag for aborting scan on exit */
static bool scan_exit;
/* Flag for scan in progress */
static bool scanning;
extern struct library_source filescanner;
#ifdef HAVE_SPOTIFY_H
extern struct library_source spotifyscanner;
#endif
static struct library_source *sources[] = {
&filescanner,
#ifdef HAVE_SPOTIFY_H
&spotifyscanner,
#endif
NULL
};
static void
sort_tag_create(char **sort_tag, char *src_tag)
{
const uint8_t *i_ptr;
const uint8_t *n_ptr;
const uint8_t *number;
uint8_t out[1024];
uint8_t *o_ptr;
int append_number;
ucs4_t puc;
int numlen;
size_t len;
int charlen;
/* Note: include terminating NUL in string length for u8_normalize */
if (*sort_tag)
{
DPRINTF(E_DBG, L_SCAN, "Existing sort tag will be normalized: %s\n", *sort_tag);
o_ptr = u8_normalize(UNINORM_NFD, (uint8_t *)*sort_tag, strlen(*sort_tag) + 1, NULL, &len);
free(*sort_tag);
*sort_tag = (char *)o_ptr;
return;
}
if (!src_tag || ((len = strlen(src_tag)) == 0))
{
*sort_tag = NULL;
return;
}
// Set input pointer past article if present
if ((strncasecmp(src_tag, "a ", 2) == 0) && (len > 2))
i_ptr = (uint8_t *)(src_tag + 2);
else if ((strncasecmp(src_tag, "an ", 3) == 0) && (len > 3))
i_ptr = (uint8_t *)(src_tag + 3);
else if ((strncasecmp(src_tag, "the ", 4) == 0) && (len > 4))
i_ptr = (uint8_t *)(src_tag + 4);
else
i_ptr = (uint8_t *)src_tag;
// Poor man's natural sort. Makes sure we sort like this: a1, a2, a10, a11, a21, a111
// We do this by padding zeroes to (short) numbers. As an alternative we could have
// made a proper natural sort algorithm in sqlext.c, but we don't, since we don't
// want any risk of hurting response times
memset(&out, 0, sizeof(out));
o_ptr = (uint8_t *)&out;
number = NULL;
append_number = 0;
do
{
n_ptr = u8_next(&puc, i_ptr);
if (uc_is_digit(puc))
{
if (!number) // We have encountered the beginning of a number
number = i_ptr;
append_number = (n_ptr == NULL); // If last char in string append number now
}
else
{
if (number)
append_number = 1; // A number has ended so time to append it
else
{
charlen = u8_strmblen(i_ptr);
if (charlen >= 0)
o_ptr = u8_stpncpy(o_ptr, i_ptr, charlen); // No numbers in sight, just append char
}
}
// Break if less than 100 bytes remain (prevent buffer overflow)
if (sizeof(out) - u8_strlen(out) < 100)
break;
// Break if number is very large (prevent buffer overflow)
if (number && (i_ptr - number > 50))
break;
if (append_number)
{
numlen = i_ptr - number;
if (numlen < 5) // Max pad width
{
u8_strcpy(o_ptr, (uint8_t *)"00000");
o_ptr += (5 - numlen);
}
o_ptr = u8_stpncpy(o_ptr, number, numlen + u8_strmblen(i_ptr));
number = NULL;
append_number = 0;
}
i_ptr = n_ptr;
}
while (n_ptr);
*sort_tag = (char *)u8_normalize(UNINORM_NFD, (uint8_t *)&out, u8_strlen(out) + 1, NULL, &len);
}
static void
fixup_tags(struct media_file_info *mfi)
{
cfg_t *lib;
size_t len;
char *tag;
char *sep = " - ";
char *ca;
if (mfi->genre && (strlen(mfi->genre) == 0))
{
free(mfi->genre);
mfi->genre = NULL;
}
if (mfi->artist && (strlen(mfi->artist) == 0))
{
free(mfi->artist);
mfi->artist = NULL;
}
if (mfi->title && (strlen(mfi->title) == 0))
{
free(mfi->title);
mfi->title = NULL;
}
/*
* Default to mpeg4 video/audio for unknown file types
* in an attempt to allow streaming of DRM-afflicted files
*/
if (mfi->codectype && strcmp(mfi->codectype, "unkn") == 0)
{
if (mfi->has_video)
{
strcpy(mfi->codectype, "mp4v");
strcpy(mfi->type, "m4v");
}
else
{
strcpy(mfi->codectype, "mp4a");
strcpy(mfi->type, "m4a");
}
}
if (!mfi->artist)
{
if (mfi->orchestra && mfi->conductor)
{
len = strlen(mfi->orchestra) + strlen(sep) + strlen(mfi->conductor);
tag = (char *)malloc(len + 1);
if (tag)
{
sprintf(tag,"%s%s%s", mfi->orchestra, sep, mfi->conductor);
mfi->artist = tag;
}
}
else if (mfi->orchestra)
{
mfi->artist = strdup(mfi->orchestra);
}
else if (mfi->conductor)
{
mfi->artist = strdup(mfi->conductor);
}
}
/* Handle TV shows, try to present prettier metadata */
if (mfi->tv_series_name && strlen(mfi->tv_series_name) != 0)
{
mfi->media_kind = MEDIA_KIND_TVSHOW; /* tv show */
/* Default to artist = series_name */
if (mfi->artist && strlen(mfi->artist) == 0)
{
free(mfi->artist);
mfi->artist = NULL;
}
if (!mfi->artist)
mfi->artist = strdup(mfi->tv_series_name);
/* Default to album = "<series_name>, Season <season_num>" */
if (mfi->album && strlen(mfi->album) == 0)
{
free(mfi->album);
mfi->album = NULL;
}
if (!mfi->album)
{
len = snprintf(NULL, 0, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
mfi->album = (char *)malloc(len + 1);
if (mfi->album)
sprintf(mfi->album, "%s, Season %d", mfi->tv_series_name, mfi->tv_season_num);
}
}
/* Check the 4 top-tags are filled */
if (!mfi->artist)
mfi->artist = strdup("Unknown artist");
if (!mfi->album)
mfi->album = strdup("Unknown album");
if (!mfi->genre)
mfi->genre = strdup("Unknown genre");
if (!mfi->title)
{
/* fname is left untouched by unicode_fixup_mfi() for
* obvious reasons, so ensure it is proper UTF-8
*/
mfi->title = unicode_fixup_string(mfi->fname, "ascii");
if (mfi->title == mfi->fname)
mfi->title = strdup(mfi->fname);
}
/* Ensure sort tags are filled, manipulated and normalized */
sort_tag_create(&mfi->artist_sort, mfi->artist);
sort_tag_create(&mfi->album_sort, mfi->album);
sort_tag_create(&mfi->title_sort, mfi->title);
/* We need to set album_artist according to media type and config */
if (mfi->compilation) /* Compilation */
{
lib = cfg_getsec(cfg, "library");
ca = cfg_getstr(lib, "compilation_artist");
if (ca && mfi->album_artist)
{
free(mfi->album_artist);
mfi->album_artist = strdup(ca);
}
else if (ca && !mfi->album_artist)
{
mfi->album_artist = strdup(ca);
}
else if (!ca && !mfi->album_artist)
{
mfi->album_artist = strdup("");
mfi->album_artist_sort = strdup("");
}
}
else if (mfi->media_kind == MEDIA_KIND_PODCAST) /* Podcast */
{
if (mfi->album_artist)
free(mfi->album_artist);
mfi->album_artist = strdup("");
mfi->album_artist_sort = strdup("");
}
else if (!mfi->album_artist) /* Regular media without album_artist */
{
mfi->album_artist = strdup(mfi->artist);
}
if (!mfi->album_artist_sort && (strcmp(mfi->album_artist, mfi->artist) == 0))
mfi->album_artist_sort = strdup(mfi->artist_sort);
else
sort_tag_create(&mfi->album_artist_sort, mfi->album_artist);
/* Composer is not one of our mandatory tags, so take extra care */
if (mfi->composer_sort || mfi->composer)
sort_tag_create(&mfi->composer_sort, mfi->composer);
}
void
library_process_media(const char *path, time_t mtime, off_t size, enum data_kind data_kind, enum media_kind force_media_kind, bool force_compilation, struct media_file_info *external_mfi, int dir_id)
{
struct media_file_info *mfi;
const char *filename;
time_t stamp;
int id;
char virtual_path[PATH_MAX];
int ret;
filename = strrchr(path, '/');
if ((!filename) || (strlen(filename) == 1))
filename = path;
else
filename++;
db_file_stamp_bypath(path, &stamp, &id);
if (stamp && (stamp >= mtime))
{
db_file_ping(id);
return;
}
if (!external_mfi)
{
mfi = (struct media_file_info*)malloc(sizeof(struct media_file_info));
if (!mfi)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for mfi\n");
return;
}
memset(mfi, 0, sizeof(struct media_file_info));
}
else
mfi = external_mfi;
if (stamp)
mfi->id = id;
mfi->fname = strdup(filename);
if (!mfi->fname)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for fname\n");
goto out;
}
mfi->path = strdup(path);
if (!mfi->path)
{
DPRINTF(E_LOG, L_SCAN, "Out of memory for path\n");
goto out;
}
mfi->time_modified = mtime;
mfi->file_size = size;
if (force_compilation)
mfi->compilation = 1;
if (force_media_kind)
mfi->media_kind = force_media_kind;
if (data_kind == DATA_KIND_FILE)
{
mfi->data_kind = DATA_KIND_FILE;
ret = scan_metadata_ffmpeg(path, mfi);
}
else if (data_kind == DATA_KIND_HTTP)
{
mfi->data_kind = DATA_KIND_HTTP;
ret = scan_metadata_ffmpeg(path, mfi);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Playlist URL '%s' is unavailable for probe/metadata, assuming MP3 encoding\n", path);
mfi->type = strdup("mp3");
mfi->codectype = strdup("mpeg");
mfi->description = strdup("MPEG audio file");
ret = 1;
}
}
else if (data_kind == DATA_KIND_SPOTIFY)
{
mfi->data_kind = DATA_KIND_SPOTIFY;
ret = mfi->artist && mfi->album && mfi->title;
}
else if (data_kind == DATA_KIND_PIPE)
{
mfi->data_kind = DATA_KIND_PIPE;
mfi->type = strdup("wav");
mfi->codectype = strdup("wav");
mfi->description = strdup("PCM16 pipe");
ret = 1;
}
else
{
DPRINTF(E_LOG, L_SCAN, "Unknown scan type for '%s', this error should not occur\n", path);
ret = -1;
}
if (ret < 0)
{
DPRINTF(E_INFO, L_SCAN, "Could not extract metadata for '%s'\n", path);
goto out;
}
if (!mfi->item_kind)
mfi->item_kind = 2; /* music */
if (!mfi->media_kind)
mfi->media_kind = MEDIA_KIND_MUSIC; /* music */
unicode_fixup_mfi(mfi);
fixup_tags(mfi);
if (data_kind == DATA_KIND_HTTP)
{
snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi->title);
mfi->virtual_path = strdup(virtual_path);
}
else if (data_kind == DATA_KIND_SPOTIFY)
{
snprintf(virtual_path, PATH_MAX, "/spotify:/%s/%s/%s", mfi->album_artist, mfi->album, mfi->title);
mfi->virtual_path = strdup(virtual_path);
}
else
{
snprintf(virtual_path, PATH_MAX, "/file:%s", mfi->path);
mfi->virtual_path = strdup(virtual_path);
}
mfi->directory_id = dir_id;
if (mfi->id == 0)
db_file_add(mfi);
else
db_file_update(mfi);
out:
if (!external_mfi)
free_mfi(mfi, 0);
}
int
library_add_playlist_info(const char *path, const char *title, const char *virtual_path, enum pl_type type, int parent_pl_id, int dir_id)
{
struct playlist_info *pli;
int plid;
int ret;
pli = db_pl_fetch_bypath(path);
if (pli)
{
DPRINTF(E_DBG, L_LIB, "Playlist found ('%s', link %s), updating\n", title, path);
plid = pli->id;
pli->type = type;
free(pli->title);
pli->title = strdup(title);
if (pli->virtual_path)
free(pli->virtual_path);
pli->virtual_path = safe_strdup(virtual_path);
pli->directory_id = dir_id;
ret = db_pl_update(pli);
if (ret < 0)
{
DPRINTF(E_LOG, L_LIB, "Error updating playlist ('%s', link %s)\n", title, path);
free_pli(pli, 0);
return -1;
}
db_pl_clear_items(plid);
}
else
{
DPRINTF(E_DBG, L_LIB, "Adding playlist ('%s', link %s)\n", title, path);
pli = (struct playlist_info *)malloc(sizeof(struct playlist_info));
if (!pli)
{
DPRINTF(E_LOG, L_LIB, "Out of memory\n");
return -1;
}
memset(pli, 0, sizeof(struct playlist_info));
pli->type = type;
pli->title = strdup(title);
pli->path = strdup(path);
pli->virtual_path = safe_strdup(virtual_path);
pli->parent_id = parent_pl_id;
pli->directory_id = dir_id;
ret = db_pl_add(pli, &plid);
if ((ret < 0) || (plid < 1))
{
DPRINTF(E_LOG, L_LIB, "Error adding playlist ('%s', link %s, ret %d, plid %d)\n", title, path, ret, plid);
free_pli(pli, 0);
return -1;
}
}
free_pli(pli, 0);
return plid;
}
static enum command_state
rescan(void *arg, int *ret)
{
time_t starttime;
time_t endtime;
int i;
starttime = time(NULL);
for (i = 0; sources[i]; i++)
{
if (!sources[i]->disabled && sources[i]->rescan)
sources[i]->rescan();
}
endtime = time(NULL);
DPRINTF(E_LOG, L_LIB, "Library rescan completed in %.f sec\n", difftime(endtime, starttime));
scanning = false;
*ret = 0;
return COMMAND_END;
}
static enum command_state
fullrescan(void *arg, int *ret)
{
int i;
for (i = 0; sources[i]; i++)
{
if (!sources[i]->disabled && sources[i]->fullrescan)
sources[i]->fullrescan();
}
scanning = false;
*ret = 0;
return COMMAND_END;
}
void
library_rescan()
{
if (scanning)
{
DPRINTF(E_INFO, L_LIB, "Scan already running, ignoring request to trigger a new init scan\n");
return;
}
scanning = true; // TODO Guard "scanning" with a mutex
commands_exec_async(cmdbase, rescan, NULL);
}
void
library_fullrescan()
{
if (scanning)
{
DPRINTF(E_INFO, L_LIB, "Scan already running, ignoring request to trigger a new full rescan\n");
return;
}
scanning = true; // TODO Guard "scanning" with a mutex
commands_exec_async(cmdbase, fullrescan, NULL);
}
static void
initscan()
{
time_t starttime;
time_t endtime;
bool clear_queue_disabled;
int i;
scanning = true;
starttime = time(NULL);
// Only clear the queue if enabled (default) in config
clear_queue_disabled = cfg_getbool(cfg_getsec(cfg, "mpd"), "clear_queue_on_stop_disable");
if (!clear_queue_disabled)
{
db_queue_clear();
}
for (i = 0; sources[i]; i++)
{
if (!sources[i]->disabled && sources[i]->initscan)
sources[i]->initscan();
}
endtime = time(NULL);
DPRINTF(E_LOG, L_LIB, "Library init scan completed in %.f sec\n", difftime(endtime, starttime));
scanning = false;
}
/*
* @return true if scan is running, otherwise false
*/
bool
library_is_scanning()
{
return scanning;
}
/*
* @param is_scanning true if scan is running, otherwise false
*/
void
library_set_scanning(bool is_scanning)
{
scanning = is_scanning;
}
/*
* @return true if a running scan should be aborted due to imminent shutdown, otherwise false
*/
bool
library_is_exiting()
{
return scan_exit;
}
static void *
library(void *arg)
{
int ret;
#ifdef __linux__
struct sched_param param;
/* Lower the priority of the thread so forked-daapd may still respond
* during library scan on low power devices. Param must be 0 for the SCHED_BATCH
* policy.
*/
memset(&param, 0, sizeof(struct sched_param));
ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, &param);
if (ret != 0)
{
DPRINTF(E_LOG, L_LIB, "Warning: Could not set thread priority to SCHED_BATCH\n");
}
#endif
ret = db_perthread_init();
if (ret < 0)
{
DPRINTF(E_LOG, L_LIB, "Error: DB init failed\n");
pthread_exit(NULL);
}
initscan();
event_base_dispatch(evbase_lib);
if (!scan_exit)
DPRINTF(E_FATAL, L_LIB, "Scan event loop terminated ahead of time!\n");
db_perthread_deinit();
pthread_exit(NULL);
}
/* Thread: main */
int
library_init(void)
{
int i;
int ret;
scan_exit = false;
scanning = false;
evbase_lib = event_base_new();
if (!evbase_lib)
{
DPRINTF(E_FATAL, L_LIB, "Could not create an event base\n");
return -1;
}
for (i = 0; sources[i]; i++)
{
ret = sources[i]->init();
if (ret < 0)
sources[i]->disabled = 1;
}
cmdbase = commands_base_new(evbase_lib, NULL);
ret = pthread_create(&tid_library, NULL, library, NULL);
if (ret != 0)
{
DPRINTF(E_FATAL, L_LIB, "Could not spawn library thread: %s\n", strerror(errno));
goto thread_fail;
}
#if defined(HAVE_PTHREAD_SETNAME_NP)
pthread_setname_np(tid_library, "library");
#elif defined(HAVE_PTHREAD_SET_NAME_NP)
pthread_set_name_np(tid_library, "library");
#endif
return 0;
thread_fail:
event_base_free(evbase_lib);
return -1;
}
/* Thread: main */
void
library_deinit()
{
int i;
int ret;
scan_exit = true;
commands_base_destroy(cmdbase);
ret = pthread_join(tid_library, NULL);
if (ret != 0)
{
DPRINTF(E_FATAL, L_LIB, "Could not join library thread: %s\n", strerror(errno));
return;
}
for (i = 0; sources[i]; i++)
{
sources[i]->deinit();
}
event_base_free(evbase_lib);
}

98
src/library.h Normal file
View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2015 Christian Meffert <christian.meffert@googlemail.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
*/
#ifndef SRC_LIBRARY_H_
#define SRC_LIBRARY_H_
#include <stdbool.h>
#include <stdio.h>
#include <time.h>
#include "db.h"
/*
* Definition of a library source
*
* A library source is responsible for scanning items into the library db.
*/
struct library_source
{
char *name;
int disabled;
/*
* Initialize library source (called from the main thread)
*/
int (*init)(void);
/*
* Shutdown library source (called from the main thread after
* terminating the library thread)
*/
void (*deinit)(void);
/*
* Run initial scan after startup (called from the library thread)
*/
int (*initscan)(void);
/*
* Run rescan (called from the library thread)
*/
int (*rescan)(void);
/*
* Run a full rescan (purge library entries and rescan) (called from the library thread)
*/
int (*fullrescan)(void);
};
int
scan_metadata_ffmpeg(const char *file, struct media_file_info *mfi);
void
library_process_media(const char *path, time_t mtime, off_t size, enum data_kind data_kind, enum media_kind force_media_kind, bool force_compilation, struct media_file_info *external_mfi, int dir_id);
int
library_add_playlist_info(const char *path, const char *title, const char *virtual_path, enum pl_type type, int parent_pl_id, int dir_id);
void
library_rescan();
void
library_fullrescan();
bool
library_is_scanning();
void
library_set_scanning(bool is_scanning);
bool
library_is_exiting();
int
library_init();
void
library_deinit();
#endif /* SRC_LIBRARY_H_ */

View File

@ -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", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast", "fifo" }; static char *labels[] = { "config", "daap", "db", "httpd", "http", "main", "mdns", "misc", "rsp", "scan", "xcode", "event", "remote", "dacp", "ffmpeg", "artwork", "player", "raop", "laudio", "dmap", "dbperf", "spotify", "lastfm", "cache", "mpd", "stream", "cast", "fifo", "lib" };
static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" }; static char *severities[] = { "FATAL", "LOG", "WARN", "INFO", "DEBUG", "SPAM" };

View File

@ -34,8 +34,9 @@
#define L_STREAMING 25 #define L_STREAMING 25
#define L_CAST 26 #define L_CAST 26
#define L_FIFO 27 #define L_FIFO 27
#define L_LIB 28
#define N_LOGDOMAINS 28 #define N_LOGDOMAINS 29
/* Severities */ /* Severities */
#define E_FATAL 0 #define E_FATAL 0

View File

@ -60,20 +60,17 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
#include "logger.h" #include "logger.h"
#include "misc.h" #include "misc.h"
#include "cache.h" #include "cache.h"
#include "filescanner.h"
#include "httpd.h" #include "httpd.h"
#include "mpd.h" #include "mpd.h"
#include "mdns.h" #include "mdns.h"
#include "remote_pairing.h" #include "remote_pairing.h"
#include "player.h" #include "player.h"
#include "worker.h" #include "worker.h"
#include "library.h"
#ifdef HAVE_LIBCURL #ifdef HAVE_LIBCURL
# include <curl/curl.h> # include <curl/curl.h>
#endif #endif
#ifdef HAVE_SPOTIFY_H
# include "spotify.h"
#endif
#define PIDFILE STATEDIR "/run/" PACKAGE ".pid" #define PIDFILE STATEDIR "/run/" PACKAGE ".pid"
@ -738,25 +735,16 @@ main(int argc, char **argv)
goto cache_fail; goto cache_fail;
} }
/* Spawn file scanner thread */ /* Spawn library scan thread */
ret = filescanner_init(); ret = library_init();
if (ret != 0) if (ret != 0)
{ {
DPRINTF(E_FATAL, L_MAIN, "File scanner thread failed to start\n"); DPRINTF(E_FATAL, L_MAIN, "Library thread failed to start\n");
ret = EXIT_FAILURE; ret = EXIT_FAILURE;
goto filescanner_fail; goto library_fail;
} }
#ifdef HAVE_SPOTIFY_H
/* Spawn Spotify thread */
ret = spotify_init();
if (ret < 0)
{
DPRINTF(E_INFO, L_MAIN, "Spotify thread not started\n");;
}
#endif
/* Spawn player thread */ /* Spawn player thread */
ret = player_init(); ret = player_init();
if (ret != 0) if (ret != 0)
@ -895,14 +883,10 @@ main(int argc, char **argv)
player_deinit(); player_deinit();
player_fail: player_fail:
#ifdef HAVE_SPOTIFY_H DPRINTF(E_LOG, L_MAIN, "Library scaner deinit\n");
DPRINTF(E_LOG, L_MAIN, "Spotify deinit\n"); library_deinit();
spotify_deinit();
#endif
DPRINTF(E_LOG, L_MAIN, "File scanner deinit\n");
filescanner_deinit();
filescanner_fail: library_fail:
DPRINTF(E_LOG, L_MAIN, "Cache deinit\n"); DPRINTF(E_LOG, L_MAIN, "Cache deinit\n");
cache_deinit(); cache_deinit();

View File

@ -56,8 +56,8 @@
#include "artwork.h" #include "artwork.h"
#include "player.h" #include "player.h"
#include "filescanner.h"
#include "commands.h" #include "commands.h"
#include "library.h"
static pthread_t tid_mpd; static pthread_t tid_mpd;
@ -748,7 +748,7 @@ mpd_command_status(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
(status.pos_ms / 1000.0)); (status.pos_ms / 1000.0));
} }
if (filescanner_scanning()) if (library_is_scanning())
{ {
evbuffer_add(evbuf, "updating_db: 1\n", 15); evbuffer_add(evbuf, "updating_db: 1\n", 15);
} }
@ -3218,7 +3218,7 @@ mpd_command_update(struct evbuffer *evbuf, int argc, char **argv, char **errmsg)
return ACK_ERROR_ARG; return ACK_ERROR_ARG;
} }
filescanner_trigger_initscan(); library_rescan();
evbuffer_add(evbuf, "updating_db: 1\n", 15); evbuffer_add(evbuf, "updating_db: 1\n", 15);

View File

@ -24,6 +24,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
@ -50,9 +51,9 @@
#include "misc.h" #include "misc.h"
#include "http.h" #include "http.h"
#include "conffile.h" #include "conffile.h"
#include "filescanner.h"
#include "cache.h" #include "cache.h"
#include "commands.h" #include "commands.h"
#include "library.h"
/* TODO for the web api: /* TODO for the web api:
* - UI should be prettier * - UI should be prettier
@ -711,7 +712,7 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
// DPRINTF(E_DBG, L_SPOTIFY, "Saving track '%s': '%s' by %s (%s)\n", url, mfi.title, mfi.artist, mfi.album); // DPRINTF(E_DBG, L_SPOTIFY, "Saving track '%s': '%s' by %s (%s)\n", url, mfi.title, mfi.artist, mfi.album);
filescanner_process_media(url, time(NULL), 0, F_SCAN_TYPE_SPOTIFY, &mfi, dir_id); library_process_media(url, time(NULL), 0, DATA_KIND_SPOTIFY, 0, 0, &mfi, dir_id);
free_mfi(&mfi, 1); free_mfi(&mfi, 1);
@ -2554,6 +2555,28 @@ spotify_login(char *path)
} }
} }
/* Thread: library */
static int
spotify_initscan()
{
spotify_login(NULL);
return 0;
}
/* Thread: library */
static int
spotify_rescan()
{
return 0;
}
/* Thread: library */
static int
spotify_fullrescan()
{
return 0;
}
/* Thread: main */ /* Thread: main */
int int
spotify_init(void) spotify_init(void)
@ -2748,3 +2771,14 @@ spotify_deinit(void)
/* Release libspotify handle */ /* Release libspotify handle */
dlclose(g_libhandle); dlclose(g_libhandle);
} }
struct library_source spotifyscanner =
{
.name = "spotifyscanner",
.disabled = 0,
.init = spotify_init,
.deinit = spotify_deinit,
.rescan = spotify_rescan,
.initscan = spotify_initscan,
.fullrescan = spotify_fullrescan,
};