Skip to content

Instantly share code, notes, and snippets.

@jhenkens
Last active August 29, 2015 14:01
Show Gist options
  • Save jhenkens/935e4096da16c5f28990 to your computer and use it in GitHub Desktop.
Save jhenkens/935e4096da16c5f28990 to your computer and use it in GitHub Desktop.
Patch to transmission to enable sequential torrenting on a per-torrent basis in the web gui. This allows 'streaming' media as it is downloaded.
Index: daemon/remote.c
===================================================================
--- daemon/remote.c (revision 14252)
+++ daemon/remote.c (working copy)
@@ -718,6 +718,7 @@
TR_KEY_seedRatioMode,
TR_KEY_seedRatioLimit,
TR_KEY_sizeWhenDone,
+ TR_KEY_sequential,
TR_KEY_startDate,
TR_KEY_status,
TR_KEY_totalSize,
@@ -741,6 +742,7 @@
TR_KEY_rateDownload,
TR_KEY_rateUpload,
TR_KEY_sizeWhenDone,
+ TR_KEY_sequential,
TR_KEY_status,
TR_KEY_uploadRatio
};
@@ -1011,7 +1013,7 @@
if (tr_variantDictFindInt (t, TR_KEY_secondsSeeding, &i) && (i > 0))
printf (" Seeding Time: %s\n", tr_strltime (buf, i, sizeof (buf)));
printf ("\n");
-
+
printf ("ORIGINS\n");
if (tr_variantDictFindInt (t, TR_KEY_dateCreated, &i) && i)
{
@@ -1072,7 +1074,10 @@
if (tr_variantDictFindInt (t, TR_KEY_bandwidthPriority, &i))
printf (" Bandwidth Priority: %s\n",
bandwidthPriorityNames[ (i + 1) & 3]);
-
+ if (tr_variantDictFindStr (t, TR_KEY_sequential, &str, NULL) && str && *str)
+ {
+ printf (" Downloading sequentially: %s\n", str);
+ }
printf ("\n");
}
}
Index: libtransmission/peer-mgr.c
===================================================================
--- libtransmission/peer-mgr.c (revision 14252)
+++ libtransmission/peer-mgr.c (working copy)
@@ -997,17 +997,19 @@
assert (state==PIECES_SORTED_BY_INDEX
|| state==PIECES_SORTED_BY_WEIGHT);
- if (state == PIECES_SORTED_BY_WEIGHT)
+ if (!s->tor->isSequential && state == PIECES_SORTED_BY_WEIGHT)
{
setComparePieceByWeightTorrent (s);
qsort (s->pieces, s->pieceCount, sizeof (struct weighted_piece), comparePieceByWeight);
+ s->pieceSortState = PIECES_SORTED_BY_WEIGHT;
+ tr_logAddDebug("Sorted pieces by weight");
}
else
{
qsort (s->pieces, s->pieceCount, sizeof (struct weighted_piece), comparePieceByIndex);
+ tr_logAddDebug("Sorted pieces by index");
+ s->pieceSortState = PIECES_SORTED_BY_INDEX;
}
-
- s->pieceSortState = state;
}
/**
@@ -1127,7 +1129,7 @@
s->pieces = pieces;
s->pieceCount = pieceCount;
- pieceListSort (s, PIECES_SORTED_BY_WEIGHT);
+ pieceListSort (s, (s->tor->isSequential?PIECES_SORTED_BY_INDEX:PIECES_SORTED_BY_WEIGHT));
/* cleanup */
tr_free (pool);
@@ -1167,15 +1169,24 @@
/* is the torrent already sorted? */
pos = p - s->pieces;
- setComparePieceByWeightTorrent (s);
- if (isSorted && (pos > 0) && (comparePieceByWeight (p-1, p) > 0))
- isSorted = false;
- if (isSorted && (pos < s->pieceCount - 1) && (comparePieceByWeight (p, p+1) > 0))
- isSorted = false;
+ if (s->tor->isSequential){
+ if (isSorted && (pos > 0) && (comparePieceByIndex(p-1, p) > 0))
+ isSorted = false;
+ if (isSorted && (pos < s->pieceCount - 1) && (comparePieceByIndex (p, p+1) > 0))
+ isSorted = false;
+ }
+ else
+ {
+ setComparePieceByWeightTorrent (s);
+ if (isSorted && (pos > 0) && (comparePieceByWeight (p-1, p) > 0))
+ isSorted = false;
+ if (isSorted && (pos < s->pieceCount - 1) && (comparePieceByWeight (p, p+1) > 0))
+ isSorted = false;
+ }
- if (s->pieceSortState != PIECES_SORTED_BY_WEIGHT)
+ if (s->pieceSortState != (s->tor->isSequential?PIECES_SORTED_BY_INDEX:PIECES_SORTED_BY_WEIGHT))
{
- pieceListSort (s, PIECES_SORTED_BY_WEIGHT);
+ pieceListSort (s, (s->tor->isSequential?PIECES_SORTED_BY_INDEX:PIECES_SORTED_BY_WEIGHT));
isSorted = true;
}
@@ -1192,7 +1203,7 @@
pos = tr_lowerBound (&tmp, s->pieces, s->pieceCount,
sizeof (struct weighted_piece),
- comparePieceByWeight, &exact);
+ (s->tor->isSequential)?comparePieceByIndex:comparePieceByWeight, &exact);
memmove (&s->pieces[pos + 1],
&s->pieces[pos],
@@ -1238,7 +1249,8 @@
++s->pieceReplication[index];
/* we only resort the piece if the list is already sorted */
- if (s->pieceSortState == PIECES_SORTED_BY_WEIGHT)
+ /* if it sequential, we don't need to resort */
+ if (s->pieceSortState == PIECES_SORTED_BY_WEIGHT && ! s->tor->isSequential)
pieceListResortPiece (s, pieceListLookup (s, index));
}
@@ -1344,8 +1356,8 @@
if (s->pieces == NULL)
pieceListRebuild (s);
- if (s->pieceSortState != PIECES_SORTED_BY_WEIGHT)
- pieceListSort (s, PIECES_SORTED_BY_WEIGHT);
+ if (s->pieceSortState != (s->tor->isSequential?PIECES_SORTED_BY_INDEX:PIECES_SORTED_BY_WEIGHT))
+ pieceListSort (s, (s->tor->isSequential?PIECES_SORTED_BY_INDEX:PIECES_SORTED_BY_WEIGHT));
assertReplicationCountIsExact (s);
assertWeightedPiecesAreSorted (s);
@@ -1381,8 +1393,9 @@
peers = (tr_peer **) tr_ptrArrayPeek (&peerArr, &peerCount);
if (peerCount != 0)
{
+ bool sequentialKickStart = (tor->isSequential && b<((100*1024*1024)/tor->blockCount)); // Prioritize the first 100M
/* don't make a second block request until the endgame */
- if (!s->endgame)
+ if (!sequentialKickStart && !s->endgame)
continue;
/* don't have more than two peers requesting this block */
@@ -1396,8 +1409,9 @@
/* in the endgame allow an additional peer to download a
block but only if the peer seems to be handling requests
relatively fast */
- if (peer->pendingReqsToPeer + numwant - got < s->endgame)
+ if (!sequentialKickStart && peer->pendingReqsToPeer + numwant - got < s->endgame)
continue;
+ tr_logAddDebug("Adding a second peer for block %"PRIu32" for sequential kickstarter %d %d %d",b, sequentialKickStart,tor->blockSize, tor->blockCount);
}
/* update the caller's table */
@@ -1446,7 +1460,7 @@
const int newpos = tr_lowerBound (&s->pieces[i], &s->pieces[i + 1],
s->pieceCount - (i + 1),
sizeof (struct weighted_piece),
- comparePieceByWeight, &exact);
+ (tor->isSequential)?comparePieceByIndex:comparePieceByWeight, &exact);
if (newpos > 0)
{
const struct weighted_piece piece = s->pieces[i];
@@ -1665,6 +1679,13 @@
tr_ptrArrayDestruct (&peerArr, NULL);
}
+tr_file_index_t tr_getFirstPieceIndex(tr_torrent * tor)
+{
+ if (tor->swarm->pieceCount)
+ return tor->swarm->pieces[0].index;
+ else return 0;
+}
+
void
tr_peerMgrPieceCompleted (tr_torrent * tor, tr_piece_index_t p)
{
Index: libtransmission/peer-mgr.h
===================================================================
--- libtransmission/peer-mgr.h (revision 14252)
+++ libtransmission/peer-mgr.h (working copy)
@@ -183,6 +183,9 @@
void tr_peerMgrPieceCompleted (tr_torrent * tor,
tr_piece_index_t pieceIndex);
+
+tr_file_index_t tr_getFirstPieceIndex(tr_torrent * tor);
+
Index: libtransmission/quark.c
===================================================================
--- libtransmission/quark.c (revision 14252)
+++ libtransmission/quark.c (working copy)
@@ -315,6 +315,7 @@
{ "seedRatioMode", 13 },
{ "seederCount", 11 },
{ "seeding-time-seconds", 20 },
+ { "sequential", 10},
{ "session-count", 13 },
{ "sessionCount", 12 },
{ "show-backup-trackers", 20 },
Index: libtransmission/quark.h
===================================================================
--- libtransmission/quark.h (revision 14252)
+++ libtransmission/quark.h (working copy)
@@ -313,6 +313,7 @@
TR_KEY_seedRatioMode,
TR_KEY_seederCount,
TR_KEY_seeding_time_seconds,
+ TR_KEY_sequential,
TR_KEY_session_count,
TR_KEY_sessionCount,
TR_KEY_show_backup_trackers,
Index: libtransmission/resume.c
===================================================================
--- libtransmission/resume.c (revision 14252)
+++ libtransmission/resume.c (working copy)
@@ -662,6 +662,7 @@
tr_variantDictAddInt (&top, TR_KEY_seeding_time_seconds, tor->secondsSeeding);
tr_variantDictAddInt (&top, TR_KEY_downloading_time_seconds, tor->secondsDownloading);
tr_variantDictAddInt (&top, TR_KEY_activity_date, tor->activityDate);
+ tr_variantDictAddBool(&top, TR_KEY_sequential, tor->isSequential);
tr_variantDictAddInt (&top, TR_KEY_added_date, tor->addedDate);
tr_variantDictAddInt (&top, TR_KEY_corrupt, tor->corruptPrev + tor->corruptCur);
tr_variantDictAddInt (&top, TR_KEY_done_date, tor->doneDate);
@@ -799,7 +800,15 @@
tr_torrentSetActivityDate (tor, i);
fieldsLoaded |= TR_FR_ACTIVITY_DATE;
}
+
+ if ((fieldsToLoad & TR_FR_SEQUENTIAL)
+ && tr_variantDictFindBool(&top, TR_KEY_sequential, &boolVal))
+ {
+ tor->isSequential = boolVal;
+ fieldsLoaded |= TR_FR_SEQUENTIAL;
+ }
+
if ((fieldsToLoad & TR_FR_TIME_SEEDING)
&& tr_variantDictFindInt (&top, TR_KEY_seeding_time_seconds, &i))
{
@@ -848,7 +857,7 @@
if (fieldsToLoad & TR_FR_NAME)
fieldsLoaded |= loadName (&top, tor);
-
+
/* loading the resume file triggers of a lot of changes,
* but none of them needs to trigger a re-saving of the
* same resume information... */
Index: libtransmission/resume.h
===================================================================
--- libtransmission/resume.h (revision 14252)
+++ libtransmission/resume.h (working copy)
@@ -38,6 +38,7 @@
TR_FR_TIME_DOWNLOADING = (1 << 19),
TR_FR_FILENAMES = (1 << 20),
TR_FR_NAME = (1 << 21),
+ TR_FR_SEQUENTIAL = (1 << 22),
};
/**
Index: libtransmission/rpcimpl.c
===================================================================
--- libtransmission/rpcimpl.c (revision 14252)
+++ libtransmission/rpcimpl.c (working copy)
@@ -382,26 +382,84 @@
tr_variant * args_out UNUSED,
struct tr_rpc_idle_data * idle_data UNUSED)
{
- int i;
- int torrentCount;
- tr_torrent ** torrents;
+ int i;
+ int torrentCount;
+ tr_torrent ** torrents;
+
+ assert (idle_data == NULL);
+
+ torrents = getTorrents (session, args_in, &torrentCount);
+ for (i=0; i<torrentCount; ++i)
+ {
+ tr_torrent * tor = torrents[i];
+
+ if (tr_torrentCanManualUpdate (tor))
+ {
+ tr_torrentManualUpdate (tor);
+ notify (session, TR_RPC_TORRENT_CHANGED, tor);
+ }
+ }
+
+ tr_free (torrents);
+ return NULL;
+}
- assert (idle_data == NULL);
-
- torrents = getTorrents (session, args_in, &torrentCount);
- for (i=0; i<torrentCount; ++i)
+static const char*
+torrentSetSequential (tr_session * session,
+ tr_variant * args_in,
+ tr_variant * args_out UNUSED,
+ struct tr_rpc_idle_data * idle_data UNUSED)
+{
+ int i;
+ int torrentCount;
+ tr_torrent ** torrents;
+
+ assert (idle_data == NULL);
+
+ torrents = getTorrents (session, args_in, &torrentCount);
+ for (i=0; i<torrentCount; ++i)
{
- tr_torrent * tor = torrents[i];
+ tr_torrent * tor = torrents[i];
- if (tr_torrentCanManualUpdate (tor))
+ if (!tor->isSequential)
{
- tr_torrentManualUpdate (tor);
- notify (session, TR_RPC_TORRENT_CHANGED, tor);
+ tor->isSequential = true;
+ tr_torrentManualUpdate (tor);
+ notify (session, TR_RPC_TORRENT_CHANGED, tor);
}
}
+
+ tr_free (torrents);
+ return NULL;
+}
- tr_free (torrents);
- return NULL;
+static const char*
+torrentUnSetSequential (tr_session * session,
+ tr_variant * args_in,
+ tr_variant * args_out UNUSED,
+ struct tr_rpc_idle_data * idle_data UNUSED)
+{
+ int i;
+ int torrentCount;
+ tr_torrent ** torrents;
+
+ assert (idle_data == NULL);
+
+ torrents = getTorrents (session, args_in, &torrentCount);
+ for (i=0; i<torrentCount; ++i)
+ {
+ tr_torrent * tor = torrents[i];
+
+ if (tor->isSequential)
+ {
+ tor->isSequential = false;
+ tr_torrentManualUpdate (tor);
+ notify (session, TR_RPC_TORRENT_CHANGED, tor);
+ }
+ }
+
+ tr_free (torrents);
+ return NULL;
}
static const char*
@@ -579,13 +637,16 @@
const tr_quark key)
{
char * str;
-
+
switch (key)
{
case TR_KEY_activityDate:
tr_variantDictAddInt (d, key, st->activityDate);
break;
-
+ case TR_KEY_sequential:
+ tr_variantDictAddStr (d, key, st->isSequential ? "Yes" : "No");
+ break;
+
case TR_KEY_addedDate:
tr_variantDictAddInt (d, key, st->addedDate);
break;
@@ -2159,6 +2220,8 @@
{ "torrent-stop", true, torrentStop },
{ "torrent-verify", true, torrentVerify },
{ "torrent-reannounce", true, torrentReannounce },
+ { "torrent-set-sequential",true, torrentSetSequential},
+ { "torrent-unset-sequential",true, torrentUnSetSequential},
{ "queue-move-top", true, queueMoveTop },
{ "queue-move-up", true, queueMoveUp },
{ "queue-move-down", true, queueMoveDown },
Index: libtransmission/torrent.c
===================================================================
--- libtransmission/torrent.c (revision 14252)
+++ libtransmission/torrent.c (working copy)
@@ -1295,6 +1295,7 @@
s->sizeWhenDone = tr_cpSizeWhenDone (&tor->completion);
s->recheckProgress = s->activity == TR_STATUS_CHECK ? getVerifyProgress (tor) : 0;
s->activityDate = tor->activityDate;
+ s->isSequential = tor->isSequential;
s->addedDate = tor->addedDate;
s->doneDate = tor->doneDate;
s->startDate = tor->startDate;
@@ -1642,6 +1643,7 @@
now = tr_time ();
tor->isRunning = true;
+ tor->isSequential = false;
tor->completeness = tr_cpGetStatus (&tor->completion);
tor->startDate = tor->anyDate = now;
tr_torrentClearError (tor);
@@ -3239,15 +3241,16 @@
if (block_is_new)
{
- tr_piece_index_t p;
+ tr_piece_index_t p, p2;
tr_cpBlockAdd (&tor->completion, block);
tr_torrentSetDirty (tor);
p = tr_torBlockPiece (tor, block);
+ p2 = tr_getFirstPieceIndex(tor);
if (tr_torrentPieceIsComplete (tor, p))
{
- tr_logAddTorDbg (tor, "[LAZY] checking just-completed piece %"TR_PRIuSIZE, (size_t)p);
+ tr_logAddTorDbg (tor, "[LAZY] checking just-completed piece %"TR_PRIuSIZE", largest completed piece is %"TR_PRIuSIZE, (size_t)p, (size_t)p2);
if (tr_torrentCheckPiece (tor, p))
{
Index: libtransmission/torrent.h
===================================================================
--- libtransmission/torrent.h (revision 14252)
+++ libtransmission/torrent.h (working copy)
@@ -246,6 +246,7 @@
bool startAfterVerify;
bool isDirty;
bool isQueued;
+ bool isSequential;
bool magnetVerify;
@@ -498,6 +499,11 @@
return tr_cpHaveTotal (&tor->completion);
}
+static inline bool
+tr_torrentIsSequential (const tr_torrent * tor)
+{
+ return tor->isSequential;
+}
static inline bool
tr_torrentIsQueued (const tr_torrent * tor)
Index: libtransmission/transmission.h
===================================================================
--- libtransmission/transmission.h (revision 14252)
+++ libtransmission/transmission.h (working copy)
@@ -2055,6 +2055,9 @@
/** True if the torrent is running, but has been idle for long enough
to be considered stalled. @see tr_sessionGetQueueStalledMinutes () */
bool isStalled;
+
+ /** True if the torrent is downloading in sequential mode */
+ bool isSequential;
}
tr_stat;
Index: web/index.html
===================================================================
--- web/index.html (revision 14252)
+++ web/index.html (working copy)
@@ -208,6 +208,7 @@
<div class="row"><div class="key">Running Time:</div><div class="value" id="inspector-info-running-time">&nbsp;</div></div>
<div class="row"><div class="key">Remaining Time:</div><div class="value" id="inspector-info-remaining-time">&nbsp;</div></div>
<div class="row"><div class="key">Last Activity:</div><div class="value" id="inspector-info-last-activity">&nbsp;</div></div>
+ <div class="row"><div class="key">Sequential:</div><div class="value" id="inspector-info-sequential">&nbsp;</div></div>
<div class="row"><div class="key">Error:</div><div class="value" id="inspector-info-error">&nbsp;</div></div>
</div>
<div class="prefs-section">
@@ -425,6 +426,9 @@
<li id="context_move">Set Location…</li>
<li id="context_rename">Rename…</li>
<li class="separator"></li>
+ <li id="context_set_sequential">Set torrent to sequential mode</li>
+ <li id="context_unset_sequential">Set torrent to non-sequential mode</li>
+ <li class="separator"></li>
<li id="context_reannounce">Ask tracker for more peers</li>
<li class="separator"></li>
<li id="context_select_all">Select All</li>
Index: web/javascript/inspector.js
===================================================================
--- web/javascript/inspector.js (revision 14252)
+++ web/javascript/inspector.js (working copy)
@@ -301,6 +301,25 @@
str = fmt.timeInterval(d) + ' ago';
}
setTextContent(e.last_activity_lb, str);
+
+ //
+ // sequential
+ //
+
+ if(torrents.length < 1)
+ str = none;
+ else {
+ str = torrents[0].getSequential();
+ for(i=0; t=torrents[i]; ++i) {
+ if(str != t.getSequential()) {
+ str = mixed;
+ break;
+ }
+ }
+ }
+ if(!str)
+ str = none;
+ setTextContent(e.sequential_lb, str);
//
// error
@@ -774,6 +793,7 @@
data.elements.running_time_lb = $('#inspector-info-running-time')[0];
data.elements.remaining_time_lb = $('#inspector-info-remaining-time')[0];
data.elements.last_activity_lb = $('#inspector-info-last-activity')[0];
+ data.elements.sequential_lb = $('#inspector-info-sequential')[0];
data.elements.error_lb = $('#inspector-info-error')[0];
data.elements.size_lb = $('#inspector-info-size')[0];
data.elements.foldername_lb = $('#inspector-info-location')[0];
Index: web/javascript/remote.js
===================================================================
--- web/javascript/remote.js (revision 14252)
+++ web/javascript/remote.js (working copy)
@@ -215,6 +215,12 @@
reannounceTorrents: function(torrent_ids, callback, context) {
this.sendTorrentActionRequests('torrent-reannounce', torrent_ids, callback, context);
},
+ setSequentialTorrents: function(torrent_ids, callback, context) {
+ this.sendTorrentActionRequests('torrent-set-sequential', torrent_ids, callback, context);
+ },
+ unsetSequentialTorrents: function(torrent_ids, callback, context) {
+ this.sendTorrentActionRequests('torrent-unset-sequential', torrent_ids, callback, context);
+ },
addTorrentByUrl: function(url, options) {
var remote = this;
if (url.match(/^[0-9a-f]{40}$/i)) {
Index: web/javascript/torrent.js
===================================================================
--- web/javascript/torrent.js (revision 14252)
+++ web/javascript/torrent.js (working copy)
@@ -97,6 +97,7 @@
// fields used in the inspector which need to be periodically refreshed
Torrent.Fields.StatsExtra = [
'activityDate',
+ 'sequential',
'corruptEver',
'desiredAvailable',
'downloadedEver',
@@ -228,6 +229,7 @@
getHaveValid: function() { return this.fields.haveValid; },
getId: function() { return this.fields.id; },
getLastActivity: function() { return this.fields.activityDate; },
+ getSequential: function() { return this.fields.sequential; },
getLeftUntilDone: function() { return this.fields.leftUntilDone; },
getMetadataPercentComplete: function() { return this.fields.metadataPercentComplete; },
getName: function() { return this.fields.name || 'Unknown'; },
Index: web/javascript/transmission.js
===================================================================
--- web/javascript/transmission.js (revision 14252)
+++ web/javascript/transmission.js (working copy)
@@ -189,6 +189,8 @@
context_verify: function() { tr.verifySelectedTorrents(); },
context_rename: function() { tr.renameSelectedTorrents(); },
context_reannounce: function() { tr.reannounceSelectedTorrents(); },
+ context_set_sequential: function() { tr.setSequentialSelectedTorrents(); },
+ context_unset_sequential: function() { tr.unsetSequentialSelectedTorrents(); },
context_move_top: function() { tr.moveTop(); },
context_move_up: function() { tr.moveUp(); },
context_move_down: function() { tr.moveDown(); },
@@ -1107,6 +1109,14 @@
reannounceSelectedTorrents: function() {
this.reannounceTorrents(this.getSelectedTorrents());
},
+
+ setSequentialSelectedTorrents: function() {
+ this.setSequentialTorrents(this.getSelectedTorrents());
+ },
+
+ unsetSequentialSelectedTorrents: function() {
+ this.unsetSequentialTorrents(this.getSelectedTorrents());
+ },
startAllTorrents: function(force) {
this.startTorrents(this.getAllTorrents(), force);
@@ -1138,6 +1148,22 @@
this.refreshTorrents, this);
},
+ setSequentialTorrent: function(torrent) {
+ this.setSequentialTorrents([ torrent ]);
+ },
+ setSequentialTorrents: function(torrents) {
+ this.remote.setSequentialTorrents(this.getTorrentIds(torrents),
+ this.refreshTorrents, this);
+ },
+
+ unsetSequentialTorrent: function(torrent) {
+ this.unsetSequentialTorrents([ torrent ]);
+ },
+ unsetSequentialTorrents: function(torrents) {
+ this.remote.unsetSequentialTorrents(this.getTorrentIds(torrents),
+ this.refreshTorrents, this);
+ },
+
stopAllTorrents: function() {
this.stopTorrents(this.getAllTorrents());
},
@jhenkens
Copy link
Author

jhenkens commented Feb 8, 2015

@nelsonjchecn I never noticed that you commented on this! I wish gists would send emails....

I hardly ever use my sequential patch - it is not the default download option, and I have to manually set it for each torrent. It has worked very well, although occasionally transmission does crash while running in the background (I have no idea why - I'm thinking of updating this patch to use a current version of the source). I use transmission on a headless server, because I find most torrent clients way to bloated with features.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment