diff --git a/CHANGELOG.md b/CHANGELOG.md index fc5948d..25440fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,4 +31,7 @@ - Change congestion control ## 0.3.0 -- Add Send Metadata extension (BEP0009) \ No newline at end of file +- Add Send Metadata extension (BEP0009) + +## 0.3.1 +- nullsafety \ No newline at end of file diff --git a/README.md b/README.md index a18b36e..4595c3e 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Dart library for implementing BitTorrent client. Whole Dart Torrent client contains serival parts : -- [Bencode](https://pub.dev/packages/bencode_dart) +- [Bencode](https://pub.dev/packages/b_encode_decode) - [Tracker](https://pub.dev/packages/torrent_tracker) - [DHT](https://pub.dev/packages/dht_dart) -- [Torrent model](https://pub.dev/packages/torrent_model) -- [Common library](https://pub.dev/packages/dartorrent_common) +- [Torrent model](https://pub.dev/packages/dtorrent_parser) +- [Common library](https://pub.dev/packages/dtorrent_common) - [UTP](https://pub.dev/packages/utp) This package implements regular BitTorrent Protocol and manage above packages to work together for downloading. @@ -29,10 +29,10 @@ Other support will come soon. ## How to use -This package need to dependency [`torrent_model`](https://pub.dev/packages/torrent_model): +This package need to dependency [`dtorrent_parser`](https://pub.dev/packages/dtorrent_parser): ``` dependencies: - torrent_model : ^1.0.3 + dtorrent_parser : ^1.0.4 torrent_task : '>= 0.2.1 < 2.0.0' ``` diff --git a/analysis_options.yaml b/analysis_options.yaml index a686c1b..f274664 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,14 +1,29 @@ -# Defines a default set of lint rules enforced for -# projects at Google. For details and rationale, -# see https://github.com/dart-lang/pedantic#enabled-lints. -include: package:pedantic/analysis_options.yaml +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml +# Uncomment the following section to specify additional rules. -# For lint rules and documentation, see http://dart-lang.github.io/linter/lints. -# Uncomment to specify additional rules. # linter: # rules: # - camel_case_types -analyzer: +# analyzer: # exclude: # - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/example/lsd_example.dart b/example/lsd_example.dart index 52314bc..fc8cf4e 100644 --- a/example/lsd_example.dart +++ b/example/lsd_example.dart @@ -1,14 +1,12 @@ import 'dart:io'; -import 'package:torrent_model/torrent_model.dart'; -import 'package:torrent_task/src/lsd/lsd.dart'; -import 'package:torrent_task/torrent_task.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_task/src/lsd/lsd.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; void main(List args) async { print(await getTorrenTaskVersion()); - exit(1); - var torrentFile = 'example/12.torrent'; - var savePath = 'g:/bttest'; + var torrentFile = 'example${Platform.pathSeparator}test4.torrent'; var model = await Torrent.parse(torrentFile); var infoHash = model.infoHash; var lsd = LSD(infoHash, 'daa231dfa'); diff --git a/example/metadata_example.dart b/example/metadata_example.dart index 314a255..9909eed 100644 --- a/example/metadata_example.dart +++ b/example/metadata_example.dart @@ -1,9 +1,12 @@ +import 'dart:async'; import 'dart:typed_data'; -import 'package:bencode_dart/bencode_dart.dart'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:torrent_task/src/metadata/metadata_downloader.dart'; -import 'package:torrent_tracker/torrent_tracker.dart'; +import 'package:b_encode_decode/b_encode_decode.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_task/src/metadata/metadata_downloader.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; +import 'package:dtorrent_tracker/dtorrent_tracker.dart'; void main(List args) async { var infohashString = '217bddb5816f2abc56ce1d9fe430711542b109cc'; @@ -15,10 +18,46 @@ void main(List args) async { var tracker = TorrentAnnounceTracker(metadata); // When metadata contents download complete , it will send this event and stop itself: - metadata.onDownloadComplete((data) { + metadata.onDownloadComplete((data) async { + tracker.stop(true); var msg = decode(Uint8List.fromList(data)); - print('complete , info : $msg'); - tracker?.stop(true); + Map torrent = {}; + torrent['info'] = msg; + var torrentModel = parseTorrentFileContent(torrent); + if (torrentModel != null) { + print('complete , info : ${torrentModel.name}'); + var startTime = DateTime.now().millisecondsSinceEpoch; + var task = TorrentTask.newTask(torrentModel, 'tmp'); + Timer? timer; + task.onTaskComplete(() { + print( + 'Complete! spend time : ${((DateTime.now().millisecondsSinceEpoch - startTime) / 60000).toStringAsFixed(2)} minutes'); + timer?.cancel(); + task.stop(); + }); + task.onStop(() async { + print('Task Stopped'); + }); + timer = Timer.periodic(Duration(seconds: 2), (timer) async { + var progress = '${(task.progress * 100).toStringAsFixed(2)}%'; + var ads = + ((task.averageDownloadSpeed) * 1000 / 1024).toStringAsFixed(2); + var aps = ((task.averageUploadSpeed) * 1000 / 1024).toStringAsFixed(2); + var ds = ((task.currentDownloadSpeed) * 1000 / 1024).toStringAsFixed(2); + var ps = ((task.uploadSpeed) * 1000 / 1024).toStringAsFixed(2); + + var utpd = ((task.utpDownloadSpeed) * 1000 / 1024).toStringAsFixed(2); + var utpu = ((task.utpUploadSpeed) * 1000 / 1024).toStringAsFixed(2); + var utpc = task.utpPeerCount; + + var active = task.connectedPeersNumber; + var seeders = task.seederNumber; + var all = task.allPeersNumber; + print( + 'Progress : $progress , Peers:($active/$seeders/$all)($utpc) . Download speed : ($utpd)($ads/$ds)kb/s , upload speed : ($utpu)($aps/$ps)kb/s'); + }); + await task.start(); + } }); var u8List = Uint8List.fromList(metadata.infoHashBuffer); @@ -26,14 +65,14 @@ void main(List args) async { tracker.onPeerEvent((source, event) { if (event == null) return; var peers = event.peers; - peers.forEach((element) { - metadata.addNewPeerAddress(element); - }); + for (var element in peers) { + metadata.addNewPeerAddress(element, PeerSource.tracker); + } }); // ignore: unawaited_futures findPublicTrackers().listen((alist) { - alist.forEach((element) { + for (var element in alist) { tracker.runTracker(element, u8List); - }); + } }); } diff --git a/example/torrent_task_comin_example.dart b/example/torrent_task_comin_example.dart index e929ba2..b399c74 100644 --- a/example/torrent_task_comin_example.dart +++ b/example/torrent_task_comin_example.dart @@ -1,18 +1,19 @@ import 'dart:async'; import 'dart:io'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:torrent_model/torrent_model.dart'; -import 'package:torrent_task/torrent_task.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; /// This example is for connect local Future main() async { - var model = await Torrent.parse('example/test8.torrent'); - // 不获取peers + var model = + await Torrent.parse('example${Platform.pathSeparator}test4.torrent'); + // No peers retrieval model.announces.clear(); - var task = TorrentTask.newTask(model, 'g:/bttest5/'); - Timer timer; - Timer timer1; + var task = TorrentTask.newTask(model, 'tmp${Platform.pathSeparator}test'); + Timer? timer; + Timer? timer1; task.onFileComplete((filepath) { print('$filepath downloaded complete'); }); @@ -31,7 +32,7 @@ Future main() async { timer = Timer.periodic(Duration(seconds: 2), (timer) { try { print( - 'Downloaed: ${task.downloaded / (1024 * 1024)} mb , ${((task.downloaded / model.length) * 100).toStringAsFixed(2)}%'); + 'Downloaed: ${task.downloaded ?? 0 / (1024 * 1024)} mb , ${((task.downloaded ?? 0 / model.length) * 100).toStringAsFixed(2)}%'); } finally {} }); @@ -50,7 +51,7 @@ Future main() async { // await Future.delayed(Duration(seconds: 120)); // task.resume(); // }); - // 自己下载自己 - task.addPeer(CompactAddress(InternetAddress.tryParse('192.168.0.24'), 57331), - PeerType.UTP); + // download from yourself + task.addPeer(CompactAddress(InternetAddress.tryParse('192.168.0.24')!, 57331), + PeerSource.manual); } diff --git a/example/torrent_task_example.dart b/example/torrent_task_example.dart index 1b9cea7..375f344 100644 --- a/example/torrent_task_example.dart +++ b/example/torrent_task_example.dart @@ -1,19 +1,20 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:torrent_model/torrent_model.dart'; -import 'package:torrent_task/torrent_task.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; void main() async { try { - var torrentFile = 'example/test4.torrent'; - var savePath = 'g:/bttest'; + var torrentFile = 'example${Platform.pathSeparator}test4.torrent'; + var savePath = 'tmp${Platform.pathSeparator}test'; var model = await Torrent.parse(torrentFile); // model.announces.clear(); var task = TorrentTask.newTask(model, savePath); - Timer timer; - Timer timer1; + Timer? timer; + Timer? timer1; var startTime = DateTime.now().millisecondsSinceEpoch; task.onTaskComplete(() { print( @@ -29,29 +30,26 @@ void main() async { // ignore: unawaited_futures findPublicTrackers().listen((alist) { - alist.forEach((element) { + for (var element in alist) { task.startAnnounceUrl(element, model.infoHashBuffer); - }); + } }); - - model.nodes?.forEach((element) { + log('Adding dht nodes'); + for (var element in model.nodes) { + log('dht node $element'); task.addDHTNode(element); - }); + } print(map); timer = Timer.periodic(Duration(seconds: 2), (timer) async { var progress = '${(task.progress * 100).toStringAsFixed(2)}%'; - var ads = - '${((task.averageDownloadSpeed) * 1000 / 1024).toStringAsFixed(2)}'; - var aps = - '${((task.averageUploadSpeed) * 1000 / 1024).toStringAsFixed(2)}'; - var ds = - '${((task.currentDownloadSpeed) * 1000 / 1024).toStringAsFixed(2)}'; - var ps = '${((task.uploadSpeed) * 1000 / 1024).toStringAsFixed(2)}'; + var ads = ((task.averageDownloadSpeed) * 1000 / 1024).toStringAsFixed(2); + var aps = ((task.averageUploadSpeed) * 1000 / 1024).toStringAsFixed(2); + var ds = ((task.currentDownloadSpeed) * 1000 / 1024).toStringAsFixed(2); + var ps = ((task.uploadSpeed) * 1000 / 1024).toStringAsFixed(2); - var utpd = - '${((task.utpDownloadSpeed) * 1000 / 1024).toStringAsFixed(2)}'; - var utpu = '${((task.utpUploadSpeed) * 1000 / 1024).toStringAsFixed(2)}'; + var utpd = ((task.utpDownloadSpeed) * 1000 / 1024).toStringAsFixed(2); + var utpu = ((task.utpUploadSpeed) * 1000 / 1024).toStringAsFixed(2); var utpc = task.utpPeerCount; var active = task.connectedPeersNumber; diff --git a/lib/torrent_task.dart b/lib/dtorrent_task.dart similarity index 83% rename from lib/torrent_task.dart rename to lib/dtorrent_task.dart index dc025e7..6e3c6f7 100644 --- a/lib/torrent_task.dart +++ b/lib/dtorrent_task.dart @@ -1,4 +1,4 @@ -library torrent_task; +library dtorrent_task; import 'dart:io'; @@ -7,11 +7,11 @@ export 'src/file/file_base.dart'; export 'src/piece/piece_base.dart'; export 'src/peer/peer_base.dart'; -/// Peer ID前缀 +/// Peer ID prefix const ID_PREFIX = '-DT0201-'; -/// 当前版本号 -Future getTorrenTaskVersion() async { +/// Current version number +Future getTorrenTaskVersion() async { var file = File('pubspec.yaml'); if (await file.exists()) { var lines = await file.readAsLines(); diff --git a/lib/src/file/download_file.dart b/lib/src/file/download_file.dart index 413a151..94cfc89 100644 --- a/lib/src/file/download_file.dart +++ b/lib/src/file/download_file.dart @@ -15,15 +15,15 @@ class DownloadFile { final int length; - File _file; + File? _file; - RandomAccessFile _writeAcces; + RandomAccessFile? _writeAcces; - RandomAccessFile _readAccess; + RandomAccessFile? _readAccess; - StreamController _sc; + StreamController? _sc; - StreamSubscription _ss; + StreamSubscription? _ss; DownloadFile(this.filePath, this.start, this.length); @@ -45,7 +45,7 @@ class DownloadFile { int position, List block, int start, int end) async { _writeAcces ??= await getRandomAccessFile(WRITE); var completer = Completer(); - _sc.add({ + _sc?.add({ 'type': WRITE, 'position': position, 'block': block, @@ -59,7 +59,7 @@ class DownloadFile { Future> requestRead(int position, int length) async { _readAccess ??= await getRandomAccessFile(READ); var completer = Completer>(); - _sc.add({ + _sc?.add({ 'type': READ, 'position': position, 'length': length, @@ -68,11 +68,13 @@ class DownloadFile { return completer.future; } - /// 处理读写请求 + /// Process read and write requests. /// - /// 每次只处理一个请求。`Stream`在进入该方法后通过`StreamSubscription`暂停通道信息读取,直到处理完一条请求后才恢复 + /// Only one request is processed at a time. The Stream is paused through StreamSubscription + /// upon entering this method, and it resumes reading from the channel only after processing + /// the current request. void _processRequest(event) async { - _ss.pause(); + _ss?.pause(); if (event['type'] == WRITE) { await _write(event); } @@ -82,10 +84,10 @@ class DownloadFile { if (event['type'] == FLUSH) { await _flush(event); } - _ss.resume(); + _ss?.resume(); } - void _write(event) async { + Future _write(event) async { Completer completer = event['completer']; try { int position = event['position']; @@ -94,8 +96,8 @@ class DownloadFile { List block = event['block']; _writeAcces = await getRandomAccessFile(WRITE); - _writeAcces = await _writeAcces.setPosition(position); - _writeAcces = await _writeAcces.writeFrom(block, start, end); + _writeAcces = await _writeAcces?.setPosition(position); + _writeAcces = await _writeAcces?.writeFrom(block, start, end); completer.complete(true); } catch (e) { log('Write file error:', error: e, name: runtimeType.toString()); @@ -103,19 +105,19 @@ class DownloadFile { } } - /// 请求将缓冲区写入磁盘 + /// Request to write the buffer to disk. Future requestFlush() async { _writeAcces ??= await getRandomAccessFile(WRITE); var completer = Completer(); - _sc.add({'type': FLUSH, 'completer': completer}); + _sc?.add({'type': FLUSH, 'completer': completer}); return completer.future; } - void _flush(event) async { + Future _flush(event) async { Completer completer = event['completer']; try { _writeAcces = await getRandomAccessFile(WRITE); - await _writeAcces.flush(); + await _writeAcces?.flush(); completer.complete(true); } catch (e) { log('Flush error:', error: e, name: runtimeType.toString()); @@ -123,7 +125,7 @@ class DownloadFile { } } - void _read(event) async { + Future _read(event) async { Completer completer = event['completer']; try { int position = event['position']; @@ -140,40 +142,38 @@ class DownloadFile { } /// - /// 获取对应文件,如果文件不存在会创建一个新文件 - Future _getOrCreateFile() async { - if (filePath == null) return null; + /// Get the corresponding file, and if the file does not exist, create a new file. + Future _getOrCreateFile() async { _file ??= File(filePath); - var exists = await _file.exists(); - if (!exists) { - _file = await _file.create(recursive: true); - var access = await _file.open(mode: FileMode.write); - access = await access.truncate(length); - await access.close(); + var exists = await _file?.exists(); + if (exists != null && !exists) { + _file = await _file?.create(recursive: true); + var access = await _file?.open(mode: FileMode.write); + access = await access?.truncate(length); + await access?.close(); } return _file; } - Future get exists { - if (filePath == null) return null; + Future? get exists { return File(filePath).exists(); } Future getRandomAccessFile(String type) async { var file = await _getOrCreateFile(); - var access; + RandomAccessFile? access; if (type == WRITE) { - _writeAcces ??= await file.open(mode: FileMode.writeOnlyAppend); + _writeAcces ??= await file?.open(mode: FileMode.writeOnlyAppend); access = _writeAcces; } else if (type == READ) { - _readAccess ??= await file.open(mode: FileMode.read); + _readAccess ??= await file?.open(mode: FileMode.read); access = _readAccess; } if (_sc == null) { _sc = StreamController(); - _ss = _sc.stream.listen(_processRequest); + _ss = _sc?.stream.listen(_processRequest); } - return access; + return access!; } Future close() async { @@ -216,13 +216,13 @@ class DownloadFile { } @override - bool operator ==(n) { - if (n is DownloadFile) { - return n.filePath == filePath; + bool operator ==(other) { + if (other is DownloadFile) { + return other.filePath == filePath; } return false; } @override - int get hashCode => filePath?.hashCode; + int get hashCode => filePath.hashCode; } diff --git a/lib/src/file/download_file_manager.dart b/lib/src/file/download_file_manager.dart index dedd51a..e3ad13b 100644 --- a/lib/src/file/download_file_manager.dart +++ b/lib/src/file/download_file_manager.dart @@ -1,7 +1,8 @@ import 'dart:async'; import 'dart:developer'; +import 'dart:io'; -import 'package:torrent_model/torrent_model.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; import '../peer/peer_base.dart'; import 'download_file.dart'; @@ -18,9 +19,9 @@ class DownloadFileManager { final Set _files = {}; - List> _piece2fileMap; + List?>? _piece2fileMap; - final Map> _file2pieceMap = {}; + final Map?> _file2pieceMap = {}; final List _subPieceCompleteHandles = []; @@ -32,10 +33,9 @@ class DownloadFileManager { final StateFile _stateFile; - /// TODO - /// - 没有建立文件读取缓存 + /// TODO: File read caching DownloadFileManager(this.metainfo, this._stateFile) { - _piece2fileMap = List(_stateFile.bitfield.piecesNum); + _piece2fileMap = List.filled(_stateFile.bitfield.piecesNum, null); } static Future createFileManager( @@ -47,8 +47,8 @@ class DownloadFileManager { Future _init(String directory) async { var lastc = directory.substring(directory.length - 1); - if (lastc != '\\' || lastc != '/') { - directory = directory + '\\'; + if (lastc != Platform.pathSeparator) { + directory = directory + Platform.pathSeparator; } _initFileMap(directory); return this; @@ -68,15 +68,15 @@ class DownloadFileManager { int get piecesNumber => _stateFile.bitfield.piecesNum; void _subPieceWriteComplete(int pieceIndex, int begin, int length) { - _subPieceCompleteHandles.forEach((handle) { + for (var handle in _subPieceCompleteHandles) { Timer.run(() => handle(pieceIndex, begin, length)); - }); + } } void _subPieceWriteFailed(int pieceIndex, int begin, int length) { - _subPieceFailedHandles.forEach((handle) { + for (var handle in _subPieceFailedHandles) { Timer.run(() => handle(pieceIndex, begin, length)); - }); + } } Future updateBitfield(int index, [bool have = true]) { @@ -92,22 +92,24 @@ class DownloadFileManager { } void _subPieceReadComplete(int pieceIndex, int begin, List block) { - _subPieceReadHandles.forEach((h) { + for (var h in _subPieceReadHandles) { Timer.run(() => h(pieceIndex, begin, block)); - }); + } } - int get downloaded => _stateFile?.downloaded; + int get downloaded => _stateFile.downloaded; - /// 该方法看似只将缓冲区内容写入磁盘,实际上 - /// 每当缓存写入后都会认为该[pieceIndex]对应`Piece`已经完成,则会去移除 - /// `_file2pieceMap`中文件对应的piece index,当全部移除完毕,会抛出File Complete事件 + /// This method appears to only write the buffer content to the disk, but in + /// reality,every time the cache is written, it is considered that the [Piece] + /// corresponding to [pieceIndex] has been completed. Therefore, it will + /// remove the file's corresponding piece index from the _file2pieceMap. When + /// all the pieces have been removed, a File Complete event will be triggered. Future flushFiles(Set pieceIndices) async { var d = _stateFile.downloaded; var flushed = {}; for (var i = 0; i < pieceIndices.length; i++) { var pieceIndex = pieceIndices.elementAt(i); - var fs = _piece2fileMap[pieceIndex]; + var fs = _piece2fileMap?[pieceIndex]; if (fs == null || fs.isEmpty) continue; for (var i = 0; i < fs.length; i++) { var file = fs[i]; @@ -125,7 +127,7 @@ class DownloadFileManager { } var msg = - '已下载:${d / (1024 * 1024)} mb , 完成度 ${((d / metainfo.length) * 10000).toInt() / 100} %'; + 'downloaded:${d / (1024 * 1024)} mb , Progress ${((d / metainfo.length) * 10000).toInt() / 100} %'; log(msg, name: runtimeType.toString()); return true; } @@ -139,9 +141,9 @@ class DownloadFileManager { } void _fireFileComplete(String path) { - _fileCompleteHandles.forEach((element) { + for (var element in _fileCompleteHandles) { Timer.run(() => element(path)); - }); + } } void _initFileMap(String directory) { @@ -160,11 +162,11 @@ class DownloadFileManager { } if (fe.remainder(metainfo.pieceLength) == 0) endPiece--; for (var pieceIndex = startPiece; pieceIndex <= endPiece; pieceIndex++) { - var l = _piece2fileMap[pieceIndex]; + var l = _piece2fileMap?[pieceIndex]; if (l == null) { l = []; - _piece2fileMap[pieceIndex] = l; - if (!localHave(pieceIndex)) pieces.add(pieceIndex); + _piece2fileMap?[pieceIndex] = l; + if (localHave(pieceIndex)) pieces.add(pieceIndex); } l.add(df); } @@ -196,7 +198,7 @@ class DownloadFileManager { } void readFile(int pieceIndex, int begin, int length) { - var tempFiles = _piece2fileMap[pieceIndex]; + var tempFiles = _piece2fileMap?[pieceIndex]; var ps = pieceIndex * metainfo.pieceLength + begin; var pe = ps + length; if (tempFiles == null || tempFiles.isEmpty) return; @@ -218,13 +220,13 @@ class DownloadFileManager { } /// - /// 将`Sub Piece`的内容写入文件中。完成后会发送 `sub piece complete`事件, - /// 如果失败,就会发送`sub piece failed`事件 + // Writes the content of a Sub Piece to the file. After completion, a sub piece complete event will be sent. + /// If it fails, a sub piece failed event will be sent. /// - /// 该`Sub Piece`是来自于[pieceIndex]对应的`Piece`,内容为[block],起始位置是[begin]。 - /// 该类不会去验证写入的Sub Piece是否重复,重复内容直接覆盖之前内容 + /// The Sub Piece is from the Piece corresponding to [pieceIndex], and the content is [block] starting from [begin]. + /// This class does not validate if the written Sub Piece is a duplicate; it simply overwrites the previous content. void writeFile(int pieceIndex, int begin, List block) { - var tempFiles = _piece2fileMap[pieceIndex]; + var tempFiles = _piece2fileMap?[pieceIndex]; var ps = pieceIndex * metainfo.pieceLength + begin; var blockSize = block.length; var pe = ps + blockSize; @@ -251,7 +253,7 @@ class DownloadFileManager { return; } - Map _mapDownloadFilePosition( + Map? _mapDownloadFilePosition( int pieceStart, int pieceEnd, int length, DownloadFile tempFile) { var fs = tempFile.start; var fe = fs + tempFile.length; @@ -275,7 +277,7 @@ class DownloadFileManager { } Future close() async { - await _stateFile?.close(); + await _stateFile.close(); for (var i = 0; i < _files.length; i++) { var file = _files.elementAt(i); await file.close(); @@ -284,16 +286,16 @@ class DownloadFileManager { } void _clean() { - _subPieceCompleteHandles?.clear(); - _subPieceFailedHandles?.clear(); - _subPieceReadHandles?.clear(); - _fileCompleteHandles?.clear(); - _file2pieceMap?.clear(); + _subPieceCompleteHandles.clear(); + _subPieceFailedHandles.clear(); + _subPieceReadHandles.clear(); + _fileCompleteHandles.clear(); + _file2pieceMap.clear(); _piece2fileMap = null; } Future delete() async { - await _stateFile?.delete(); + await _stateFile.delete(); for (var i = 0; i < _files.length; i++) { var file = _files.elementAt(i); await file.delete(); diff --git a/lib/src/file/state_file.dart b/lib/src/file/state_file.dart index cdd56e5..094ca18 100644 --- a/lib/src/file/state_file.dart +++ b/lib/src/file/state_file.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; -import 'package:torrent_model/torrent_model.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; import '../peer/bitfield.dart'; const BITFIELD_TYPE = 'bitfield'; @@ -10,12 +10,12 @@ const DOWNLOADED_TYPE = 'downloaded'; const UPLOADED_TYPE = 'uploaded'; /// -/// 下载状态保存文件 +/// Download state save file /// -/// 文件内容:``,其中``是一个64位整数, -/// 文件名:`.bt.state` +/// Document Content:``,where``is a 64-bit integer, +/// file name:`.bt.state` class StateFile { - Bitfield _bitfield; + late Bitfield _bitfield; bool _closed = false; @@ -25,13 +25,13 @@ class StateFile { StateFile(this.metainfo); - RandomAccessFile _access; + RandomAccessFile? _access; - File _bitfieldFile; + File? _bitfieldFile; - StreamSubscription _ss; + StreamSubscription? _ss; - StreamController _sc; + StreamController? _sc; bool get isClosed => _closed; @@ -45,32 +45,32 @@ class StateFile { Bitfield get bitfield => _bitfield; int get downloaded { - var _downloaded = bitfield.completedPieces.length * metainfo.pieceLength; + var downloaded = bitfield.completedPieces.length * metainfo.pieceLength; if (bitfield.completedPieces.contains(bitfield.piecesNum - 1)) { - _downloaded -= metainfo.pieceLength - metainfo.lastPriceLength; + downloaded -= metainfo.pieceLength - metainfo.lastPieceLength; } - return _downloaded; + return downloaded; } int get uploaded => _uploaded; Future init(String directoryPath, Torrent metainfo) async { var lastc = directoryPath.substring(directoryPath.length - 1); - if (lastc != '\\' || lastc != '/') { - directoryPath = directoryPath + '\\\\'; + if (lastc != Platform.pathSeparator) { + directoryPath = directoryPath + Platform.pathSeparator; } - _bitfieldFile = File('${directoryPath}${metainfo.infoHash}.bt.state'); - var exists = await _bitfieldFile.exists(); - if (!exists) { - _bitfieldFile = await _bitfieldFile.create(recursive: true); + _bitfieldFile = File('$directoryPath${metainfo.infoHash}.bt.state'); + var exists = await _bitfieldFile?.exists(); + if (exists != null && !exists) { + _bitfieldFile = await _bitfieldFile?.create(recursive: true); _bitfield = Bitfield.createEmptyBitfield(metainfo.pieces.length); _uploaded = 0; - var acc = await _bitfieldFile.open(mode: FileMode.writeOnly); - acc = await acc.truncate(_bitfield.length + 8); - await acc.close(); + var acc = await _bitfieldFile?.open(mode: FileMode.writeOnly); + acc = await acc?.truncate(_bitfield.length + 8); + await acc?.close(); } else { - var bytes = await _bitfieldFile.readAsBytes(); + var bytes = await _bitfieldFile!.readAsBytes(); var piecesNum = metainfo.pieces.length; var bitfieldBufferLength = piecesNum ~/ 8; if (bitfieldBufferLength * 8 != piecesNum) bitfieldBufferLength++; @@ -79,13 +79,13 @@ class StateFile { _uploaded = view.getUint64(_bitfield.length); } - return _bitfieldFile; + return _bitfieldFile!; } Future update(int index, {bool have = true, int uploaded = 0}) async { _access = await getAccess(); var completer = Completer(); - _sc.add({ + _sc?.add({ 'type': 'single', 'index': index, 'uploaded': uploaded, @@ -96,10 +96,10 @@ class StateFile { } Future updateAll(List indices, - {List have, int uploaded = 0}) async { + {List? have, int uploaded = 0}) async { _access = await getAccess(); var completer = Completer(); - _sc.add({ + _sc?.add({ 'type': 'all', 'indices': indices, 'uploaded': uploaded, @@ -121,22 +121,22 @@ class StateFile { } _bitfield.setBit(index, have); } else { - if (_uploaded == uploaded) return false; + if (_uploaded == uploaded) return; } _uploaded = uploaded; try { var access = await getAccess(); if (index != -1) { var i = index ~/ 8; - await access.setPosition(i); - await access.writeByte(_bitfield.buffer[i]); + await access?.setPosition(i); + await access?.writeByte(_bitfield.buffer[i]); } - await access.setPosition(_bitfield.buffer.length); + await access?.setPosition(_bitfield.buffer.length); var data = Uint8List(8); var d = ByteData.view(data.buffer); d.setUint64(0, uploaded); - access = await access.writeFrom(data); - await access.flush(); + access = await access?.writeFrom(data); + await access?.flush(); c.complete(true); } catch (e) { log('Update bitfield piece:[$index],uploaded:$uploaded error :', @@ -161,21 +161,21 @@ class StateFile { } void _processRequest(event) async { - _ss.pause(); + _ss?.pause(); // if (event['type'] == 'all') { // await _updateAll(event); // } if (event['type'] == 'single') { await _update(event); } - _ss.resume(); + _ss?.resume(); } - Future getAccess() async { + Future getAccess() async { if (_access == null) { - _access = await _bitfieldFile.open(mode: FileMode.writeOnlyAppend); + _access = await _bitfieldFile?.open(mode: FileMode.writeOnlyAppend); _sc = StreamController(); - _ss = _sc.stream.listen(_processRequest, onError: (e) => print(e)); + _ss = _sc?.stream.listen(_processRequest, onError: (e) => print(e)); } return _access; } @@ -189,7 +189,8 @@ class StateFile { await _access?.flush(); await _access?.close(); } catch (e) { - log('关闭状态文件出错:', error: e, name: runtimeType.toString()); + log('Error while closing the status file: ', + error: e, name: runtimeType.toString()); } finally { _access = null; _ss = null; @@ -197,7 +198,7 @@ class StateFile { } } - Future delete() async { + Future delete() async { await close(); var r = _bitfieldFile?.delete(); _bitfieldFile = null; diff --git a/lib/src/lsd/lsd.dart b/lib/src/lsd/lsd.dart index c8c64ae..b1254e6 100644 --- a/lib/src/lsd/lsd.dart +++ b/lib/src/lsd/lsd.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'dart:typed_data'; -import 'package:dartorrent_common/dartorrent_common.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; // const LSD_HOST = '239.192.152.143'; // const LSD_PORT = 6771; @@ -21,11 +21,11 @@ class LSD { bool get isClosed => _closed; - RawDatagramSocket _socket; + RawDatagramSocket? _socket; final String _infoHashHex; - int port; + int? port; final String _peerId; @@ -34,16 +34,19 @@ class LSD { LSD(this._infoHashHex, this._peerId); - Timer _timer; + Timer? _timer; - void start() async { - _socket ??= await RawDatagramSocket.bind(InternetAddress.anyIPv4, LSD_PORT); - _socket.listen((event) { + Future start() async { + _socket ??= await RawDatagramSocket.bind(InternetAddress.anyIPv4, LSD_PORT, + reusePort: true); + _socket?.listen((event) { if (event == RawSocketEvent.read) { - var datagram = _socket.receive(); - var datas = datagram.data; - var str = String.fromCharCodes(datas); - _processReceive(str, datagram.address); + var datagram = _socket?.receive(); + if (datagram != null) { + var datas = datagram.data; + var str = String.fromCharCodes(datas); + _processReceive(str, datagram.address); + } } }, onDone: () {}, onError: (e) {}); await _announce(); @@ -59,16 +62,16 @@ class LSD { void _fireLSDPeerEvent(InternetAddress address, int port, String infoHash) { var add = CompactAddress(address, port); - _peerHandlers.forEach((element) { + for (var element in _peerHandlers) { Timer.run(() => element(add, infoHash)); - }); + } } void _processReceive(String str, InternetAddress source) { var strs = str.split('\r\n'); if (strs[0] != ANNOUNCE_FIREST_LINE) return; - var port; - var infoHash; + int? port; + String? infoHash; for (var i = 1; i < strs.length; i++) { var element = strs[i]; if (element.startsWith('Port:')) { @@ -89,18 +92,18 @@ class LSD { } } - void _announce() async { + Future _announce() async { _timer?.cancel(); var message = _createMessage(); await _sendMessage(message); _timer = Timer(Duration(seconds: 5 * 60), () => _announce()); } - Future _sendMessage(String message, [Completer completer]) { + Future? _sendMessage(String message, [Completer? completer]) { if (_socket == null) return null; completer ??= Completer(); - var success = _socket.send(message.codeUnits, LSD_HOST, LSD_PORT) > 0; - if (!success) { + var success = _socket?.send(message.codeUnits, LSD_HOST, LSD_PORT); + if (success != null && !(success > 0)) { Timer.run(() => _sendMessage(message, completer)); } else { completer.complete(); @@ -122,7 +125,7 @@ class LSD { /// ///\r\n String _createMessage() { - return '${ANNOUNCE_FIREST_LINE}Host: ${LSD_HOST_STRING}Port: ${port}\r\nInfohash: ${_infoHashHex}\r\ncookie: dt-client${_peerId}\r\n\r\n\r\n'; + return '${ANNOUNCE_FIREST_LINE}Host: ${LSD_HOST_STRING}Port: $port\r\nInfohash: ${_infoHashHex}\r\ncookie: dt-client${_peerId}\r\n\r\n\r\n'; } void close() { diff --git a/lib/src/metadata/metadata_downloader.dart b/lib/src/metadata/metadata_downloader.dart index 24e8378..aa6907e 100644 --- a/lib/src/metadata/metadata_downloader.dart +++ b/lib/src/metadata/metadata_downloader.dart @@ -3,10 +3,11 @@ import 'dart:collection'; import 'dart:io'; import 'dart:typed_data'; -import 'package:bencode_dart/bencode_dart.dart'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:dht_dart/dht_dart.dart'; -import 'package:torrent_tracker/torrent_tracker.dart'; +import 'package:b_encode_decode/b_encode_decode.dart'; +import 'package:dart_ipify/dart_ipify.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:bittorrent_dht/bittorrent_dht.dart'; +import 'package:dtorrent_tracker/dtorrent_tracker.dart'; import '../peer/peer.dart'; import '../peer/holepunch.dart'; @@ -20,25 +21,29 @@ class MetadataDownloader final Set data)> _handlers = {}; final List IGNORE_IPS = [ - InternetAddress.tryParse('0.0.0.0'), - InternetAddress.tryParse('127.0.0.1') + InternetAddress.tryParse('0.0.0.0')!, + InternetAddress.tryParse('127.0.0.1')! ]; - InternetAddress localExtenelIP; + InternetAddress? localExternalIP; - int _metaDataSize; + int? _metaDataSize; - int _metaDataBlockNum; + int? _metaDataBlockNum; - int get metaDataSize => _metaDataSize; + int? get metaDataSize => _metaDataSize; - String _localPeerId; + num get progress => _metaDataBlockNum != null + ? _completedPieces.length / _metaDataBlockNum! * 100 + : 0; - List _infoHashBuffer; + late String _localPeerId; + + late List _infoHashBuffer; List get infoHashBuffer => _infoHashBuffer; - String _infoHashString; + final String _infoHashString; final Set _activePeers = {}; @@ -48,13 +53,13 @@ class MetadataDownloader final Set _incomingAddress = {}; - DHT _dht; + final DHT _dht = DHT(); bool _running = false; - int _E; + final int E = 'e'.codeUnits[0]; - List _infoDatas; + List _infoDatas = []; final Queue _metaDataPieces = Queue(); @@ -62,14 +67,15 @@ class MetadataDownloader final Map _requestTimeout = {}; - MetadataDownloader(String infoHashString) { - _E = 'e'.codeUnits[0]; - _infoHashString = infoHashString; + MetadataDownloader(this._infoHashString) { _localPeerId = generatePeerId(); - _infoHashBuffer = hexString2Buffer(infoHashString); + _infoHashBuffer = hexString2Buffer(_infoHashString)!; assert(_infoHashBuffer.isNotEmpty && _infoHashBuffer.length == 20, 'Info Hash String is incorrect'); - _dht = DHT(); + _init(); + } + Future _init() async { + localExternalIP = InternetAddress.tryParse(await Ipify.ipv4()); } void startDownload() { @@ -85,10 +91,10 @@ class MetadataDownloader _running = false; await _dht.stop(); var fs = []; - _activePeers.forEach((peer) { + for (var peer in _activePeers) { unHookPeer(peer); fs.add(peer.dispose()); - }); + } _activePeers.clear(); _avalidatedPeers.clear(); _peersAddress.clear(); @@ -112,7 +118,7 @@ class MetadataDownloader void _processDHTPeer(CompactAddress peer, String infoHash) { if (infoHash == _infoHashString) { - addNewPeerAddress(peer); + addNewPeerAddress(peer, PeerSource.dht); } } @@ -121,33 +127,33 @@ class MetadataDownloader /// /// Usually [socket] is null , unless this peer was incoming connection, but /// this type peer was managed by [TorrentTask] , user don't need to know that. - void addNewPeerAddress(CompactAddress address, - [PeerType type = PeerType.TCP, Socket socket]) { + void addNewPeerAddress(CompactAddress address, PeerSource source, + [PeerType type = PeerType.TCP, dynamic socket]) { if (!_running) return; - if (address == null) return; - if (address.address == localExtenelIP) return; + if (address.address == localExternalIP) return; if (socket != null) { - // 说明是主动连接的peer,目前只允许一个ip连一次 + // Indicates that it is an actively connecting peer, and currently, only + // one connection per IP address is allowed. if (!_incomingAddress.add(address.address)) { return; } } if (_peersAddress.add(address)) { - Peer peer; + Peer? peer; if (type == PeerType.TCP) { - peer = - Peer.newTCPPeer(_localPeerId, address, _infoHashBuffer, 0, socket); + peer = Peer.newTCPPeer( + _localPeerId, address, _infoHashBuffer, 0, socket, source); } if (type == PeerType.UTP) { - peer = - Peer.newUTPPeer(_localPeerId, address, _infoHashBuffer, 0, socket); + peer = Peer.newUTPPeer( + _localPeerId, address, _infoHashBuffer, 0, socket, source); } if (peer != null) _hookPeer(peer); } } void _hookPeer(Peer peer) { - if (peer.address.address == localExtenelIP) return; + if (peer.address.address == localExternalIP) return; if (_peerExsist(peer)) return; peer.onDispose(_processPeerDispose); peer.onHandShake(_processPeerHandshake); @@ -161,7 +167,7 @@ class MetadataDownloader return _activePeers.contains(id); } - /// 支持哪些扩展在这里添加 + /// Add supported extensions here void _registerExtended(Peer peer) { peer.registerExtened('ut_metadata'); peer.registerExtened('ut_pex'); @@ -169,7 +175,6 @@ class MetadataDownloader } void unHookPeer(Peer peer) { - if (peer == null) return; peer.offDispose(_processPeerDispose); peer.offHandShake(_processPeerHandshake); peer.offConnect(_peerConnected); @@ -210,17 +215,17 @@ class MetadataDownloader if (name == 'handshake') { if (data['metadata_size'] != null && _metaDataSize == null) { _metaDataSize = data['metadata_size']; - _infoDatas = List.filled(_metaDataSize, 0); - _metaDataBlockNum = _metaDataSize ~/ (16 * 1024); - if (_metaDataBlockNum * (16 * 1024) != _metaDataSize) { - _metaDataBlockNum++; + _infoDatas = List.filled(_metaDataSize!, 0); + _metaDataBlockNum = _metaDataSize! ~/ (16 * 1024); + if (_metaDataBlockNum! * (16 * 1024) != _metaDataSize) { + _metaDataBlockNum = _metaDataBlockNum! + 1; } - for (var i = 0; i < _metaDataBlockNum; i++) { + for (var i = 0; i < _metaDataBlockNum!; i++) { _metaDataPieces.add(i); } } - if (localExtenelIP != null && + if (localExternalIP != null && data['yourip'] != null && (data['yourip'].length == 4 || data['yourip'].length == 16)) { InternetAddress myip; @@ -230,7 +235,7 @@ class MetadataDownloader return; } if (IGNORE_IPS.contains(myip)) return; - localExtenelIP = InternetAddress.fromRawAddress(data['yourip']); + localExternalIP = InternetAddress.fromRawAddress(data['yourip']); } var metaDataEventId = peer.getExtendedEventId('ut_metadata'); @@ -242,11 +247,11 @@ class MetadataDownloader } void parseMetaDataMessage(Peer peer, Uint8List data) { - var index; + int? index; var remotePeerId = peer.remotePeerId; try { for (var i = 0; i < data.length; i++) { - if (data[i] == _E && data[i + 1] == _E) { + if (data[i] == E && data[i + 1] == E) { index = i + 1; break; } @@ -265,7 +270,7 @@ class MetadataDownloader if (msg['msg_type'] == 2) { var piece = msg['piece']; if (piece != null && piece < _metaDataBlockNum) { - _metaDataPieces.add(piece); //退还拒绝的piece + _metaDataPieces.add(piece); //Return rejected piece var timer = _requestTimeout.remove(remotePeerId); timer?.cancel(); _requestMetaData(); @@ -278,34 +283,34 @@ class MetadataDownloader } void _pieceDownloadComplete(int piece, int start, List bytes) async { - // 防止多次调用 - if (_completedPieces.length >= _metaDataBlockNum || + // Prevent multiple invocations" + if (_completedPieces.length >= _metaDataBlockNum! || _completedPieces.contains(piece)) { return; } var started = piece * 16 * 1024; List.copyRange(_infoDatas, started, bytes, start); _completedPieces.add(piece); - if (_completedPieces.length >= _metaDataBlockNum) { - // 此时就停止,然后抛出事件 + if (_completedPieces.length >= _metaDataBlockNum!) { + // At this point, stop and emit the event await stop(); - _handlers.forEach((h) { + for (var h in _handlers) { Timer.run(() { h(_infoDatas); }); - }); + } return; } } - Peer _randomAvalidatedPeer() { + Peer? _randomAvalidatedPeer() { if (_avalidatedPeers.isEmpty) return null; var n = _avalidatedPeers.length; var index = randomInt(n); return _avalidatedPeers.elementAt(index); } - void _requestMetaData([Peer peer]) { + void _requestMetaData([Peer? peer]) { if (_metaDataPieces.isNotEmpty) { peer ??= _randomAvalidatedPeer(); if (peer == null) return; @@ -315,7 +320,7 @@ class MetadataDownloader _metaDataPieces.add(piece); _requestMetaData(); }); - _requestTimeout[peer.remotePeerId] = timer; + _requestTimeout[peer.remotePeerId!] = timer; peer.sendExtendMessage('ut_metadata', msg); } } @@ -332,12 +337,12 @@ class MetadataDownloader peer.sendExtendMessage('ut_holepunch', message); return; } - addNewPeerAddress(address); + addNewPeerAddress(address, PeerSource.pex); } @override void holePunchConnect(CompactAddress ip) { - addNewPeerAddress(ip, PeerType.UTP); + addNewPeerAddress(ip, PeerSource.holepunch, PeerType.UTP); } @override diff --git a/lib/src/metadata/metadata_messager.dart b/lib/src/metadata/metadata_messager.dart index b4563b5..2678d6a 100644 --- a/lib/src/metadata/metadata_messager.dart +++ b/lib/src/metadata/metadata_messager.dart @@ -1,28 +1,28 @@ -import 'package:bencode_dart/bencode_dart.dart'; +import 'package:b_encode_decode/b_encode_decode.dart'; -mixin MetaDataMessager {} +mixin MetaDataMessager { + List createRequestMessage(int piece) { + // {'msg_type': 0, 'piece': 0} + var message = {}; + message['msg_type'] = 0; + message['piece'] = piece; + return encode(message); + } -List createRequestMessage(int piece) { - // {'msg_type': 0, 'piece': 0} - var message = {}; - message['msg_type'] = 0; - message['piece'] = piece; - return encode(message); -} - -List createRejectMessage(int piece) { - // {'msg_type': 2, 'piece': 0} - var message = {}; - message['msg_type'] = 2; - message['piece'] = piece; - return encode(message); -} + List createRejectMessage(int piece) { + // {'msg_type': 2, 'piece': 0} + var message = {}; + message['msg_type'] = 2; + message['piece'] = piece; + return encode(message); + } -List createDataMessage(int piece, List bytes) { - // {'msg_type': 1, 'piece': 0 , 'total_size' : xxxx}xxxx - var message = {}; - message['msg_type'] = 1; - message['piece'] = piece; - message['total_size'] = bytes.length; - return encode(message); + List createDataMessage(int piece, List bytes) { + // {'msg_type': 1, 'piece': 0 , 'total_size' : xxxx}xxxx + var message = {}; + message['msg_type'] = 1; + message['piece'] = piece; + message['total_size'] = bytes.length; + return encode(message); + } } diff --git a/lib/src/peer/bitfield.dart b/lib/src/peer/bitfield.dart index f65fdcd..7fd8326 100644 --- a/lib/src/peer/bitfield.dart +++ b/lib/src/peer/bitfield.dart @@ -1,36 +1,39 @@ import 'dart:typed_data'; -/// 用来于的基础数字,靠右移动获取要`&`和`|`的数 +/// The base number used for bitwise AND and OR operations. /// -/// 即:10000000 +/// It is represented as: 10000000 (binary representation) const BASE_NUM = 128; class Bitfield { final int piecesNum; final Uint8List buffer; - List _completedIndex; + List? _completedIndex; Bitfield(this.piecesNum, this.buffer); bool getBit(int index) { if (index < 0 || index >= piecesNum) return false; - var i = index ~/ 8; // 表示第几个数字 - var b = index.remainder(8); // 这表示该数字的第几位bit + var i = index ~/ 8; // This represents the position of the byte. + var b = index + .remainder(8); // This represents the position of the bit in the byte var andNum = BASE_NUM >> b; - return ((andNum & buffer[i]) != 0); // 等于0说明该位上的数字为0,即false + return ((andNum & buffer[i]) != + 0); // If it is equal to 0, it means that the bit at that position is 0, which is equivalent to false. } /// - /// [index] 如果不在 [0 - piecesNum]范围内,不会报错,直接返回 + /// If [index] is not within the range [0 - piecesNum], no error will be thrown, and the method will return directly. void setBit(int index, bool bit) { if (index < 0 || index >= piecesNum) return; if (getBit(index) == bit) return; - var i = index ~/ 8; // 表示第几个数字 - var b = index.remainder(8); // 这表示该数字的第几位bit + var i = index ~/ 8; // This represents the position of the byte. + var b = index + .remainder(8); // This represents the position of the bit in the byte var orNum = BASE_NUM >> b; if (bit) { _completedIndex = completedPieces; - _completedIndex.add(index); + _completedIndex?.add(index); buffer[i] = buffer[i] | orNum; } else { _completedIndex?.remove(index); @@ -38,7 +41,7 @@ class Bitfield { } } - /// 如果有完成的piece就返回ture,不一定会全部检索 + /// Returns `true` if there is a completed piece, without necessarily checking all of them. bool haveCompletePiece() { for (var i = 0; i < buffer.length; i++) { var a = buffer[i]; @@ -81,13 +84,13 @@ class Bitfield { for (var j = 0; j < 8; j++) { var index = i * 8 + j; if (getBit(index)) { - _completedIndex.add(index); + _completedIndex?.add(index); } } } } } - return _completedIndex; + return _completedIndex!; } int get length => buffer.length; @@ -98,14 +101,14 @@ class Bitfield { var str = element.toRadixString(2); var l = str.length; for (var i = 0; i < 8 - l; i++) { - str = '0' + str; + str = '0$str'; } - return previousValue + str + '-'; + return '$previousValue$str-'; }); } static Bitfield copyFrom(int piecesNum, List list, - [int offset = 0, int end]) { + [int offset = 0, int? end]) { var b = piecesNum ~/ 8; if (b * 8 != piecesNum) b++; var mybuffer = Uint8List(b); diff --git a/lib/src/peer/congestion_control.dart b/lib/src/peer/congestion_control.dart index 000c85a..03eb2b5 100644 --- a/lib/src/peer/congestion_control.dart +++ b/lib/src/peer/congestion_control.dart @@ -10,21 +10,21 @@ const MAX_WINDOW = 1048576; const RECORD_TIME = 5000000; -/// 最大每次增加的request为3 +/// The maximum number of requests to be increased in each round is 3. const MAX_CWND_INCREASE_REQUESTS_PER_RTT = 3 * 16384; -/// LEDBAT拥塞控制 +/// LEDBAT Congestion Control /// -/// 注意,所有时间单位都是微秒 +/// Note: All time units are in microseconds mixin CongestionControl { - // 初始是10秒 + // The initial value is 10 seconds. double _rto = 10000000; - double _srtt; + double? _srtt; - double _rttvar; + double? _rttvar; - Timer _timeout; + Timer? _timeout; int _allowWindowSize = DEFAULT_REQUEST_LENGTH; @@ -45,26 +45,26 @@ mixin CongestionControl { return _handles.remove(handle); } - /// 更新超时时间 + /// Update the timeout. void updateRTO(int rtt) { if (rtt == 0) return; if (_srtt == null) { _srtt = rtt.toDouble(); _rttvar = rtt / 2; } else { - _rttvar = (1 - 0.25) * _rttvar + 0.25 * (_srtt - rtt).abs(); - _srtt = (1 - 0.125) * _srtt + 0.125 * rtt; + _rttvar = (1 - 0.25) * _rttvar! + 0.25 * (_srtt! - rtt).abs(); + _srtt = (1 - 0.125) * _srtt! + 0.125 * rtt; } - _rto = _srtt + max(100000, 4 * _rttvar); - // 不到1秒,就设置为1秒 + _rto = _srtt! + max(100000, 4 * _rttvar!); + // If less than 1 second, set it to 1 second. _rto = max(_rto, 1000000); } void fireRequestTimeoutEvent(List> requests) { - if (requests == null || requests.isEmpty) return; - _handles.forEach((f) { + if (requests.isEmpty) return; + for (var f in _handles) { Timer.run(() => f(this, requests)); - }); + } } List> get currentRequestBuffer; @@ -76,7 +76,7 @@ mixin CongestionControl { void startRequestDataTimeout([int times = 0]) { _timeout?.cancel(); var requests = currentRequestBuffer; - if (requests == null || requests.isEmpty) return; + if (requests.isEmpty) return; _timeout = Timer(Duration(microseconds: _rto.toInt()), () { if (requests.isEmpty) return; if (times + 1 >= 5) { @@ -93,9 +93,9 @@ mixin CongestionControl { if (requests.isEmpty) break; first = requests.first; } - timeoutR.forEach((request) { + for (var request in timeoutR) { orderResendRequest(request[0], request[1], request[2], request[4]); - }); + } times++; _rto *= 2; @@ -108,25 +108,25 @@ mixin CongestionControl { void ackRequest(List> requests) { if (requests.isEmpty) return; var downloaded = 0; - int minRtt; - requests.forEach((request) { - // 重发后收到的不管 - if (request == null || request[4] != 0) return; + int? minRtt; + for (var request in requests) { + // Ignore the received packets after resending. + if (request[4] != 0) continue; var now = DateTime.now().microsecondsSinceEpoch; var rtt = now - request[3]; minRtt ??= rtt; minRtt = min(minRtt, rtt); updateRTO(rtt); downloaded += request[2]; - }); + } if (downloaded == 0 || minRtt == null) return; var artt = minRtt; - var delay_factor = (CCONTROL_TARGET - artt) / CCONTROL_TARGET; - var window_factor = downloaded / _allowWindowSize; - var scaled_gain = - MAX_CWND_INCREASE_REQUESTS_PER_RTT * delay_factor * window_factor; + var delayFactor = (CCONTROL_TARGET - artt) / CCONTROL_TARGET; + var windowFactor = downloaded / _allowWindowSize; + var scaledGain = + MAX_CWND_INCREASE_REQUESTS_PER_RTT * delayFactor * windowFactor; - _allowWindowSize += scaled_gain.toInt(); + _allowWindowSize += scaledGain.toInt(); _allowWindowSize = max(DEFAULT_REQUEST_LENGTH, _allowWindowSize); _allowWindowSize = min(MAX_WINDOW, _allowWindowSize); } diff --git a/lib/src/peer/extended_proccessor.dart b/lib/src/peer/extended_proccessor.dart index e563874..8b23427 100644 --- a/lib/src/peer/extended_proccessor.dart +++ b/lib/src/peer/extended_proccessor.dart @@ -1,12 +1,12 @@ import 'dart:async'; import 'dart:typed_data'; -import 'package:bencode_dart/bencode_dart.dart'; +import 'package:b_encode_decode/b_encode_decode.dart'; mixin ExtendedProcessor { final Map _extendedEventMap = {}; int _id = 1; - Map _rawMap; + Map? _rawMap; final Map _localExtended = {}; Map get localExtened { @@ -35,9 +35,9 @@ mixin ExtendedProcessor { _id++; } - int getExtendedEventId(String name) { + int? getExtendedEventId(String name) { if (_rawMap != null) { - return _rawMap[name]; + return _rawMap![name]; } return null; } @@ -55,20 +55,21 @@ mixin ExtendedProcessor { } void _fireExtendedEvent(String name, dynamic data) { - _eventHandler.forEach((element) { + for (var element in _eventHandler) { Timer.run(() => element(this, name, data)); - }); + } } void processExtendHandshake(dynamic data) { - var m = data['m'] as Map; - _rawMap = m; - if (m != null) { - m.forEach((key, value) { - if (value == 0) return; - _extendedEventMap[value] = key; - }); + if (data == null || !(data as Map).containsKey('m')) { + return; } + var m = data['m'] as Map; + _rawMap = m; + m.forEach((key, value) { + if (value == 0) return; + _extendedEventMap[value] = key; + }); _fireExtendedEvent('handshake', data); } diff --git a/lib/src/peer/holepunch.dart b/lib/src/peer/holepunch.dart index 52c458a..bd7d9c2 100644 --- a/lib/src/peer/holepunch.dart +++ b/lib/src/peer/holepunch.dart @@ -1,8 +1,9 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; -import 'package:dartorrent_common/dartorrent_common.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; enum HolepunchType { rendezvous, connect, error } @@ -21,7 +22,7 @@ mixin Holepunch { ]; List getRendezvousMessage(CompactAddress address) { - List message; + List message = List.empty(); if (address.address.type == InternetAddressType.IPv4) { message = List.filled(12, 0); List.copyRange(message, 2, address.toBytes()); @@ -43,10 +44,11 @@ mixin Holepunch { /// /// err_code (4 bytes): void parseHolepuchMessage(List data) { + log('Parsing holepunch message', name: runtimeType.toString()); var type = data[0]; var iptype = data[1]; var offset = 0; - CompactAddress ip; + CompactAddress? ip; try { if (iptype == 0) { ip = CompactAddress.parseIPv4Address(data, 2); @@ -58,10 +60,11 @@ mixin Holepunch { } catch (e) { // do nothing } - var err; + if (ip == null) return; + int err; if (type == 0x02) { var e = Uint8List(4); - // 有些客户端返回的error不到4位: + // Some clients return less than 4 errors: if (data.length < offset + 4) { var start = offset + 4 - data.length; List.copyRange(e, start, data, offset); @@ -70,24 +73,24 @@ mixin Holepunch { } err = ByteData.view(e.buffer).getUint32(0); if (err >= 1000) { - err = e[0]; // 有些客户端把错误码放在第一位 + err = e[0]; // Some clients put the error code first } err--; var errMsg = 'Unknown error'; if (err >= 0) { errMsg = ERROR_MSG[err]; } - Timer.run(() => holePunchError(errMsg, ip)); + Timer.run(() => holePunchError(errMsg, ip!)); return; } if (type == 0x00) { - Timer.run(() => holePunchRendezvous(ip)); + Timer.run(() => holePunchRendezvous(ip!)); return; } if (type == 0x01) { - Timer.run(() => holePunchConnect(ip)); + Timer.run(() => holePunchConnect(ip!)); return; } } diff --git a/lib/src/peer/peer.dart b/lib/src/peer/peer.dart index c66f0ef..9ed7b7f 100644 --- a/lib/src/peer/peer.dart +++ b/lib/src/peer/peer.dart @@ -4,13 +4,11 @@ import 'dart:developer' as dev; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; -import 'package:bencode_dart/bencode_dart.dart'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:torrent_task/torrent_task.dart'; -import 'package:utp/utp.dart'; +import 'package:b_encode_decode/b_encode_decode.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; +import 'package:utp_protocol/utp_protocol.dart'; -import '../utils.dart'; -import 'bitfield.dart'; import 'peer_event_dispatcher.dart'; import 'congestion_control.dart'; import 'speed_calculator.dart'; @@ -64,7 +62,6 @@ enum PeerType { TCP, UTP } /// 30 Seconds const DEFAULT_CONNECT_TIMEOUT = 30; -/// 带有 [index],[begin],[length]参数的方法 typedef PieceConfigHandle = void Function( Peer peer, int index, int begin, int length); typedef NoneParamHandle = void Function(Peer peer); @@ -73,6 +70,8 @@ typedef BoolHandle = void Function(Peer peer, bool value); typedef SingleIntHandle = void Function(Peer peer, int value); +enum PeerSource { tracker, dht, pex, lsd, incoming, manual, holepunch } + abstract class Peer with PeerEventDispatcher, @@ -81,51 +80,52 @@ abstract class Peer SpeedCalculator { /// Countdown time , when peer don't receive or send any message from/to remote , /// this class will invoke close. - /// 单位:秒 + /// Unit: second int countdownTime = 150; String get id { - return address?.toContactEncodingString(); + return address.toContactEncodingString(); } - /// 下载项目的piece总数 + /// The total number of pieces of downloaded items final int _piecesNum; - /// 远程的Bitfield - Bitfield _remoteBitfield; + /// Remote Bitfield + Bitfield? _remoteBitfield; - /// 该peer是否已经disposed + /// Whether the peer has been disposed bool _disposed = false; - /// 倒计时关闭Timer - Timer _countdownTimer; + /// Countdown to close Timer. + Timer? _countdownTimer; - /// 对方是否choke了我,初始默认true + /// Whether the other party choke me, the initial default is true bool _chokeMe = true; - /// 我是否choke了对方,默认true + /// Did I choke the other party, the default is true bool chokeRemote = true; - /// 对方是否对我的资源感兴趣,默认false + /// Whether the other party is interested in my resources, the default is false bool _interestedMe = false; - /// 我是否对对方的资源感兴趣,默认false + /// Am I interested in the resources of the other party, the default is false bool interestedRemote = false; - /// Debug 使用 + /// Debug use // ignore: unused_field dynamic _disposeReason; - /// 远程Peer的地址和端口 + /// The address and port of the remote peer final CompactAddress address; /// Torrent infohash buffer final List _infoHashBuffer; /// Local Peer Id - final String _localPeerId; // 本机的peer id。发送消息会用到 + final String + _localPeerId; // The local peer ID. It is used when sending messages. - String _remotePeerId; + String? _remotePeerId; /// has this peer send handshake message already? bool _handShaked = false; @@ -133,19 +133,19 @@ abstract class Peer /// has this peer send local bitfield to remote? bool _bitfieldSended = false; - /// 远程数据接受,监听subcription - StreamSubscription _streamChunk; + /// Remote data reception, listening to subscription. + StreamSubscription? _streamChunk; - /// 从通道中获取数据的buffer + /// Buffer to obtain data from the channel. List _cacheBuffer = []; - /// 本地发送请求buffer。格式位:[index,begin,length] + /// The local sends a request buffer. The format is: [index, begin, length]. final _requestBuffer = >[]; - /// 远程发送请求buffer。格式位:[index,begin,length] + /// The remote sends a request buffer. The format is: [index, begin, length]. final _remoteRequestBuffer = >[]; - // /// Max request count in one piple ,5 + /// Max request count in one piple ,5 static const MAX_REQUEST_COUNT = 5; bool remoteEnableFastPeer = false; @@ -156,28 +156,37 @@ abstract class Peer bool localEnableExtended = true; - /// 本地的Allow Fast pieces + /// Local Allow Fast pieces. final Set _allowFastPieces = {}; - /// 远程发送的Allow Fast pieces + /// Remote Allow Fast pieces. final Set _remoteAllowFastPieces = {}; - /// 远程发送的Suggest pieces + /// Remote Suggest pieces. final Set _remoteSuggestPieces = {}; final PeerType type; - int reqq; + final PeerSource source; - int remoteReqq; + int reqq; - /// - /// [_id] 是用于区分不同Peer的Id,和[_localPeerId]不同,[_localPeerId]是bt协议中的Peer_id。 - /// [address]是远程peer的地址和端口,子类在实现的时候可以利用该值进行远程连接。[_infoHashBuffer] - /// 是torrent文件中的infohash值,[_piecesNum]是下载项目的总piece数目,用于构建远程`Bitfield`数据 - /// 使用。可选项[localEnableFastPeer]默认位`true`,表示本地是否开启[Fast Extension(BEP 0006)](http://www.bittorrent.org/beps/bep_0006.html), - /// [localEnableExtended]表示本地是否可以使用[Extension Protocol](http://www.bittorrent.org/beps/bep_0010.html) + int? remoteReqq; + + /// [_id] is used to differentiate between different peers. It is different from + /// [_localPeerId], which is the Peer_id in the BitTorrent protocol. + /// [address] is the remote peer's address and port, and subclasses can use this + /// value for remote connections. + /// [_infoHashBuffer] is the infohash value from the torrent file, + /// and [_piecesNum] is the total number of pieces in the download project, + /// which is used to construct the remote `Bitfield` data. + /// The optional parameter [localEnableFastPeer] is set to `true` by default, + /// indicating whether local peers can use the + /// [Fast Extension (BEP 0006)](http://www.bittorrent.org/beps/bep_0006.html). + /// [localEnableExtended] indicates whether local peers can use the + /// [Extension Protocol](http://www.bittorrent.org/beps/bep_0010.html). Peer(this._localPeerId, this.address, this._infoHashBuffer, this._piecesNum, + this.source, {this.type = PeerType.TCP, this.localEnableFastPeer = true, this.localEnableExtended = true, @@ -185,43 +194,57 @@ abstract class Peer _remoteBitfield = Bitfield.createEmptyBitfield(_piecesNum); } - factory Peer.newTCPPeer(String localPeerId, CompactAddress address, - List infoHashBuffer, int piecesNum, Socket socket, - {bool enableExtend = true, bool enableFast = true}) { - return _TCPPeer(localPeerId, address, infoHashBuffer, piecesNum, socket, + factory Peer.newTCPPeer( + String localPeerId, + CompactAddress address, + List infoHashBuffer, + int piecesNum, + Socket? socket, + PeerSource source, + {bool enableExtend = true, + bool enableFast = true}) { + return _TCPPeer( + localPeerId, address, infoHashBuffer, piecesNum, socket, source, enableExtend: enableExtend, enableFast: enableFast); } - factory Peer.newUTPPeer(String localPeerId, CompactAddress address, - List infoHashBuffer, int piecesNum, Socket socket, - {bool enableExtend = true, bool enableFast = true}) { - return _UTPPeer(localPeerId, address, infoHashBuffer, piecesNum, socket, + factory Peer.newUTPPeer( + String localPeerId, + CompactAddress address, + List infoHashBuffer, + int piecesNum, + UTPSocket? socket, + PeerSource source, + {bool enableExtend = true, + bool enableFast = true}) { + return _UTPPeer( + localPeerId, address, infoHashBuffer, piecesNum, socket, source, enableExtend: enableExtend, enableFast: enableFast); } - /// 远程的Bitfield - Bitfield get remoteBitfield => _remoteBitfield; + /// The remote peer's bitfield. + Bitfield? get remoteBitfield => _remoteBitfield; - /// 是否已经发送local bitfield给对方 + /// Whether the local bitfield has been sent to the remote peer. bool get bitfieldSended => _bitfieldSended; bool get isLeecher => !isSeeder; - /// 如果具备完整的torrent文件,那它就是一个seeder + /// If it has the complete torrent file, then it is a seeder. bool get isSeeder { if (_remoteBitfield == null) return false; - if (_remoteBitfield.haveAll()) return true; + if (_remoteBitfield!.haveAll()) return true; return false; } - String get remotePeerId => _remotePeerId; + String? get remotePeerId => _remotePeerId; String get localPeerId => _localPeerId; - /// 远程发送的Request请求 + /// Requests received from the remote peer. List> get remoteRequestbuffer => _remoteRequestBuffer; - /// 本地发送给远程的Request请求 + /// Requests sent from the local peer to the remote peer. List> get requestBuffer => _requestBuffer; Set get remoteSuggestPieces => _remoteSuggestPieces; @@ -237,8 +260,8 @@ abstract class Peer } } - bool remoteHave(int index) { - return _remoteBitfield.getBit(index); + bool? remoteHave(int index) { + return _remoteBitfield?.getBit(index); } bool get interestedMe => _interestedMe; @@ -250,21 +273,21 @@ abstract class Peer } } - /// 远程所有的已完成Piece + /// All completed pieces of the remote peer. List get remoteCompletePieces { if (_remoteBitfield == null) return []; - return _remoteBitfield.completedPieces; + return _remoteBitfield!.completedPieces; } /// Connect remote peer Future connect([int timeout = DEFAULT_CONNECT_TIMEOUT]) async { try { _init(); - var _stream = await connectRemote(timeout); + var stream = await connectRemote(timeout); startSpeedCalculator(); - _streamChunk = _stream.listen(_processReceiveData, onDone: () { + _streamChunk = stream?.listen(_processReceiveData, onDone: () { _log('Connection is closed $address'); - dispose(BadException('远程关闭了连接')); + dispose(BadException('The remote peer closed the connection')); }, onError: (e) { _log('Error happen: $address', e); dispose(e); @@ -276,43 +299,48 @@ abstract class Peer } } - /// 初始化一些基本数据 + /// Initialize some basic data. void _init() { - // 初始化数据 + /// Initialize data. _disposeReason = null; _disposed = false; _handShaked = false; - // 清空通道数据缓存: + + /// Clear the channel data cache. _cacheBuffer.clear(); - // 清空请求缓存 + + /// Clear the request cache. _requestBuffer.clear(); _remoteRequestBuffer.clear(); - // 重置fast pieces + + /// Reset the fast pieces. _remoteAllowFastPieces.clear(); _allowFastPieces.clear(); - // 重置suggest pieces + + /// Reset the suggest pieces. _remoteSuggestPieces.clear(); - // 重置远程fast extension标识 + + /// Reset the remote fast extension flag. remoteEnableFastPeer = false; } - List removeRequest(int index, int begin, int length) { + List? removeRequest(int index, int begin, int length) { var request = _removeRequestFromBuffer(index, begin, length); return request; } - /// 添加一个request到buffer中 - /// - /// 这个request是一个数组 : - /// - 0 : index - /// - 1 : begin - /// - 2 : length - /// - 3 : send time - /// - 4 : resend times + /// Add a request to the buffer. + + /// This request is an array: + /// - 0: index + /// - 1: begin + /// - 2: length + /// - 3: send time + /// - 4: resend times bool addRequest(int index, int begin, int length) { var maxCount = currentWindow; // maxCount = oldCount; - if (remoteReqq != null) maxCount = min(remoteReqq, maxCount); + if (remoteReqq != null) maxCount = min(remoteReqq!, maxCount); if (_requestBuffer.length >= maxCount) return false; _requestBuffer .add([index, begin, length, DateTime.now().microsecondsSinceEpoch, 0]); @@ -328,12 +356,15 @@ abstract class Peer } void _processReceiveData(dynamic data) { - // 不管收到什么消息,只要不是空的,重置倒计时: + // Regardless of what message is received, as long as it is not empty, reset the countdown timer. if (data != null && data.isNotEmpty) _startToCountdown(); - // if (data.isNotEmpty) log('收到数据 $data'); - if (data != null) _cacheBuffer.addAll(data); // 接受remote发送数据。缓冲到一处 + // if (data.isNotEmpty) log('Received data: $data'); + if (data != null) { + _cacheBuffer.addAll( + data); // Accept data sent by the remote peer and buffer it in one place. + } if (_cacheBuffer.isEmpty) return; - // 查看是不是handshake头 + // Check if it's a handshake header. if (_cacheBuffer[0] == 19 && _cacheBuffer.length >= 68) { if (_isHandShakeHead(_cacheBuffer)) { if (_validateInfoHash(_cacheBuffer)) { @@ -357,8 +388,8 @@ abstract class Peer var lengthBuffer = Uint8List(4); List.copyRange(lengthBuffer, 0, _cacheBuffer, start, 4); var length = ByteData.view(lengthBuffer.buffer).getInt32(0, Endian.big); - List piecesMessage; - List haveMessages; + List? piecesMessage; + List? haveMessages; while (_cacheBuffer.length - start - 4 >= length) { if (length == 0) { Timer.run(() => _processMessage(null, null)); @@ -386,10 +417,10 @@ abstract class Peer length = ByteData.view(lengthBuffer.buffer).getInt32(0, Endian.big); } if (piecesMessage != null && piecesMessage.isNotEmpty) { - Timer.run(() => _processReceivePieces(piecesMessage)); + Timer.run(() => _processReceivePieces(piecesMessage!)); } if (haveMessages != null && haveMessages.isNotEmpty) { - Timer.run(() => _processHave(haveMessages)); + Timer.run(() => _processHave(haveMessages!)); } if (start != 0) _cacheBuffer = _cacheBuffer.sublist(start); } @@ -410,7 +441,7 @@ abstract class Peer return true; } - void _processMessage(int id, Uint8List message) { + void _processMessage(int? id, Uint8List? message) { if (id == null) { _log('process keep alive $address'); fireKeepAlive(); @@ -440,11 +471,11 @@ abstract class Peer // return; // have message case ID_BITFIELD: // log('process bitfield from $address'); - initRemoteBitfield(message); + if (message != null) initRemoteBitfield(message); return; // bitfield message case ID_REQUEST: - _log('process request from ${address}'); - _processRemoteRequest(message); + _log('process request from $address'); + if (message != null) _processRemoteRequest(message); return; // request message // case ID_PIECE: // _log('process pices : $address'); @@ -452,12 +483,14 @@ abstract class Peer // return; // pices message case ID_CANCEL: _log('process cancel : $address'); - _processCancel(message); + if (message != null) _processCancel(message); return; // cancel message case ID_PORT: _log('process port : $address'); - var port = ByteData.view(message.buffer).getUint16(0); - _processPortChange(port); + if (message != null) { + var port = ByteData.view(message.buffer).getUint16(0); + _processPortChange(port); + } return; // port message case OP_HAVE_ALL: _log('process have all : $address'); @@ -469,30 +502,32 @@ abstract class Peer return; case OP_SUGGEST_PIECE: _log('process suggest pieces : $address'); - _processSuggestPiece(message); + if (message != null) _processSuggestPiece(message); return; case OP_REJECT_REQUEST: _log('process reject request : $address'); - _processRejectRequest(message); + if (message != null) _processRejectRequest(message); return; case OP_ALLOW_FAST: _log('process allow fast : $address'); - _processAllowFast(message); + if (message != null) _processAllowFast(message); return; case ID_EXTENDED: - var extid = message[0]; - message = message.sublist(1); - processExtendMessage(extid, message); + if (message != null) { + var extid = message[0]; + message = message.sublist(1); + processExtendMessage(extid, message); + } return; } } - _log('Cannot process the message', 'Unknown message : ${message}'); + _log('Cannot process the message', 'Unknown message : $message'); } - /// 从requestbuffer中将request删除 + /// Remove a request from the request buffer. /// - /// 每当得到了piece回应或者request超时,都会调用此方法 - List _removeRequestFromBuffer(int index, int begin, int length) { + /// This method is called whenever a piece response is received or a request times out. + List? _removeRequestFromBuffer(int index, int begin, int length) { var i = _findRequestIndexFromBuffer(index, begin, length); if (i != -1) { return _requestBuffer.removeAt(i); @@ -533,7 +568,7 @@ abstract class Peer var index = view.getUint32(0); var begin = view.getUint32(4); var length = view.getUint32(8); - var requestIndex; + int? requestIndex; for (var i = 0; i < _remoteRequestBuffer.length; i++) { var r = _remoteRequestBuffer[i]; if (r[0] == index && r[1] == begin) { @@ -557,13 +592,14 @@ abstract class Peer dispose('Remote disabled fast extension but receive \'have all\''); return; } - for (var i = 0; i < _remoteBitfield.buffer.length - 1; i++) { - _remoteBitfield.buffer[i] = 255; + if (_remoteBitfield == null) return; + for (var i = 0; i < _remoteBitfield!.buffer.length - 1; i++) { + _remoteBitfield?.buffer[i] = 255; } - var index = _remoteBitfield.buffer.length - 1; + var index = _remoteBitfield!.buffer.length - 1; index = index * 8; - for (var i = index; i < _remoteBitfield.piecesNum; i++) { - _remoteBitfield.setBit(i, true); + for (var i = index; i < _remoteBitfield!.piecesNum; i++) { + _remoteBitfield?.setBit(i, true); } fireRemoteHaveAll(); } @@ -604,7 +640,7 @@ abstract class Peer startRequestDataTimeout(); fireRejectRequest(index, begin, length); } else { - // 有可能被删除了,但是reject来慢了而已 + // It's possible that the peer was deleted, but the reject message arrived too late. // dispose('Never send request ($index,$begin) but recieve a rejection'); return; } @@ -632,9 +668,9 @@ abstract class Peer void _processRemoteRequest(Uint8List message) { if (_remoteRequestBuffer.length > reqq) { dev.log('Request Error:', - error: 'Too many requests from ${address}', + error: 'Too many requests from $address', name: runtimeType.toString()); - dispose(BadException('Too many requests from ${address}')); + dispose(BadException('Too many requests from $address')); return; } var view = ByteData.view(message.buffer); @@ -645,7 +681,7 @@ abstract class Peer dev.log('TOO LARGEt BLOCK', error: 'BLOCK $length', name: runtimeType.toString()); dispose(BadException( - '${address} : request block length larger than limit : $length > $MAX_REQUEST_LENGTH')); + '$address : request block length larger than limit : $length > $MAX_REQUEST_LENGTH')); return; } if (chokeRemote) { @@ -654,23 +690,23 @@ abstract class Peer fireRequest(index, begin, length); return; } else { - // choke对方我不需要应答 + // Choking the remote peer without sending an acknowledgment. // sendRejectRequest(index, begin, length); return; } } _remoteRequestBuffer.add([index, begin, length]); - // TODO 这里做速度限制! + // TODO Implement speed limit here! fireRequest(index, begin, length); } - /// 处理接收到的PIECE消息 + /// Handle the received PIECE messages. /// - /// 不同于其他消息处理,PIECE消息是进行批量处理的。 + /// Unlike other message types, PIECE messages are processed in batches. void _processReceivePieces(List messages) { var requests = >[]; - messages.forEach((message) { + for (var message in messages) { var dataHead = Uint8List(8); List.copyRange(dataHead, 0, message, 0, 8); var view = ByteData.view(dataHead.buffer); @@ -678,16 +714,18 @@ abstract class Peer var begin = view.getUint32(4); var blockLength = message.length - 8; var request = removeRequest(index, begin, blockLength); - // 没有请求的就不处理 + + /// Ignore if there are no requests to process. if (request == null) { - return; + continue; } var block = Uint8List(message.length - 8); List.copyRange(block, 0, message, 8); requests.add(request); - _log('收到请求Piece ($index,$begin) 内容, 从当前Peer已下载 $downloaded bytes '); + _log( + 'Received request for Piece ($index, $begin) content, downloaded $downloaded bytes from the current Peer $type $address'); firePiece(index, begin, block); - }); + } messages.clear(); ackRequest(requests); updateDownload(requests); @@ -696,17 +734,17 @@ abstract class Peer void _processHave(List messages) { var indices = []; - messages.forEach((message) { + for (var message in messages) { var index = ByteData.view(message.buffer).getUint32(0); indices.add(index); updateRemoteBitfield(index, true); - }); + } fireHave(indices); } - /// 更新远程Bitfield + /// Update the remote peer's bitfield. void updateRemoteBitfield(int index, bool have) { - _remoteBitfield.setBit(index, have); + _remoteBitfield?.setBit(index, have); } void initRemoteBitfield(Uint8List bitfield) { @@ -733,7 +771,7 @@ abstract class Peer } } - String _parseRemotePeerId(dynamic data) { + String? _parseRemotePeerId(dynamic data) { if (data is List) { return String.fromCharCodes(data.sublist(48, 68)); } @@ -745,12 +783,12 @@ abstract class Peer /// [timeout] defaul value is 30 seconds /// Different type peer use different protocol , such as TCP,uTP, /// so this method should be implemented by sub-class - Future connectRemote(int timeout); + Future connectRemote(int timeout); /// Send message to remote /// /// this method will transform the [message] and id to be the peer protocol message bytes - void sendMessage(int id, [List message]) { + void sendMessage(int? id, [List? message]) { if (isDisposed) return; if (id == null) { // it's keep alive @@ -763,7 +801,7 @@ abstract class Peer _startToCountdown(); } - List _createByteMessage(int id, List message) { + List _createByteMessage(int id, List? message) { var length = 0; if (message != null) length = message.length; length = length + 1; @@ -784,9 +822,9 @@ abstract class Peer /// See : [Peer protocol message](https://wiki.theory.org/BitTorrentSpecification#Messages) void sendByteMessage(List bytes); - /// 发送handshake消息。 + /// Send a handshake message. /// - /// 在发送handshake后,会主动发送bitfield和have消息给对方 + /// After sending the handshake message, this method will also proactively send the bitfield and have messages to the remote peer. void sendHandShake() { if (_handShaked) return; var message = []; @@ -845,7 +883,7 @@ abstract class Peer return false; } } - int requestIndex; + int? requestIndex; for (var i = 0; i < _remoteRequestBuffer.length; i++) { var r = _remoteRequestBuffer[i]; if (r[0] == index && r[1] == begin) { @@ -912,9 +950,9 @@ abstract class Peer @override List> get currentRequestBuffer => _requestBuffer; - /// 请求取消某个request + /// Cancel a specific request by removing it from the request queue. /// - /// 如果在请求队列中就删除,否则返回 + /// If the request is present in the queue, it will be removed; otherwise, this operation will simply return. void requestCancel(int index, int begin, int length) { var request = removeRequest(index, begin, length); if (request != null) { @@ -946,7 +984,7 @@ abstract class Peer /// valid and available piece. Spare bits at the end are set to zero. /// void sendBitfield(Bitfield bitfield) { - _log('发送bitfile信息给对方 : ${bitfield.buffer}'); + _log('Sending bitfile information to the peer: ${bitfield.buffer}'); if (_bitfieldSended) return; _bitfieldSended = true; if (remoteEnableFastPeer && localEnableFastPeer) { @@ -968,7 +1006,7 @@ abstract class Peer /// index of a piece that has just been successfully downloaded and verified via the hash. void sendHave(int index) { var bytes = Uint8List(4); - _log('发送have信息给对方 : ${bytes},$index'); + _log('Sending have information to the peer: $bytes, $index'); ByteData.view(bytes.buffer).setUint32(0, index, Endian.big); sendMessage(ID_HAVE, bytes); } @@ -987,7 +1025,7 @@ abstract class Peer sendMessage(id); } - /// 发送`interested` 或 `not interested` 到 对方,表明自己是否对它拥有资源感兴趣 + ///Send interested or not interested to the other party to indicate whether you are interested in its resources or not. /// /// - `interested: ` /// - `not interested: ` @@ -1105,7 +1143,7 @@ abstract class Peer } } - /// 开始倒计时。 + /// Countdown started. /// /// Over `countdownTime` seconds , peer will close to disconnect the remote. /// but if peer send or receive any message from/to remote during countdown, @@ -1113,14 +1151,13 @@ abstract class Peer void _startToCountdown() { _countdownTimer?.cancel(); _countdownTimer = Timer(Duration(seconds: countdownTime), () { - dispose('Over ${countdownTime} seconds no communication, close'); + dispose('Over $countdownTime seconds no communication, close'); }); } - /// 该Peer被dispose。 + /// The Peer has been disposed. /// - /// 被dispose后的peer将无法再发送或监听数据,状态数据也恢复到初始状态,并且之前添加的事件监听 - /// 器都会被移除。 + /// After disposal, the Peer will no longer be able to send or receive data, its state data will be reset to its initial state, and all previously added event listeners will be removed. Future dispose([dynamic reason]) async { if (_disposed) return; _disposeReason = reason; @@ -1141,12 +1178,17 @@ abstract class Peer void _log(String message, [dynamic error]) { if (error != null) { - // dev.log(message, error: error, name: runtimeType.toString()); + dev.log(message, error: error, name: runtimeType.toString()); } else { - // log(message, name: runtimeType.toString()); + dev.log(message, name: runtimeType.toString()); } } + @override + String toString() { + return '$type:${_remotePeerId?.codeUnits != null ? Uint8List.fromList(_remotePeerId!.codeUnits).toHexString() : ''} $address $source'; + } + @override int get hashCode => address.address.address.hashCode; @@ -1164,7 +1206,7 @@ class BadException implements Exception { BadException(this.e); @override String toString() { - return '不需重连错误 : $e'; + return 'No need to reconnect error: $e'; } } @@ -1174,23 +1216,23 @@ class TCPConnectException implements Exception { } class _TCPPeer extends Peer { - Socket _socket; + Socket? _socket; _TCPPeer(String localPeerId, CompactAddress address, List infoHashBuffer, - int piecesNum, this._socket, + int piecesNum, this._socket, PeerSource source, {bool enableExtend = true, bool enableFast = true}) - : super(localPeerId, address, infoHashBuffer, piecesNum, + : super(localPeerId, address, infoHashBuffer, piecesNum, source, type: PeerType.TCP, localEnableExtended: enableExtend, localEnableFastPeer: enableFast); @override - Future connectRemote(int timeout) async { + Future connectRemote(int? timeout) async { timeout ??= 30; try { _socket ??= await Socket.connect(address.address, address.port, timeout: Duration(seconds: timeout)); return _socket; - } catch (e) { + } on Exception catch (e) { throw TCPConnectException(e); } } @@ -1223,21 +1265,27 @@ class _TCPPeer extends Peer { /// actually , one UTPSocketClient should maintain several uTP socket(uTP peer), /// this class need to improve. class _UTPPeer extends Peer { - UTPSocketClient _client; - UTPSocket _socket; - _UTPPeer(String localPeerId, CompactAddress address, List infoHashBuffer, - int piecesNum, this._socket, - {bool enableExtend = true, bool enableFast = true}) - : super(localPeerId, address, infoHashBuffer, piecesNum, + UTPSocketClient? _client; + UTPSocket? _socket; + _UTPPeer( + String localPeerId, + CompactAddress address, + List infoHashBuffer, + int piecesNum, + this._socket, + PeerSource source, { + bool enableExtend = true, + bool enableFast = true, + }) : super(localPeerId, address, infoHashBuffer, piecesNum, source, type: PeerType.UTP, localEnableExtended: enableExtend, localEnableFastPeer: enableFast); @override - Future connectRemote(int timeout) async { + Future connectRemote(int timeout) async { if (_socket != null) return _socket; _client ??= UTPSocketClient(); - _socket = await _client.connect(address.address, address.port); + _socket = await _client?.connect(address.address, address.port); return _socket; } diff --git a/lib/src/peer/peer_event_dispatcher.dart b/lib/src/peer/peer_event_dispatcher.dart index e849c2a..9a05148 100644 --- a/lib/src/peer/peer_event_dispatcher.dart +++ b/lib/src/peer/peer_event_dispatcher.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'bitfield.dart'; -/// 带有 [index],[begin],[length]参数的方法 typedef PieceConfigHandle = void Function( dynamic source, int index, int begin, int length); typedef NoneParamHandle = void Function(dynamic source); @@ -29,9 +28,9 @@ const PEER_EVENT_SUGGEST_PIECE = 'suggest_piece'; const PEER_EVENT_ALLOW_FAST = 'allow_fast'; const PEER_EVENT_REJECT_REQUEST = 'reject_request'; -/// 专门负责添加、删除Peer事件回调方法的 `mixin` +// A mixin specifically responsible for adding and removing Peer event callback methods. mixin PeerEventDispatcher { - /// 所有事件回调方法Map + /// Map of all event callback methods. final _handleFunctions = >{}; Set _getFunctionSet(String key) { @@ -57,7 +56,7 @@ mixin PeerEventDispatcher { }); } - void fireHandshakeEvent(String remotePeerId, dynamic data) { + void fireHandshakeEvent(String? remotePeerId, dynamic data) { var fSet = _handleFunctions[PEER_EVENT_HANDSHAKE]; fSet?.forEach((f) { Timer.run(() => f(this, remotePeerId, data)); @@ -92,7 +91,7 @@ mixin PeerEventDispatcher { }); } - void fireBitfield(final Bitfield bitfield) { + void fireBitfield(final Bitfield? bitfield) { var fSet = _handleFunctions[PEER_EVENT_BITFIELD]; fSet?.forEach((f) { Timer.run(() => f(this, bitfield)); diff --git a/lib/src/peer/peers_manager.dart b/lib/src/peer/peers_manager.dart index 48f8976..7ee7adf 100644 --- a/lib/src/peer/peers_manager.dart +++ b/lib/src/peer/peers_manager.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:developer'; import 'dart:io'; -import 'package:torrent_model/torrent_model.dart'; -import 'package:dartorrent_common/dartorrent_common.dart'; +import 'package:dart_ipify/dart_ipify.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; import 'bitfield.dart'; import 'peer.dart'; @@ -22,11 +24,11 @@ const MAX_UPLOADED_NOTIFY_SIZE = 1024 * 1024 * 10; // 10 mb /// /// TODO: -/// - 没有处理对外的Suggest Piece/Fast Allow +/// - The external Suggest Piece/Fast Allow requests are not handled. class PeersManager with Holepunch, PEX { final List IGNORE_IPS = [ - InternetAddress.tryParse('0.0.0.0'), - InternetAddress.tryParse('127.0.0.1') + InternetAddress.tryParse('0.0.0.0')!, + InternetAddress.tryParse('127.0.0.1')! ]; bool _disposed = false; @@ -39,9 +41,9 @@ class PeersManager with Holepunch, PEX { final Set _incomingAddress = {}; - InternetAddress localExtenelIP; + InternetAddress? localExternalIP; - /// 写入磁盘的缓存最大值 + /// The maximum size of the disk write cache. int maxWriteBufferSize; final _flushIndicesBuffer = {}; @@ -56,9 +58,9 @@ class PeersManager with Holepunch, PEX { int _downloaded = 0; - int _startedTime; + int? _startedTime; - int _endTime; + int? _endTime; int _uploadedNotifySize = 0; @@ -72,7 +74,7 @@ class PeersManager with Holepunch, PEX { bool _paused = false; - Timer _keepAliveTimer; + Timer? _keepAliveTimer; final List _pausedRequest = []; @@ -83,36 +85,37 @@ class PeersManager with Holepunch, PEX { PeersManager(this._localPeerId, this._pieceManager, this._pieceProvider, this._fileManager, this._metaInfo, [this.maxWriteBufferSize = MAX_WRITE_BUFFER_SIZE]) { - assert(_pieceManager != null && - _pieceProvider != null && - _fileManager != null); // hook FileManager and PieceManager _fileManager.onSubPieceWriteComplete(_processSubPieceWriteComplte); _fileManager.onSubPieceReadComplete(readSubPieceComplete); _pieceManager.onPieceComplete(_processPieceWriteComplete); - + _init(); // Start pex interval startPEX(); } + Future _init() async { + localExternalIP = InternetAddress.tryParse(await Ipify.ipv4()); + } + /// Task is paused bool get isPaused => _paused; /// All peers number. Include the connecting peer. int get peersNumber { - if (_peersAddress == null || _peersAddress.isEmpty) return 0; + if (_peersAddress.isEmpty) return 0; return _peersAddress.length; } /// All connected peers number. Include seeder. int get connectedPeersNumber { - if (_activePeers == null || _activePeers.isEmpty) return 0; + if (_activePeers.isEmpty) return 0; return _activePeers.length; } /// All seeder number int get seederNumber { - if (_activePeers == null || _activePeers.isEmpty) return 0; + if (_activePeers.isEmpty) return 0; var c = 0; return _activePeers.fold(c, (previousValue, element) { if (element.isSeeder) { @@ -128,9 +131,9 @@ class PeersManager with Holepunch, PEX { /// the end time is when manager was disposed. int get liveTime { if (_startedTime == null) return 0; - var passed = DateTime.now().millisecondsSinceEpoch - _startedTime; + var passed = DateTime.now().millisecondsSinceEpoch - _startedTime!; if (_endTime != null) { - passed = _endTime - _startedTime; + passed = _endTime! - _startedTime!; } return passed; } @@ -157,7 +160,7 @@ class PeersManager with Holepunch, PEX { /// /// This speed caculation: sum(`active peer download speed`) double get currentDownloadSpeed { - if (_activePeers == null || _activePeers.isEmpty) return 0.0; + if (_activePeers.isEmpty) return 0.0; return _activePeers.fold( 0.0, (p, element) => p + element.currentDownloadSpeed); } @@ -166,13 +169,13 @@ class PeersManager with Holepunch, PEX { /// /// This speed caculation: sum(`active peer upload speed`) double get uploadSpeed { - if (_activePeers == null || _activePeers.isEmpty) return 0.0; + if (_activePeers.isEmpty) return 0.0; return _activePeers.fold( 0.0, (p, element) => p + element.averageUploadSpeed); } void _hookPeer(Peer peer) { - if (peer.address.address == localExtenelIP) return; + if (peer.address.address == localExternalIP) return; if (_peerExsist(peer)) return; peer.onDispose(_processPeerDispose); peer.onBitfield(_processBitfieldUpdate); @@ -194,14 +197,15 @@ class PeersManager with Holepunch, PEX { peer.connect(); } - /// 支持哪些扩展在这里添加 + /// Add supported extensions here void _registerExtended(Peer peer) { + log('registering extensions for peer ${peer.address}', + name: runtimeType.toString()); peer.registerExtened('ut_pex'); peer.registerExtened('ut_holepunch'); } void unHookPeer(Peer peer) { - if (peer == null) return; peer.offDispose(_processPeerDispose); peer.offBitfield(_processBitfieldUpdate); peer.offHaveAll(_processHaveAll); @@ -224,6 +228,7 @@ class PeersManager with Holepunch, PEX { } void _processExtendedMessage(dynamic source, String name, dynamic data) { + log('Processing Extended Message $name', name: runtimeType.toString()); if (name == 'ut_holepunch') { parseHolepuchMessage(data); } @@ -231,7 +236,7 @@ class PeersManager with Holepunch, PEX { parsePEXDatas(source, data); } if (name == 'handshake') { - if (localExtenelIP != null && + if (localExternalIP != null && data['yourip'] != null && (data['yourip'].length == 4 || data['yourip'].length == 16)) { InternetAddress myip; @@ -241,7 +246,7 @@ class PeersManager with Holepunch, PEX { return; } if (IGNORE_IPS.contains(myip)) return; - localExtenelIP = InternetAddress.fromRawAddress(data['yourip']); + localExternalIP = InternetAddress.fromRawAddress(data['yourip']); } } } @@ -251,25 +256,26 @@ class PeersManager with Holepunch, PEX { /// /// Usually [socket] is null , unless this peer was incoming connection, but /// this type peer was managed by [TorrentTask] , user don't need to know that. - void addNewPeerAddress(CompactAddress address, - [PeerType type = PeerType.TCP, Socket socket]) { + void addNewPeerAddress(CompactAddress? address, PeerSource source, + {PeerType? type, dynamic socket}) { if (address == null) return; - if (address.address == localExtenelIP) return; + if (IGNORE_IPS.contains(address.address)) return; + if (address.address == localExternalIP) return; if (socket != null) { - // 说明是主动连接的peer,目前只允许一个ip连一次 + // Indicates that it is an actively connected peer, and currently, only one IP address is allowed to connect at a time. if (!_incomingAddress.add(address.address)) { return; } } if (_peersAddress.add(address)) { - Peer peer; - if (type == PeerType.TCP) { + Peer? peer; + if (type == null || type == PeerType.TCP) { peer = Peer.newTCPPeer(_localPeerId, address, _metaInfo.infoHashBuffer, - _metaInfo.pieces.length, socket); + _metaInfo.pieces.length, socket, source); } - if (type == PeerType.UTP) { + if (type == null || type == PeerType.UTP) { peer = Peer.newUTPPeer(_localPeerId, address, _metaInfo.infoHashBuffer, - _metaInfo.pieces.length, socket); + _metaInfo.pieces.length, socket, source); } if (peer != null) _hookPeer(peer); } @@ -282,11 +288,11 @@ class PeersManager with Holepunch, PEX { void _processPieceWriteComplete(int index) async { if (_fileManager.localHave(index)) return; await _fileManager.updateBitfield(index); - _activePeers.forEach((peer) { + for (var peer in _activePeers) { // if (!peer.remoteHave(index)) { peer.sendHave(index); // } - }); + } _flushIndicesBuffer.add(index); if (_fileManager.isAllComplete) { await _flushFiles(_flushIndicesBuffer); @@ -299,8 +305,8 @@ class PeersManager with Holepunch, PEX { Future _flushFiles(final Set indices) async { if (indices.isEmpty) return; var piecesSize = _metaInfo.pieceLength; - var _buffer = indices.length * piecesSize; - if (_buffer >= maxWriteBufferSize || _fileManager.isAllComplete) { + var buffer = indices.length * piecesSize; + if (buffer >= maxWriteBufferSize || _fileManager.isAllComplete) { var temp = Set.from(indices); indices.clear(); await _fileManager.flushFiles(temp); @@ -309,9 +315,9 @@ class PeersManager with Holepunch, PEX { } void _fireAllComplete() { - _allcompletehandles.forEach((element) { + for (var element in _allcompletehandles) { Timer.run(() => element()); - }); + } } bool onAllComplete(void Function() h) { @@ -334,7 +340,7 @@ class PeersManager with Holepunch, PEX { if (request[0] == pieceIndex && request[1] == begin) { dindex.add(i); var peer = request[2] as Peer; - if (peer != null && !peer.isDisposed) { + if (!peer.isDisposed) { if (peer.sendPiece(pieceIndex, begin, block)) { _uploaded += block.length; _uploadedNotifySize += block.length; @@ -344,9 +350,9 @@ class PeersManager with Holepunch, PEX { } } if (dindex.isNotEmpty) { - dindex.forEach((i) { + for (var i in dindex) { _remoteRequest.removeAt(i); - }); + } if (_uploadedNotifySize >= MAX_UPLOADED_NOTIFY_SIZE) { _uploadedNotifySize = 0; _fileManager.updateUpload(_uploaded); @@ -354,7 +360,7 @@ class PeersManager with Holepunch, PEX { } } - /// 即使对方choke了我,也可以下载 + /// Even if the other peer has choked me, I can still download. void _processAllowFast(dynamic source, int index) { var peer = source as Peer; var piece = _pieceProvider[index]; @@ -373,15 +379,15 @@ class PeersManager with Holepunch, PEX { } void _pushSubpicesBack(List> requests) { - if (requests == null || requests.isEmpty) return; - requests.forEach((element) { + if (requests.isEmpty) return; + for (var element in requests) { var pindex = element[0]; var begin = element[1]; - // TODO 这里很危险,目前都是已16kb来分解一个piece,如果不是呢? + // TODO This is dangerous here. Currently, we are dividing a piece into 16 KB chunks. What if it's not the case? var piece = _pieceManager[pindex]; var subindex = begin ~/ DEFAULT_REQUEST_LENGTH; piece?.pushSubPiece(subindex); - }); + } } void _processPeerDispose(dynamic source, [dynamic reason]) { @@ -399,9 +405,9 @@ class PeersManager with Holepunch, PEX { _pushSubpicesBack(bufferRequests); var completedPieces = peer.remoteCompletePieces; - completedPieces.forEach((index) { + for (var index in completedPieces) { _pieceProvider[index]?.removeAvalidatePeer(peer.id); - }); + } _pausedRemoteRequest.remove(peer.id); var tempIndex = []; for (var i = 0; i < _pausedRequest.length; i++) { @@ -410,9 +416,9 @@ class PeersManager with Holepunch, PEX { tempIndex.add(i); } } - tempIndex.forEach((index) { + for (var index in tempIndex) { _pausedRequest.removeAt(index); - }); + } if (reason is TCPConnectException) { // print('TCPConnectException'); @@ -422,11 +428,15 @@ class PeersManager with Holepunch, PEX { if (reconnect) { if (_activePeers.length < MAX_ACTIVE_PEERS && !isDisposed) { - addNewPeerAddress(peer.address, peer.type); + addNewPeerAddress( + peer.address, + peer.source, + type: peer.type, + ); } } else { if (peer.isSeeder && !_fileManager.isAllComplete && !isDisposed) { - addNewPeerAddress(peer.address, peer.type); + addNewPeerAddress(peer.address, peer.source, type: peer.type); } } } @@ -446,10 +456,10 @@ class PeersManager with Holepunch, PEX { } var peer = source as Peer; - Piece piece; + Piece? piece; if (pieceIndex != -1) { piece = _pieceProvider[pieceIndex]; - if (!piece.haveAvalidateSubPiece()) { + if (piece != null && !piece.haveAvalidateSubPiece()) { piece = _pieceManager.selectPiece(peer.id, peer.remoteCompletePieces, _pieceProvider, peer.remoteSuggestPieces); } @@ -459,8 +469,8 @@ class PeersManager with Holepunch, PEX { } if (piece == null) return; - var subIndex = piece.popSubPiece(); - var size = DEFAULT_REQUEST_LENGTH; // block大小现算 + var subIndex = piece.popSubPiece()!; + var size = DEFAULT_REQUEST_LENGTH; // Block size is calculated dynamically. var begin = subIndex * size; if ((begin + size) > piece.byteLength) { size = piece.byteLength - begin; @@ -497,7 +507,7 @@ class PeersManager with Holepunch, PEX { var peer = source as Peer; _pausedRemoteRequest[peer.id] ??= []; var pausedRequest = _pausedRemoteRequest[peer.id]; - pausedRequest.add([source, index, begin, length]); + pausedRequest?.add([source, index, begin, length]); return; } var peer = source as Peer; @@ -514,12 +524,13 @@ class PeersManager with Holepunch, PEX { _processBitfieldUpdate(source, null); } - void _processBitfieldUpdate(dynamic source, Bitfield bitfield) { + void _processBitfieldUpdate(dynamic source, Bitfield? bitfield) { var peer = source as Peer; if (bitfield != null) { if (peer.interestedRemote) return; if (_fileManager.isAllComplete && peer.isSeeder) { - peer.dispose(BadException('已经下载完成不再连接Seeder')); + peer.dispose(BadException( + "Do not connect to Seeder if the download is already completed")); return; } for (var i = 0; i < _fileManager.piecesNumber; i++) { @@ -537,8 +548,8 @@ class PeersManager with Holepunch, PEX { void _processHaveUpdate(dynamic source, List indices) { var peer = source as Peer; var flag = false; - indices.forEach((index) { - if (_pieceProvider[index] == null) return; + for (var index in indices) { + if (_pieceProvider[index] == null) continue; if (!_fileManager.localHave(index)) { if (peer.chokeMe) { @@ -548,25 +559,25 @@ class PeersManager with Holepunch, PEX { _pieceProvider[index]?.addAvalidatePeer(peer.id); } } - }); + } if (flag && peer.isSleeping) Timer.run(() => _requestPieces(peer)); } void _processChokeChange(dynamic source, bool choke) { var peer = source as Peer; - // 更新pieces的可用Peer + // Update available peers for pieces. if (!choke) { var completedPieces = peer.remoteCompletePieces; - completedPieces.forEach((index) { + for (var index in completedPieces) { _pieceProvider[index]?.addAvalidatePeer(peer.id); - }); - // 这里开始通知request; + } + // Here, start notifying requests. Timer.run(() => _requestPieces(peer)); } else { var completedPieces = peer.remoteCompletePieces; - completedPieces.forEach((index) { + for (var index in completedPieces) { _pieceProvider[index]?.removeAvalidatePeer(peer.id); - }); + } } } @@ -575,14 +586,14 @@ class PeersManager with Holepunch, PEX { if (interested) { peer.sendChoke(false); } else { - peer.sendChoke(true); // 不感兴趣就choke它 + peer.sendChoke(true); // Choke it if not interested. } } void _processRequestTimeout(dynamic source, List> requests) { var peer = source as Peer; var flag = false; - requests.forEach((element) { + for (var element in requests) { if (element[4] >= 3) { flag = true; Timer.run(() => peer.requestCancel(element[0], element[1], element[2])); @@ -592,21 +603,21 @@ class PeersManager with Holepunch, PEX { var piece = _pieceManager[index]; piece?.pushSubPiece(subindex); } - }); - // 唤醒其他可能没有工作的peer + } + // Wake up other possibly idle peers. if (flag) { - _activePeers.forEach((p) { + for (var p in _activePeers) { if (p != peer && p.isSleeping) { Timer.run(() => _requestPieces(p)); } - }); + } } } void _sendKeepAliveToAll() { - _activePeers?.forEach((peer) { + for (var peer in _activePeers) { Timer.run(() => _keepAlive(peer)); - }); + } } void _keepAlive(Peer peer) { @@ -632,15 +643,15 @@ class PeersManager with Holepunch, PEX { _paused = false; _keepAliveTimer?.cancel(); _keepAliveTimer = null; - _pausedRequest.forEach((element) { + for (var element in _pausedRequest) { var peer = element[0] as Peer; var index = element[1]; if (!peer.isDisposed) Timer.run(() => _requestPieces(peer, index)); - }); + } _pausedRequest.clear(); _pausedRemoteRequest.forEach((key, value) { - value.forEach((element) { + for (var element in value) { var peer = element[0] as Peer; var index = element[1]; var begin = element[2]; @@ -648,17 +659,17 @@ class PeersManager with Holepunch, PEX { if (!peer.isDisposed) { Timer.run(() => _processRemoteRequest(peer, index, begin, length)); } - }); + } }); _pausedRemoteRequest.clear(); } Future disposeAllSeeder([dynamic reason]) async { - _activePeers?.forEach((peer) async { + for (var peer in _activePeers) { if (peer.isSeeder) { await peer.dispose(reason); } - }); + } return; } @@ -674,14 +685,14 @@ class PeersManager with Holepunch, PEX { _pieceManager.offPieceComplete(_processPieceWriteComplete); await _flushFiles(_flushIndicesBuffer); - _flushIndicesBuffer?.clear(); - _allcompletehandles?.clear(); - _noActivePeerhandles?.clear(); - _remoteRequest?.clear(); - _pausedRequest?.clear(); - _pausedRemoteRequest?.clear(); - Function _disposePeers = (Set peers) async { - if (peers != null && peers.isNotEmpty) { + _flushIndicesBuffer.clear(); + _allcompletehandles.clear(); + _noActivePeerhandles.clear(); + _remoteRequest.clear(); + _pausedRequest.clear(); + _pausedRemoteRequest.clear(); + disposePeers(Set peers) async { + if (peers.isNotEmpty) { for (var i = 0; i < peers.length; i++) { var peer = peers.elementAt(i); unHookPeer(peer); @@ -689,8 +700,9 @@ class PeersManager with Holepunch, PEX { } } peers.clear(); - }; - await _disposePeers(_activePeers); + } + + await disposePeers(_activePeers); } //TODO test: @@ -713,7 +725,7 @@ class PeersManager with Holepunch, PEX { peer.sendExtendMessage('ut_holepunch', message); return; } - addNewPeerAddress(address); + addNewPeerAddress(address, PeerSource.pex); } @override @@ -721,7 +733,8 @@ class PeersManager with Holepunch, PEX { @override void holePunchConnect(CompactAddress ip) { - addNewPeerAddress(ip, PeerType.UTP); + log("holePunch connect $ip"); + addNewPeerAddress(ip, PeerSource.holepunch, type: PeerType.UTP); } int get utpPeerCount { @@ -753,12 +766,12 @@ class PeersManager with Holepunch, PEX { @override void holePunchError(String err, CompactAddress ip) { - // print('holepunch error - $err'); + log('holepunch error - $err'); } @override void holePunchRendezvous(CompactAddress ip) { // TODO: implement holePunchRendezvous - // print('收到 holePunch Rendezvous'); + log('Received holePunch Rendezvous from $ip'); } } diff --git a/lib/src/peer/pex.dart b/lib/src/peer/pex.dart index 8b3a487..9abba08 100644 --- a/lib/src/peer/pex.dart +++ b/lib/src/peer/pex.dart @@ -1,8 +1,9 @@ import 'dart:async'; import 'dart:io'; +import 'dart:typed_data'; -import 'package:bencode_dart/bencode_dart.dart'; -import 'package:dartorrent_common/dartorrent_common.dart'; +import 'package:b_encode_decode/b_encode_decode.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; import '../peer/peer.dart'; @@ -17,51 +18,51 @@ const pex_flag_supports_holepunch = 0x08; const pex_flag_reachable = 0x10; mixin PEX { - Timer _timer; + Timer? _timer; final Set _lastUTPEX = {}; void startPEX() { _timer?.cancel(); _timer = Timer.periodic(Duration(seconds: 60), (timer) { - _sendUt_pex_peers(); + sendUtPexPeers(); }); } Iterable get activePeers; - void _sendUt_pex_peers() { + void sendUtPexPeers() { var dropped = []; var added = []; - activePeers.forEach((p) { + for (var p in activePeers) { if (!_lastUTPEX.remove(p.address)) { added.add(p.address); } - }); - _lastUTPEX.forEach((element) { + } + for (var element in _lastUTPEX) { dropped.add(element); - }); + } _lastUTPEX.clear(); var data = {}; data['added'] = []; - added.forEach((element) { + for (var element in added) { _lastUTPEX.add(element); data['added'].addAll(element.toBytes()); - }); + } data['dropped'] = []; - dropped.forEach((element) { + for (var element in dropped) { data['dropped'].addAll(element.toBytes()); - }); + } if (data['added'].isEmpty && data['dropped'].isEmpty) return; var message = encode(data); - activePeers.forEach((peer) { + for (var peer in activePeers) { peer.sendExtendMessage('ut_pex', message); - }); + } } dynamic parsePEXDatas(dynamic source, List message) { - var datas = decode(message); + var datas = decode(Uint8List.fromList(message)); _parseAdded(source, datas); _parseAdded(source, datas, 'added6', InternetAddressType.IPv6); } @@ -74,7 +75,7 @@ mixin PEX { if (added is! List) { added = _convert(added); } - List ips; + List? ips; try { if (type == InternetAddressType.IPv4) { ips = CompactAddress.parseIPv4Addresses(added); @@ -111,7 +112,7 @@ mixin PEX { if (f & pex_flag_reachable == pex_flag_reachable) { opts['reachable'] = true; } - Timer.run(() => addPEXPeer(source, ips[i], opts)); + Timer.run(() => addPEXPeer(source, ips?[i], opts)); } } } @@ -120,7 +121,7 @@ mixin PEX { void addPEXPeer(dynamic source, CompactAddress address, Map options); - List _convert(List added) { + List? _convert(List added) { var intList = []; for (var i = 0; i < added.length; i++) { var n = added[i]; diff --git a/lib/src/peer/speed_calculator.dart b/lib/src/peer/speed_calculator.dart index 391e165..c997a96 100644 --- a/lib/src/peer/speed_calculator.dart +++ b/lib/src/peer/speed_calculator.dart @@ -3,16 +3,16 @@ import 'dart:math'; /// 5 seconds const RECORD_TIME = 5000000; -/// 上传下载速度计算器 +/// Upload and download speed calculator. mixin SpeedCalculator { final List> _downloadedHistory = >[]; - /// 当前5秒内的平均下载速度 + /// The average download speed within the last 5 seconds. double get currentDownloadSpeed { if (_downloadedHistory.isEmpty) return 0.0; var now = DateTime.now().microsecondsSinceEpoch; var d = 0; - int s; + int? s; for (var i = 0; i < _downloadedHistory.length;) { var dd = _downloadedHistory[i]; if ((now - dd[1]) > RECORD_TIME) { @@ -25,55 +25,56 @@ mixin SpeedCalculator { } } if (d == 0) return 0.0; - var passed = now - s; + var passed = now - s!; if (passed == 0) return 0.0; return (d / 1024) / (passed / 1000000); } - /// 从Peer连接开始到当前的平均下载速度 + /// The average download speed from the start of the Peer connection until the current moment. double get averageDownloadSpeed { var passed = livingTime; if (passed == null || passed == 0) return 0.0; return (_downloaded / 1024) / (passed / 1000000); } - /// 从Peer连接开始到当前的平均上传速度 + /// The average upload speed from the start of the Peer connection until the current moment. double get averageUploadSpeed { var passed = livingTime; if (passed == null || passed == 0) return 0.0; return (_uploaded / 1024) / (passed / 1000000); } - /// 从连接开始,直到peer销毁之前所持续时间 - int get livingTime { + /// The duration from the start of the connection until the peer is destroyed. + int? get livingTime { if (_startTime == null) return null; var e = _endTime; e ??= DateTime.now().microsecondsSinceEpoch; - return e - _startTime; + return e - _startTime!; } - int _startTime; + int? _startTime; - int _endTime; + int? _endTime; int _downloaded = 0; - /// 从远程下载的总数据量,单位bytes + /// The total amount of data downloaded from the remote, in bytes. int get downloaded => _downloaded; int _uploaded = 0; - /// 上传到远程的总数据量,单位bytes + /// The total amount of data uploaded to the remote, in bytes. int get uploaded => _uploaded; - /// 更新下载 + /// Update the download. void updateDownload(List> requests) { - if (requests == null || requests.isEmpty) return; + if (requests.isEmpty) return; var downloaded = 0; - requests.forEach((request) { - if (request[4] != 0) return; // 重新计时的不算 + for (var request in requests) { + if (request[4] != 0) + continue; // Do not count the time for re-calculation. downloaded += request[2]; - }); + } _downloadedHistory.add([downloaded, DateTime.now().microsecondsSinceEpoch]); _downloaded += downloaded; } @@ -82,12 +83,12 @@ mixin SpeedCalculator { _uploaded += uploaded; } - /// 速度计算开始计时 + /// Start the speed calculation timer. void startSpeedCalculator() { _startTime = DateTime.now().microsecondsSinceEpoch; } - /// 速度计算停止计时 + /// Stop the speed calculation timer. void stopSpeedCalculator() { _endTime = DateTime.now().microsecondsSinceEpoch; _downloadedHistory.clear(); diff --git a/lib/src/piece/base_piece_selector.dart b/lib/src/piece/base_piece_selector.dart index a3ce671..994e7ca 100644 --- a/lib/src/piece/base_piece_selector.dart +++ b/lib/src/piece/base_piece_selector.dart @@ -1,26 +1,25 @@ -import 'package:dartorrent_common/dartorrent_common.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; -import '../utils.dart'; import 'piece.dart'; import 'piece_provider.dart'; import 'piece_selector.dart'; /// -/// `Piece`基础选择器。 +/// Basic piece selector. /// -/// 基本策略为: +/// The basic strategy is: /// -/// - `Piece`可用`Peer`数量最多 -/// - 在可用`Peer`数量都相同的情况下,选用`Sub Piece`数量最少的 +/// - Choose the Piece with the highest number of available Peers. +/// - If multiple Pieces have the same number of available Peers, choose the one with the fewest Sub Pieces remaining. class BasePieceSelector implements PieceSelector { @override - Piece selectPiece( + Piece? selectPiece( String remotePeerId, List piecesIndexList, PieceProvider provider, [bool random = false]) { // random = true; var maxList = []; - var a; - var startIndex; + Piece? a; + int? startIndex; for (var i = 0; i < piecesIndexList.length; i++) { var p = provider[piecesIndexList[i]]; if (p != null && @@ -32,7 +31,7 @@ class BasePieceSelector implements PieceSelector { } } if (startIndex == null) return null; - maxList.add(a); + maxList.add(a!); for (var i = startIndex; i < piecesIndexList.length; i++) { var p = provider[piecesIndexList[i]]; if (p == null || @@ -40,15 +39,15 @@ class BasePieceSelector implements PieceSelector { !p.containsAvalidatePeer(remotePeerId)) { continue; } - // 选择稀有piece - if (a.avalidatePeersCount > p.avalidatePeersCount) { + // Select rare pieces + if (a!.avalidatePeersCount > p.avalidatePeersCount) { if (!random) return p; maxList.clear(); a = p; maxList.add(a); } else { if (a.avalidatePeersCount == p.avalidatePeersCount) { - // 如果同样数量可用下载peer的piece所具有的sub piece少,优先处理 + // If multiple Pieces have the same number of available downloading Peers, prioritize the one with the fewest remaining Sub Pieces. if (p.avalidateSubPieceCount < a.avalidateSubPieceCount) { if (!random) return p; maxList.clear(); diff --git a/lib/src/piece/piece.dart b/lib/src/piece/piece.dart index 9535661..6b8f1de 100644 --- a/lib/src/piece/piece.dart +++ b/lib/src/piece/piece.dart @@ -11,7 +11,7 @@ class Piece { final Set _avalidatePeers = {}; - Queue _subPiecesQueue; + late Queue _subPiecesQueue; final Set _downloadedSubPieces = {}; @@ -75,21 +75,20 @@ class Piece { } /// - /// 子Piece下载完成。 + /// SubPiece download completed. /// - /// 将子piece放入 `_writtingSubPieces` 队列中 - /// 设置子Piece为完成状态。如果该子Piece已经设置过,返回`false`,没有设置 - /// 过说明设置成功,返回`true` + /// Put the subpiece into the _writtingSubPieces queue and mark it as completed. + /// If the subpiece has already been marked, return false; if it hasn't been marked + /// yet, mark it as completed and return true. bool subPieceDownloadComplete(int begin) { var subindex = begin ~/ DEFAULT_REQUEST_LENGTH; _subPiecesQueue.remove(subindex); return _writtingSubPieces.add(subindex); } - bool subPieceWriteComplete(int begin) { var subindex = begin ~/ DEFAULT_REQUEST_LENGTH; - // _subPiecesQueue.remove(subindex); // 有这可能? + // _subPiecesQueue.remove(subindex); // Is this possible? _writtingSubPieces.remove(subindex); var re = _downloadedSubPieces.add(subindex); if (isCompleted) { @@ -99,11 +98,12 @@ class Piece { } /// - ///子Piece [subIndex]是否还在。 + /// Whether the sub-piece [subIndex] is still available. /// - ///当子Piece被弹出栈用于下载,或者子Piece已经下载完成,那么就视为该Piece已经不再包含该子Piece + /// When a sub-piece is popped from the stack for download or if the sub-piece has already been downloaded, + /// the piece is considered to no longer contain that sub-piece. bool containsSubpiece(int subIndex) { - return subPieceQueue?.contains(subIndex); + return subPieceQueue.contains(subIndex); } bool containsAvalidatePeer(String id) { @@ -111,7 +111,7 @@ class Piece { } bool removeSubpiece(int subIndex) { - return subPieceQueue?.remove(subIndex); + return subPieceQueue.remove(subIndex); } bool addAvalidatePeer(String id) { @@ -126,7 +126,7 @@ class Piece { _avalidatePeers.clear(); } - int popSubPiece() { + int? popSubPiece() { if (subPieceQueue.isNotEmpty) return subPieceQueue.removeFirst(); return null; } @@ -139,7 +139,7 @@ class Piece { return true; } - int popLastSubPiece() { + int? popLastSubPiece() { if (subPieceQueue.isNotEmpty) return subPieceQueue.removeLast(); return null; } @@ -168,9 +168,9 @@ class Piece { int get hashCode => hashString.hashCode; @override - bool operator ==(b) { - if (b is Piece) { - return b.hashString == hashString; + bool operator ==(other) { + if (other is Piece) { + return other.hashString == hashString; } return false; } diff --git a/lib/src/piece/piece_manager.dart b/lib/src/piece/piece_manager.dart index 63d11c9..80f6490 100644 --- a/lib/src/piece/piece_manager.dart +++ b/lib/src/piece/piece_manager.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:torrent_model/torrent_model.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; import '../peer/bitfield.dart'; import 'piece.dart'; import 'piece_provider.dart'; @@ -34,7 +34,7 @@ class PieceManager implements PieceProvider { for (var i = 0; i < metaInfo.pieces.length; i++) { var byteLength = metaInfo.pieceLength; if (i == metaInfo.pieces.length - 1) { - byteLength = metaInfo.lastPriceLength; + byteLength = metaInfo.lastPieceLength; } var piece = Piece(metaInfo.pieces[i], i, byteLength); if (!bitfield.getBit(i)) _pieces[i] = piece; @@ -49,12 +49,12 @@ class PieceManager implements PieceProvider { _pieceCompleteHandles.remove(handle); } - /// 这个接口是用于FIleManager回调使用。 + /// This interface is used for FileManager callback. /// - /// 只有所有子Piece写入完成才认为该Piece算完成。 + /// Only when all sub-pieces have been written, the piece is considered complete. /// - /// 因为如果仅下载完成就修改bitfield,会造成发送have给对方后,对方请求的子piece还没在 - /// 文件系统中,会读取出错误的数据 + /// Because if we modify the bitfield only after downloading, it will cause the remote peer + /// to request sub-pieces that are not yet present in the file system, leading to errors in data reading. void processSubPieceWriteComplete(int pieceIndex, int begin, int length) { var piece = _pieces[pieceIndex]; if (piece != null) { @@ -63,11 +63,11 @@ class PieceManager implements PieceProvider { } } - Piece selectPiece(String remotePeerId, List remoteHavePieces, - PieceProvider provider, final Set suggestPieces) { - // 查看当前下载piece中是否可以使用该peer + Piece? selectPiece(String remotePeerId, List remoteHavePieces, + PieceProvider provider, final Set? suggestPieces) { + // Check if the current downloading piece can be used by this peer. var avalidatePiece = []; - // 优先下载Suggest Pieces + // Prioritize downloading Suggest Pieces. if (suggestPieces != null && suggestPieces.isNotEmpty) { for (var i = 0; i < suggestPieces.length; i++) { var p = _pieces[suggestPieces.elementAt(i)]; @@ -86,7 +86,9 @@ class PieceManager implements PieceProvider { } } - // 如果可以下载正在下载中的piece,就下载该piece(多个Peer同时下载一个piece使其尽快完成的原则) + // If it is possible to download a piece that is currently being downloaded, + // prioritize downloading that piece (following the principle of multiple + // peers downloading the same piece to complete it as soon as possible). if (avalidatePiece.isNotEmpty) { candidatePieces = avalidatePiece; } @@ -102,18 +104,18 @@ class PieceManager implements PieceProvider { _donwloadingPieces.add(pieceIndex); } - /// 完成后的Piece需要一些处理 - /// - 从`_pieces`列表中删除 - /// - 从`_downloadingPieces`列表中删除 - /// - 通知监听器 + /// After completing a piece, some processing is required: + /// - Remove it from the _pieces list. + /// - Remove it from the _downloadingPieces list. + /// - Notify the listeners. void _processCompletePiece(int index) { var piece = _pieces.remove(index); _donwloadingPieces.remove(index); if (piece != null) { piece.dispose(); - _pieceCompleteHandles.forEach((handle) { + for (var handle in _pieceCompleteHandles) { Timer.run(() => handle(index)); - }); + } } } @@ -133,7 +135,7 @@ class PieceManager implements PieceProvider { } @override - Piece operator [](index) { + Piece? operator [](index) { return _pieces[index]; } diff --git a/lib/src/piece/piece_provider.dart b/lib/src/piece/piece_provider.dart index d3ccd5d..6b88bc3 100644 --- a/lib/src/piece/piece_provider.dart +++ b/lib/src/piece/piece_provider.dart @@ -3,7 +3,7 @@ import 'piece.dart'; abstract class PieceProvider { // Piece getPiece(int index); - Piece operator [](int index); + Piece? operator [](int index); int get length; } diff --git a/lib/src/piece/piece_selector.dart b/lib/src/piece/piece_selector.dart index 9632e85..ff05454 100644 --- a/lib/src/piece/piece_selector.dart +++ b/lib/src/piece/piece_selector.dart @@ -1,18 +1,16 @@ import 'piece.dart'; import 'piece_provider.dart'; -/// Piece选择器。 +/// Piece selector. /// -/// 当客户端开始下载前,通过这个类选择出恰当的Piece来下载 +/// When the client starts downloading, this class selects appropriate Pieces to download. abstract class PieceSelector { - /// 选择恰当的Piece应该Peer下载. + /// Selects the appropriate Piece for the Peer to download. /// - /// [remotePeerId]是即将下载的`Peer`的标识,这个标识并**不一定**是协议中的`peer_id`, - /// 而是`Piece`类中区分`Peer`的标识。 - /// 该方法通过[provider]以及[piecesIndexList]获取对应的`Piece`对象,并在[piecesIndexList] - /// 集合中进行筛选。 + /// [remotePeerId] is the identifier of the Peer that is about to download. This identifier may not necessarily be the peer_id in the protocol, but rather a unique identifier used by the Piece class to distinguish Peers. + /// This method retrieves the corresponding Piece object using [provider] and [piecesIndexList], and filters it within the [piecesIndexList] collection. /// - Piece selectPiece( + Piece? selectPiece( String remotePeerId, List piecesIndexList, PieceProvider provider, [bool first = false]); } diff --git a/lib/src/stream/stream.dart b/lib/src/stream/stream.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/stream/torrent_stream.dart b/lib/src/stream/torrent_stream.dart new file mode 100644 index 0000000..a5263a0 --- /dev/null +++ b/lib/src/stream/torrent_stream.dart @@ -0,0 +1,190 @@ +import 'dart:async'; +import 'dart:developer'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_task/src/piece/base_piece_selector.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; +import 'package:dtorrent_tracker/dtorrent_tracker.dart'; + +class TorrentStream implements AnnounceOptionsProvider { + static InternetAddress LOCAL_ADDRESS = + InternetAddress.fromRawAddress(Uint8List.fromList([127, 0, 0, 1])); + + TorrentAnnounceTracker? _tracker; + StateFile? _stateFile; + + PieceManager? _pieceManager; + + DownloadFileManager? _fileManager; + + PeersManager? _peersManager; + + final Torrent _metaInfo; + + final String _savePath; + final Set _peerIds = {}; + + late String + _peerId; // This is the generated local peer ID, which is different from the ID used in the Peer class. + + ServerSocket? _serverSocket; + + final Set _cominIp = {}; + + bool _paused = false; + late String _infoHashString; + StreamController _fileStream = StreamController(); + Stream get fileStream => _fileStream.stream; + + void seek(int position) { + if (position < 1 || position > _metaInfo.length) return; + var pieceIndex = position ~/ _metaInfo.pieceLength; + // _pieceManager + } + + TorrentStream(this._metaInfo, this._savePath) { + _peerId = generatePeerId(); + } + final Set _taskCompleteHandlers = {}; + + void _fireTaskComplete() { + for (var element in _taskCompleteHandlers) { + Timer.run(() => element()); + } + } + + void addPeer(CompactAddress address, PeerSource source, + {PeerType? type, Socket? socket}) { + _peersManager?.addNewPeerAddress(address, source, + type: type, socket: socket); + } + + void _whenTaskDownloadComplete() async { + await _peersManager + ?.disposeAllSeeder('Download complete,disconnect seeder'); + await _tracker?.complete(); + _fireTaskComplete(); + } + + final Set _fileCompleteHandlers = {}; + + void _fireFileComplete(String filepath) { + for (var handler in _fileCompleteHandlers) { + Timer.run(() => handler(filepath)); + } + } + + void _whenFileDownloadComplete(String filePath) { + _fireFileComplete(filePath); + } + + void _processTrackerPeerEvent(Tracker source, PeerEvent? event) { + if (event == null) return; + var ps = event.peers; + if (ps.isNotEmpty) { + for (var url in ps) { + _processNewPeerFound(url, PeerSource.tracker); + } + } + } + + void _processLSDPeerEvent(CompactAddress address, String infoHash) { + print('There is LSD! !'); + } + + void _processNewPeerFound(CompactAddress url, PeerSource source) { + log("Add new peer ${url.toString()} from ${source.name} to peersManager", + name: runtimeType.toString()); + _peersManager?.addNewPeerAddress(url, source); + } + + void _processDHTPeer(CompactAddress peer, String infoHash) { + log("Got new peer from $peer DHT for infohash: ${Uint8List.fromList(infoHash.codeUnits).toHexString()}", + name: runtimeType.toString()); + if (infoHash == _infoHashString) { + _processNewPeerFound(peer, PeerSource.dht); + } + } + + void _hookInPeer(Socket socket) { + if (socket.remoteAddress == LOCAL_ADDRESS) { + socket.close(); + return; + } + if (_cominIp.length >= MAX_IN_PEERS || !_cominIp.add(socket.address)) { + socket.close(); + return; + } + log('incoming connect: ${socket.remoteAddress.address}:${socket.remotePort}', + name: runtimeType.toString()); + _peersManager?.addNewPeerAddress( + CompactAddress(socket.remoteAddress, socket.remotePort), + PeerSource.incoming, + type: PeerType.TCP, + socket: socket); + } + + Future _init(Torrent model, String savePath) async { + _infoHashString = String.fromCharCodes(model.infoHashBuffer); + _tracker ??= TorrentAnnounceTracker(this); + _stateFile ??= await StateFile.getStateFile(savePath, model); + _pieceManager ??= PieceManager.createPieceManager( + BasePieceSelector(), model, _stateFile!.bitfield); + _fileManager ??= await DownloadFileManager.createFileManager( + model, savePath, _stateFile!); + _peersManager ??= PeersManager( + _peerId, _pieceManager!, _pieceManager!, _fileManager!, model); + return _peersManager!; + } + + Future start() async { + // Incoming peer: + _serverSocket ??= await ServerSocket.bind(InternetAddress.anyIPv4, 0); + await _init(_metaInfo, _savePath); + _serverSocket?.listen(_hookInPeer); + + var map = {}; + map['name'] = _metaInfo.name; + map['tcp_socket'] = _serverSocket?.port; + map['comoplete_pieces'] = List.from(_stateFile!.bitfield.completedPieces); + map['total_pieces_num'] = _stateFile!.bitfield.piecesNum; + map['downloaded'] = _stateFile!.downloaded; + map['uploaded'] = _stateFile!.uploaded; + map['total_length'] = _metaInfo.length; + // Outgoing peer: + _tracker?.onPeerEvent(_processTrackerPeerEvent); + _peersManager?.onAllComplete(_whenTaskDownloadComplete); + _fileManager?.onFileComplete(_whenFileDownloadComplete); + + // _dht?.announce( + // String.fromCharCodes(_metaInfo.infoHashBuffer), _serverSocket!.port); + // _dht?.onNewPeer(_processDHTPeer); + // ignore: unawaited_futures + // _dht?.bootstrap(); + if (_fileManager != null && _fileManager!.isAllComplete) { + // ignore: unawaited_futures + _tracker?.complete(); + } else { + _tracker?.runTrackers(_metaInfo.announces, _metaInfo.infoHashBuffer, + event: EVENT_STARTED); + } + return map; + } + + @override + Future> getOptions(Uri uri, String infoHash) { + var map = { + 'downloaded': _stateFile?.downloaded, + 'uploaded': _stateFile?.uploaded, + 'left': _metaInfo.length - _stateFile!.downloaded, + 'numwant': 50, + 'compact': 1, + 'peerId': _peerId, + 'port': _serverSocket?.port + }; + return Future.value(map); + } +} diff --git a/lib/src/task.dart b/lib/src/task.dart index 2efed44..8c80c27 100644 --- a/lib/src/task.dart +++ b/lib/src/task.dart @@ -3,10 +3,11 @@ import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; -import 'package:torrent_model/torrent_model.dart'; -import 'package:torrent_tracker/torrent_tracker.dart'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:dht_dart/dht_dart.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_tracker/dtorrent_tracker.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:bittorrent_dht/bittorrent_dht.dart'; +import 'package:utp_protocol/utp_protocol.dart'; import 'file/download_file_manager.dart'; import 'file/state_file.dart'; @@ -52,7 +53,7 @@ abstract class TorrentTask { int get utpPeerCount; /// Downloaded total bytes length - int get downloaded; + int? get downloaded; /// Downloaded percent double get progress; @@ -93,14 +94,14 @@ abstract class TorrentTask { bool offResume(void Function() handler); - /// 增加DHT node,一般是将torrent文件中的nodes加入进去。 + /// Adding a DHT node usually involves adding the nodes from the torrent file into the DHT network. /// - /// 当然也可以直接添加已知的node地址 + /// Alternatively, you can directly add known node addresses. void addDHTNode(Uri uri); - /// 添加已知的Peer地址 - void addPeer(CompactAddress address, - [PeerType type = PeerType.TCP, Socket socket]); + /// Add known Peer addresses. + void addPeer(CompactAddress address, PeerSource source, + {PeerType? type, Socket socket}); } class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @@ -117,19 +118,19 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { final Set _pauseHandlers = {}; - TorrentAnnounceTracker _tracker; + TorrentAnnounceTracker? _tracker; - DHT _dht; + DHT? _dht = DHT(); - LSD _lsd; + LSD? _lsd; - StateFile _stateFile; + StateFile? _stateFile; - PieceManager _pieceManager; + PieceManager? _pieceManager; - DownloadFileManager _fileManager; + DownloadFileManager? _fileManager; - PeersManager _peersManager; + PeersManager? _peersManager; final Torrent _metaInfo; @@ -137,9 +138,11 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { final Set _peerIds = {}; - String _peerId; // 这个是生成的本地peer的id,和Peer类的id是两回事 + late String + _peerId; // This is the generated local peer ID, which is different from the ID used in the Peer class. - ServerSocket _serverSocket; + ServerSocket? _serverSocket; + ServerUTPSocket? _utpServer; final Set _cominIp = {}; @@ -152,7 +155,7 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override double get averageDownloadSpeed { if (_peersManager != null) { - return _peersManager.averageDownloadSpeed; + return _peersManager!.averageDownloadSpeed; } else { return 0.0; } @@ -161,7 +164,7 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override double get averageUploadSpeed { if (_peersManager != null) { - return _peersManager.averageUploadSpeed; + return _peersManager!.averageUploadSpeed; } else { return 0.0; } @@ -170,7 +173,7 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override double get currentDownloadSpeed { if (_peersManager != null) { - return _peersManager.currentDownloadSpeed; + return _peersManager!.currentDownloadSpeed; } else { return 0.0; } @@ -179,40 +182,41 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override double get uploadSpeed { if (_peersManager != null) { - return _peersManager.uploadSpeed; + return _peersManager!.uploadSpeed; } else { return 0.0; } } - String _infoHashString; + late String _infoHashString; - Timer _dhtRepeatTimer; + Timer? _dhtRepeatTimer; Future _init(Torrent model, String savePath) async { - _dht = DHT(); _lsd = LSD(model.infoHash, _peerId); _infoHashString = String.fromCharCodes(model.infoHashBuffer); _tracker ??= TorrentAnnounceTracker(this); _stateFile ??= await StateFile.getStateFile(savePath, model); _pieceManager ??= PieceManager.createPieceManager( - BasePieceSelector(), model, _stateFile.bitfield); + BasePieceSelector(), model, _stateFile!.bitfield); _fileManager ??= await DownloadFileManager.createFileManager( - model, savePath, _stateFile); + model, savePath, _stateFile!); _peersManager ??= PeersManager( - _peerId, _pieceManager, _pieceManager, _fileManager, model); - return _peersManager; + _peerId, _pieceManager!, _pieceManager!, _fileManager!, model); + return _peersManager!; } @override - void addPeer(CompactAddress address, - [PeerType type = PeerType.TCP, Socket socket]) { - _peersManager.addNewPeerAddress(address, type, socket); + void addPeer(CompactAddress address, PeerSource source, + {PeerType? type, Socket? socket}) { + _peersManager?.addNewPeerAddress(address, source, + type: type, socket: socket); } void _whenTaskDownloadComplete() async { - await _peersManager.disposeAllSeeder('Download complete,disconnect seeder'); - await _tracker.complete(); + await _peersManager + ?.disposeAllSeeder('Download complete,disconnect seeder'); + await _tracker?.complete(); _fireTaskComplete(); } @@ -220,30 +224,52 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { _fireFileComplete(filePath); } - void _processTrackerPeerEvent(Tracker source, PeerEvent event) { + void _processTrackerPeerEvent(Tracker source, PeerEvent? event) { if (event == null) return; var ps = event.peers; - if (ps != null && ps.isNotEmpty) { - ps.forEach((url) { - _processNewPeerFound(url); - }); + if (ps.isNotEmpty) { + for (var url in ps) { + _processNewPeerFound(url, PeerSource.tracker); + } } } void _processLSDPeerEvent(CompactAddress address, String infoHash) { - print('居然有LSD!!'); + print('There is LSD! !'); } - void _processNewPeerFound(CompactAddress url) { - _peersManager.addNewPeerAddress(url); + void _processNewPeerFound(CompactAddress url, PeerSource source) { + log("Add new peer ${url.toString()} from ${source.name} to peersManager", + name: runtimeType.toString()); + _peersManager?.addNewPeerAddress(url, source); } void _processDHTPeer(CompactAddress peer, String infoHash) { + log("Got new peer from $peer DHT for infohash: ${Uint8List.fromList(infoHash.codeUnits).toHexString()}", + name: runtimeType.toString()); if (infoHash == _infoHashString) { - _processNewPeerFound(peer); + _processNewPeerFound(peer, PeerSource.dht); } } + void _hookUTP(UTPSocket socket) { + if (socket.remoteAddress == LOCAL_ADDRESS) { + socket.close(); + return; + } + if (_cominIp.length >= MAX_IN_PEERS || !_cominIp.add(socket.address)) { + socket.close(); + return; + } + log('incoming connect: ${socket.remoteAddress.address}:${socket.remotePort}', + name: runtimeType.toString()); + _peersManager?.addNewPeerAddress( + CompactAddress(socket.remoteAddress, socket.remotePort), + PeerSource.incoming, + type: PeerType.UTP, + socket: socket); + } + void _hookInPeer(Socket socket) { if (socket.remoteAddress == LOCAL_ADDRESS) { socket.close(); @@ -255,8 +281,11 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { } log('incoming connect: ${socket.remoteAddress.address}:${socket.remotePort}', name: runtimeType.toString()); - _peersManager.addNewPeerAddress( - CompactAddress(socket.address, socket.port), PeerType.TCP, socket); + _peersManager?.addNewPeerAddress( + CompactAddress(socket.remoteAddress, socket.remotePort), + PeerSource.incoming, + type: PeerType.TCP, + socket: socket); } @override @@ -281,41 +310,42 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override Future start() async { - // 进入的peer: + // Incoming peer: _serverSocket ??= await ServerSocket.bind(InternetAddress.anyIPv4, 0); await _init(_metaInfo, _savePath); - _serverSocket.listen(_hookInPeer); - // _utpServer ??= await ServerUTPSocket.bind(InternetAddress.anyIPv4, 0); - // _utpServer.listen(_hookUTP); - // print(_utpServer.port); + _serverSocket?.listen(_hookInPeer); + _utpServer ??= await ServerUTPSocket.bind( + InternetAddress.anyIPv4, _serverSocket?.port ?? 0); + _utpServer?.listen(_hookUTP); + print(_utpServer?.port); var map = {}; map['name'] = _metaInfo.name; - map['tcp_socket'] = _serverSocket.port; - map['comoplete_pieces'] = List.from(_stateFile.bitfield.completedPieces); - map['total_pieces_num'] = _stateFile.bitfield.piecesNum; - map['downloaded'] = _stateFile.downloaded; - map['uploaded'] = _stateFile.uploaded; + map['tcp_socket'] = _serverSocket?.port; + map['comoplete_pieces'] = List.from(_stateFile!.bitfield.completedPieces); + map['total_pieces_num'] = _stateFile!.bitfield.piecesNum; + map['downloaded'] = _stateFile!.downloaded; + map['uploaded'] = _stateFile!.uploaded; map['total_length'] = _metaInfo.length; - // 主动访问的peer: - _tracker.onPeerEvent(_processTrackerPeerEvent); - _peersManager.onAllComplete(_whenTaskDownloadComplete); - _fileManager.onFileComplete(_whenFileDownloadComplete); - - _lsd.onLSDPeer(_processLSDPeerEvent); - _lsd.port = _serverSocket.port; - _lsd.start(); - - _dht.announce( - String.fromCharCodes(_metaInfo.infoHashBuffer), _serverSocket.port); - _dht.onNewPeer(_processDHTPeer); + // Outgoing peer: + _tracker?.onPeerEvent(_processTrackerPeerEvent); + _peersManager?.onAllComplete(_whenTaskDownloadComplete); + _fileManager?.onFileComplete(_whenFileDownloadComplete); + + _lsd?.onLSDPeer(_processLSDPeerEvent); + _lsd?.port = _utpServer?.port; + _lsd?.start(); + + _dht?.announce( + String.fromCharCodes(_metaInfo.infoHashBuffer), _serverSocket!.port); + _dht?.onNewPeer(_processDHTPeer); // ignore: unawaited_futures - _dht.bootstrap(); - if (_fileManager.isAllComplete) { + _dht?.bootstrap(); + if (_fileManager != null && _fileManager!.isAllComplete) { // ignore: unawaited_futures - _tracker.complete(); + _tracker?.complete(); } else { - _tracker.runTrackers(_metaInfo.announces, _metaInfo.infoHashBuffer, + _tracker?.runTrackers(_metaInfo.announces, _metaInfo.infoHashBuffer, event: EVENT_STARTED); } return map; @@ -324,11 +354,11 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override Future stop([bool force = false]) async { await _tracker?.stop(force); - var tempHandler = Set.from(_stopHandlers); + Set? tempHandler = Set.from(_stopHandlers); await dispose(); - tempHandler.forEach((element) { + for (var element in tempHandler) { Timer.run(() => element()); - }); + } tempHandler.clear(); tempHandler = null; } @@ -344,7 +374,7 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { _tracker?.offPeerEvent(_processTrackerPeerEvent); _peersManager?.offAllComplete(_whenTaskDownloadComplete); _fileManager?.offFileComplete(_whenFileDownloadComplete); - // 这是有顺序的,先停止tracker运行,然后停止监听serversocket以及所有的peer,最后关闭文件系统 + // This is in order, first stop the tracker, then stop listening on the server socket and all peers, finally close the file system. await _tracker?.dispose(); _tracker = null; await _peersManager?.dispose(); @@ -368,7 +398,7 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { var map = { 'downloaded': _stateFile?.downloaded, 'uploaded': _stateFile?.uploaded, - 'left': _metaInfo.length - _stateFile.downloaded, + 'left': _metaInfo.length - _stateFile!.downloaded, 'numwant': 50, 'compact': 1, 'peerId': _peerId, @@ -383,9 +413,9 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { } void _fireFileComplete(String filepath) { - _fileCompleteHandlers.forEach((handler) { + for (var handler in _fileCompleteHandlers) { Timer.run(() => handler(filepath)); - }); + } } @override @@ -434,39 +464,38 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { } void _fireTaskComplete() { - _taskCompleteHandlers.forEach((element) { + for (var element in _taskCompleteHandlers) { Timer.run(() => element()); - }); + } } @override - int get downloaded => _fileManager?.downloaded; + int? get downloaded => _fileManager?.downloaded; @override double get progress { var d = downloaded; if (d == null) return 0.0; - var l = _metaInfo?.length; - if (l == null) return 0.0; + var l = _metaInfo.length; return d / l; } void _fireTaskPaused() { - _pauseHandlers.forEach((element) { + for (var element in _pauseHandlers) { Timer.run(() => element()); - }); + } } void _fireTaskResume() { - _resumeHandlers.forEach((element) { + for (var element in _resumeHandlers) { Timer.run(() => element()); - }); + } } @override int get allPeersNumber { if (_peersManager != null) { - return _peersManager.peersNumber; + return _peersManager!.peersNumber; } else { return 0; } @@ -480,7 +509,7 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override int get connectedPeersNumber { if (_peersManager != null) { - return _peersManager.connectedPeersNumber; + return _peersManager!.connectedPeersNumber; } else { return 0; } @@ -489,7 +518,7 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override int get seederNumber { if (_peersManager != null) { - return _peersManager.seederNumber; + return _peersManager!.seederNumber; } else { return 0; } @@ -499,21 +528,21 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override double get utpDownloadSpeed { if (_peersManager == null) return 0.0; - return _peersManager.utpDownloadSpeed; + return _peersManager!.utpDownloadSpeed; } // TODO debug: @override double get utpUploadSpeed { if (_peersManager == null) return 0.0; - return _peersManager.utpUploadSpeed; + return _peersManager!.utpUploadSpeed; } // TODO debug: @override int get utpPeerCount { if (_peersManager == null) return 0; - return _peersManager.utpPeerCount; + return _peersManager!.utpPeerCount; } @override @@ -523,7 +552,6 @@ class _TorrentTask implements TorrentTask, AnnounceOptionsProvider { @override void requestPeersFromDHT() { - if (_metaInfo == null) return; _dht?.requestPeers(String.fromCharCodes(_metaInfo.infoHashBuffer)); } } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index d0bc0bb..2fbacba 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,8 +1,7 @@ import 'dart:convert'; -import 'dart:math'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:torrent_task/torrent_task.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; String generatePeerId([String prefix = ID_PREFIX]) { var r = randomBytes(9); @@ -11,7 +10,7 @@ String generatePeerId([String prefix = ID_PREFIX]) { return id; } -List hexString2Buffer(String hexStr) { +List? hexString2Buffer(String hexStr) { // ignore: prefer_is_empty if (hexStr.isEmpty || hexStr.length.remainder(2) != 0) return null; var size = hexStr.length ~/ 2; diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index d23ecaa..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,404 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - url: "https://pub.flutter-io.cn" - source: hosted - version: "14.0.0" - analyzer: - dependency: transitive - description: - name: analyzer - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.41.1" - args: - dependency: transitive - description: - name: args - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.6.0" - async: - dependency: transitive - description: - name: async - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.2" - bencode_dart: - dependency: "direct main" - description: - name: bencode_dart - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.2" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.3" - cli_util: - dependency: transitive - description: - name: cli_util - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.0" - collection: - dependency: transitive - description: - name: collection - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.15.0" - convert: - dependency: transitive - description: - name: convert - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.1" - coverage: - dependency: transitive - description: - name: coverage - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.14.2" - crypto: - dependency: transitive - description: - name: crypto - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.5" - dartorrent_common: - dependency: "direct main" - description: - name: dartorrent_common - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.3" - dht_dart: - dependency: "direct main" - description: - name: dht_dart - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.0.7" - file: - dependency: transitive - description: - name: file - url: "https://pub.flutter-io.cn" - source: hosted - version: "5.2.1" - glob: - dependency: transitive - description: - name: glob - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.0" - http: - dependency: transitive - description: - name: http - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.12.2" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.0" - http_parser: - dependency: transitive - description: - name: http_parser - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.4" - intl: - dependency: transitive - description: - name: intl - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.16.1" - io: - dependency: transitive - description: - name: io - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.3.4" - js: - dependency: transitive - description: - name: js - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.6.2" - logging: - dependency: transitive - description: - name: logging - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.11.4" - matcher: - dependency: transitive - description: - name: matcher - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.12.9" - meta: - dependency: transitive - description: - name: meta - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.0" - mime: - dependency: transitive - description: - name: mime - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.9.7" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.1" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.12" - package_config: - dependency: transitive - description: - name: package_config - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.9.3" - path: - dependency: transitive - description: - name: path - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.7.0" - pedantic: - dependency: "direct dev" - description: - name: pedantic - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.9.2" - pool: - dependency: transitive - description: - name: pool - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.0" - pub_semver: - dependency: transitive - description: - name: pub_semver - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.4" - shelf: - dependency: transitive - description: - name: shelf - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.9" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.0" - shelf_static: - dependency: transitive - description: - name: shelf_static - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.8" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.3" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.0" - source_maps: - dependency: transitive - description: - name: source_maps - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.10.9" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.7.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.9.6" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.0.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.5" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.0" - test: - dependency: "direct dev" - description: - name: test - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.15.7" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.2.18+1" - test_core: - dependency: transitive - description: - name: test_core - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.3.11+4" - torrent_model: - dependency: "direct main" - description: - name: torrent_model - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.3" - torrent_tracker: - dependency: "direct main" - description: - name: torrent_tracker - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.12" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.0" - utp: - dependency: "direct main" - description: - name: utp - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.1" - vm_service: - dependency: transitive - description: - name: vm_service - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.2.0" - watcher: - dependency: transitive - description: - name: watcher - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.9.7+15" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.0" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.4" - yaml: - dependency: transitive - description: - name: yaml - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.1" -sdks: - dart: ">=2.12.0-0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index a2787b8..b4715cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,28 +1,21 @@ -name: torrent_task +name: dtorrent_task description: BitTorrent download client package written by pure Dart language. -version: 0.3.0 -repository: https://github.com/eclipseglory/torrent_task -issue_tracker : https://github.com/eclipseglory/torrent_task/issues -publish_to: https://pub.dev/ +version: 0.3.1 +repository: https://github.com/moham96/dtorrent_task +issue_tracker: https://github.com/moham96/dtorrent_task/issues environment: - sdk: '>=2.10.0 <3.0.0' + sdk: ">=2.12.0 <3.0.0" dependencies: -# path: ^1.7.0 - torrent_model : '>=1.0.3 <2.0.0' - torrent_tracker: '>=1.3.12 <2.0.0' - - # torrent_tracker: - # path: '../../dd/torrent_tracker' - - dht_dart: '>=0.0.7 <1.0.0' - dartorrent_common: '>=1.0.3 <2.0.0' - bencode_dart : ^1.0.2 - utp: '>= 1.0.1 < 2.0.0' - # utp: - # path: '../../dd/utp' + dtorrent_parser: ^1.0.4 + dtorrent_tracker: ^1.3.14 + bittorrent_dht: ^0.0.8 + dtorrent_common: ^1.0.4 + b_encode_decode: ^1.0.3 + utp_protocol: ^1.0.2 + dart_ipify: ^1.1.1 dev_dependencies: - pedantic: ^1.9.0 - test: ^1.14.4 + lints: ^2.0.1 + test: ^1.24.3 diff --git a/test/holepunch_test.dart b/test/holepunch_test.dart new file mode 100644 index 0000000..ab73b3a --- /dev/null +++ b/test/holepunch_test.dart @@ -0,0 +1 @@ +void main() {} diff --git a/test/peer_communicate.dart b/test/peer_communicate.dart index 8b021fc..3823a9a 100644 --- a/test/peer_communicate.dart +++ b/test/peer_communicate.dart @@ -3,11 +3,11 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:dartorrent_common/dartorrent_common.dart'; -import 'package:torrent_task/torrent_task.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; void main() async { - ServerSocket serverSocket; + ServerSocket? serverSocket; int serverPort; var infoBuffer = randomBytes(20); var piecesNum = 20; @@ -42,14 +42,15 @@ void main() async { CompactAddress(socket.address, socket.port), infoBuffer, piecesNum, - socket); + socket, + PeerSource.incoming); peer.onConnect((peer) { callMap['connect1'] = true; peer.sendHandShake(); }); peer.onHandShake((peer, remotePeerId, data) { callMap['handshake1'] = true; - print('receive ${remotePeerId} handshake'); + print('receive $remotePeerId handshake'); peer.sendInterested(true); print('send interested to $remotePeerId'); }); @@ -125,8 +126,8 @@ void main() async { var id = String.fromCharCodes(block.getRange(2, 22)); assert(id == peer.remotePeerId); if (index == 4) { - print('测试完毕 $callMap'); - await peer.dispose(BadException('测试完成')); + print('Testing completed. $callMap'); + await peer.dispose(BadException('Testing completed')); } }); peer.onDispose((peer, [reason]) async { @@ -140,10 +141,11 @@ void main() async { var pid = generatePeerId(); var peer = Peer.newTCPPeer( pid, - CompactAddress(InternetAddress.tryParse('127.0.0.1'), serverPort), + CompactAddress(InternetAddress.tryParse('127.0.0.1')!, serverPort), infoBuffer, piecesNum, - null); + null, + PeerSource.manual); peer.onConnect((peer) { callMap['connect2'] = true; print('connect server success'); @@ -152,7 +154,7 @@ void main() async { }); peer.onHandShake((peer, remotePeerId, data) { callMap['handshake2'] = true; - print('receive ${remotePeerId} handshake'); + print('receive $remotePeerId handshake'); peer.sendBitfield(bitfield); print('send bitfield to server'); peer.sendInterested(true); @@ -190,15 +192,15 @@ void main() async { view.setUint8(i + 2, idcontent[i]); } peer.sendPiece(index, begin, content); - peer.sendChoke(true); // 测试allow fast + peer.sendChoke(true); // Testing "allow fast". peer.sendAllowFast(4); }); peer.onDispose((peer, [reason]) async { print('come out destroyed : $reason'); await serverSocket?.close(); serverSocket = null; - var callAll = callMap.values - .fold(true, (previousValue, element) => (previousValue && element)); + var callAll = callMap.values.fold( + true, (previousValue, element) => (previousValue && element)); assert(callAll); }); print('connect to : ${peer.address}'); diff --git a/test/torrent_client_test.dart b/test/torrent_client_test.dart index 95648c6..0171a1c 100644 --- a/test/torrent_client_test.dart +++ b/test/torrent_client_test.dart @@ -4,15 +4,15 @@ import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; -import 'package:dartorrent_common/dartorrent_common.dart'; +import 'package:dtorrent_common/dtorrent_common.dart'; import 'package:test/test.dart'; -import 'package:torrent_model/torrent_model.dart'; -import 'package:torrent_task/torrent_task.dart'; +import 'package:dtorrent_parser/dtorrent_parser.dart'; +import 'package:dtorrent_task/dtorrent_task.dart'; void main() { group('Bitfield test - ', () { - Bitfield bitfield; - var pieces = 123; // 不要给8的倍数 + Bitfield? bitfield; + var pieces = 123; // Do not provide a number that is multiple of 8 setUp(() { bitfield = Bitfield.createEmptyBitfield(pieces); }); @@ -21,95 +21,96 @@ void main() { var c = pieces ~/ 8; if (c * 8 != pieces) c++; print('check buffer length'); - assert(bitfield.buffer.length == c); - bitfield.buffer.forEach((element) { + assert(bitfield!.buffer.length == c); + for (var element in bitfield!.buffer) { assert(element == 0); - }); + } }); test('random set/get test, and check the complete index ', () { - var t = Random(); //放大范围 + var t = Random(); // Increase the range. var randomIndex = {}; for (var i = 0; i < pieces; i++) { var index = t.nextInt(pieces * 2); if (index >= pieces) { - bitfield.setBit(index, true); - assert(bitfield.getBit(index) == false); - bitfield.setBit(index, false); - assert(bitfield.getBit(index) == false); + bitfield!.setBit(index, true); + assert(bitfield!.getBit(index) == false); + bitfield!.setBit(index, false); + assert(bitfield!.getBit(index) == false); } else { randomIndex.add(index); - bitfield.setBit(index, true); - assert(bitfield.getBit(index) == true); + bitfield!.setBit(index, true); + assert(bitfield!.getBit(index) == true); } } var indexList = randomIndex.toList(); indexList.sort((a, b) => a - b); - var list = bitfield.completedPieces; + var list = bitfield!.completedPieces; print('Check completed index list...'); for (var i = 0; i < list.length; i++) { indexList.remove(list[i]); } assert(indexList.isEmpty); - assert(bitfield.haveCompletePiece()); + assert(bitfield!.haveCompletePiece()); print('Check bitfield value...'); - list.forEach((index) { - assert(bitfield.getBit(index)); - }); + for (var index in list) { + assert(bitfield!.getBit(index)); + } var tempList = []; tempList.addAll(list); - tempList.forEach((index) { - bitfield.setBit(index, false); - }); + for (var index in tempList) { + bitfield!.setBit(index, false); + } print('Clean all bitfield...'); - bitfield.buffer.forEach((element) { + for (var element in bitfield!.buffer) { assert(element == 0); - }); + } }); test('add/remote complete list', () { - var t = Random(); //放大范围 + var t = Random(); //Increase the range. var randomIndex = {}; for (var i = 0; i < pieces; i++) { var index = t.nextInt(pieces * 2); if (index >= pieces) { - bitfield.setBit(index, true); - assert(bitfield.getBit(index) == false); - bitfield.setBit(index, false); - assert(bitfield.getBit(index) == false); + bitfield!.setBit(index, true); + assert(bitfield!.getBit(index) == false); + bitfield!.setBit(index, false); + assert(bitfield!.getBit(index) == false); } else { randomIndex.add(index); - bitfield.setBit(index, true); - assert(bitfield.getBit(index) == true); + bitfield!.setBit(index, true); + assert(bitfield!.getBit(index) == true); } } - bitfield.completedPieces; - bitfield.setBit(t.nextInt(pieces * 2), true); - var length = bitfield.completedPieces.length; - bitfield.setBit(bitfield.completedPieces.last, true); - assert(length == bitfield.completedPieces.length); - bitfield.setBit(bitfield.completedPieces.first, true); - assert(length == bitfield.completedPieces.length); - var first = bitfield.completedPieces.first; - bitfield.setBit(bitfield.completedPieces.first, false); - assert(!bitfield.completedPieces.contains(first)); + bitfield!.completedPieces; + bitfield!.setBit(t.nextInt(pieces * 2), true); + var length = bitfield!.completedPieces.length; + bitfield!.setBit(bitfield!.completedPieces.last, true); + assert(length == bitfield!.completedPieces.length); + bitfield!.setBit(bitfield!.completedPieces.first, true); + assert(length == bitfield!.completedPieces.length); + var first = bitfield!.completedPieces.first; + bitfield!.setBit(bitfield!.completedPieces.first, false); + assert(!bitfield!.completedPieces.contains(first)); }); }); group('Piece test - ', () { test('create sub-pieces', () async { - // 能整除的 + // Simulate bitfields that are divisible. var totalsize = 163840; var remain = Random().nextInt(100); totalsize = totalsize + remain; var p = Piece('aaaaaaa', 0, totalsize); var size = DEFAULT_REQUEST_LENGTH; - var subIndex = await p.popSubPiece(); + var subIndex = p.popSubPiece(); subIndex = p.popLastSubPiece(); - var begin = subIndex * size; + assert(subIndex != null); + var begin = subIndex! * size; if ((begin + size) > p.byteLength) { size = p.byteLength - begin; assert(remain == size); @@ -127,15 +128,14 @@ void main() { }); group('test same piece find - ', () { - var pieces = 123; // 不要给8的倍数 - var bitfieldList = List(10); - // 模拟多个peer的bitfield: + var pieces = 123; // Do not provide a number that is multiple of 8 + List bitfieldList = []; + // Simulate bitfields of multiple peers. setUp(() { - for (var i = 0; i < bitfieldList.length; i++) { - bitfieldList[i] = Bitfield.createEmptyBitfield(pieces); - } + bitfieldList = + List.generate(10, (index) => Bitfield.createEmptyBitfield(pieces)); - bitfieldList.forEach((bitfield) { + for (var bitfield in bitfieldList) { var t = Random(); var randomIndex = {}; for (var i = 0; i < pieces; i++) { @@ -151,38 +151,41 @@ void main() { assert(bitfield.getBit(index) == true); } } - }); + } }); test('ramdon pieces', () { - bitfieldList.forEach((element) => print(element.toString())); + for (var element in bitfieldList) { + print(element.toString()); + } }); }); group('StateFile Test - ', () { var directory = 'test'; - // var torrent = await Torrent.parse('$directory/sample3.torrent'); - Torrent torrent; + Torrent? torrent; setUpAll(() async { - torrent = await Torrent.parse('$directory/test4.torrent'); - var f = File('$directory/${torrent.infoHash}.bt.state'); + torrent = await Torrent.parse( + '$directory${Platform.pathSeparator}test4.torrent'); + var f = File( + '$directory${Platform.pathSeparator}${torrent!.infoHash}.bt.state'); if (await f.exists()) await f.delete(); }); test('Write/Read StateFile', () async { - var stateFile = await StateFile.getStateFile(directory, torrent); - var b = torrent.pieces.length ~/ 8; - if (b * 8 != torrent.pieces.length) b++; + var stateFile = await StateFile.getStateFile(directory, torrent!); + var b = torrent!.pieces.length ~/ 8; + if (b * 8 != torrent!.pieces.length) b++; assert(stateFile.bitfield.length == b); - assert(stateFile.bitfield.piecesNum == torrent.pieces.length); + assert(stateFile.bitfield.piecesNum == torrent!.pieces.length); assert(!stateFile.bitfield.haveCompletePiece()); await stateFile.close(); - // 测试建立空文件后读取内容 - stateFile = await StateFile.getStateFile(directory, torrent); - b = torrent.pieces.length ~/ 8; - if (b * 8 != torrent.pieces.length) b++; + // To test reading the contents of an empty file after creating it. + stateFile = await StateFile.getStateFile(directory, torrent!); + b = torrent!.pieces.length ~/ 8; + if (b * 8 != torrent!.pieces.length) b++; assert(stateFile.bitfield.length == b); - assert(stateFile.bitfield.piecesNum == torrent.pieces.length); + assert(stateFile.bitfield.piecesNum == torrent!.pieces.length); assert(!stateFile.bitfield.haveCompletePiece()); assert(stateFile.downloaded == 0); assert(stateFile.uploaded == 0); @@ -203,8 +206,9 @@ void main() { assert(stateFile.uploaded == 987654321); await stateFile.close(); - await stateFile.close(); //关闭两次会怎样? - var f = File('$directory/${torrent.infoHash}.bt.state'); + await stateFile.close(); // What will happen if closed twice? + var f = File( + '$directory${Platform.pathSeparator}${torrent!.infoHash}.bt.state'); var locker = Completer(); var data = []; f.openRead().listen((event) { @@ -223,12 +227,12 @@ void main() { }, onError: (e) => locker.complete()); await locker.future; - stateFile = await StateFile.getStateFile(directory, torrent); - b = torrent.pieces.length ~/ 8; - if (b * 8 != torrent.pieces.length) b++; + stateFile = await StateFile.getStateFile(directory, torrent!); + b = torrent!.pieces.length ~/ 8; + if (b * 8 != torrent!.pieces.length) b++; assert(stateFile.bitfield.length == b); - var sd = stateFile.bitfield.completedPieces.length * torrent.pieceLength; - sd = sd - (torrent.pieceLength - torrent.lastPriceLength); + var sd = stateFile.bitfield.completedPieces.length * torrent!.pieceLength; + sd = sd - (torrent!.pieceLength - torrent!.lastPieceLength); assert(stateFile.downloaded == sd); print('download: $sd'); assert(stateFile.uploaded == 987654321); @@ -239,11 +243,12 @@ void main() { }); test('Delete StateFile', () async { - var stateFile = await StateFile.getStateFile(directory, torrent); - var t = File('$directory/${torrent.infoHash}.bt.state'); + var stateFile = await StateFile.getStateFile(directory, torrent!); + var t = File( + '$directory${Platform.pathSeparator}${torrent!.infoHash}.bt.state'); assert(await t.exists()); await stateFile.delete(); - await stateFile.delete(); //删除两次 + await stateFile.delete(); //Deleting twice. assert(!await t.exists()); }); }); @@ -256,9 +261,9 @@ void main() { assert(await file.requestWrite(0, buffer, 0, buffer.length)); var bytes = await file.requestRead(0, buffer.length); var result = String.fromCharCodes(bytes); - assert(result == content, '验证读取内容错误'); + assert(result == content, 'Validating reading content error.'); await file.close(); - await file.close(); // 关闭两次 + await file.close(); // Closing twice. var file1 = File('test/test.txt'); assert(await file1.exists()); var b = []; @@ -267,13 +272,13 @@ void main() { b.addAll(data); }, onDone: () { var result = String.fromCharCodes(b); - assert(result == content, '验证文件内容错误'); + assert(result == content, 'File content verification error.'); lock.complete(); }); await lock.future; await file.delete(); file1 = File('test/test.txt'); - assert(!await file1.exists(), '文件删除错误'); + assert(!await file1.exists(), 'File deletion error.'); }); }); }