From b1f132ec6fd806c77adcb14c4fe5c78035e8fb97 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Sat, 22 Mar 2014 07:29:21 -0700 Subject: [PATCH] Use a proper auto-deleting temp file for the HD audio cache --- libaegisub/common/file_mapping.cpp | 153 ++++++++++++------- libaegisub/include/libaegisub/file_mapping.h | 16 +- src/audio_provider_hd.cpp | 121 +++++---------- src/audio_provider_hd.h | 53 ++----- src/libresrc/default_config.json | 1 - src/libresrc/osx/default_config.json | 1 - src/preferences.cpp | 2 - 7 files changed, 164 insertions(+), 183 deletions(-) diff --git a/libaegisub/common/file_mapping.cpp b/libaegisub/common/file_mapping.cpp index 99803347d..3e592d9e6 100644 --- a/libaegisub/common/file_mapping.cpp +++ b/libaegisub/common/file_mapping.cpp @@ -31,62 +31,14 @@ using namespace boost::interprocess; -namespace agi { -file_mapping::file_mapping(agi::fs::path const& filename, boost::interprocess::mode_t mode) -#ifdef _WIN32 -: handle(CreateFileW(filename.wstring().c_str(), (unsigned int)mode, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, 0)) +namespace { +char *map(int64_t s_offset, uint64_t length, boost::interprocess::mode_t mode, + uint64_t file_size, agi::file_mapping const& file, + std::unique_ptr& region, uint64_t& mapping_start) { - if (handle == ipcdetail::invalid_file()) { - switch (GetLastError()) { - case ERROR_FILE_NOT_FOUND: - case ERROR_PATH_NOT_FOUND: - throw fs::FileNotFound(filename); - case ERROR_ACCESS_DENIED: - throw fs::ReadDenied(filename); - default: - throw fs::FileSystemUnknownError(util::ErrorString(GetLastError())); - } - } -#else -: handle(ipcdetail::open_existing_file(filename.string().c_str(), mode)) -{ - if (handle == ipcdetail::invalid_file()) { - switch (errno) { - case ENOENT: - throw fs::FileNotFound(filename); - case EACCES: - throw fs::ReadDenied(filename); - case EIO: - throw fs::FileSystemUnknownError("Fatal I/O opening path: " + filename.string()); - } - } -#endif -} - -file_mapping::~file_mapping() { - if (handle != ipcdetail::invalid_file()) { - ipcdetail::close_file(handle); - } -} - -read_file_mapping::read_file_mapping(fs::path const& filename) -: file(filename, read_only) -{ - offset_t size; - ipcdetail::get_file_size(file.get_mapping_handle().handle, size); - file_size = static_cast(size); -} - -read_file_mapping::~read_file_mapping() { } - -const char *read_file_mapping::read() { - return read(0, size()); -} - -const char *read_file_mapping::read(int64_t s_offset, uint64_t length) { auto offset = static_cast(s_offset); if (offset + length > file_size) - throw InternalError("Attempted to map beyond end of file", nullptr); + throw agi::InternalError("Attempted to map beyond end of file", nullptr); // Check if we can just use the current mapping if (region && offset >= mapping_start && offset + length <= mapping_start + region->get_size()) @@ -108,12 +60,103 @@ const char *read_file_mapping::read(int64_t s_offset, uint64_t length) { throw std::bad_alloc(); try { - region = agi::util::make_unique(file, read_only, mapping_start, static_cast(length)); + region = agi::util::make_unique(file, mode, mapping_start, static_cast(length)); } catch (interprocess_exception const&) { - throw fs::FileSystemUnknownError("Failed mapping a view of the file"); + throw agi::fs::FileSystemUnknownError("Failed mapping a view of the file"); } return static_cast(region->get_address()) + offset - mapping_start; } + +} + +namespace agi { +file_mapping::file_mapping(fs::path const& filename, bool temporary) +#ifdef _WIN32 +: handle(CreateFileW(filename.wstring().c_str(), + temporary ? read_write : read_only, + temporary ? FILE_SHARE_READ | FILE_SHARE_WRITE : FILE_SHARE_READ, + nullptr, + temporary ? OPEN_ALWAYS : OPEN_EXISTING, + 0, 0)) +{ + if (handle == ipcdetail::invalid_file()) { + switch (GetLastError()) { + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + throw fs::FileNotFound(filename); + case ERROR_ACCESS_DENIED: + throw fs::ReadDenied(filename); + default: + throw fs::FileSystemUnknownError(util::ErrorString(GetLastError())); + } + } +#else +: handle(temporary + ? ipcdetail::create_or_open_file(filename.string().c_str(), read_write) + : ipcdetail::open_existing_file(filename.string().c_str(), read_only)) +{ + if (handle == ipcdetail::invalid_file()) { + switch (errno) { + case ENOENT: + throw fs::FileNotFound(filename); + case EACCES: + throw fs::ReadDenied(filename); + case EIO: + throw fs::FileSystemUnknownError("Fatal I/O opening path: " + filename.string()); + } + } +#endif +} + +file_mapping::~file_mapping() { + if (handle != ipcdetail::invalid_file()) { + ipcdetail::close_file(handle); + } +} + +read_file_mapping::read_file_mapping(fs::path const& filename) +: file(filename, false) +{ + offset_t size; + ipcdetail::get_file_size(file.get_mapping_handle().handle, size); + file_size = static_cast(size); +} + +read_file_mapping::~read_file_mapping() { } + +const char *read_file_mapping::read() { + return read(0, size()); +} + +const char *read_file_mapping::read(int64_t offset, uint64_t length) { + return map(offset, length, read_only, file_size, file, region, mapping_start); +} + +temp_file_mapping::temp_file_mapping(fs::path const& filename, uint64_t size) +: file(filename, true) +, file_size(size) +{ + auto handle = file.get_mapping_handle().handle; +#ifdef _WIN32 + LARGE_INTEGER li; + li.QuadPart = size; + SetFilePointerEx(handle, li, nullptr, FILE_BEGIN); + SetEndOfFile(handle); +#else + ftruncate(handle, size); + unlink(filename.string().c_str()); +#endif +} + +temp_file_mapping::~temp_file_mapping() { } + +const char *temp_file_mapping::read(int64_t offset, uint64_t length) { + return write(offset, length); +} + +char *temp_file_mapping::write(int64_t offset, uint64_t length) { + return map(offset, length, read_write, file_size, file, region, mapping_start); +} } diff --git a/libaegisub/include/libaegisub/file_mapping.h b/libaegisub/include/libaegisub/file_mapping.h index ce531404e..d5136a51a 100644 --- a/libaegisub/include/libaegisub/file_mapping.h +++ b/libaegisub/include/libaegisub/file_mapping.h @@ -25,7 +25,7 @@ namespace agi { boost::interprocess::file_handle_t handle; public: - file_mapping(fs::path const& filename, boost::interprocess::mode_t mode); + file_mapping(fs::path const& filename, bool temporary); ~file_mapping(); boost::interprocess::mapping_handle_t get_mapping_handle() const { return boost::interprocess::ipcdetail::mapping_handle_from_file_handle(handle); @@ -46,4 +46,18 @@ namespace agi { const char *read(int64_t offset, uint64_t length); const char *read(); // Map the entire file }; + + class temp_file_mapping { + file_mapping file; + std::unique_ptr region; + uint64_t mapping_start = 0; + uint64_t file_size = 0; + + public: + temp_file_mapping(fs::path const& filename, uint64_t size); + ~temp_file_mapping(); + + const char *read(int64_t offset, uint64_t length); + char *write(int64_t offset, uint64_t length); + }; } diff --git a/src/audio_provider_hd.cpp b/src/audio_provider_hd.cpp index 5704eb2ec..611ab9976 100644 --- a/src/audio_provider_hd.cpp +++ b/src/audio_provider_hd.cpp @@ -1,37 +1,19 @@ -// Copyright (c) 2005-2006, Rodrigo Braz Monteiro, Fredrik Mellbin -// All rights reserved. +// Copyright (c) 2014, Thomas Goyne // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. // -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of the Aegisub Group nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // // Aegisub Project http://www.aegisub.org/ -/// @file audio_provider_hd.cpp -/// @brief Caching audio provider using a file for backing -/// @ingroup audio_input -/// - #include "config.h" #include "audio_provider_hd.h" @@ -39,82 +21,55 @@ #include "audio_controller.h" #include "compat.h" #include "options.h" -#include "utils.h" -#include #include #include #include -#include #include #include #include #include #include - -namespace { -agi::fs::path cache_dir() { - std::string path = OPT_GET("Audio/Cache/HD/Location")->GetString(); - if (path == "default") - path = "?temp"; - - return config::path->MakeAbsolute(config::path->Decode(path), "?temp"); -} - -agi::fs::path cache_path() { - std::string pattern = OPT_GET("Audio/Cache/HD/Name")->GetString(); - if (!boost::contains(pattern, "%02i")) pattern = "audio%02i.tmp"; - boost::replace_all(pattern, "%02i", "%%%%-%%%%-%%%%-%%%%"); - return unique_path(cache_dir()/pattern); -} -} +#include +#include HDAudioProvider::HDAudioProvider(std::unique_ptr src, agi::BackgroundRunner *br) : AudioProviderWrapper(std::move(src)) -, cache_filename(cache_path()) { + auto path = OPT_GET("Audio/Cache/HD/Location")->GetString(); + if (path == "default") + path = "?temp"; + auto cache_dir = config::path->MakeAbsolute(config::path->Decode(path), "?temp"); + + auto bps = bytes_per_sample * channels; + // Check free space - if ((uint64_t)num_samples * channels * bytes_per_sample > agi::fs::FreeSpace(cache_dir())) - throw agi::AudioCacheOpenError("Not enough free disk space in " + cache_dir().string() + " to cache the audio", nullptr); + if ((uint64_t)num_samples * bps > agi::fs::FreeSpace(cache_dir)) + throw agi::AudioCacheOpenError("Not enough free disk space in " + cache_dir.string() + " to cache the audio", nullptr); - try { - { - agi::io::Save out(cache_filename, true); - br->Run(bind(&HDAudioProvider::FillCache, this, source.get(), &out.Get(), std::placeholders::_1)); + auto filename = str(boost::format("audio-%lld-%lld") + % (long long)time(nullptr) + % (long long)boost::interprocess::ipcdetail::get_current_process_id()); + + file = agi::util::make_unique(cache_dir / filename, num_samples * bps); + br->Run([&] (agi::ProgressSink *ps) { + ps->SetMessage(from_wx(_("Reading to Hard Disk cache"))); + + int64_t block = 65536; + for (int64_t i = 0; i < num_samples; i += block) { + block = std::min(block, num_samples - i); + source->GetAudio(file->write(i * bps, block * bps), i, block); + ps->SetProgress(i, num_samples); + if (ps->IsCancelled()) return; } - file = agi::util::make_unique(cache_filename); - } - catch (...) { - agi::fs::Remove(cache_filename); - throw; - } + }); } -HDAudioProvider::~HDAudioProvider() { - file.reset(); // explicitly close the file so we can delete it - agi::fs::Remove(cache_filename); -} +HDAudioProvider::~HDAudioProvider() { } void HDAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const { start *= channels * bytes_per_sample; count *= channels * bytes_per_sample; memcpy(buf, file->read(start, count), count); } - -void HDAudioProvider::FillCache(AudioProvider *src, std::ostream *out, agi::ProgressSink *ps) { - ps->SetMessage(from_wx(_("Reading to Hard Disk cache"))); - - int64_t block = 65536; - std::vector read_buf; - read_buf.resize(block * channels * bytes_per_sample); - - for (int64_t i = 0; i < num_samples; i += block) { - block = std::min(block, num_samples - i); - src->GetAudio(&read_buf[0], i, block); - out->write(&read_buf[0], block * channels * bytes_per_sample); - ps->SetProgress(i, num_samples); - - if (ps->IsCancelled()) return; - } -} diff --git a/src/audio_provider_hd.h b/src/audio_provider_hd.h index c65ede51b..549fd8fd0 100644 --- a/src/audio_provider_hd.h +++ b/src/audio_provider_hd.h @@ -1,55 +1,28 @@ -// Copyright (c) 2006, Rodrigo Braz Monteiro -// All rights reserved. +// Copyright (c) 2014, Thomas Goyne // -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. // -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// * Neither the name of the Aegisub Group nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -// POSSIBILITY OF SUCH DAMAGE. +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // // Aegisub Project http://www.aegisub.org/ -/// @file audio_provider_hd.h -/// @see audio_provider_hd.cpp -/// @ingroup audio_input -/// - #include "include/aegisub/audio_provider.h" namespace agi { class BackgroundRunner; - class ProgressSink; - class read_file_mapping; + class temp_file_mapping; } class HDAudioProvider final : public AudioProviderWrapper { - /// Name of the file which the decoded audio is written to - agi::fs::path cache_filename; - std::unique_ptr file; - - /// Fill the cache with all of the data from the source audio provider - /// @param src Audio data to cache - /// @param file File to write to - /// @param ps Sink for progress reporting - void FillCache(AudioProvider *src, std::ostream *file, agi::ProgressSink *ps); + std::unique_ptr file; void FillBuffer(void *buf, int64_t start, int64_t count) const override; diff --git a/src/libresrc/default_config.json b/src/libresrc/default_config.json index 00c672eed..aed115ae9 100644 --- a/src/libresrc/default_config.json +++ b/src/libresrc/default_config.json @@ -29,7 +29,6 @@ "Cache" : { "HD" : { "Location" : "default", - "Name" : "audio%02i.tmp" }, "Type" : 1 }, diff --git a/src/libresrc/osx/default_config.json b/src/libresrc/osx/default_config.json index 2cadaa80d..f6f5630bb 100644 --- a/src/libresrc/osx/default_config.json +++ b/src/libresrc/osx/default_config.json @@ -29,7 +29,6 @@ "Cache" : { "HD" : { "Location" : "default", - "Name" : "audio%02i.tmp" }, "Type" : 1 }, diff --git a/src/preferences.cpp b/src/preferences.cpp index d7590ecf0..1956b920f 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -507,9 +507,7 @@ Advanced_Audio::Advanced_Audio(wxTreebook *book, Preferences *parent): OptionPag const wxString ct_arr[3] = { _("None (NOT RECOMMENDED)"), _("RAM"), _("Hard Disk") }; wxArrayString ct_choice(3, ct_arr); OptionChoice(cache, _("Cache type"), ct_choice, "Audio/Cache/Type"); - OptionBrowse(cache, _("Path"), "Audio/Cache/HD/Location"); - OptionAdd(cache, _("File name"), "Audio/Cache/HD/Name"); wxFlexGridSizer *spectrum = PageSizer(_("Spectrum"));