Refactor library scan of media files

Remove the data_kind specific parts out of the general library functions
and into their (library) source specific functions.
This commit is contained in:
chme 2017-01-29 10:01:05 +01:00 committed by ejurgensen
parent bcb19908f4
commit dadba23efc
6 changed files with 257 additions and 255 deletions

View File

@ -42,7 +42,6 @@
#include "commands.h"
#include "conffile.h"
#include "db.h"
#include "library/filescanner.h"
#include "logger.h"
#include "misc.h"
#include "listener.h"
@ -345,109 +344,20 @@ fixup_tags(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)
library_process_media(struct media_file_info *mfi)
{
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))
if (!mfi->path || !mfi->fname || !mfi->data_kind)
{
db_file_ping(id);
DPRINTF(E_LOG, L_LIB, "Ignoring media file with missing values (path='%s', fname='%s', data_kind='%d')\n",
mfi->path, mfi->fname, mfi->data_kind);
return;
}
if (!external_mfi)
if (!mfi->directory_id || mfi->virtual_path)
{
mfi = (struct media_file_info*)malloc(sizeof(struct media_file_info));
if (!mfi)
{
DPRINTF(E_LOG, L_LIB, "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_LIB, "Out of memory for fname\n");
goto out;
}
mfi->path = strdup(path);
if (!mfi->path)
{
DPRINTF(E_LOG, L_LIB, "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_LIB, "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_LIB, "Unknown scan type for '%s', this error should not occur\n", path);
ret = -1;
}
if (ret < 0)
{
DPRINTF(E_INFO, L_LIB, "Could not extract metadata for '%s'\n", path);
goto out;
// Missing informations for virtual_path and directory_id (may) lead to misplaced appearance in mpd clients
DPRINTF(E_WARN, L_LIB, "Media file with missing values (path='%s', directory='%d', virtual_path='%s')\n",
mfi->path, mfi->directory_id, mfi->virtual_path);
}
if (!mfi->item_kind)
@ -459,32 +369,10 @@ library_process_media(const char *path, time_t mtime, off_t size, enum data_kind
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

View File

@ -66,7 +66,7 @@ struct library_source
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);
library_process_media(struct media_file_info *mfi);
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);

View File

@ -77,6 +77,12 @@
#define F_SCAN_FAST (1 << 2)
#define F_SCAN_MOVED (1 << 3)
#define F_SCAN_TYPE_FILE (1 << 0)
#define F_SCAN_TYPE_PODCAST (1 << 1)
#define F_SCAN_TYPE_AUDIOBOOK (1 << 2)
#define F_SCAN_TYPE_COMPILATION (1 << 3)
enum file_type {
FILE_UNKNOWN = 0,
FILE_IGNORE,
@ -389,37 +395,83 @@ process_deferred_playlists(void)
}
}
static void
process_regular_file(char *file, struct stat *sb, int type, int flags, int dir_id)
{
time_t stamp;
int id;
bool is_bulkscan = (flags & F_SCAN_BULK);
struct media_file_info mfi;
char virtual_path[PATH_MAX];
int ret;
// Do not rescan metadata if file did not change since last scan
db_file_stamp_bypath(file, &stamp, &id);
if (stamp && (stamp >= sb->st_mtime))
{
db_file_ping(id);
return;
}
// File is new or modified - (re)scan metadata and update file in library
memset(&mfi, 0, sizeof(struct media_file_info));
mfi.id = id;
mfi.fname = strdup(basename(file));
mfi.path = strdup(file);
mfi.time_modified = sb->st_mtime;
mfi.file_size = sb->st_size;
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
mfi.virtual_path = strdup(virtual_path);
mfi.directory_id = dir_id;
if (S_ISFIFO(sb->st_mode))
{
mfi.data_kind = DATA_KIND_PIPE;
mfi.type = strdup("wav");
mfi.codectype = strdup("wav");
mfi.description = strdup("PCM16 pipe");
mfi.media_kind = MEDIA_KIND_MUSIC;
}
else
{
mfi.data_kind = DATA_KIND_FILE;
if (type & F_SCAN_TYPE_AUDIOBOOK)
mfi.media_kind = MEDIA_KIND_AUDIOBOOK;
else if (type & F_SCAN_TYPE_PODCAST)
mfi.media_kind = MEDIA_KIND_PODCAST;
mfi.compilation = (type & F_SCAN_TYPE_COMPILATION);
mfi.file_size = sb->st_size;
ret = scan_metadata_ffmpeg(file, &mfi);
if (ret < 0)
{
free_mfi(&mfi, 1);
return;
}
}
library_process_media(&mfi);
cache_artwork_ping(file, sb->st_mtime, !is_bulkscan);
// TODO [artworkcache] If entry in artwork cache exists for no artwork available, delete the entry if media file has embedded artwork
free_mfi(&mfi, 1);
}
/* Thread: scan */
static void
process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_id)
process_file(char *file, struct stat *sb, int type, int flags, int dir_id)
{
bool is_bulkscan;
bool is_type_compilation;
enum media_kind media_kind;
enum data_kind data_kind;
is_bulkscan = (flags & F_SCAN_BULK);
switch (file_type_get(file))
{
case FILE_REGULAR:
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;
if (F_SCAN_TYPE_PIPE & type)
data_kind = DATA_KIND_PIPE;
else
data_kind = DATA_KIND_FILE;
library_process_media(file, mtime, size, data_kind, media_kind, is_type_compilation, NULL, dir_id);
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
process_regular_file(file, sb, type, flags, dir_id);
counter++;
@ -435,19 +487,19 @@ process_file(char *file, time_t mtime, off_t size, int type, int flags, int dir_
case FILE_PLAYLIST:
case FILE_ITUNES:
if (flags & F_SCAN_BULK)
defer_playlist(file, mtime, dir_id);
defer_playlist(file, sb->st_mtime, dir_id);
else
process_playlist(file, mtime, dir_id);
process_playlist(file, sb->st_mtime, dir_id);
break;
case FILE_SMARTPL:
DPRINTF(E_DBG, L_SCAN, "Smart playlist file: %s\n", file);
scan_smartpl(file, mtime, dir_id);
scan_smartpl(file, sb->st_mtime, dir_id);
break;
case FILE_ARTWORK:
DPRINTF(E_DBG, L_SCAN, "Artwork file: %s\n", file);
cache_artwork_ping(file, mtime, !is_bulkscan);
cache_artwork_ping(file, sb->st_mtime, !(flags & F_SCAN_BULK));
// TODO [artworkcache] If entry in artwork cache exists for no artwork available for a album with files in the same directory, delete the entry
@ -539,13 +591,58 @@ create_virtual_path(char *path, char *virtual_path, int virtual_path_len)
return 0;
}
/*
* Returns informations about the attributes of the file at the given 'path' in the structure
* pointed to by 'sb'.
*
* If path is a symbolic link, the attributes in sb describe the file that the link points to
* and resolved_path contains the resolved path (resolved_path must be of length PATH_MAX).
* If path is not a symbolic link, resolved_path holds the same value as path.
*
* The return value is 0 if the operation is successful, or -1 on failure. In addition
*/
static int
read_attributes(const char *path, struct stat *sb, char *resolved_path)
{
int ret;
ret = lstat(path, sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, lstat() failed: %s\n", path, strerror(errno));
return -1;
}
if (S_ISLNK(sb->st_mode))
{
if (!realpath(path, resolved_path))
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, could not dereference symlink: %s\n", path, strerror(errno));
return -1;
}
ret = stat(resolved_path, sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, stat() failed: %s\n", resolved_path, strerror(errno));
return -1;
}
}
else
{
strcpy(resolved_path, path);
}
return 0;
}
static void
process_directory(char *path, int parent_id, int flags)
{
DIR *dirp;
struct dirent *de;
char entry[PATH_MAX];
char *deref;
char resolved_path[PATH_MAX];
struct stat sb;
struct watch_info wi;
int type;
@ -612,59 +709,25 @@ process_directory(char *path, int parent_id, int flags)
continue;
}
ret = lstat(entry, &sb);
ret = read_attributes(entry, &sb, resolved_path);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, lstat() failed: %s\n", entry, strerror(errno));
DPRINTF(E_LOG, L_SCAN, "Skipping %s, read_attributes() failed\n", entry);
continue;
}
if (S_ISLNK(sb.st_mode))
if (S_ISDIR(sb.st_mode))
{
deref = m_realpath(entry);
if (!deref)
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, could not dereference symlink: %s\n", entry, strerror(errno));
continue;
}
ret = stat(deref, &sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, stat() failed: %s\n", deref, strerror(errno));
free(deref);
continue;
}
ret = snprintf(entry, sizeof(entry), "%s", deref);
if ((ret < 0) || (ret >= sizeof(entry)))
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, PATH_MAX exceeded\n", entry);
free(deref);
continue;
}
free(deref);
push_dir(&dirstack, resolved_path, dir_id);
}
if (S_ISREG(sb.st_mode))
else if (!(flags & F_SCAN_FAST))
{
if (!(flags & F_SCAN_FAST))
process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, flags, dir_id);
if (S_ISREG(sb.st_mode) || S_ISFIFO(sb.st_mode))
process_file(resolved_path, &sb, type, flags, dir_id);
else
DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry);
}
else if (S_ISFIFO(sb.st_mode))
{
if (!(flags & F_SCAN_FAST))
process_file(entry, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, flags, dir_id);
}
else if (S_ISDIR(sb.st_mode))
push_dir(&dirstack, entry, dir_id);
else
DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", entry);
}
closedir(dirp);
@ -1040,8 +1103,8 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
{
struct stat sb;
uint32_t path_hash;
char *deref = NULL;
char *file = path;
char resolved_path[PATH_MAX];
char *dir;
char dir_vpath[PATH_MAX];
int type;
@ -1177,44 +1240,14 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
incomingfiles_buffer[i] = 0;
}
ret = lstat(path, &sb);
ret = read_attributes(path, &sb, resolved_path);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Could not lstat() '%s': %s\n", path, strerror(errno));
{
DPRINTF(E_LOG, L_SCAN, "Skipping %s, read_attributes() failed\n", path);
return;
}
if (S_ISLNK(sb.st_mode))
{
deref = m_realpath(path);
if (!deref)
{
DPRINTF(E_LOG, L_SCAN, "Could not dereference symlink '%s': %s\n", path, strerror(errno));
return;
}
file = deref;
ret = stat(deref, &sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno));
free(deref);
return;
}
if (S_ISDIR(sb.st_mode))
{
process_inotify_dir(wi, deref, ie);
free(deref);
return;
}
}
type = 0;
if (check_speciallib(path, "compilations"))
type |= F_SCAN_TYPE_COMPILATION;
@ -1225,15 +1258,18 @@ process_inotify_file(struct watch_info *wi, char *path, struct inotify_event *ie
dir_id = get_parent_dir_id(file);
if (S_ISREG(sb.st_mode))
{
process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_FILE | type, 0, dir_id);
}
else if (S_ISFIFO(sb.st_mode))
process_file(file, sb.st_mtime, sb.st_size, F_SCAN_TYPE_PIPE | type, 0, dir_id);
if (S_ISDIR(sb.st_mode))
{
process_inotify_dir(wi, resolved_path, ie);
if (deref)
free(deref);
return;
}
else if (S_ISREG(sb.st_mode) || S_ISFIFO(sb.st_mode))
{
process_file(resolved_path, &sb, type, 0, dir_id);
}
else
DPRINTF(E_LOG, L_SCAN, "Skipping %s, not a directory, symlink, pipe nor regular file\n", resolved_path);
}
}

