owntone-server/src/library/filescanner_playlist.c

351 lines
8.4 KiB
C
Raw Normal View History

/*
* Copyright (C) 2015-2017 Espen Jürgensen <espenjurgensen@gmail.com>
2010-01-05 13:34:00 -05:00
* Copyright (C) 2009-2010 Julien BLACHE <jb@jblache.org>
*
* 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 <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <limits.h>
2010-01-09 07:42:23 -05:00
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
2009-05-08 11:46:32 -04:00
#include "logger.h"
2009-06-07 12:58:02 -04:00
#include "db.h"
#include "library/filescanner.h"
#include "misc.h"
#include "library.h"
2014-08-27 15:57:16 -04:00
/* Formats we can read so far */
#define PLAYLIST_PLS 1
#define PLAYLIST_M3U 2
2013-10-16 05:09:24 -04:00
/* Get metadata from the EXTINF tag */
2013-10-15 07:36:11 -04:00
static int
extinf_get(char *string, struct media_file_info *mfi, int *extinf)
2013-10-15 07:36:11 -04:00
{
2013-10-16 05:09:24 -04:00
char *ptr;
2013-10-15 07:36:11 -04:00
2014-04-18 07:08:31 -04:00
if (strncmp(string, "#EXTINF:", strlen("#EXTINF:")) != 0)
2013-10-16 05:09:24 -04:00
return 0;
2013-10-15 07:36:11 -04:00
2013-10-16 05:09:24 -04:00
ptr = strchr(string, ',');
if (!ptr || strlen(ptr) < 2)
return 0;
2013-10-15 07:36:11 -04:00
2013-10-16 05:09:24 -04:00
/* New extinf found, so clear old data */
2014-04-18 07:08:31 -04:00
free_mfi(mfi, 1);
2013-10-15 07:36:11 -04:00
*extinf = 1;
mfi->artist = strdup(ptr + 1);
2013-10-15 07:36:11 -04:00
ptr = strstr(mfi->artist, " -");
2013-10-16 05:09:24 -04:00
if (ptr && strlen(ptr) > 3)
mfi->title = strdup(ptr + 3);
2013-10-16 05:09:24 -04:00
else
mfi->title = strdup("");
2013-10-16 05:09:24 -04:00
if (ptr)
*ptr = '\0';
2013-10-15 07:36:11 -04:00
2013-10-16 05:09:24 -04:00
return 1;
2013-10-15 07:36:11 -04:00
}
static int
process_url(const char *path, time_t mtime, int extinf, struct media_file_info *mfi, char **filename)
{
char virtual_path[PATH_MAX];
int ret;
*filename = strdup(path);
// Playlist hasn't changed since last probe of this item, so we wont't probe again
ret = db_file_ping_bypath(path, mtime);
if ((mtime != 0) && (ret != 0))
return 0;
if (extinf)
DPRINTF(E_INFO, L_SCAN, "Playlist has EXTINF metadata, artist is '%s', title is '%s'\n", mfi->artist, mfi->title);
mfi->id = db_file_id_bypath(path);
mfi->path = strdup(path);
mfi->fname = strdup(filename_from_path(path));
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");
}
if (!mfi->title)
mfi->title = strdup(mfi->fname);
snprintf(virtual_path, PATH_MAX, "/http:/%s", mfi->title);
mfi->virtual_path = strdup(virtual_path);
library_add_media(mfi);
return 0;
}
static int
process_regular_file(char **filename, char *path)
{
int i;
int mfi_id;
char *ptr;
char *entry;
int ret;
/* Playlist might be from Windows so we change backslash to forward slash */
for (i = 0; i < strlen(path); i++)
{
if (path[i] == '\\')
path[i] = '/';
}
/* Now search for the library item where the path has closest match to playlist item */
/* Succes is when we find an unambiguous match, or when we no longer can expand the */
/* the path to refine our search. */
entry = NULL;
do
{
ptr = strrchr(path, '/');
if (entry)
*(entry - 1) = '/';
if (ptr)
{
*ptr = '\0';
entry = ptr + 1;
}
else
entry = path;
DPRINTF(E_SPAM, L_SCAN, "Playlist entry is now %s\n", entry);
ret = db_files_get_count_bymatch(entry);
}
while (ptr && (ret > 1));
if (ret > 0)
{
mfi_id = db_file_id_bymatch(entry);
DPRINTF(E_DBG, L_SCAN, "Found playlist entry match, id is %d, entry is %s\n", mfi_id, entry);
*filename = db_file_path_byid(mfi_id);
if (!(*filename))
{
DPRINTF(E_LOG, L_SCAN, "Playlist entry %s matches file id %d, but file path is missing.\n", entry, mfi_id);
return -1;
}
}
else
{
DPRINTF(E_DBG, L_SCAN, "No match for playlist entry %s\n", entry);
return -1;
}
return 0;
}
void
scan_playlist(char *file, time_t mtime, int dir_id)
{
FILE *fp;
struct media_file_info mfi;
struct playlist_info *pli;
struct stat sb;
char buf[PATH_MAX];
2014-08-27 15:57:16 -04:00
char *path;
const char *filename;
char *ptr;
size_t len;
int extinf;
int pl_id;
2014-08-27 15:57:16 -04:00
int pl_format;
int counter;
int ret;
2014-12-21 14:41:44 -05:00
char virtual_path[PATH_MAX];
char *plitem_path;
DPRINTF(E_LOG, L_SCAN, "Processing static playlist: '%s'\n", file);
2014-08-27 15:57:16 -04:00
ptr = strrchr(file, '.');
if (!ptr)
return;
if (strcasecmp(ptr, ".m3u") == 0)
pl_format = PLAYLIST_M3U;
else if (strcasecmp(ptr, ".pls") == 0)
pl_format = PLAYLIST_PLS;
else
return;
filename = filename_from_path(file);
/* Fetch or create playlist */
pli = db_pl_fetch_bypath(file);
if (pli)
{
db_pl_ping(pli->id);
if (mtime && (pli->db_timestamp >= mtime))
{
DPRINTF(E_LOG, L_SCAN, "Playlist not modified since last processing, skipping\n");
free_pli(pli, 0);
return;
}
DPRINTF(E_INFO, L_SCAN, "Updating playlist\n");
pl_id = pli->id;
db_pl_clear_items(pl_id);
}
else
{
CHECK_NULL(L_SCAN, pli = calloc(1, sizeof(struct playlist_info)));
pli->type = PL_PLAIN;
/* Get only the basename, to be used as the playlist title */
pli->title = strip_extension(filename);
pli->path = strdup(file);
2014-12-21 14:41:44 -05:00
snprintf(virtual_path, PATH_MAX, "/file:%s", file);
pli->virtual_path = strip_extension(virtual_path);
2014-12-21 14:41:44 -05:00
pli->directory_id = dir_id;
ret = db_pl_add(pli, &pl_id);
if (ret < 0)
{
2014-08-27 15:57:16 -04:00
DPRINTF(E_LOG, L_SCAN, "Error adding playlist '%s'\n", file);
free_pli(pli, 0);
return;
}
DPRINTF(E_INFO, L_SCAN, "Added new playlist as id %d\n", pl_id);
}
free_pli(pli, 0);
ret = stat(file, &sb);
if (ret < 0)
{
DPRINTF(E_LOG, L_SCAN, "Could not stat() '%s': %s\n", file, strerror(errno));
return;
}
fp = fopen(file, "r");
if (!fp)
{
DPRINTF(E_LOG, L_SCAN, "Could not open playlist '%s': %s\n", file, strerror(errno));
return;
}
extinf = 0;
2014-04-18 07:08:31 -04:00
memset(&mfi, 0, sizeof(struct media_file_info));
counter = 0;
while (fgets(buf, sizeof(buf), fp) != NULL)
{
len = strlen(buf);
2013-10-15 07:36:11 -04:00
/* rtrim and check that length is sane (ignore blank lines) */
while ((len > 0) && isspace(buf[len - 1]))
{
len--;
buf[len] = '\0';
}
2013-10-15 07:36:11 -04:00
if (len < 1)
continue;
2014-04-18 07:08:31 -04:00
/* Saves metadata in mfi if EXTINF metadata line */
2014-08-27 15:57:16 -04:00
if ((pl_format == PLAYLIST_M3U) && extinf_get(buf, &mfi, &extinf))
continue;
/* For pls files we are only interested in the part after the FileX= entry */
path = NULL;
if ((pl_format == PLAYLIST_PLS) && (strncasecmp(buf, "file", strlen("file")) == 0))
path = strchr(buf, '=') + 1;
else if (pl_format == PLAYLIST_M3U)
path = buf;
if (!path)
continue;
2013-10-15 07:36:11 -04:00
/* Check that first char is sane for a path */
2014-08-27 15:57:16 -04:00
if ((!isalnum(path[0])) && (path[0] != '/') && (path[0] != '.'))
2013-10-15 07:36:11 -04:00
continue;
/* Check if line is an URL, will be added to library, otherwise it should already be there */
if (strncasecmp(path, "http://", 7) == 0)
ret = process_url(path, sb.st_mtime, extinf, &mfi, &plitem_path);
else
ret = process_regular_file(&plitem_path, path);
if (ret < 0)
continue;
ret = db_pl_add_item_bypath(pl_id, plitem_path);
if (ret == 0)
{
counter++;
if (counter % 200 == 0)
{
DPRINTF(E_LOG, L_SCAN, "Added %d items to playlist ...\n", counter);
db_transaction_end();
db_transaction_begin();
}
}
else
DPRINTF(E_WARN, L_SCAN, "Could not add '%s' to playlist\n", plitem_path);
/* Clean up in preparation for next item */
extinf = 0;
free_mfi(&mfi, 1);
free(plitem_path);
}
2015-04-01 17:45:21 -04:00
/* We had some extinf that we never got to use, free it now */
if (extinf)
free_mfi(&mfi, 1);
if (!feof(fp))
DPRINTF(E_LOG, L_SCAN, "Error reading playlist '%s' (only added %d tracks): %s\n", file, counter, strerror(errno));
else
DPRINTF(E_LOG, L_SCAN, "Done processing playlist, added %d items\n", counter);
[-] Fix alsa.c null pointer deref + some minor bugs and do some housekeeping Thanks to Denis Denisov and cppcheck for notifying about the below. The leaks are edge cases, but the warning of dereference of avail in alsa.c points at a bug that could probably cause actual crashes. [src/evrtsp/rtsp.c:1352]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [src/httpd_daap.c:228]: (error) Memory leak: s [src/library.c:280]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'unsigned int'. [src/library.c:284]: (warning) %d in format string (no. 2) requires 'int' but the argument type is 'unsigned int'. [src/library/filescanner_playlist.c:251]: (error) Resource leak: fp [src/library/filescanner_playlist.c:273]: (error) Resource leak: fp [src/outputs/alsa.c:143]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [src/outputs/alsa.c:657]: (warning) Possible null pointer dereference: avail [src/outputs/dummy.c:75]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [src/outputs/fifo.c:245]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [src/outputs/raop.c:1806]: (warning) Assignment of function parameter has no effect outside the function. Did you forget dereferencing it? [src/outputs/raop.c:1371]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'signed int'. [src/outputs/raop.c:1471]: (warning) %u in format string (no. 1) requires 'unsigned int' but the argument type is 'signed int'. [src/outputs/raop_verification.c:705] -> [src/outputs/raop_verification.c:667]: (warning) Either the condition 'if(len_M)' is redundant or there is possible null pointer dereference: len_M.
2017-10-05 16:13:01 -04:00
fclose(fp);
}