Skip to content

Commit 1fcfd7e

Browse files
committed
feat: 允许将 hybrid-torrent 进行降级
1 parent 7ff9cb6 commit 1fcfd7e

3 files changed

Lines changed: 146 additions & 3 deletions

File tree

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,6 @@ $count = $torrent->getFileCount();
203203
*/
204204
$fileList = $torrent->getFileList();
205205

206-
207206
/**
208207
* Return a dict like:
209208
* [
@@ -226,6 +225,22 @@ $fileList = $torrent->getFileList();
226225
*/
227226
$fileTree = $torrent->getFileTree(?$sortType = TorrentFile::FILETREE_SORT_NORMAL);
228227

228+
// 6. unhybridized
229+
/**
230+
* > Add in v2.5.0
231+
*
232+
* Create an unhybridized copy of a hybrid torrent for the specified single protocol version
233+
* (does not modify the original instance).
234+
*
235+
* This method returns a clone of the current object and removes metadata fields from the clone
236+
* that are incompatible with the target version to produce an "unhybridized" torrent.
237+
* For example:
238+
* when the target is `TorrentFile::PROTOCOL_V1`, v2 fields are removed;
239+
* when the target is `TorrentFile::PROTOCOL_V2`, v1 fields are removed.
240+
*/
241+
$v1ProtocolOnlyTorrent = $hybridTorrent->unhybridized(TorrentFile::PROTOCOL_V1);
242+
$v2ProtocolOnlyTorrent = $hybridTorrent->unhybridized(TorrentFile::PROTOCOL_V2);
243+
229244
// 6. Other method
230245
$torrent->cleanCache();
231246

src/TorrentFile.php

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ function str_contains($haystack, $needle)
2222
* @param array $array
2323
* @return false|mixed|null
2424
*/
25-
function array_last(array $array) {
25+
function array_last(array $array)
26+
{
2627
return $array ? current(array_slice($array, -1)) : null;
2728
}
2829
}
@@ -767,6 +768,14 @@ private static function sortFileTreeRecursive(array &$fileTree, $sortByString =
767768
* "filename1" => 123 // 123 is file size
768769
* ]
769770
* ]
771+
*
772+
* > Add in v2.4.0
773+
* You can now pass argument to control the fileTree sort type. By default, this argument is TorrentFile::FILETREE_SORT_NORMAL.
774+
* Control Const (see different in `tests/TorrentFileTreeSortTest.php` file):
775+
* - TorrentFile::FILETREE_SORT_NORMAL : not sort, also means sort by torrent file parsed order
776+
* - TorrentFile::FILETREE_SORT_STRING : sort by filename ASC ("natural ordering" and "case-insensitively")
777+
* - TorrentFile::FILETREE_SORT_FOLDER : sort by filetype (first folder, then file)
778+
* - TorrentFile::FILETREE_SORT_NATURAL: sort by both filetype and filename ( same as `TorrentFile::FILETREE_SORT_STRING | TorrentFile::FILETREE_SORT_FOLDER` )
770779
*/
771780
public function getFileTree($sortType = self::FILETREE_SORT_NORMAL)
772781
{
@@ -785,6 +794,55 @@ public function getFileTree($sortType = self::FILETREE_SORT_NORMAL)
785794
return $fileTree;
786795
}
787796