View File

@ -4,14 +4,6 @@
#include "db.h"
#define F_SCAN_TYPE_FILE (1 << 0)
#define F_SCAN_TYPE_PODCAST (1 << 1)
#define F_SCAN_TYPE_AUDIOBOOK (1 << 2)
#define F_SCAN_TYPE_COMPILATION (1 << 3)
#define F_SCAN_TYPE_URL (1 << 4)
#define F_SCAN_TYPE_SPOTIFY (1 << 5)
#define F_SCAN_TYPE_PIPE (1 << 6)
/* Actual scanners */
int

View File

@ -33,6 +33,7 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <libgen.h>
#include "logger.h"
#include "db.h"
@ -94,6 +95,8 @@ scan_playlist(char *file, time_t mtime, int dir_id)
int ret;
char virtual_path[PATH_MAX];
int i;
time_t stamp;
int id;
DPRINTF(E_LOG, L_SCAN, "Processing static playlist: %s\n", file);
@ -226,7 +229,14 @@ scan_playlist(char *file, time_t mtime, int dir_id)
/* Check if line is an URL, will be added to library */
if (strncasecmp(path, "http://", strlen("http://")) == 0)
{
DPRINTF(E_DBG, L_SCAN, "Playlist contains URL entry\n");
DPRINTF(E_DBG, L_SCAN, "Playlist contains URL entry: '%s'\n", path);
db_file_stamp_bypath(path, &stamp, &id);
if (stamp && (stamp >= sb.st_mtime))
{
db_file_ping(id);
continue;
}
filename = strdup(path);
if (!filename)
@ -239,7 +249,25 @@ scan_playlist(char *file, time_t mtime, int dir_id)
if (extinf)
DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi.artist, mfi.title);
library_process_media(filename, mtime, 0, DATA_KIND_HTTP, 0, false, &mfi, DIR_HTTP);
mfi.id = id;
mfi.fname = strdup(basename(filename));
mfi.path = strdup(filename);
mfi.data_kind = DATA_KIND_HTTP;
mfi.time_modified = mtime;
mfi.directory_id = DIR_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");
}
snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi.title); //TODO can title be null at this point?
mfi.virtual_path = strdup(virtual_path);
library_process_media(&mfi);
}
/* Regular file, should already be in library */
else

