refactor: add tr_saveFile() (#2267)

* refactor: add tr_saveFile()

* refactor: add tr_ctorSaveContents()
This commit is contained in:
Charles Kerr 2021-12-04 19:32:35 -06:00 committed by GitHub
parent cc4cf1da5a
commit c656bee061
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 157 additions and 116 deletions

View File

@ -243,8 +243,6 @@ int tr_main(int argc, char* argv[])
tr_torrent* tor = nullptr;
tr_variant settings;
char const* configDir;
uint8_t* fileContents;
size_t fileLength;
tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
@ -304,12 +302,11 @@ int tr_main(int argc, char* argv[])
ctor = tr_ctorNew(h);
fileContents = tr_loadFile(torrentPath, &fileLength, nullptr);
tr_ctorSetPaused(ctor, TR_FORCE, false);
if (fileContents != nullptr)
if (tr_sys_path_exists(torrentPath, nullptr))
{
tr_ctorSetMetainfo(ctor, fileContents, fileLength);
tr_ctorSetMetainfoFromFile(ctor, torrentPath);
}
else if (memcmp(torrentPath, "magnet:?", 8) == 0)
{
@ -334,8 +331,6 @@ int tr_main(int argc, char* argv[])
return EXIT_FAILURE;
}
tr_free(fileContents);
tor = tr_torrentNew(ctor, nullptr, nullptr);
tr_ctorFree(ctor);

View File

@ -1148,7 +1148,7 @@ void Session::Impl::add_file_async_callback(
{
g_message(_("Couldn't read \"%s\""), file->get_parse_name().c_str());
}
else if (tr_ctorSetMetainfo(ctor, (uint8_t const*)contents, length) == 0)
else if (tr_ctorSetMetainfo(ctor, contents, length) == 0)
{
add_ctor(ctor, do_prompt, do_notify);
}

View File

@ -12,6 +12,8 @@
#include <vector>
#include "transmission.h"
#include "error.h"
#include "file.h"
#include "magnet-metainfo.h"
#include "session.h"
@ -88,12 +90,11 @@ static int parseMetainfoContents(tr_ctor* ctor)
return ctor->isSet_metainfo ? 0 : EILSEQ;
}
int tr_ctorSetMetainfo(tr_ctor* ctor, void const* metainfo, size_t len)
int tr_ctorSetMetainfo(tr_ctor* ctor, char const* metainfo, size_t len)
{
clearMetainfo(ctor);
ctor->contents.resize(len);
std::copy_n(static_cast<char const*>(metainfo), len, std::begin(ctor->contents));
ctor->contents.assign(metainfo, metainfo + len);
return parseMetainfoContents(ctor);
}
@ -115,7 +116,7 @@ int tr_ctorSetMetainfoFromMagnetLink(tr_ctor* ctor, char const* magnet_link)
mm.toVariant(&tmp);
auto len = size_t{};
char* const str = tr_variantToStr(&tmp, TR_VARIANT_FMT_BENC, &len);
auto const err = tr_ctorSetMetainfo(ctor, (uint8_t const*)str, len);
auto const err = tr_ctorSetMetainfo(ctor, str, len);
tr_free(str);
tr_variantFree(&tmp);
@ -164,6 +165,20 @@ int tr_ctorSetMetainfoFromFile(tr_ctor* ctor, char const* filename)
return 0;
}
bool tr_ctorSaveContents(tr_ctor const* ctor, char const* filename, tr_error** error)
{
TR_ASSERT(ctor != nullptr);
TR_ASSERT(filename != nullptr);
if (std::empty(ctor->contents))
{
tr_error_set_literal(error, EINVAL, "torrent ctor has no contents to save");
return false;
}
return tr_saveFile(filename, { std::data(ctor->contents), std::size(ctor->contents) }, error);
}
/***
****
***/

View File

@ -763,17 +763,12 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
/* maybe save our own copy of the metainfo */
if (tr_ctorGetSave(ctor))
{
tr_variant const* val = nullptr;
if (tr_ctorGetMetainfo(ctor, &val))
tr_error* error = nullptr;
if (!tr_ctorSaveContents(ctor, tor->info.torrent, &error))
{
char const* path = tor->info.torrent;
int const err = tr_variantToFile(val, TR_VARIANT_FMT_BENC, path);
if (err != 0)
{
tr_torrentSetLocalError(tor, "Unable to save torrent file: %s", tr_strerror(err));
}
tr_torrentSetLocalError(tor, "Unable to save torrent file: %s (%d)", error->message, error->code);
}
tr_error_clear(&error);
}
tor->tiers = tr_announcerAddTorrent(tor, onTrackerResponse, nullptr);

