NfsInputPlugin.cxx 5.41 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2015 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * 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.
 */

#include "config.h"
#include "NfsInputPlugin.hxx"
22
#include "../AsyncInputStream.hxx"
23
#include "../InputPlugin.hxx"
24
#include "lib/nfs/Domain.hxx"
25 26 27
#include "lib/nfs/Glue.hxx"
#include "lib/nfs/FileReader.hxx"
#include "util/HugeAllocator.hxx"
28 29 30
#include "util/StringUtil.hxx"
#include "util/Error.hxx"

31
#include <string.h>
32 33 34
#include <sys/stat.h>
#include <fcntl.h>

35 36 37 38 39 40 41 42 43 44 45 46 47 48
/**
 * Do not buffer more than this number of bytes.  It should be a
 * reasonable limit that doesn't make low-end machines suffer too
 * much, but doesn't cause stuttering on high-latency lines.
 */
static const size_t NFS_MAX_BUFFERED = 512 * 1024;

/**
 * Resume the stream at this number of bytes after it has been paused.
 */
static const size_t NFS_RESUME_AT = 384 * 1024;

class NfsInputStream final : public AsyncInputStream, NfsFileReader {
	uint64_t next_offset;
49

50 51
	bool reconnect_on_resume, reconnecting;

52
public:
53 54
	NfsInputStream(const char *_uri,
		       Mutex &_mutex, Cond &_cond,
55 56 57
		       void *_buffer)
		:AsyncInputStream(_uri, _mutex, _cond,
				  _buffer, NFS_MAX_BUFFERED,
58 59
				  NFS_RESUME_AT),
		 reconnect_on_resume(false), reconnecting(false) {}
60

61 62
	virtual ~NfsInputStream() {
		DeferClose();
63 64
	}

65 66
	bool Open(Error &error) {
		assert(!IsReady());
67

68
		return NfsFileReader::Open(GetURI(), error);
69 70
	}

71 72 73 74 75 76 77 78 79 80 81 82 83
private:
	bool DoRead();

protected:
	/* virtual methods from AsyncInputStream */
	virtual void DoResume() override;
	virtual void DoSeek(offset_type new_offset) override;

private:
	/* virtual methods from NfsFileReader */
	void OnNfsFileOpen(uint64_t size) override;
	void OnNfsFileRead(const void *data, size_t size) override;
	void OnNfsFileError(Error &&error) override;
84
};
85

86 87
bool
NfsInputStream::DoRead()
88
{
89 90 91 92 93 94
	assert(NfsFileReader::IsIdle());

	int64_t remaining = size - next_offset;
	if (remaining <= 0)
		return true;

95 96
	const size_t buffer_space = GetBufferSpace();
	if (buffer_space == 0) {
97 98
		Pause();
		return true;
99 100
	}

101 102
	size_t nbytes = std::min<size_t>(std::min<uint64_t>(remaining, 32768),
					 buffer_space);
103

104 105 106 107 108 109 110
	mutex.unlock();
	Error error;
	bool success = NfsFileReader::Read(next_offset, nbytes, error);
	mutex.lock();

	if (!success) {
		PostponeError(std::move(error));
111
		return false;
112
	}
113 114 115

	return true;
}
116

117 118 119
void
NfsInputStream::DoResume()
{
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
	if (reconnect_on_resume) {
		/* the NFS connection has died while this stream was
		   "paused" - attempt to reconnect */

		reconnect_on_resume = false;
		reconnecting = true;

		mutex.unlock();
		NfsFileReader::Close();

		Error error;
		bool success = NfsFileReader::Open(GetURI(), error);
		mutex.lock();

		if (!success) {
			postponed_error = std::move(error);
			cond.broadcast();
		}

		return;
	}

142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
	assert(NfsFileReader::IsIdle());

	DoRead();
}

void
NfsInputStream::DoSeek(offset_type new_offset)
{
	mutex.unlock();
	NfsFileReader::CancelRead();
	mutex.lock();

	next_offset = offset = new_offset;
	SeekDone();
	DoRead();
}

void
NfsInputStream::OnNfsFileOpen(uint64_t _size)
{
	const ScopeLock protect(mutex);

164 165 166 167 168 169 170 171
	if (reconnecting) {
		/* reconnect has succeeded */

		reconnecting = false;
		DoRead();
		return;
	}

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
	size = _size;
	seekable = true;
	next_offset = 0;
	SetReady();
	DoRead();
}

void
NfsInputStream::OnNfsFileRead(const void *data, size_t data_size)
{
	const ScopeLock protect(mutex);
	assert(!IsBufferFull());
	assert(IsBufferFull() == (GetBufferSpace() == 0));
	AppendToBuffer(data, data_size);

	next_offset += data_size;

	DoRead();
}

void
NfsInputStream::OnNfsFileError(Error &&error)
{
	const ScopeLock protect(mutex);
196 197 198 199 200 201 202 203 204 205 206 207

	if (IsPaused()) {
		/* while we're paused, don't report this error to the
		   client just yet (it might just be timeout, maybe
		   playback has been paused for quite some time) -
		   wait until the stream gets resumed and try to
		   reconnect, to give it another chance */

		reconnect_on_resume = true;
		return;
	}

208 209 210 211 212 213
	postponed_error = std::move(error);

	if (IsSeekPending())
		SeekDone();
	else if (!IsReady())
		SetReady();
214 215
	else
		cond.broadcast();
216 217
}

218 219 220 221 222
/*
 * InputPlugin methods
 *
 */

223
static InputPlugin::InitResult
224
input_nfs_init(const ConfigBlock &, Error &)
225 226 227 228 229 230 231 232 233 234 235
{
	nfs_init();
	return InputPlugin::InitResult::SUCCESS;
}

static void
input_nfs_finish()
{
	nfs_finish();
}

236 237 238 239 240 241 242 243
static InputStream *
input_nfs_open(const char *uri,
	       Mutex &mutex, Cond &cond,
	       Error &error)
{
	if (!StringStartsWith(uri, "nfs://"))
		return nullptr;

244 245 246
	void *buffer = HugeAllocate(NFS_MAX_BUFFERED);
	if (buffer == nullptr) {
		error.Set(nfs_domain, "Out of memory");
247 248 249
		return nullptr;
	}

250 251 252
	NfsInputStream *is = new NfsInputStream(uri, mutex, cond, buffer);
	if (!is->Open(error)) {
		delete is;
253 254 255
		return nullptr;
	}

256
	return is;
257 258 259 260
}

const InputPlugin input_plugin_nfs = {
	"nfs",
261 262
	input_nfs_init,
	input_nfs_finish,
263 264
	input_nfs_open,
};