View File

@ -640,7 +640,10 @@ spotify_track_save(int plid, sp_track *track, const char *pltitle, int time_adde
sp_link *link;
char url[1024];
int ret;
char virtual_path[PATH_MAX];
int dir_id;
time_t stamp;
int id;
memset(&mfi, 0, sizeof(struct media_file_info));
@ -697,7 +700,18 @@ 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);
library_process_media(url, time(NULL), 0, DATA_KIND_SPOTIFY, 0, false, &mfi, dir_id);
db_file_stamp_bypath(url, &stamp, &id);
mfi.id = id;
mfi.path = strdup(url);
mfi.fname = strdup(url);
mfi.time_modified = time(NULL);
mfi.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);
mfi.directory_id = dir_id;
library_process_media(&mfi);
free_mfi(&mfi, 1);
@ -2008,6 +2022,9 @@ map_track_to_mfi(const struct spotify_track *track, struct media_file_info* mfi)
mfi->type = strdup("spotify");
mfi->codectype = strdup("wav");
mfi->description = strdup("Spotify audio");
mfi->path = strdup(track->uri);
mfi->fname = strdup(track->uri);
}
static void
@ -2018,6 +2035,7 @@ map_album_to_mfi(const struct spotify_album *album, struct media_file_info* mfi)
mfi->genre = safe_strdup(album->genre);
mfi->compilation = album->is_compilation;
mfi->year = album->release_year;
mfi->time_modified = album->mtime;
}
/* Thread: library */
@ -2030,7 +2048,10 @@ scan_saved_albums()
struct spotify_album album;
struct spotify_track track;
struct media_file_info mfi;
char virtual_path[PATH_MAX];
int dir_id;
time_t stamp;
int id;
int i;
int count;
int ret;
@ -2054,17 +2075,34 @@ scan_saved_albums()
ret = spotifywebapi_album_track_fetch(jsontracks, i, &track);
if (ret == 0 && track.uri)
{
memset(&mfi, 0, sizeof(struct media_file_info));
map_track_to_mfi(&track, &mfi);
map_album_to_mfi(&album, &mfi);
db_file_stamp_bypath(track.uri, &stamp, &id);
if (stamp && (stamp >= track.mtime))
{
db_file_ping(id);
}
else
{
memset(&mfi, 0, sizeof(struct media_file_info));
mfi.id = id;
map_track_to_mfi(&track, &mfi);
map_album_to_mfi(&album, &mfi);
mfi.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);
mfi.directory_id = dir_id;
library_process_media(&mfi);
free_mfi(&mfi, 1);
}
library_process_media(track.uri, album.mtime, 0, DATA_KIND_SPOTIFY, 0, album.is_compilation, &mfi, dir_id);
spotify_uri_register(track.uri);
cache_artwork_ping(track.uri, album.mtime, 0);
free_mfi(&mfi, 1);
if (spotify_saved_plid)
db_pl_add_item_bypath(spotify_saved_plid, track.uri);
}
@ -2093,7 +2131,10 @@ scan_playlisttracks(struct spotify_playlist *playlist, int plid)
struct spotify_request request;
struct spotify_track track;
struct media_file_info mfi;
char virtual_path[PATH_MAX];
int dir_id;
time_t stamp;
int id;
memset(&request, 0, sizeof(struct spotify_request));
@ -2121,9 +2162,19 @@ scan_playlisttracks(struct spotify_playlist *playlist, int plid)
if (track.linked_from_uri)
DPRINTF(E_DBG, L_SPOTIFY, "Track '%s' (%s) linked from %s\n", track.name, track.uri, track.linked_from_uri);
dir_id = prepare_directories(track.album_artist, track.album);
db_file_stamp_bypath(track.uri, &stamp, &id);
if (stamp)
{
db_file_ping(id);
continue;
}
memset(&mfi, 0, sizeof(struct media_file_info));
mfi.id = id;
dir_id = prepare_directories(track.album_artist, track.album);
map_track_to_mfi(&track, &mfi);
mfi.compilation = (track.is_compilation || artist_override);
@ -2133,7 +2184,14 @@ scan_playlisttracks(struct spotify_playlist *playlist, int plid)
mfi.album = strdup(playlist->name);
}
library_process_media(track.uri, 1 /* TODO passing one prevents overwriting existing entries */, 0, DATA_KIND_SPOTIFY, 0, track.is_compilation, &mfi, dir_id);
mfi.time_modified = time(NULL);
mfi.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);
mfi.directory_id = dir_id;
library_process_media(&mfi);
spotify_uri_register(track.uri);
cache_artwork_ping(track.uri, 1, 0);