1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2025-02-23 14:40:43 +00:00
transmission/utils/create.cc
Mike Gelfand 411b66cf88 fix: use user-preferred locale (#5444)
* Use user-preferred locale

Previous fix adding `L` format specifier was correct but only fixed half
of the problem, as C++ locale is set up to be "C" by default. GTK client
used to call `setlocale(LC_ALL, "")` to set up user-preferred locale
which only affected C functions and `std::locale` (used by libfmt) was
unaware of those changes.

Apply the fix to all the binaries since they're all doing some sort of
output to the user and calling libtransmission helpers, as well as using
libfmt directly.

* Improve libtransmission's json-test

Set the locale C++ way to avoid any sort of inconsistencies, and also
restore it to the old one once finished testing.

* Improve transmission-show test runner script

Quote outputs to avoid CMake error about `message()` being called with
no arguments. Capture stderr to the same output file. Fallback to `git
diff` if `diff` wasn't found. A few other minor changes.
2023-04-27 09:11:06 -05:00

287 lines
8.2 KiB
C++

// This file Copyright © 2012-2023 Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include <array>
#include <cstdlib> // for strtoul()
#include <chrono>
#include <cstdint> // for uint32_t
#include <future>
#include <string>
#include <string_view>
#include <fmt/format.h>
#include <libtransmission/transmission.h>
#include <libtransmission/error.h>
#include <libtransmission/file.h>
#include <libtransmission/log.h>
#include <libtransmission/makemeta.h>
#include <libtransmission/tr-getopt.h>
#include <libtransmission/utils.h>
#include <libtransmission/version.h>
#include "units.h"
using namespace std::literals;
namespace
{
char constexpr MyName[] = "transmission-create";
char constexpr Usage[] = "Usage: transmission-create [options] <file|directory>";
uint32_t constexpr KiB = 1024;
auto constexpr Options = std::array<tr_option, 10>{
{ { 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", false, nullptr },
{ 'r', "source", "Set the source for private trackers", "r", true, "<source>" },
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "<file>" },
{ 's', "piecesize", "Set the piece size in KiB, overriding the preferred default", "s", true, "<KiB>" },
{ 'c', "comment", "Add a comment", "c", true, "<comment>" },
{ 't', "tracker", "Add a tracker's announce URL", "t", true, "<url>" },
{ 'w', "webseed", "Add a webseed URL", "w", true, "<url>" },
{ 'x', "anonymize", "Omit \"Creation date\" and \"Created by\" info", nullptr, false, nullptr },
{ 'V', "version", "Show version number and exit", "V", false, nullptr },
{ 0, nullptr, nullptr, nullptr, false, nullptr } }
};
struct app_options
{
tr_announce_list trackers;
std::vector<std::string> webseeds;
std::string outfile;
std::string_view comment;
std::string_view infile;
std::string_view source;
uint32_t piece_size = 0;
bool anonymize = false;
bool is_private = false;
bool show_version = false;
};
int parseCommandLine(app_options& options, int argc, char const* const* argv)
{
int c;
char const* optarg;
while ((c = tr_getopt(Usage, argc, argv, std::data(Options), &optarg)) != TR_OPT_DONE)
{
switch (c)
{
case 'V':
options.show_version = true;
break;
case 'p':
options.is_private = true;
break;
case 'o':
options.outfile = optarg;
break;
case 'c':
options.comment = optarg;
break;
case 't':
options.trackers.add(optarg, options.trackers.nextTier());
break;
case 'w':
options.webseeds.emplace_back(optarg);
break;
case 's':
if (optarg != nullptr)
{
char* endptr = nullptr;
options.piece_size = strtoul(optarg, &endptr, 10) * KiB;
if (endptr != nullptr && *endptr == 'M')
{
options.piece_size *= KiB;
}
}
break;
case 'r':
options.source = optarg;
break;
case 'x':
options.anonymize = true;
break;
case TR_OPT_UNK:
options.infile = optarg;
break;
default:
return 1;
}
}
return 0;
}
std::string tr_getcwd()
{
tr_error* error = nullptr;
auto cur = tr_sys_dir_get_current(&error);
if (error != nullptr)
{
fprintf(stderr, "getcwd error: \"%s\"", error->message);
tr_error_free(error);
}
return cur;
}
} // namespace
int tr_main(int argc, char* argv[])
{
tr_locale_set_global("");
tr_logSetLevel(TR_LOG_ERROR);
tr_formatter_mem_init(MemK, MemKStr, MemMStr, MemGStr, MemTStr);
tr_formatter_size_init(DiskK, DiskKStr, DiskMStr, DiskGStr, DiskTStr);
tr_formatter_speed_init(SpeedK, SpeedKStr, SpeedMStr, SpeedGStr, SpeedTStr);
auto options = app_options{};
if (parseCommandLine(options, argc, (char const* const*)argv) != 0)
{
return EXIT_FAILURE;
}
if (options.show_version)
{
fprintf(stderr, "%s %s\n", MyName, LONG_VERSION_STRING);
return EXIT_SUCCESS;
}
if (std::empty(options.infile))
{
fprintf(stderr, "ERROR: No input file or directory specified.\n");
tr_getopt_usage(MyName, Usage, std::data(Options));
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
if (std::empty(options.outfile))
{
tr_error* error = nullptr;
auto const base = tr_sys_path_basename(options.infile, &error);
if (std::empty(base))
{
fprintf(stderr, "ERROR: Cannot deduce output path from input path: %s\n", error->message);
return EXIT_FAILURE;
}
options.outfile = fmt::format("{:s}/{:s}.torrent"sv, tr_getcwd(), base);
}
if (std::empty(options.trackers))
{
if (options.is_private)
{
fprintf(stderr, "ERROR: no trackers specified for a private torrent\n");
return EXIT_FAILURE;
}
else
{
printf("WARNING: no trackers specified\n");
}
}
fmt::print("Creating torrent \"{:s}\"\n", options.outfile);
auto builder = tr_metainfo_builder(options.infile);
auto const n_files = builder.fileCount();
if (n_files == 0U)
{
fprintf(stderr, "ERROR: Cannot find specified input file or directory.\n");
return EXIT_FAILURE;
}
for (tr_file_index_t i = 0; i < n_files; ++i)
{
auto const& path = builder.path(i);
if (!tr_torrent_files::isSubpathPortable(path))
{
fmt::print(stderr, "WARNING\n");
fmt::print(stderr, "filename \"{:s}\" may not be portable on all systems.\n", path);
fmt::print(stderr, "consider \"{:s}\" instead.\n", tr_torrent_files::makeSubpathPortable(path));
}
}
if (options.piece_size != 0 && !builder.setPieceSize(options.piece_size))
{
fmt::print(stderr, "ERROR: piece size must be at least 16 KiB and must be a power of two.\n");
return EXIT_FAILURE;
}
fmt::print(
tr_ngettext("{file_count:L} file, {total_size}\n", "{file_count:L} files, {total_size}\n", builder.fileCount()),
fmt::arg("file_count", builder.fileCount()),
fmt::arg("total_size", tr_formatter_size_B(builder.totalSize())));
fmt::print(
tr_ngettext(
"{piece_count:L} piece, {piece_size}\n",
"{piece_count:L} pieces, {piece_size} each\n",
builder.pieceCount()),
fmt::arg("piece_count", builder.pieceCount()),
fmt::arg("piece_size", tr_formatter_size_B(builder.pieceSize())));
if (!std::empty(options.comment))
{
builder.setComment(options.comment);
}
if (!std::empty(options.source))
{
builder.setSource(options.source);
}
builder.setPrivate(options.is_private);
builder.setAnonymize(options.anonymize);
builder.setWebseeds(std::move(options.webseeds));
builder.setAnnounceList(std::move(options.trackers));
auto future = builder.makeChecksums();
auto last = std::optional<tr_piece_index_t>{};
while (future.wait_for(std::chrono::milliseconds(500)) != std::future_status::ready)
{
auto const [current, total] = builder.checksumStatus();
if (!last || current != *last)
{
fmt::print("\rPiece {:d}/{:d} ...", current, total);
fflush(stdout);
last = current;
}
}
fmt::print(" ");
if (tr_error* error = future.get(); error != nullptr)
{
fmt::print("ERROR: {:s} {:d}\n", error->message, error->code);
tr_error_free(error);
return EXIT_FAILURE;
}
if (tr_error* error = nullptr; !builder.save(options.outfile, &error))
{
fmt::print("ERROR: could not save \"{:s}\": {:s} {:d}\n", options.outfile, error->message, error->code);
tr_error_free(error);
return EXIT_FAILURE;
}
fmt::print("done!\n");
return EXIT_SUCCESS;
}