Temporary file (#498)

* TemporaryFile: use shared file-handle.

* Tests. Uncomment all.
This commit is contained in:
Leonid Stryzhevskyi 2021-10-27 01:34:19 +03:00 committed by GitHub
parent e65a686982
commit bc3d17750d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 374 additions and 24 deletions

View File

@ -105,6 +105,8 @@ add_library(oatpp
oatpp/core/data/share/MemoryLabel.hpp
oatpp/core/data/share/StringTemplate.cpp
oatpp/core/data/share/StringTemplate.hpp
oatpp/core/data/share/TemporaryFile.cpp
oatpp/core/data/share/TemporaryFile.hpp
oatpp/core/data/stream/BufferStream.cpp
oatpp/core/data/stream/BufferStream.hpp
oatpp/core/data/stream/ChunkedBuffer.cpp

View File

@ -0,0 +1,112 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#include "TemporaryFile.hpp"
#include "oatpp/core/data/stream/BufferStream.hpp"
#include "oatpp/encoding/Hex.hpp"
#include "oatpp/core/utils/Random.hpp"
namespace oatpp { namespace data { namespace share {
TemporaryFile::FileHandle::~FileHandle() {
if(fileName) {
std::remove(fileName->c_str());
}
}
oatpp::String TemporaryFile::concatDirAndName(const oatpp::String& dir, const oatpp::String& filename) {
if(dir && dir->size() > 0) {
auto lastChar = dir->data()[dir->size() - 1];
if(lastChar != '/' && lastChar != '\\') {
return dir + "/" + filename;
}
return dir + filename;
}
return filename;
}
oatpp::String TemporaryFile::constructRandomFilename(const oatpp::String& dir, v_int32 randomWordSizeBytes) {
std::unique_ptr<v_char8[]> buff(new v_char8[randomWordSizeBytes]);
utils::random::Random::randomBytes(buff.get(), randomWordSizeBytes);
data::stream::BufferOutputStream s(randomWordSizeBytes * 2 + 4);
encoding::Hex::encode(&s, buff.get(), randomWordSizeBytes, encoding::Hex::ALPHABET_LOWER);
s << ".tmp";
return concatDirAndName(dir, s.toString());
}
TemporaryFile::TemporaryFile(const oatpp::String& tmpDirectory, v_int32 randomWordSizeBytes)
: m_handle(std::make_shared<FileHandle>(constructRandomFilename(tmpDirectory, randomWordSizeBytes)))
{}
TemporaryFile::TemporaryFile(const oatpp::String& tmpDirectory, const oatpp::String& tmpFileName)
: m_handle(std::make_shared<FileHandle>(concatDirAndName(tmpDirectory, tmpFileName)))
{}
oatpp::String TemporaryFile::getFullFileName() {
if(m_handle) {
return m_handle->fileName;
}
return nullptr;
}
data::stream::FileOutputStream TemporaryFile::openOutputStream() {
if(m_handle) {
return data::stream::FileOutputStream(m_handle->fileName->c_str(), "wb", m_handle);
}
throw std::runtime_error("[oatpp::data::share::TemporaryFile::openOutputStream()]: Error. FileHandle is NOT initialized.");
}
data::stream::FileInputStream TemporaryFile::openInputStream() {
if(m_handle) {
return data::stream::FileInputStream(m_handle->fileName->c_str(), m_handle);
}
throw std::runtime_error("[oatpp::data::share::TemporaryFile::openInputStream()]: Error. FileHandle is NOT initialized.");
}
std::shared_ptr<data::stream::FileOutputStream> TemporaryFile::openOutputStreamShared() {
if(m_handle) {
return std::make_shared<data::stream::FileOutputStream>(m_handle->fileName->c_str(), "wb", m_handle);
}
throw std::runtime_error("[oatpp::data::share::TemporaryFile::openOutputStreamShared()]: Error. FileHandle is NOT initialized.");
}
std::shared_ptr<data::stream::FileInputStream> TemporaryFile::openInputStreamShared() {
if(m_handle) {
return std::make_shared<data::stream::FileInputStream>(m_handle->fileName->c_str(), m_handle);
}
throw std::runtime_error("[oatpp::data::share::TemporaryFile::openInputStreamShared()]: Error. FileHandle is NOT initialized.");
}
bool TemporaryFile::moveFile(const oatpp::String& fullFileName) {
if(m_handle) {
return std::rename(m_handle->fileName->c_str(), fullFileName->c_str()) == 0;
}
return false;
}
}}}

View File

@ -0,0 +1,134 @@
/***************************************************************************
*
* Project _____ __ ____ _ _
* ( _ ) /__\ (_ _)_| |_ _| |_
* )(_)( /(__)\ )( (_ _)(_ _)
* (_____)(__)(__)(__) |_| |_|
*
*
* Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
***************************************************************************/
#ifndef oatpp_data_share_TemporaryFile_hpp
#define oatpp_data_share_TemporaryFile_hpp
#include "oatpp/core/data/stream/FileStream.hpp"
#include "oatpp/core/Types.hpp"
namespace oatpp { namespace data { namespace share {
/**
* Temporary file - the file which gets deleted when the destructor is called
* (more precisely when all copies of the same `TemporaryFile` object deleted). <br>
* The `TemporaryFile` object internally stores a `shared_ptr` to a file handle.
* When file handle deleted it also deletes the underlying file. <br>
* Thus it's safe to copy `TemporaryFile` object and you may treat `TemporaryFile` object
* as a shared_ptr to a temporary file.
*/
class TemporaryFile {
private:
/*
* Shared handle.
* File is deleted on handle destroy.
*/
struct FileHandle {
oatpp::String fileName;
FileHandle(const oatpp::String& fullFileName)
: fileName(fullFileName)
{}
~FileHandle();
};
private:
static oatpp::String concatDirAndName(const oatpp::String& dir, const oatpp::String& filename);
static oatpp::String constructRandomFilename(const oatpp::String& dir, v_int32 randomWordSizeBytes);
private:
std::shared_ptr<FileHandle> m_handle;
public:
/**
* Default constructor.
*/
TemporaryFile() = default;
/**
* Constructor. <br>
* Create temporary file with a random name in the `tmpDirectory`. <br>
* The actual file will be created only after first write to that file. <br>
* Example of the generated random file name: `f7c6ecd44024ff31.tmp`.
* @param tmpDirectory - directory where to create a temporary file.
* @param randomWordSizeBytes - number of random bytes to generate file name.
*/
TemporaryFile(const oatpp::String& tmpDirectory, v_int32 randomWordSizeBytes = 8);
/**
* Constructor.<br>
* Create temporary file with the `tmpFileName` name in the `tmpDirectory`. <br>
* @param tmpDirectory - directory where to create a temporary file.
* @param tmpFileName - predefined name for the temporary file.
*/
TemporaryFile(const oatpp::String& tmpDirectory, const oatpp::String& tmpFileName);
/**
* Get full name of a temporary file.
* @return
*/
oatpp::String getFullFileName();
/**
* Open output stream to a temporary file. <br>
* *Note: stream also captures file-handle. The temporary file won't be deleted until the stream is deleted.*
* @return - &id:oatpp::data::stream::FileOutputStream;.
*/
data::stream::FileOutputStream openOutputStream();
/**
* Open input stream to a temporary file.
* *Note: stream also captures file-handle. The temporary file won't be deleted until the stream is deleted.*
* @return - &id:oatpp::data::stream::FileInputStream;.
*/
data::stream::FileInputStream openInputStream();
/**
* Open output stream to a temporary file. <br>
* *Note: stream also captures file-handle. The temporary file won't be deleted until the stream is deleted.*
* @return - `std::shared_ptr` to &id:oatpp::data::stream::FileOutputStream;.
*/
std::shared_ptr<data::stream::FileOutputStream> openOutputStreamShared();
/**
* Open input stream to a temporary file.
* *Note: stream also captures file-handle. The temporary file won't be deleted until the stream is deleted.*
* @return - `std::shared_ptr` &id:oatpp::data::stream::FileInputStream;.
*/
std::shared_ptr<data::stream::FileInputStream> openInputStreamShared();
/**
* Move payload to a different file. <br>
* @param fullFileName - full-file-name. Note: all the parent folders must exist.
* @return - `true` - file was successfully moved, `false` - otherwise.
*/
bool moveFile(const oatpp::String& fullFileName);
};
}}}
#endif //oatpp_data_share_TemporaryFile_hpp

View File

@ -32,14 +32,24 @@ namespace oatpp { namespace data{ namespace stream {
oatpp::data::stream::DefaultInitializedContext FileInputStream::DEFAULT_CONTEXT(data::stream::StreamType::STREAM_FINITE);
FileInputStream::FileInputStream(std::FILE* file, bool ownsFile)
FileInputStream::FileInputStream(FileInputStream&& other)
: m_file(other.m_file)
, m_ownsFile(other.m_ownsFile)
, m_ioMode(other.m_ioMode)
{
other.m_file = nullptr;
other.m_ownsFile = false;
}
FileInputStream::FileInputStream(std::FILE* file, bool ownsFile, const std::shared_ptr<void>& captureData)
: m_file(file)
, m_ownsFile(ownsFile)
, m_ioMode(IOMode::ASYNCHRONOUS)
, m_capturedData(captureData)
{}
FileInputStream::FileInputStream(const char* filename)
: FileInputStream(std::fopen(filename, "rb"), true)
FileInputStream::FileInputStream(const char* filename, const std::shared_ptr<void>& captureData)
: FileInputStream(std::fopen(filename, "rb"), true, captureData)
{
if(!m_file) {
OATPP_LOGE("[oatpp::data::stream::FileInputStream::FileInputStream(filename)]", "Error. Can't open file '%s'.", filename);
@ -48,9 +58,7 @@ FileInputStream::FileInputStream(const char* filename)
}
FileInputStream::~FileInputStream() {
if(m_ownsFile && m_file) {
std::fclose(m_file);
}
this->close();
}
std::FILE* FileInputStream::getFile() {
@ -59,7 +67,10 @@ std::FILE* FileInputStream::getFile() {
v_io_size FileInputStream::read(void *data, v_buff_size count, async::Action& action) {
(void) action;
return std::fread(data, 1, count, m_file);
if(m_file != nullptr) {
return std::fread(data, 1, count, m_file);
}
return oatpp::IOError::BROKEN_PIPE;
}
void FileInputStream::setInputStreamIOMode(IOMode ioMode) {
@ -74,19 +85,51 @@ Context& FileInputStream::getInputStreamContext() {
return DEFAULT_CONTEXT;
}
void FileInputStream::close() {
if(m_ownsFile && m_file) {
std::fclose(m_file);
}
}
FileInputStream& FileInputStream::operator=(FileInputStream&& other) {
if(this != &other) {
close();
}
m_file = other.m_file;
m_ownsFile = other.m_ownsFile;
m_ioMode = other.m_ioMode;
other.m_file = nullptr;
other.m_ownsFile = false;
return *this;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FileOutputStream
oatpp::data::stream::DefaultInitializedContext FileOutputStream::DEFAULT_CONTEXT(data::stream::StreamType::STREAM_FINITE);
FileOutputStream::FileOutputStream(std::FILE* file, bool ownsFile)
FileOutputStream::FileOutputStream(FileOutputStream&& other)
: m_file(other.m_file)
, m_ownsFile(other.m_ownsFile)
, m_ioMode(other.m_ioMode)
{
other.m_file = nullptr;
other.m_ownsFile = false;
}
FileOutputStream::FileOutputStream(std::FILE* file, bool ownsFile, const std::shared_ptr<void>& captureData)
: m_file(file)
, m_ownsFile(ownsFile)
, m_ioMode(IOMode::ASYNCHRONOUS)
, m_capturedData(captureData)
{}
FileOutputStream::FileOutputStream(const char* filename, const char* mode)
: FileOutputStream(std::fopen(filename, mode), true)
FileOutputStream::FileOutputStream(const char* filename, const char* mode, const std::shared_ptr<void>& captureData)
: FileOutputStream(std::fopen(filename, mode), true, captureData)
{
if(!m_file) {
OATPP_LOGE("[oatpp::data::stream::FileOutputStream::FileOutputStream(filename, mode)]", "Error. Can't open file '%s'.", filename);
@ -95,9 +138,7 @@ FileOutputStream::FileOutputStream(const char* filename, const char* mode)
}
FileOutputStream::~FileOutputStream() {
if(m_ownsFile && m_file) {
std::fclose(m_file);
}
this->close();
}
std::FILE* FileOutputStream::getFile() {
@ -121,4 +162,26 @@ Context& FileOutputStream::getOutputStreamContext() {
return DEFAULT_CONTEXT;
}
void FileOutputStream::close() {
if(m_ownsFile && m_file) {
std::fclose(m_file);
}
}
FileOutputStream& FileOutputStream::operator=(FileOutputStream&& other) {
if(this != &other) {
close();
}
m_file = other.m_file;
m_ownsFile = other.m_ownsFile;
m_ioMode = other.m_ioMode;
other.m_file = nullptr;
other.m_ownsFile = false;
return *this;
}
}}}

View File

@ -41,20 +41,33 @@ private:
std::FILE* m_file;
bool m_ownsFile;
IOMode m_ioMode;
private:
std::shared_ptr<void> m_capturedData;
public:
FileInputStream(const FileInputStream&) = delete;
FileInputStream &operator=(const FileInputStream&) = delete;
/**
* Move constructor.
* @param other
*/
FileInputStream(FileInputStream&& other);
/**
* Constructor.
* @param file - file.
* @param ownsFile - if `true` then call close on `FileInputStream` destruction.
* @param captureData - capture auxiliary data to not get deleted until it's done with the stream.
*/
FileInputStream(std::FILE* file, bool ownsFile);
FileInputStream(std::FILE* file, bool ownsFile, const std::shared_ptr<void>& captureData = nullptr);
/**
* Constructor.
* @param filename - name of the file.
* @param captureData - capture auxiliary data to not get deleted until it's done with the stream.
*/
FileInputStream(const char* filename);
FileInputStream(const char* filename, const std::shared_ptr<void>& captureData = nullptr);
/**
* Virtual destructor.
@ -96,6 +109,13 @@ public:
*/
Context& getInputStreamContext() override;
/**
* Close file.
*/
void close();
FileInputStream& operator=(FileInputStream&& other);
};
/**
@ -108,21 +128,34 @@ private:
std::FILE* m_file;
bool m_ownsFile;
IOMode m_ioMode;
private:
std::shared_ptr<void> m_capturedData;
public:
FileOutputStream(const FileOutputStream&) = delete;
FileOutputStream &operator=(const FileOutputStream&) = delete;
/**
* Move constructor.
* @param other
*/
FileOutputStream(FileOutputStream&& other);
/**
* Constructor.
* @param file - file.
* @param ownsFile - if `true` then call close on `FileInputStream` destruction.
* @param captureData - capture auxiliary data to not get deleted until it's done with the stream.
*/
FileOutputStream(std::FILE* file, bool ownsFile);
FileOutputStream(std::FILE* file, bool ownsFile, const std::shared_ptr<void>& captureData = nullptr);
/**
* Constructor.
* @param filename - name of the file.
* @param mode - ("wb" - create new/override, "ab" - create new/append).
* @param captureData - capture auxiliary data to not get deleted until it's done with the stream.
*/
FileOutputStream(const char* filename, const char* mode = "wb");
FileOutputStream(const char* filename, const char* mode = "wb", const std::shared_ptr<void>& captureData = nullptr);
/**
* Virtual destructor.
@ -164,6 +197,13 @@ public:
*/
Context& getOutputStreamContext() override;
/**
* Close file.
*/
void close();
FileOutputStream& operator=(FileOutputStream&& other);
};
}}}

View File

@ -62,7 +62,6 @@
#include "oatpp/core/async/Coroutine.hpp"
#include "oatpp/core/Types.hpp"
#include "oatpp/core/concurrency/SpinLock.hpp"
#include "oatpp/core/base/Environment.hpp"
#include <iostream>
@ -218,21 +217,21 @@ void runTests() {
}
int main() {
oatpp::base::Environment::init();
runTests();
/* Print how much objects were created during app running, and what have left-probably leaked */
/* Disable object counting for release builds using '-D OATPP_DISABLE_ENV_OBJECT_COUNTERS' flag for better performance */
std::cout << "\nEnvironment:\n";
std::cout << "objectsCount = " << oatpp::base::Environment::getObjectsCount() << "\n";
std::cout << "objectsCreated = " << oatpp::base::Environment::getObjectsCreated() << "\n\n";
OATPP_ASSERT(oatpp::base::Environment::getObjectsCount() == 0);
oatpp::base::Environment::destroy();
return 0;
}