From 77b11232f2b4c5de09a64cf44b7b1ddded657932 Mon Sep 17 00:00:00 2001 From: Daniel Kamil Kozar Date: Tue, 19 Oct 2021 01:05:39 +0200 Subject: [PATCH] Add support for creating torrents with a source flag (#443) * Add support for creating torrents with a source flag * Add the source flag functionality for Mac OSX * Source flag should be a part of the info dictionary * Address review comments * Rename "sourceFlag" to "source" since "Flag" is usually reserved for booleans. * Free the "source" pointer in tr_metainfoFree. * Add information about transmission-create argument to its manpage. * Replace all occurences of "sourceFlag" with "source" and use "Source tag" in UI * Settle on just "Source" in UI * The last usage of "flag" hopefully bites the dust! ;-) * Add a missing free for the source in tr_metainfoFree * Add a "source" field to the torrent-get RPC method * uncrustify * Test for torrents having different infohashes due to different source flags. This is the whole point of this feature, so it makes sense to test it. * case is important * try to incorporate the macosx xml changes --- gtk/makemeta-ui.cc | 14 ++++- libtransmission/makemeta.cc | 10 +++- libtransmission/makemeta.h | 4 +- libtransmission/metainfo.cc | 14 +++++ libtransmission/quark.cc | 3 +- libtransmission/quark.h | 1 + libtransmission/rpcimpl.cc | 4 ++ libtransmission/transmission.h | 4 ++ macosx/CreatorWindowController.h | 1 + macosx/CreatorWindowController.mm | 11 +++- macosx/en.lproj/Creator.xib | 19 +++++++ qt/MakeDialog.cc | 11 +++- qt/MakeDialog.ui | 32 ++++++++++- tests/libtransmission/makemeta-test.cc | 74 ++++++++++++++++++++++---- utils/create.cc | 8 ++- utils/remote.cc | 6 +++ utils/transmission-create.1 | 2 + 17 files changed, 199 insertions(+), 19 deletions(-) diff --git a/gtk/makemeta-ui.cc b/gtk/makemeta-ui.cc index 5fa6d421a..a21c12d9d 100644 --- a/gtk/makemeta-ui.cc +++ b/gtk/makemeta-ui.cc @@ -89,6 +89,8 @@ private: Gtk::CheckButton* comment_check_ = nullptr; Gtk::Entry* comment_entry_ = nullptr; Gtk::CheckButton* private_check_ = nullptr; + Gtk::CheckButton* source_check_ = nullptr; + Gtk::Entry* source_entry_ = nullptr; std::unique_ptr progress_dialog_; Glib::RefPtr announce_text_buffer_; std::unique_ptr builder_ = { nullptr, nullptr }; @@ -252,6 +254,8 @@ void MakeDialog::Impl::onResponse(int response) auto const comment = comment_entry_->get_text(); bool const isPrivate = private_check_->get_active(); bool const useComment = comment_check_->get_active(); + bool const useSource = source_check_->get_active(); + auto const source = source_entry_->get_text(); /* destination file */ auto const dir = destination_chooser_->get_filename(); @@ -288,7 +292,8 @@ void MakeDialog::Impl::onResponse(int response) trackers.data(), trackers.size(), useComment ? comment.c_str() : nullptr, - isPrivate); + isPrivate, + useSource ? source.c_str() : nullptr); } } else if (response == Gtk::RESPONSE_CLOSE) @@ -495,6 +500,13 @@ MakeDialog::Impl::Impl(MakeDialog& dialog, Glib::RefPtr const& core) comment_check_->signal_toggled().connect([this]() { onSourceToggled(comment_check_, comment_entry_); }); t->add_row_w(row, *comment_check_, *comment_entry_); + source_check_ = Gtk::make_managed(_("_Source:"), true); + source_check_->set_active(false); + source_entry_ = Gtk::make_managed(); + source_entry_->set_sensitive(false); + source_check_->signal_toggled().connect([this]() { onSourceToggled(source_check_, source_entry_); }); + t->add_row_w(row, *source_check_, *source_entry_); + private_check_ = t->add_wide_checkbutton(row, _("_Private torrent"), false); gtr_dialog_set_content(dialog_, *t); diff --git a/libtransmission/makemeta.cc b/libtransmission/makemeta.cc index 5fceb33a0..3ab1c91ab 100644 --- a/libtransmission/makemeta.cc +++ b/libtransmission/makemeta.cc @@ -234,6 +234,7 @@ void tr_metaInfoBuilderFree(tr_metainfo_builder* builder) tr_free(builder->files); tr_free(builder->top); tr_free(builder->comment); + tr_free(builder->source); for (int i = 0; i < builder->trackerCount; ++i) { @@ -428,6 +429,10 @@ static void makeInfoDict(tr_variant* dict, tr_metainfo_builder* builder) } tr_variantDictAddInt(dict, TR_KEY_private, builder->isPrivate ? 1 : 0); + if (builder->source != nullptr) + { + tr_variantDictAddStr(dict, TR_KEY_source, builder->source); + } } static void tr_realMakeMetaInfo(tr_metainfo_builder* builder) @@ -569,7 +574,8 @@ void tr_makeMetaInfo( tr_tracker_info const* trackers, int trackerCount, char const* comment, - bool isPrivate) + bool isPrivate, + char const* source) { tr_lock* lock; @@ -581,6 +587,7 @@ void tr_makeMetaInfo( tr_free(builder->trackers); tr_free(builder->comment); + tr_free(builder->source); tr_free(builder->outputFile); /* initialize the builder variables */ @@ -599,6 +606,7 @@ void tr_makeMetaInfo( builder->comment = tr_strdup(comment); builder->isPrivate = isPrivate; + builder->source = tr_strdup(source); if (!tr_str_is_empty(outputFile)) { diff --git a/libtransmission/makemeta.h b/libtransmission/makemeta.h index 58dc21d6b..60fce0de2 100644 --- a/libtransmission/makemeta.h +++ b/libtransmission/makemeta.h @@ -52,6 +52,7 @@ struct tr_metainfo_builder char* comment; char* outputFile; bool isPrivate; + char* source; /** *** These are set inside tr_makeMetaInfo() so the client @@ -115,4 +116,5 @@ void tr_makeMetaInfo( tr_tracker_info const* trackers, int trackerCount, char const* comment, - bool isPrivate); + bool isPrivate, + char const* source); diff --git a/libtransmission/metainfo.cc b/libtransmission/metainfo.cc index 8f856dd41..79a6ae413 100644 --- a/libtransmission/metainfo.cc +++ b/libtransmission/metainfo.cc @@ -683,6 +683,19 @@ static char const* tr_metainfoParseImpl( inf->isPrivate = i != 0; + /* source */ + len = 0; + if (!tr_variantDictFindStr(infoDict, TR_KEY_source, &str, &len)) + { + if (!tr_variantDictFindStr(meta, TR_KEY_source, &str, &len)) + { + str = ""; + } + } + + tr_free(inf->source); + inf->source = tr_utf8clean(std::string_view{ str, len }); + /* piece length */ if (!isMagnet) { @@ -788,6 +801,7 @@ void tr_metainfoFree(tr_info* inf) tr_free(inf->files); tr_free(inf->comment); tr_free(inf->creator); + tr_free(inf->source); tr_free(inf->torrent); tr_free(inf->originalName); tr_free(inf->name); diff --git a/libtransmission/quark.cc b/libtransmission/quark.cc index 4e9101619..702675f36 100644 --- a/libtransmission/quark.cc +++ b/libtransmission/quark.cc @@ -24,7 +24,7 @@ using namespace std::literals; namespace { -auto constexpr my_static = std::array{ ""sv, +auto constexpr my_static = std::array{ ""sv, "activeTorrentCount"sv, "activity-date"sv, "activityDate"sv, @@ -345,6 +345,7 @@ auto constexpr my_static = std::array{ ""sv, "sizeWhenDone"sv, "sort-mode"sv, "sort-reversed"sv, + "source"sv, "speed"sv, "speed-Bps"sv, "speed-bytes"sv, diff --git a/libtransmission/quark.h b/libtransmission/quark.h index 37cb46f23..53a1fcd24 100644 --- a/libtransmission/quark.h +++ b/libtransmission/quark.h @@ -346,6 +346,7 @@ enum TR_KEY_sizeWhenDone, TR_KEY_sort_mode, TR_KEY_sort_reversed, + TR_KEY_source, TR_KEY_speed, TR_KEY_speed_Bps, TR_KEY_speed_bytes, diff --git a/libtransmission/rpcimpl.cc b/libtransmission/rpcimpl.cc index 0ad699fd3..c15b0a78b 100644 --- a/libtransmission/rpcimpl.cc +++ b/libtransmission/rpcimpl.cc @@ -777,6 +777,10 @@ static void initField( tr_variantInitInt(initme, st->sizeWhenDone); break; + case TR_KEY_source: + tr_variantDictAddStr(initme, key, inf->source); + break; + case TR_KEY_startDate: tr_variantInitInt(initme, st->startDate); break; diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h index 5aaf182e5..94c1a77af 100644 --- a/libtransmission/transmission.h +++ b/libtransmission/transmission.h @@ -1624,6 +1624,10 @@ struct tr_info char* comment; char* creator; + + /* torrent's source. empty if not set. */ + char* source; + tr_file* files; tr_piece* pieces; diff --git a/macosx/CreatorWindowController.h b/macosx/CreatorWindowController.h index bf48654a5..7110bdeb4 100644 --- a/macosx/CreatorWindowController.h +++ b/macosx/CreatorWindowController.h @@ -37,6 +37,7 @@ IBOutlet NSTextView* fCommentView; IBOutlet NSButton* fPrivateCheck; IBOutlet NSButton* fOpenCheck; + IBOutlet NSTextField* fSource; IBOutlet NSView* fProgressView; IBOutlet NSProgressIndicator* fProgressIndicator; diff --git a/macosx/CreatorWindowController.mm b/macosx/CreatorWindowController.mm index 3b5105dcc..ab86b4272 100644 --- a/macosx/CreatorWindowController.mm +++ b/macosx/CreatorWindowController.mm @@ -206,6 +206,11 @@ NSMutableSet* creatorWindowControllerSet = nil; fPrivateCheck.state = [fDefaults boolForKey:@"CreatorPrivate"] ? NSOnState : NSOffState; } + if ([fDefaults objectForKey:@"CreatorSource"]) + { + fSource.stringValue = [fDefaults stringForKey:@"CreatorSource"]; + } + fOpenCheck.state = [fDefaults boolForKey:@"CreatorOpen"] ? NSOnState : NSOffState; } @@ -241,6 +246,7 @@ NSMutableSet* creatorWindowControllerSet = nil; [state encodeObject:fTrackers forKey:@"TRCreatorTrackers"]; [state encodeInteger:fOpenCheck.state forKey:@"TRCreatorOpenCheck"]; [state encodeInteger:fPrivateCheck.state forKey:@"TRCreatorPrivateCheck"]; + [state encodeObject:fSource.stringValue forKey:@"TRCreatorSource"]; [state encodeObject:fCommentView.string forKey:@"TRCreatorPrivateComment"]; } @@ -254,6 +260,7 @@ NSMutableSet* creatorWindowControllerSet = nil; fOpenCheck.state = [coder decodeIntegerForKey:@"TRCreatorOpenCheck"]; fPrivateCheck.state = [coder decodeIntegerForKey:@"TRCreatorPrivateCheck"]; + fSource.stringValue = [coder decodeObjectForKey:@"TRCreatorSource"]; fCommentView.string = [coder decodeObjectForKey:@"TRCreatorPrivateComment"]; } @@ -570,6 +577,7 @@ NSMutableSet* creatorWindowControllerSet = nil; //store values [fDefaults setObject:fTrackers forKey:@"CreatorTrackers"]; [fDefaults setBool:fPrivateCheck.state == NSOnState forKey:@"CreatorPrivate"]; + [fDefaults setObject: [fSource stringValue] forKey: @"CreatorSource"]; [fDefaults setBool:fOpenCheck.state == NSOnState forKey:@"CreatorOpen"]; fOpenWhenCreated = fOpenCheck.state == NSOnState; //need this since the check box might not exist, and value in prefs might have changed from another creator window [fDefaults setURL:fLocation.URLByDeletingLastPathComponent forKey:@"CreatorLocationURL"]; @@ -583,7 +591,8 @@ NSMutableSet* creatorWindowControllerSet = nil; trackerInfo, fTrackers.count, fCommentView.string.UTF8String, - fPrivateCheck.state == NSOnState); + fPrivateCheck.state == NSOnState, + fSource.stringValue.UTF8String); tr_free(trackerInfo); fTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(checkProgress) userInfo:nil repeats:YES]; diff --git a/macosx/en.lproj/Creator.xib b/macosx/en.lproj/Creator.xib index 6e1a797a7..8e09a2809 100644 --- a/macosx/en.lproj/Creator.xib +++ b/macosx/en.lproj/Creator.xib @@ -17,6 +17,7 @@ + @@ -262,6 +263,24 @@ Gw + + + + + + + + + + + + + + + + + + diff --git a/qt/MakeDialog.cc b/qt/MakeDialog.cc index 3748e0c6c..d2a07ee18 100644 --- a/qt/MakeDialog.cc +++ b/qt/MakeDialog.cc @@ -176,6 +176,14 @@ void MakeDialog::makeTorrent() comment = ui_.commentEdit->text(); } + // source + QString source; + + if (ui_.sourceCheck->isChecked()) + { + source = ui_.sourceEdit->text(); + } + // start making the torrent tr_makeMetaInfo( builder_.get(), @@ -183,7 +191,8 @@ void MakeDialog::makeTorrent() trackers.isEmpty() ? nullptr : trackers.data(), trackers.size(), comment.isEmpty() ? nullptr : comment.toUtf8().constData(), - ui_.privateCheck->isChecked()); + ui_.privateCheck->isChecked(), + source.isNull() ? nullptr : source.toUtf8().constData()); // pop up the dialog auto* dialog = new MakeProgressDialog(session_, *builder_, this); diff --git a/qt/MakeDialog.ui b/qt/MakeDialog.ui index aad69e15d..a5f233fe1 100644 --- a/qt/MakeDialog.ui +++ b/qt/MakeDialog.ui @@ -157,7 +157,21 @@ To add another primary URL, add it after a blank line. - + + + + &Source: + + + + + + + false + + + + &Private torrent @@ -235,5 +249,21 @@ To add another primary URL, add it after a blank line. + + sourceCheck + toggled(bool) + sourceEdit + setEnabled(bool) + + + 75 + 347 + + + 342 + 347 + + + diff --git a/tests/libtransmission/makemeta-test.cc b/tests/libtransmission/makemeta-test.cc index 47e92ef4e..f8d79ad1b 100644 --- a/tests/libtransmission/makemeta-test.cc +++ b/tests/libtransmission/makemeta-test.cc @@ -29,15 +29,15 @@ class MakemetaTest : public SandboxedTest { protected: void testSingleFileImpl( + tr_info& inf, tr_tracker_info const* trackers, int const trackerCount, void const* payload, size_t const payloadSize, char const* comment, - bool isPrivate) + bool isPrivate, + char const* source) { - // char* sandbox; - auto inf = tr_info{}; // create a single input file auto input_file = makeString(tr_buildPath(sandboxDir().data(), "test.XXXXXX", nullptr)); @@ -54,10 +54,11 @@ protected: // have tr_makeMetaInfo() build the .torrent file auto* torrent_file = tr_strdup_printf("%s.torrent", input_file.data()); - tr_makeMetaInfo(builder, torrent_file, trackers, trackerCount, comment, isPrivate); + tr_makeMetaInfo(builder, torrent_file, trackers, trackerCount, comment, isPrivate, source); EXPECT_EQ(isPrivate, builder->isPrivate); EXPECT_STREQ(torrent_file, builder->outputFile); EXPECT_STREQ(comment, builder->comment); + EXPECT_STREQ(source, builder->source); EXPECT_EQ(trackerCount, builder->trackerCount); while (!builder->isDone) @@ -84,7 +85,6 @@ protected: // cleanup tr_free(torrent_file); tr_ctorFree(ctor); - tr_metainfoFree(&inf); tr_metaInfoBuilderFree(builder); } @@ -95,7 +95,8 @@ protected: size_t const* payload_sizes, size_t const payload_count, char const* comment, - bool const is_private) + bool const is_private, + char const* source) { // create the top temp directory auto* top = tr_buildPath(sandboxDir().data(), "folder.XXXXXX", nullptr); @@ -136,10 +137,11 @@ protected: // build the .torrent file auto* torrent_file = tr_strdup_printf("%s.torrent", top); - tr_makeMetaInfo(builder, torrent_file, trackers, tracker_count, comment, is_private); + tr_makeMetaInfo(builder, torrent_file, trackers, tracker_count, comment, is_private, source); EXPECT_EQ(is_private, builder->isPrivate); EXPECT_STREQ(torrent_file, builder->outputFile); EXPECT_STREQ(comment, builder->comment); + EXPECT_STREQ(source, builder->source); EXPECT_EQ(tracker_count, builder->trackerCount); auto test = [&builder]() { @@ -161,6 +163,7 @@ protected: EXPECT_STREQ(tmpstr, inf.name); tr_free(tmpstr); EXPECT_STREQ(comment, inf.comment); + EXPECT_STREQ(source, inf.source); EXPECT_EQ(payload_count, inf.fileCount); EXPECT_EQ(is_private, inf.isPrivate); EXPECT_EQ(builder->isFolder, inf.isFolder); @@ -181,7 +184,8 @@ protected: size_t const max_file_count, size_t const max_file_size, char const* comment, - bool const is_private) + bool const is_private, + char const* source) { // build random payloads size_t payload_count = 1 + tr_rand_int_weak(max_file_count); @@ -204,7 +208,8 @@ protected: payload_sizes, payload_count, comment, - is_private); + is_private, + source); // cleanup for (size_t i = 0; i < payload_count; i++) @@ -230,7 +235,52 @@ TEST_F(MakemetaTest, singleFile) auto const payload = std::string{ "Hello, World!\n" }; char const* const comment = "This is the comment"; bool const is_private = false; - testSingleFileImpl(trackers.data(), tracker_count, payload.data(), payload.size(), comment, is_private); + char const* const source = "TESTME"; + tr_info inf{}; + testSingleFileImpl(inf, trackers.data(), tracker_count, payload.data(), payload.size(), comment, is_private, source); + tr_metainfoFree(&inf); +} + +TEST_F(MakemetaTest, singleFileDifferentSourceFlags) +{ + auto trackers = std::array{}; + auto tracker_count = int{}; + trackers[tracker_count].tier = tracker_count; + trackers[tracker_count].announce = const_cast("udp://tracker.openbittorrent.com:80"); + ++tracker_count; + trackers[tracker_count].tier = tracker_count; + trackers[tracker_count].announce = const_cast("udp://tracker.publicbt.com:80"); + ++tracker_count; + auto const payload = std::string{ "Hello, World!\n" }; + char const* const comment = "This is the comment"; + bool const is_private = false; + + tr_info inf_foobar{}; + testSingleFileImpl( + inf_foobar, + trackers.data(), + tracker_count, + payload.data(), + payload.size(), + comment, + is_private, + "FOOBAR"); + + tr_info inf_testme{}; + testSingleFileImpl( + inf_testme, + trackers.data(), + tracker_count, + payload.data(), + payload.size(), + comment, + is_private, + "TESTME"); + + EXPECT_TRUE(std::memcmp(inf_foobar.hash, inf_testme.hash, sizeof(inf_foobar.hash)) != 0); + + tr_metainfoFree(&inf_foobar); + tr_metainfoFree(&inf_testme); } TEST_F(MakemetaTest, singleDirectoryRandomPayload) @@ -248,6 +298,7 @@ TEST_F(MakemetaTest, singleDirectoryRandomPayload) ++tracker_count; char const* const comment = "This is the comment"; bool const is_private = false; + char const* const source = "TESTME"; for (size_t i = 0; i < 10; ++i) { @@ -257,7 +308,8 @@ TEST_F(MakemetaTest, singleDirectoryRandomPayload) DefaultMaxFileCount, DefaultMaxFileSize, comment, - is_private); + is_private, + source); } } diff --git a/utils/create.cc b/utils/create.cc index 0aed2e8be..2718ed378 100644 --- a/utils/create.cc +++ b/utils/create.cc @@ -32,9 +32,11 @@ static char const* comment = nullptr; static char const* outfile = nullptr; static char const* infile = nullptr; static uint32_t piecesize_kib = 0; +static char const* source = NULL; static tr_option options[] = { { '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, "" }, { 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "" }, { 's', "piecesize", "Set how many KiB each piece should be, overriding the preferred default", "s", true, "" }, { 'c', "comment", "Add a comment", "c", true, "" }, @@ -97,6 +99,10 @@ static int parseCommandLine(int argc, char const* const* argv) break; + case 'r': + source = optarg; + break; + case TR_OPT_UNK: infile = optarg; break; @@ -212,7 +218,7 @@ int tr_main(int argc, char* argv[]) b->pieceCount, tr_formatter_size_B(buf, b->pieceSize, sizeof(buf))); - tr_makeMetaInfo(b, outfile, trackers, trackerCount, comment, isPrivate); + tr_makeMetaInfo(b, outfile, trackers, trackerCount, comment, isPrivate, source); uint32_t last = UINT32_MAX; while (!b->isDone) diff --git a/utils/remote.cc b/utils/remote.cc index 59190f49a..d095d4212 100644 --- a/utils/remote.cc +++ b/utils/remote.cc @@ -762,6 +762,7 @@ static tr_quark const details_keys[] = { TR_KEY_seedRatioMode, TR_KEY_seedRatioLimit, TR_KEY_sizeWhenDone, + TR_KEY_source, TR_KEY_startDate, TR_KEY_status, TR_KEY_totalSize, @@ -1173,6 +1174,11 @@ static void printDetails(tr_variant* top) printf(" Creator: %s\n", str); } + if (tr_variantDictFindStr(t, TR_KEY_source, &str, NULL) && str != NULL && *str != '\0') + { + printf(" Source: %s\n", str); + } + if (tr_variantDictFindInt(t, TR_KEY_pieceCount, &i)) { printf(" Piece Count: %" PRId64 "\n", i); diff --git a/utils/transmission-create.1 b/utils/transmission-create.1 index f6d2a0500..aa3a1b565 100644 --- a/utils/transmission-create.1 +++ b/utils/transmission-create.1 @@ -30,6 +30,8 @@ Flag the torrent as intended for use on private trackers. Add a comment to the torrent file. .It Fl s Fl -piecesize Set how many KiB each piece should be, overriding the preferred default +.It Fl r Fl -source +Set the torrent's source for private trackers .It Fl t Fl -tracker Add a tracker's .Ar announce URL