797+
/**
798+
* > Add in v2.5.0
799+
*
800+
* Create an unhybridized copy of a hybrid torrent for the specified single protocol version
801+
* (does not modify the original instance).
802+
*
803+
* This method returns a clone of the current object and removes metadata fields from the clone
804+
* that are incompatible with the target version to produce an "unhybridized" torrent.
805+
* For example:
806+
* when the target is `TorrentFile::PROTOCOL_V1`, v2 fields are removed;
807+
* when the target is `TorrentFile::PROTOCOL_V2`, v1 fields are removed.
808+
*
809+
* @param string $targetVersion Target protocol version, use class constants `TorrentFile::PROTOCOL_V1`
810+
* or `TorrentFile::PROTOCOL_V2`. Defaults to `TorrentFile::PROTOCOL_V1`.
811+
* @return TorrentFile The cloned `TorrentFile` instance converted to the target version.
812+
* @throws ParseException If the current torrent is not hybrid or an unknown `$targetVersion` is provided.
813+
*/
814+
public function unhybridized($targetVersion = self::PROTOCOL_V1)
815+
{
816+
$currentProtocol = $this->getProtocol();
817+
if ($currentProtocol !== self::PROTOCOL_HYBRID && $currentProtocol !== $targetVersion) {
818+
throw new ParseException("Unable to unhybridized, this torrent is {$currentProtocol}-only and can't convert to {$targetVersion}.");
819+
}
820+
821+
$unhybridizedTorrent = clone $this;
822+
unset($unhybridizedTorrent->cache['protocol']); // clean protocol cache if exist
823+
if ($targetVersion == self::PROTOCOL_HYBRID) {
824+
// Nothing need to do when hybrid torrent unhybridized to hybrid
825+
} elseif ($targetVersion == self::PROTOCOL_V1) {
826+
// Remove Bittorrent v2 field
827+
$unhybridizedTorrent
828+
->unsetRootField('piece layers')
829+
->unsetInfoField('meta version')
830+
->unsetInfoField('file tree');
831+
} elseif ($targetVersion == self::PROTOCOL_V2) {
832+
// Remove Bittorrent v1 field
833+
$unhybridizedTorrent
834+
->unsetInfoField('pieces')
835+
->unsetInfoField('files')
836+
->unsetInfoField('length')
837+
->unsetInfoField('attr')
838+
->unsetInfoField('sha1');
839+
} else {
840+
throw new ParseException('Unknown unhybridized target.');
841+
}
842+
843+
return $unhybridizedTorrent;
844+
}
845+
788846
public function cleanCache()
789847
{
790848
$this->cache = [];

tests/traits/TorrentFileCommonTrait.php

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ public function testNodes()
8484
{
8585
$this->assertNull($this->torrent->getNodes());
8686

87-
$nodes = ['udp://example.com/seed'];
87+
$nodes = [
88+
['127.0.0.1', 6881],
89+
['your.router.node', 4804],
90+
['2001:db8:100:0:d5c8:db3f:995e:c0f7', 1941]
91+
];
8892
$this->torrent->setNodes($nodes);
8993
$this->assertEquals($nodes, $this->torrent->getNodes());
9094
}
@@ -343,4 +347,70 @@ public function testCustomParseValidator()
343347
});
344348
$this->torrent->parse();
345349
}
350+
351+
public function testUnhybridizedTorrent()
352+
{
353+
$currentTorrentProtocol = $this->torrent->getProtocol();
354+
355+
if ($currentTorrentProtocol == TorrentFile::PROTOCOL_HYBRID) {
356+
// After unhybridized to target 'v1' or 'v2', the returned torrent's protocol should be target protocol.
357+
$v1ProtocolOnlyTorrent = $this->torrent->unhybridized(TorrentFile::PROTOCOL_V1);
358+
$this->assertEquals(TorrentFile::PROTOCOL_V1, $v1ProtocolOnlyTorrent->getProtocol());
359+
360+
$v2ProtocolOnlyTorrent = $this->torrent->unhybridized(TorrentFile::PROTOCOL_V2);
361+
$this->assertEquals(TorrentFile::PROTOCOL_V2, $v2ProtocolOnlyTorrent->getProtocol());
362+
363+
// The origin torrent's protocol is not change.
364+
$this->assertEquals(TorrentFile::PROTOCOL_HYBRID, $this->torrent->getProtocol());
365+
} else {
366+
$this->markTestSkipped();
367+
}
368+
}
369+
370+
public function testUnhybridizedTorrentToUnknownTarget()
371+
{
372+
$currentTorrentProtocol = $this->torrent->getProtocol();
373+
374+
if ($currentTorrentProtocol == TorrentFile::PROTOCOL_HYBRID) {
375+
$this->expectException(ParseException::class);
376+
$this->expectExceptionMessage('Unknown unhybridized target.');
377+
378+
$this->torrent->unhybridized('unknown');
379+
} else {
380+
$this->markTestSkipped();
381+
}
382+
}
383+
384+
public function testUnhybridizedTorrentToSameProtocol()
385+
{
386+
$currentTorrentProtocol = $this->torrent->getProtocol();
387+
$unhybridizedTorrentWithSameProtocol = $this->torrent->unhybridized($currentTorrentProtocol);
388+
$this->assertEquals($currentTorrentProtocol, $unhybridizedTorrentWithSameProtocol->getProtocol());
389+
}
390+
391+
public function testNonHybridTorrentCantConversion()
392+
{
393+
$currentTorrentProtocol = $this->torrent->getProtocol();
394+
if ($currentTorrentProtocol != TorrentFile::PROTOCOL_HYBRID) {
395+
// Conversion between v1-only and v2-only torrents is not allowed
396+
$this->expectException(ParseException::class);
397+
$this->expectExceptionMessage('Unable to unhybridized, this torrent is ');
398+
$this->torrent->unhybridized($currentTorrentProtocol == TorrentFile::PROTOCOL_V1 ? TorrentFile::PROTOCOL_V2 : TorrentFile::PROTOCOL_V1);
399+
} else {
400+
$this->markTestSkipped();
401+
}
402+
}
403+
404+
public function testNonHybridTorrentCantHybrid()
405+
{
406+
$currentTorrentProtocol = $this->torrent->getProtocol();
407+
if ($currentTorrentProtocol != TorrentFile::PROTOCOL_HYBRID) {
408+
// v1-only and v2-only torrents can't upgrade to hybrid torrent
409+
$this->expectException(ParseException::class);
410+
$this->expectExceptionMessage('Unable to unhybridized, this torrent is ');
411+
$this->torrent->unhybridized(TorrentFile::PROTOCOL_HYBRID);
412+
} else {
413+
$this->markTestSkipped();
414+
}
415+
}
346416
}

0 commit comments

Comments
 (0)