From aa2e4d92e0005f4516eb591803120eff89f99109 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 7 Aug 2014 18:54:06 +0200 Subject: 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. --- Makefile.am | 1 + src/PlaylistFile.cxx | 27 ++++++++- src/StateFile.cxx | 6 +- src/db/plugins/simple/SimpleDatabasePlugin.cxx | 7 +-- src/fs/StandardDirectory.cxx | 3 +- src/fs/io/BufferedReader.cxx | 82 ++++++++++++++++++++++++++ src/fs/io/BufferedReader.hxx | 75 +++++++++++++++++++++++ src/fs/io/TextFile.cxx | 59 ++++-------------- src/fs/io/TextFile.hxx | 18 +++--- 9 files changed, 210 insertions(+), 68 deletions(-) create mode 100644 src/fs/io/BufferedReader.cxx create mode 100644 src/fs/io/BufferedReader.hxx 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 @@ -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 + +class Reader; +class Error; + +class BufferedReader { + static constexpr size_t MAX_SIZE = 512 * 1024; + + Reader &reader; + + DynamicFifoBuffer 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 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 -#include -#include -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 #include 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(); -- cgit v1.2.3