aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2014-08-07 18:54:06 +0200
committerMax Kellermann <max@duempel.org>2014-08-07 19:38:25 +0200
commitaa2e4d92e0005f4516eb591803120eff89f99109 (patch)
treebeb83ec24f0cb5b527ec60bd56c2722681f1d3f5
parent0ea66a1275da319e2443fa1536cec7ea7fc53b53 (diff)
downloadmpd-aa2e4d92e0005f4516eb591803120eff89f99109.tar.gz
mpd-aa2e4d92e0005f4516eb591803120eff89f99109.tar.xz
mpd-aa2e4d92e0005f4516eb591803120eff89f99109.zip
fs/io/BufferedReader: new class to replace class TextFile
The new class is pluggable, to prepare for gzipped database files. For now, the TextFile class remains, and will be refactored away later.
-rw-r--r--Makefile.am1
-rw-r--r--src/PlaylistFile.cxx27
-rw-r--r--src/StateFile.cxx6
-rw-r--r--src/db/plugins/simple/SimpleDatabasePlugin.cxx7
-rw-r--r--src/fs/StandardDirectory.cxx3
-rw-r--r--src/fs/io/BufferedReader.cxx82
-rw-r--r--src/fs/io/BufferedReader.hxx75
-rw-r--r--src/fs/io/TextFile.cxx59
-rw-r--r--src/fs/io/TextFile.hxx18
9 files changed, 210 insertions, 68 deletions
diff --git a/Makefile.am b/Makefile.am
index 3491f2e6b..3ab492aae 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -508,6 +508,7 @@ FS_LIBS = libfs.a
libfs_a_SOURCES = \
src/fs/io/Reader.hxx \
src/fs/io/FileReader.cxx src/fs/io/FileReader.hxx \
+ src/fs/io/BufferedReader.cxx src/fs/io/BufferedReader.hxx \
src/fs/io/TextFile.cxx src/fs/io/TextFile.hxx \
src/fs/io/OutputStream.hxx \
src/fs/io/StdoutOutputStream.hxx \
diff --git a/src/PlaylistFile.cxx b/src/PlaylistFile.cxx
index 2fb28aadf..f0aa2d2d7 100644
--- a/src/PlaylistFile.cxx
+++ b/src/PlaylistFile.cxx
@@ -116,6 +116,29 @@ spl_map_to_fs(const char *name_utf8, Error &error)
return path_fs;
}
+gcc_pure
+static bool
+IsNotFoundError(const Error &error)
+{
+#ifdef WIN32
+ return error.IsDomain(win32_domain) &&
+ error.GetCode() == ERROR_FILE_NOT_FOUND;
+#else
+ return error.IsDomain(errno_domain) &&
+ error.GetCode() == ENOENT;
+#endif
+}
+
+static void
+TranslatePlaylistError(Error &error)
+{
+ if (IsNotFoundError(error)) {
+ error.Clear();
+ error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_LIST),
+ "No such playlist");
+ }
+}
+
/**
* Create an #Error for the current errno.
*/
@@ -228,9 +251,9 @@ LoadPlaylistFile(const char *utf8path, Error &error)
if (path_fs.IsNull())
return contents;
- TextFile file(path_fs);
+ TextFile file(path_fs, error);
if (file.HasFailed()) {
- playlist_errno(error);
+ TranslatePlaylistError(error);
return contents;
}
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index 408b19426..e0f0cedb1 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -103,10 +103,10 @@ StateFile::Read()
FormatDebug(state_file_domain, "Loading state file %s", path_utf8.c_str());
- TextFile file(path);
+ Error error;
+ TextFile file(path, error);
if (file.HasFailed()) {
- FormatErrno(state_file_domain, "failed to open %s",
- path_utf8.c_str());
+ LogError(error);
return;
}
diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
index ae34a523f..9e750996c 100644
--- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx
+++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx
@@ -168,12 +168,9 @@ SimpleDatabase::Load(Error &error)
assert(!path.IsNull());
assert(root != nullptr);
- TextFile file(path);
- if (file.HasFailed()) {
- error.FormatErrno("Failed to open database file \"%s\"",
- path_utf8.c_str());
+ TextFile file(path, error);
+ if (file.HasFailed())
return false;
- }
if (!db_load_internal(file, *root, error))
return false;
diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx
index 7a8666501..7a836f906 100644
--- a/src/fs/StandardDirectory.cxx
+++ b/src/fs/StandardDirectory.cxx
@@ -40,6 +40,7 @@
#endif
#ifdef USE_XDG
+#include "util/Error.hxx"
#include "util/StringUtil.hxx"
#include "io/TextFile.hxx"
#include <string.h>
@@ -204,7 +205,7 @@ static AllocatedPath GetUserDir(const char *name)
if (config_dir.IsNull())
return result;
auto dirs_file = AllocatedPath::Build(config_dir, "user-dirs.dirs");
- TextFile input(dirs_file);
+ TextFile input(dirs_file, IgnoreError());
if (input.HasFailed())
return result;
char *line;
diff --git a/src/fs/io/BufferedReader.cxx b/src/fs/io/BufferedReader.cxx
new file mode 100644
index 000000000..ba2f17dcf
--- /dev/null
+++ b/src/fs/io/BufferedReader.cxx
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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 "BufferedReader.hxx"
+#include "Reader.hxx"
+#include "util/TextFile.hxx"
+
+bool
+BufferedReader::Fill(bool need_more)
+{
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ if (eof)
+ return !need_more;
+
+ auto w = buffer.Write();
+ if (w.IsEmpty()) {
+ if (buffer.GetCapacity() >= MAX_SIZE)
+ return !need_more;
+
+ buffer.Grow(buffer.GetCapacity() * 2);
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ }
+
+ size_t nbytes = reader.Read(w.data, w.size, last_error);
+ if (nbytes == 0) {
+ if (gcc_unlikely(last_error.IsDefined()))
+ return false;
+
+ eof = true;
+ return !need_more;
+ }
+
+ buffer.Append(nbytes);
+ return true;
+}
+
+char *
+BufferedReader::ReadLine()
+{
+ do {
+ char *line = ReadBufferedLine(buffer);
+ if (line != nullptr)
+ return line;
+ } while (Fill(true));
+
+ if (last_error.IsDefined() || !eof || buffer.IsEmpty())
+ return nullptr;
+
+ auto w = buffer.Write();
+ if (w.IsEmpty()) {
+ buffer.Grow(buffer.GetCapacity() + 1);
+ w = buffer.Write();
+ assert(!w.IsEmpty());
+ }
+
+ /* terminate the last line */
+ w[0] = 0;
+
+ char *line = buffer.Read().data;
+ buffer.Clear();
+ return line;
+}
diff --git a/src/fs/io/BufferedReader.hxx b/src/fs/io/BufferedReader.hxx
new file mode 100644
index 000000000..61cc8df83
--- /dev/null
+++ b/src/fs/io/BufferedReader.hxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * 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.
+ */
+
+#ifndef MPD_BUFFERED_READER_HXX
+#define MPD_BUFFERED_READER_HXX
+
+#include "check.h"
+#include "Compiler.h"
+#include "util/DynamicFifoBuffer.hxx"
+#include "util/Error.hxx"
+
+#include <stddef.h>
+
+class Reader;
+class Error;
+
+class BufferedReader {
+ static constexpr size_t MAX_SIZE = 512 * 1024;
+
+ Reader &reader;
+
+ DynamicFifoBuffer<char> buffer;
+
+ Error last_error;
+
+ bool eof;
+
+public:
+ BufferedReader(Reader &_reader)
+ :reader(_reader), buffer(4096), eof(false) {}
+
+ gcc_pure
+ bool Check() const {
+ return !last_error.IsDefined();
+ }
+
+ bool Check(Error &error) const {
+ if (last_error.IsDefined()) {
+ error.Set(last_error);
+ return false;
+ } else
+ return true;
+ }
+
+ bool Fill(bool need_more);
+
+ gcc_pure
+ WritableBuffer<void> Read() const {
+ return buffer.Read().ToVoid();
+ }
+
+ void Consume(size_t n) {
+ buffer.Consume(n);
+ }
+
+ char *ReadLine();
+};
+
+#endif
diff --git a/src/fs/io/TextFile.cxx b/src/fs/io/TextFile.cxx
index b1a92b9cc..396d0f9cd 100644
--- a/src/fs/io/TextFile.cxx
+++ b/src/fs/io/TextFile.cxx
@@ -19,63 +19,30 @@
#include "config.h"
#include "TextFile.hxx"
-#include "util/Alloc.hxx"
+#include "FileReader.hxx"
+#include "BufferedReader.hxx"
#include "fs/Path.hxx"
-#include "fs/FileSystem.hxx"
#include <assert.h>
-#include <string.h>
-#include <stdlib.h>
-TextFile::TextFile(Path path_fs)
- :file(FOpen(path_fs, FOpenMode::ReadText)),
- buffer((char *)xalloc(step)), capacity(step), length(0) {}
+TextFile::TextFile(Path path_fs, Error &error)
+ :file_reader(new FileReader(path_fs, error)),
+ buffered_reader(file_reader->IsDefined()
+ ? new BufferedReader(*file_reader)
+ : nullptr)
+{
+}
TextFile::~TextFile()
{
- free(buffer);
-
- if (file != nullptr)
- fclose(file);
+ delete buffered_reader;
+ delete file_reader;
}
char *
TextFile::ReadLine()
{
- assert(file != nullptr);
-
- while (true) {
- if (length >= capacity) {
- if (capacity >= max_length)
- /* too large already - bail out */
- return nullptr;
-
- capacity <<= 1;
- char *new_buffer = (char *)realloc(buffer, capacity);
- if (new_buffer == nullptr)
- /* out of memory - bail out */
- return nullptr;
- }
-
- char *p = fgets(buffer + length, capacity - length, file);
- if (p == nullptr) {
- if (length == 0 || ferror(file))
- return nullptr;
- break;
- }
-
- length += strlen(buffer + length);
- if (buffer[length - 1] == '\n')
- break;
- }
-
- /* remove the newline characters */
- if (buffer[length - 1] == '\n')
- --length;
- if (buffer[length - 1] == '\r')
- --length;
+ assert(buffered_reader != nullptr);
- buffer[length] = 0;
- length = 0;
- return buffer;
+ return buffered_reader->ReadLine();
}
diff --git a/src/fs/io/TextFile.hxx b/src/fs/io/TextFile.hxx
index e3a712a88..33a1b8060 100644
--- a/src/fs/io/TextFile.hxx
+++ b/src/fs/io/TextFile.hxx
@@ -22,29 +22,26 @@
#include "Compiler.h"
-#include <stdio.h>
#include <stddef.h>
class Path;
+class Error;
+class FileReader;
+class BufferedReader;
class TextFile {
- static constexpr size_t max_length = 512 * 1024;
- static constexpr size_t step = 1024;
-
- FILE *const file;
-
- char *buffer;
- size_t capacity, length;
+ FileReader *const file_reader;
+ BufferedReader *const buffered_reader;
public:
- TextFile(Path path_fs);
+ TextFile(Path path_fs, Error &error);
TextFile(const TextFile &other) = delete;
~TextFile();
bool HasFailed() const {
- return gcc_unlikely(file == nullptr);
+ return gcc_unlikely(buffered_reader == nullptr);
}
/**
@@ -53,7 +50,6 @@ public:
* prevent denial of service.
*
* @param file the source file, opened in text mode
- * @param buffer an allocator for the buffer
* @return a pointer to the line, or nullptr on end-of-file or error
*/
char *ReadLine();