PlsPlaylistPlugin.cxx 3.78 KB
Newer Older
Qball Cow's avatar
Qball Cow committed
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 The Music Player Daemon Project
Qball Cow's avatar
Qball Cow committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * http://www.musicpd.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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

20
#include "config.h"
21
#include "PlsPlaylistPlugin.hxx"
22 23
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
Max Kellermann's avatar
Max Kellermann committed
24
#include "input/InputStream.hxx"
25
#include "DetachedSong.hxx"
26
#include "tag/TagBuilder.hxx"
27
#include "util/Error.hxx"
28 29
#include "util/Domain.hxx"
#include "Log.hxx"
30

Qball Cow's avatar
Qball Cow committed
31 32
#include <glib.h>

33
#include <string>
34
#include <stdio.h>
35

36 37
#include <stdio.h>

38 39
static constexpr Domain pls_domain("pls");

40
static void
41
pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
Qball Cow's avatar
Qball Cow committed
42 43
{
	gchar *value;
44
	GError *error = nullptr;
Qball Cow's avatar
Qball Cow committed
45 46 47
	int num_entries = g_key_file_get_integer(keyfile, "playlist",
						 "NumberOfEntries", &error);
	if (error) {
48 49
		FormatError(pls_domain,
			    "Invalid PLS file: '%s'", error->message);
Qball Cow's avatar
Qball Cow committed
50
		g_error_free(error);
51
		error = nullptr;
Qball Cow's avatar
Qball Cow committed
52 53 54 55 56 57

		/* Hack to work around shoutcast failure to comform to spec */
		num_entries = g_key_file_get_integer(keyfile, "playlist",
						     "numberofentries", &error);
		if (error) {
			g_error_free(error);
58
			error = nullptr;
Qball Cow's avatar
Qball Cow committed
59 60 61
		}
	}

62
	for (; num_entries > 0; --num_entries) {
63 64
		char key[64];
		sprintf(key, "File%u", num_entries);
65 66
		char *uri = g_key_file_get_string(keyfile, "playlist", key,
						  &error);
Qball Cow's avatar
Qball Cow committed
67
		if(error) {
68 69
			FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
				    key, error->message);
Qball Cow's avatar
Qball Cow committed
70 71 72 73
			g_error_free(error);
			return;
		}

74 75
		TagBuilder tag;

76
		sprintf(key, "Title%u", num_entries);
Qball Cow's avatar
Qball Cow committed
77
		value = g_key_file_get_string(keyfile, "playlist", key,
78 79
					      nullptr);
		if (value != nullptr)
80 81
			tag.AddItem(TAG_TITLE, value);

Qball Cow's avatar
Qball Cow committed
82 83
		g_free(value);

84
		sprintf(key, "Length%u", num_entries);
85 86
		int length = g_key_file_get_integer(keyfile, "playlist", key,
						    nullptr);
87
		if (length > 0)
88 89
			tag.SetTime(length);

90 91
		songs.emplace_front(uri, tag.Commit());
		g_free(uri);
Qball Cow's avatar
Qball Cow committed
92 93 94 95
	}

}

96
static SongEnumerator *
97
pls_open_stream(InputStream &is)
Qball Cow's avatar
Qball Cow committed
98
{
99
	GError *error = nullptr;
100
	Error error2;
101 102

	std::string kf_data;
Qball Cow's avatar
Qball Cow committed
103 104

	do {
105 106
		char buffer[1024];
		size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2);
107
		if (nbytes == 0) {
108
			if (error2.IsDefined()) {
109
				LogError(error2);
110
				return nullptr;
111 112
			}

Qball Cow's avatar
Qball Cow committed
113
			break;
114 115
		}

116
		kf_data.append(buffer, nbytes);
Qball Cow's avatar
Qball Cow committed
117
		/* Limit to 64k */
118
	} while (kf_data.length() < 65536);
Qball Cow's avatar
Qball Cow committed
119

120
	if (kf_data.empty()) {
121
		LogWarning(pls_domain, "KeyFile parser failed: No Data");
122
		return nullptr;
Qball Cow's avatar
Qball Cow committed
123 124
	}

125 126 127 128
	GKeyFile *keyfile = g_key_file_new();
	if (!g_key_file_load_from_data(keyfile,
				       kf_data.data(), kf_data.length(),
				       G_KEY_FILE_NONE, &error)) {
129 130
		FormatError(pls_domain,
			    "KeyFile parser failed: %s", error->message);
Qball Cow's avatar
Qball Cow committed
131 132
		g_error_free(error);
		g_key_file_free(keyfile);
133
		return nullptr;
Qball Cow's avatar
Qball Cow committed
134 135
	}

136
	std::forward_list<DetachedSong> songs;
137
	pls_parser(keyfile, songs);
Qball Cow's avatar
Qball Cow committed
138 139
	g_key_file_free(keyfile);

140
	return new MemorySongEnumerator(std::move(songs));
Qball Cow's avatar
Qball Cow committed
141 142 143 144
}

static const char *const pls_suffixes[] = {
	"pls",
145
	nullptr
Qball Cow's avatar
Qball Cow committed
146 147 148 149
};

static const char *const pls_mime_types[] = {
	"audio/x-scpls",
150
	nullptr
Qball Cow's avatar
Qball Cow committed
151 152 153
};

const struct playlist_plugin pls_playlist_plugin = {
154
	"pls",
Qball Cow's avatar
Qball Cow committed
155

156 157 158 159
	nullptr,
	nullptr,
	nullptr,
	pls_open_stream,
Qball Cow's avatar
Qball Cow committed
160

161 162 163
	nullptr,
	pls_suffixes,
	pls_mime_types,
Qball Cow's avatar
Qball Cow committed
164
};