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
This commit is contained in:
Daniel Kamil Kozar 2021-10-19 01:05:39 +02:00 committed by GitHub
parent bf41e1487a
commit 77b11232f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 199 additions and 19 deletions

View File

@ -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<MakeProgressDialog> progress_dialog_;
Glib::RefPtr<Gtk::TextBuffer> announce_text_buffer_;
std::unique_ptr<tr_metainfo_builder, void (*)(tr_metainfo_builder*)> 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<TrCore> 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<Gtk::CheckButton>(_("_Source:"), true);
source_check_->set_active(false);
source_entry_ = Gtk::make_managed<Gtk::Entry>();
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);

View File

@ -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))
{

View File

@ -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);

View File

@ -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);

View File

@ -24,7 +24,7 @@ using namespace std::literals;
namespace
{
auto constexpr my_static = std::array<std::string_view, 391>{ ""sv,
auto constexpr my_static = std::array<std::string_view, 392>{ ""sv,
"activeTorrentCount"sv,
"activity-date"sv,
"activityDate"sv,
@ -345,6 +345,7 @@ auto constexpr my_static = std::array<std::string_view, 391>{ ""sv,
"sizeWhenDone"sv,
"sort-mode"sv,
"sort-reversed"sv,
"source"sv,
"speed"sv,
"speed-Bps"sv,
"speed-bytes"sv,

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -37,6 +37,7 @@
IBOutlet NSTextView* fCommentView;
IBOutlet NSButton* fPrivateCheck;
IBOutlet NSButton* fOpenCheck;
IBOutlet NSTextField* fSource;
IBOutlet NSView* fProgressView;
IBOutlet NSProgressIndicator* fProgressIndicator;

View File

@ -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];

View File

@ -17,6 +17,7 @@
<outlet property="fPrivateCheck" destination="22" id="33"/>
<outlet property="fProgressIndicator" destination="57" id="61"/>
<outlet property="fProgressView" destination="56" id="60"/>
<outlet property="fSource" destination="R2K-fl-edv" id="bqE-sb-RJZ"/>
<outlet property="fStatusField" destination="10" id="34"/>
<outlet property="fTrackerAddRemoveControl" destination="103" id="105"/>
<outlet property="fTrackerTable" destination="92" id="99"/>
@ -262,6 +263,24 @@ Gw
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" misplaced="YES" id="R2K-fl-edv">
<rect key="frame" x="103" y="120" width="585" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="sNv-mo-m3a">
<font key="font" metaFont="system"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" allowsCharacterPickerTouchBarItem="YES" id="faa-JK-W9Q">
<rect key="frame" x="21" y="123" width="77" height="17"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Source:" id="f1g-Kk-weU">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<connections>

View File

@ -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);

View File

@ -157,7 +157,21 @@ To add another primary URL, add it after a blank line.</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="3" column="0">
<widget class="QCheckBox" name="sourceCheck">
<property name="text">
<string>&amp;Source:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="sourceEdit">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="privateCheck">
<property name="text">
<string>&amp;Private torrent</string>
@ -235,5 +249,21 @@ To add another primary URL, add it after a blank line.</string>
</hint>
</hints>
</connection>
<connection>
<sender>sourceCheck</sender>
<signal>toggled(bool)</signal>
<receiver>sourceEdit</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>75</x>
<y>347</y>
</hint>
<hint type="destinationlabel">
<x>342</x>
<y>347</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -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<tr_tracker_info, 16>{};
auto tracker_count = int{};
trackers[tracker_count].tier = tracker_count;
trackers[tracker_count].announce = const_cast<char*>("udp://tracker.openbittorrent.com:80");
++tracker_count;
trackers[tracker_count].tier = tracker_count;
trackers[tracker_count].announce = const_cast<char*>("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);
}
}

View File

@ -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, "<source>" },
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", true, "<file>" },
{ 's', "piecesize", "Set how many KiB each piece should be, overriding the preferred default", "s", true, "<size in KiB>" },
{ 'c', "comment", "Add a comment", "c", true, "<comment>" },
@ -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)

View File

@ -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);

View File

@ -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