View File

@ -52,6 +52,14 @@ void tr_ctorInitTorrentPriorities(tr_ctor const* ctor, tr_torrent* tor);
void tr_ctorInitTorrentWanted(tr_ctor const* ctor, tr_torrent* tor);
bool tr_ctorSaveContents(tr_ctor const* ctor, char const* filename, tr_error** error);
bool tr_ctorGetMetainfo(tr_ctor const* ctor, tr_variant const** setme);
tr_session* tr_ctorGetSession(tr_ctor const* ctor);
bool tr_ctorGetIncompleteDir(tr_ctor const* ctor, char const** setmeIncompleteDir);
/**
***
**/
@ -240,7 +248,7 @@ public:
/// WANTED
bool pieceIsWanted(tr_piece_index_t piece) const final
bool pieceIsWanted(tr_piece_index_t piece) const final override
{
return files_wanted_.pieceWanted(piece);
}

View File

@ -860,7 +860,7 @@ void tr_ctorSetDeleteSource(tr_ctor* ctor, bool doDelete);
int tr_ctorSetMetainfoFromMagnetLink(tr_ctor* ctor, char const* magnet);
/** @brief Set the constructor's metainfo from a raw benc already in memory */
int tr_ctorSetMetainfo(tr_ctor* ctor, void const* metainfo, size_t len);
int tr_ctorSetMetainfo(tr_ctor* ctor, char const* metainfo, size_t len);
/** @brief Set the constructor's metainfo from a local .torrent file */
int tr_ctorSetMetainfoFromFile(tr_ctor* ctor, char const* filename);
@ -902,18 +902,9 @@ bool tr_ctorGetPaused(tr_ctor const* ctor, tr_ctorMode mode, bool* setmeIsPaused
/** @brief Get the download path from this peer constructor */
bool tr_ctorGetDownloadDir(tr_ctor const* ctor, tr_ctorMode mode, char const** setmeDownloadDir);
/** @brief Get the incomplete directory from this peer constructor */
bool tr_ctorGetIncompleteDir(tr_ctor const* ctor, char const** setmeIncompleteDir);
/** @brief Get the metainfo from this peer constructor */
bool tr_ctorGetMetainfo(tr_ctor const* ctor, struct tr_variant const** setme);
/** @brief Get the "delete .torrent file" flag from this peer constructor */
bool tr_ctorGetDeleteSource(tr_ctor const* ctor, bool* setmeDoDelete);
/** @brief Get the tr_session poiner from this peer constructor */
tr_session* tr_ctorGetSession(tr_ctor const* ctor);
/** @brief Get the .torrent file that this ctor's metainfo came from,
or nullptr if tr_ctorSetMetainfoFromFile() wasn't used */
char const* tr_ctorGetSourceFile(tr_ctor const* ctor);

View File

@ -378,6 +378,51 @@ bool tr_loadFile(std::vector<char>& setme, char const* path, tr_error** error)
return true;
}
bool tr_saveFile(char const* filename_in, std::string_view contents, tr_error** error)
{
auto filename = std::string{ filename_in };
// follow symlinks to find the "real" file, to make sure the temporary
// we build with tr_sys_file_open_temp() is created on the right partition
char* real_filename = tr_sys_path_resolve(filename.c_str(), nullptr);
if (real_filename != nullptr)
{
filename = real_filename;
tr_free(real_filename);
}
// Write it to a temp file first.
// This is a safeguard against edge cases, e.g. disk full, crash while writing, etc.
auto tmp = tr_strvJoin(filename, ".tmp.XXXXXX"sv);
auto const fd = tr_sys_file_open_temp(std::data(tmp), error);
if (fd == TR_BAD_SYS_FILE)
{
return false;
}
// Save the contents. This might take >1 pass.
auto ok = bool{ true };
while (!std::empty(contents))
{
auto n_written = uint64_t{};
if (!tr_sys_file_write(fd, std::data(contents), std::size(contents), &n_written, error))
{
ok = false;
break;
}
contents.remove_prefix(n_written);
}
// If we saved it to disk successfully, move it from '.tmp' to the correct filename
if (!tr_sys_file_close(fd, error) || !ok || !tr_sys_path_rename(tmp.c_str(), filename.c_str(), error))
{
return false;
}
tr_logAddInfo(_("Saved \"%s\""), filename.c_str());
return true;
}
char* tr_buildPath(char const* first_element, ...)
{

View File

@ -81,6 +81,8 @@ uint8_t* tr_loadFile(char const* filename, size_t* size, struct tr_error** error
bool tr_loadFile(std::vector<char>& setme, char const* filename, tr_error** error = nullptr);
bool tr_saveFile(char const* filename_in, std::string_view contents, tr_error** error = nullptr);
/** @brief build a filename from a series of elements using the
platform's correct directory separator. */
char* tr_buildPath(char const* first_element, ...) TR_GNUC_NULL_TERMINATED TR_GNUC_MALLOC;

View File

@ -1205,87 +1205,18 @@ char* tr_variantToStr(tr_variant const* v, tr_variant_fmt fmt, size_t* len)
return evbuffer_free_to_str(buf, len);
}
static int writeVariantToFd(tr_variant const* v, tr_variant_fmt fmt, tr_sys_file_t fd, tr_error** error)
{
int err = 0;
struct evbuffer* buf = tr_variantToBuf(v, fmt);
char const* walk = (char const*)evbuffer_pullup(buf, -1);
uint64_t nleft = evbuffer_get_length(buf);
while (nleft > 0)
{
uint64_t n = 0;
tr_error* tmperr = nullptr;
if (!tr_sys_file_write(fd, walk, nleft, &n, &tmperr))
{
err = tmperr->code;
tr_error_propagate(error, &tmperr);
break;
}
nleft -= n;
walk += n;
}
evbuffer_free(buf);
return err;
}
int tr_variantToFile(tr_variant const* v, tr_variant_fmt fmt, char const* filename)
{
/* follow symlinks to find the "real" file, to make sure the temporary
* we build with tr_sys_file_open_temp() is created on the right partition */
char* real_filename = tr_sys_path_resolve(filename, nullptr);
if (real_filename != nullptr)
{
filename = real_filename;
}
/* if the file already exists, try to move it out of the way & keep it as a backup */
char* const tmp = tr_strdup_printf("%s.tmp.XXXXXX", filename);
auto contents_len = size_t{};
auto const* contents = tr_variantToStr(v, fmt, &contents_len);
tr_error* error = nullptr;
tr_sys_file_t const fd = tr_sys_file_open_temp(tmp, &error);
int err = 0;
if (fd != TR_BAD_SYS_FILE)
auto const saved = tr_saveFile(filename, { contents, contents_len }, &error);
if (error != nullptr)
{
err = writeVariantToFd(v, fmt, fd, &error);
tr_sys_file_close(fd, nullptr);
if (err)
{
tr_logAddError(_("Couldn't save temporary file \"%1$s\": %2$s"), tmp, error->message);
tr_sys_path_remove(tmp, nullptr);
tr_error_free(error);
}
else
{
tr_error_clear(&error);
if (tr_sys_path_rename(tmp, filename, &error))
{
tr_logAddInfo(_("Saved \"%s\""), filename);
}
else
{
err = error->code;
tr_logAddError(_("Couldn't save file \"%1$s\": %2$s"), filename, error->message);
tr_sys_path_remove(tmp, nullptr);
tr_error_free(error);
}
}
tr_logAddError(_("Error saving \"%s\": %s (%d)"), filename, error->message, error->code);
tr_error_clear(&error);
}
else
{
err = error->code;
tr_logAddError(_("Couldn't save temporary file \"%1$s\": %2$s"), tmp, error->message);
tr_error_free(error);
}
tr_free(tmp);
tr_free(real_filename);
return err;
return saved ? 0 : -1;
}
/***

View File

@ -8,7 +8,9 @@
#include "transmission.h"
#include "error.h"
#include "metainfo.h"
#include "torrent.h"
#include "utils.h"
#include "gtest/gtest.h"
@ -176,13 +178,42 @@ TEST(Metainfo, sanitize)
TEST(Metainfo, AndroidTorrent)
{
auto* ctor = tr_ctorNew(nullptr);
auto const filename = tr_strvJoin(LIBTRANSMISSION_TEST_ASSETS_DIR, "/Android-x86 8.1 r6 iso.torrent"sv);
auto filename = std::string{ LIBTRANSMISSION_TEST_ASSETS_DIR };
filename += '/'; // FIXME
filename += "Android-x86 8.1 r6 iso.torrent";
auto* ctor = tr_ctorNew(nullptr);
auto const err = tr_ctorSetMetainfoFromFile(ctor, filename.c_str());
EXPECT_EQ(0, err);
tr_ctorFree(ctor);
}
TEST(Metainfo, ctorSaveContents)
{
auto const src_filename = tr_strvJoin(LIBTRANSMISSION_TEST_ASSETS_DIR, "/Android-x86 8.1 r6 iso.torrent"sv);
auto const tgt_filename = tr_strvJoin(::testing::TempDir(), "save-contents-test.torrent");
// try saving without passing any metainfo.
auto* ctor = tr_ctorNew(nullptr);
tr_error* error = nullptr;
EXPECT_FALSE(tr_ctorSaveContents(ctor, tgt_filename.c_str(), &error));
ASSERT_NE(nullptr, error);
EXPECT_EQ(EINVAL, error->code);
tr_error_clear(&error);
// now try saving _with_ metainfo
EXPECT_EQ(0, tr_ctorSetMetainfoFromFile(ctor, src_filename.c_str()));
EXPECT_TRUE(tr_ctorSaveContents(ctor, tgt_filename.c_str(), &error));
EXPECT_EQ(nullptr, error);
// the saved contents should match the source file's contents
auto src_contents = std::vector<char>{};
EXPECT_TRUE(tr_loadFile(src_contents, src_filename.c_str(), &error));
auto tgt_contents = std::vector<char>{};
EXPECT_TRUE(tr_loadFile(tgt_contents, tgt_filename.c_str(), &error));
EXPECT_EQ(src_contents, tgt_contents);
// cleanup
EXPECT_TRUE(tr_sys_path_remove(tgt_filename.c_str(), &error));
EXPECT_EQ(nullptr, error);
tr_error_clear(&error);
tr_ctorFree(ctor);
}

View File

@ -73,7 +73,7 @@ protected:
auto* metainfo = static_cast<char*>(tr_base64_decode_str(metainfo_base64, &metainfo_len));
EXPECT_NE(nullptr, metainfo);
EXPECT_LT(size_t(0), metainfo_len);
tr_ctorSetMetainfo(ctor, reinterpret_cast<uint8_t const*>(metainfo), metainfo_len);
tr_ctorSetMetainfo(ctor, metainfo, metainfo_len);
tr_ctorSetPaused(ctor, TR_FORCE, true);
// create the torrent

View File

@ -374,11 +374,11 @@ protected:
// create the torrent ctor
auto metainfo_len = size_t{};
auto* metainfo = tr_base64_decode_str(metainfo_base64, &metainfo_len);
auto* const metainfo = tr_base64_decode_str(metainfo_base64, &metainfo_len);
EXPECT_NE(nullptr, metainfo);
EXPECT_LT(size_t{ 0 }, metainfo_len);
auto* ctor = tr_ctorNew(session_);
tr_ctorSetMetainfo(ctor, reinterpret_cast<uint8_t*>(metainfo), metainfo_len);
tr_ctorSetMetainfo(ctor, static_cast<char const*>(metainfo), metainfo_len);
tr_ctorSetPaused(ctor, TR_FORCE, true);
tr_free(metainfo);

View File

@ -484,3 +484,31 @@ TEST_F(UtilsTest, mimeTypes)
EXPECT_EQ("video/x-msvideo"sv, tr_get_mime_type_for_filename("/path/to/FILENAME.AVI"sv));
EXPECT_EQ("application/octet-stream"sv, tr_get_mime_type_for_filename("music.ajoijfeisfe"sv));
}
TEST_F(UtilsTest, saveFile)
{
// save a file to GoogleTest's temp dir
auto filename = tr_strvJoin(::testing::TempDir(), "filename.txt");
auto contents = "these are the contents"sv;
tr_error* error = nullptr;
EXPECT_TRUE(tr_saveFile(filename.c_str(), contents, &error));
EXPECT_EQ(nullptr, error);
// now read the file back in and confirm the contents are the same
auto buf = std::vector<char>{};
EXPECT_TRUE(tr_loadFile(buf, filename.c_str(), &error));
EXPECT_EQ(nullptr, error);
auto sv = std::string_view{ std::data(buf), std::size(buf) };
EXPECT_EQ(contents, sv);
// remove the tempfile
EXPECT_TRUE(tr_sys_path_remove(filename.c_str(), &error));
EXPECT_EQ(nullptr, error);
// try saving a file to a path that doesn't exist
filename = "/this/path/does/not/exist/foo.txt";
EXPECT_FALSE(tr_saveFile(filename.c_str(), contents, &error));
ASSERT_NE(nullptr, error);
EXPECT_NE(0, error->code);
tr_error_clear(&error);
}