introduce partial files

This commit is contained in:
Frédéric Mangano-Tarumi 2018-12-02 12:12:58 -05:00
parent 289391a9df
commit a74ea34352
5 changed files with 137 additions and 1 deletions

View File

@ -22,6 +22,7 @@ add_library(
src/cli.cc
src/ogg.cc
src/opus.cc
src/system.cc
)
target_link_libraries(libopustags PUBLIC ${OGG_LIBRARIES})

View File

@ -83,6 +83,11 @@ struct status {
std::string message;
};
/***********************************************************************************************//**
* \defgroup system System
* \{
*/
/**
* Smart auto-closing FILE* handle.
*
@ -92,6 +97,37 @@ struct file : std::unique_ptr<FILE, decltype(&fclose)> {
file(FILE* f = nullptr) : std::unique_ptr<FILE, decltype(&fclose)>(f, &fclose) {}
};
/**
* A partial file is a temporary file created to store the result of something. When it is complete,
* it is moved to a final destination. Open it with #open and then you can either #commit it to save
* it to its destination, or you can #abort to delete the temporary file. When the #partial_file
* object is destroyed, it deletes the currently opened temporary file, if any.
*/
class partial_file {
public:
~partial_file() { abort(); }
/**
* Open a temporary file meant to be moved to the specified destination file path. The
* temporary file is created in the same directory as its destination in order to make the
* final move operation instant.
*/
ot::status open(const char* destination);
/** Close then move the partial file to its final location. */
ot::status commit();
/** Delete the temporary file. */
void abort();
/** Get the underlying FILE* handle. */
FILE* get() { return file.get(); }
/** Get the name of the temporary file. */
const char* name() const { return file == nullptr ? nullptr : temporary_name.c_str(); }
private:
std::string temporary_name;
std::string final_name;
ot::file file;
};
/** \} */
/***********************************************************************************************//**
* \defgroup ogg Ogg
* \{

52
src/system.cc Normal file
View File

@ -0,0 +1,52 @@
/**
* \file src/system.cc
* \ingroup system
*
* Provide a high-level interface to system-related features, like filesystem manipulations.
*
* Ideally, all OS-specific features should be grouped here.
*
* This modules shoumd not depend on any other opustags module.
*/
#include <opustags.h>
#include <string.h>
ot::status ot::partial_file::open(const char* destination)
{
abort();
final_name = destination;
temporary_name = final_name + ".XXXXXX.part";
int fd = mkstemps(const_cast<char*>(temporary_name.data()), 5);
if (fd == -1)
return {st::standard_error,
"Could not create a partial file for '" + final_name + "': " +
strerror(errno)};
file = fdopen(fd, "w");
if (file == nullptr)
return {st::standard_error,
"Could not get the partial file handle to '" + temporary_name + "': " +
strerror(errno)};
return st::ok;
}
ot::status ot::partial_file::commit()
{
if (file == nullptr)
return st::ok;
file.reset();
if (rename(temporary_name.c_str(), final_name.c_str()) == -1)
return {st::standard_error,
"Could not move the result file '" + temporary_name + "' to '" +
final_name + "': " + strerror(errno) + "."};
return st::ok;
}
void ot::partial_file::abort()
{
if (file == nullptr)
return;
file.reset();
remove(temporary_name.c_str());
}

View File

@ -1,3 +1,6 @@
add_executable(system.t EXCLUDE_FROM_ALL system.cc)
target_link_libraries(system.t libopustags)
add_executable(opus.t EXCLUDE_FROM_ALL opus.cc)
target_link_libraries(opus.t libopustags)
@ -12,5 +15,5 @@ configure_file(gobble.opus . COPYONLY)
add_custom_target(
check
COMMAND prove "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}"
DEPENDS opustags gobble.opus opus.t ogg.t cli.t
DEPENDS opustags gobble.opus system.t opus.t ogg.t cli.t
)

44
t/system.cc Normal file
View File

@ -0,0 +1,44 @@
#include <opustags.h>
#include "tap.h"
#include <string.h>
#include <unistd.h>
void check_partial_files()
{
static const char* result = "partial_file.test";
std::string name;
{
ot::partial_file bad_tmp;
if (bad_tmp.open("/dev/null") != ot::st::standard_error)
throw failure("cannot open a device as a partial file");
if (bad_tmp.open(result) != ot::st::ok)
throw failure("could not open a simple result file");
name = bad_tmp.name();
if (name.size() != strlen(result) + 12 ||
name.compare(0, strlen(result), result) != 0)
throw failure("the temporary name is surprising: " + name);
}
if (access(name.c_str(), F_OK) != -1)
throw failure("the bad temporary file was not deleted");
ot::partial_file good_tmp;
if (good_tmp.open(result) != ot::st::ok)
throw failure("could not open the result file");
name = good_tmp.name();
if (good_tmp.commit() != ot::st::ok)
throw failure("could not commit the result file");
if (access(name.c_str(), F_OK) != -1)
throw failure("the good temporary file was not deleted");
if (access(result, F_OK) != 0)
throw failure("the final result file is not there");
if (remove(result) != 0)
throw failure("could not remove the result file");
}
int main(int argc, char **argv)
{
std::cout << "1..1\n";
run(check_partial_files, "test partial files");
return 0;
}