Added some speed trackers

This commit is contained in:
Ben Davis
2017-08-13 19:49:21 +01:00
parent 3ba233685f
commit 7e3ce5c48d
13 changed files with 406 additions and 75 deletions
+18 -6
View File
@@ -13,11 +13,14 @@
B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */; };
B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F81F0A554A00C23E7C /* UDPConnection.swift */; };
B514DD7B1F40971D00C932F8 /* TorrentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD7A1F40971D00C932F8 /* TorrentViewController.swift */; };
B514DD801F409E2200C932F8 /* BitByteString.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD7F1F409E2200C932F8 /* BitByteString.swift */; };
B514DD831F40A2B800C932F8 /* TrackerManagerTests.torrent in Resources */ = {isa = PBXBuildFile; fileRef = B514DD821F40A2B800C932F8 /* TrackerManagerTests.torrent */; };
B514DD851F40ABB300C932F8 /* TorrentClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD841F40ABB300C932F8 /* TorrentClientTests.swift */; };
B514DD881F40B2EC00C932F8 /* TorrentClientManagerStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD861F40B2C700C932F8 /* TorrentClientManagerStubs.swift */; };
B514DD891F40B58200C932F8 /* text.txt in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0E41F02F9E700EF58E3 /* text.txt */; };
B514DD8B1F40C40500C932F8 /* NetworkSpeedTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD8A1F40C40500C932F8 /* NetworkSpeedTracker.swift */; };
B514DD8F1F40C53C00C932F8 /* NetworkSpeedTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD8E1F40C53C00C932F8 /* NetworkSpeedTrackerTests.swift */; };
B514DD911F40D15C00C932F8 /* StringUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD901F40D15C00C932F8 /* StringUtilities.swift */; };
B514DD931F40D27500C932F8 /* TorrentInfoRowData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD921F40D27500C932F8 /* TorrentInfoRowData.swift */; };
B51638931F0EE9B6009E563E /* TCPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51638921F0EE9B6009E563E /* TCPConnection.swift */; };
B51638951F0EEAA4009E563E /* TCPConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51638941F0EEAA4009E563E /* TCPConnectionTests.swift */; };
B51638971F0EEC2B009E563E /* GCDAsyncSocketStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51638961F0EEC2B009E563E /* GCDAsyncSocketStub.swift */; };
@@ -163,10 +166,13 @@
B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnectionTests.swift; sourceTree = "<group>"; };
B50B24F81F0A554A00C23E7C /* UDPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnection.swift; sourceTree = "<group>"; };
B514DD7A1F40971D00C932F8 /* TorrentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentViewController.swift; sourceTree = "<group>"; };
B514DD7F1F409E2200C932F8 /* BitByteString.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BitByteString.swift; path = ../../BitTorrent_one/BitTorrent/BitByteString.swift; sourceTree = "<group>"; };
B514DD821F40A2B800C932F8 /* TrackerManagerTests.torrent */ = {isa = PBXFileReference; lastKnownFileType = file; path = TrackerManagerTests.torrent; sourceTree = "<group>"; };
B514DD841F40ABB300C932F8 /* TorrentClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentClientTests.swift; sourceTree = "<group>"; };
B514DD861F40B2C700C932F8 /* TorrentClientManagerStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentClientManagerStubs.swift; sourceTree = "<group>"; };
B514DD8A1F40C40500C932F8 /* NetworkSpeedTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSpeedTracker.swift; sourceTree = "<group>"; };
B514DD8E1F40C53C00C932F8 /* NetworkSpeedTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSpeedTrackerTests.swift; sourceTree = "<group>"; };
B514DD901F40D15C00C932F8 /* StringUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringUtilities.swift; sourceTree = "<group>"; };
B514DD921F40D27500C932F8 /* TorrentInfoRowData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentInfoRowData.swift; sourceTree = "<group>"; };
B51638921F0EE9B6009E563E /* TCPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPConnection.swift; sourceTree = "<group>"; };
B51638941F0EEAA4009E563E /* TCPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPConnectionTests.swift; sourceTree = "<group>"; };
B51638961F0EEC2B009E563E /* GCDAsyncSocketStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GCDAsyncSocketStub.swift; sourceTree = "<group>"; };
@@ -192,7 +198,7 @@
B54D0C141CA53983004343BD /* BEncode.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BEncode.xcodeproj; path = Submodules/BEncodeSwift/BEncode.xcodeproj; sourceTree = "<group>"; };
B54D0C231CA56A22004343BD /* TorrentMetaInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TorrentMetaInfo.swift; sourceTree = "<group>"; };
B54D0C261CA56ADB004343BD /* TorrentMetaInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TorrentMetaInfoTests.swift; sourceTree = "<group>"; };
B54D0C2B1CA5787E004343BD /* Data+sha1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Data+sha1.swift"; path = "Utilities/Data+sha1.swift"; sourceTree = "<group>"; };
B54D0C2B1CA5787E004343BD /* Data+sha1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data+sha1.swift"; sourceTree = "<group>"; };
B54D0C581CA58722004343BD /* CommonCrypto.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CommonCrypto.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B54D0C6F1CA58749004343BD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B54D0C711CA58767004343BD /* CommonCrypto.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CommonCrypto.xcconfig; sourceTree = "<group>"; };
@@ -385,10 +391,11 @@
B54D0C291CA5785F004343BD /* Utilities */ = {
isa = PBXGroup;
children = (
B514DD7F1F409E2200C932F8 /* BitByteString.swift */,
B54D0C2B1CA5787E004343BD /* Data+sha1.swift */,
B514DD8A1F40C40500C932F8 /* NetworkSpeedTracker.swift */,
B514DD8E1F40C53C00C932F8 /* NetworkSpeedTrackerTests.swift */,
);
name = Utilities;
path = Utilities;
sourceTree = "<group>";
};
B54D0C591CA58722004343BD /* CommonCrypto */ = {
@@ -544,6 +551,8 @@
children = (
B5E9B0D71F02E6F800EF58E3 /* AppDelegate.swift */,
B514DD7A1F40971D00C932F8 /* TorrentViewController.swift */,
B514DD921F40D27500C932F8 /* TorrentInfoRowData.swift */,
B514DD901F40D15C00C932F8 /* StringUtilities.swift */,
B5E9B0D91F02E6F800EF58E3 /* Assets.xcassets */,
B5E9B0DB1F02E6F800EF58E3 /* LaunchScreen.storyboard */,
B5E9B0DE1F02E6F800EF58E3 /* Info.plist */,
@@ -948,7 +957,7 @@
B55317DC1F02FC4D00909ADF /* TorrentHTTPTracker.swift in Sources */,
B52EE3401F129CA200AC22D6 /* TorrentPeerHandshakeMessageBuffer.swift in Sources */,
B5F27C621F3F93AC0040589C /* TorrentProgressManager.swift in Sources */,
B514DD801F409E2200C932F8 /* BitByteString.swift in Sources */,
B514DD8B1F40C40500C932F8 /* NetworkSpeedTracker.swift in Sources */,
B5E977961CAFB46B0038EBE7 /* String+URLEncode.swift in Sources */,
B558F4831F0A647D00438BB4 /* InternetProtocol.swift in Sources */,
B527F62B1F1BD5FA001F06AF /* TorrentPieceDownloadBuffer.swift in Sources */,
@@ -986,6 +995,7 @@
B556D5AE1F0BA1FD00277B8D /* GCDAsyncUdpSocketStub.swift in Sources */,
B5AF7AAD1F253055003FD66F /* FileHandleFake.swift in Sources */,
B5530DB51F03063E00F71CCD /* HTTPConnectionTests.swift in Sources */,
B514DD8F1F40C53C00C932F8 /* NetworkSpeedTrackerTests.swift in Sources */,
B535533C1F0FEE6300A6DCBE /* TCPConnectionStub.swift in Sources */,
B5C5F9651F2500D1007623B2 /* TorrentProgressTests.swift in Sources */,
B52EE3431F129CB000AC22D6 /* TorrentPeerHandshakeMessageBufferTests.swift in Sources */,
@@ -1015,7 +1025,9 @@
buildActionMask = 2147483647;
files = (
B5E9B0D81F02E6F800EF58E3 /* AppDelegate.swift in Sources */,
B514DD911F40D15C00C932F8 /* StringUtilities.swift in Sources */,
B514DD7B1F40971D00C932F8 /* TorrentViewController.swift in Sources */,
B514DD931F40D27500C932F8 /* TorrentInfoRowData.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+4 -1
View File
@@ -11,6 +11,9 @@ import Foundation
public struct BitField: Equatable {
public let size: Int
public private(set) var value: [Bool]
public var complete: Bool {
return !contains(where: { !$0.isSet })
}
init(size: Int) {
self.size = size
@@ -90,7 +93,7 @@ public struct BitField: Equatable {
}
extension BitField: Sequence {
public func makeIterator() -> AnyIterator<(Int, Bool)> {
public func makeIterator() -> AnyIterator<(index: Int, isSet: Bool)> {
var index = 0
return AnyIterator {
guard index < self.size else { return nil }
+12
View File
@@ -96,4 +96,16 @@ class BitFieldTests: XCTestCase {
XCTAssertEqual(indices, [0,1,2,3,4])
XCTAssertEqual(values, [false,false,true,true,false])
}
func test_allBitsSet() {
var example = BitField(size: 5)
example.set(at: 0)
example.set(at: 1)
example.set(at: 2)
example.set(at: 3)
XCTAssertFalse(example.complete)
example.set(at: 4)
XCTAssertTrue(example.complete)
}
}
@@ -42,9 +42,14 @@ class TorrentPeerManager {
let bitFieldSize: Int
private(set) var peers: [TorrentPeer] = []
private var numberOfConnectedPeers: Int {
var numberOfConnectedPeers: Int {
return peers.filter({ $0.connected }).count
}
var numberOfConnectedSeeds: Int {
return peers.filter({ $0.connected && $0.currentProgress.complete }).count
}
private(set) var downloadSpeedTracker = NetworkSpeedTracker ()
init(clientId: Data, infoHash: Data, bitFieldSize: Int) {
self.clientId = clientId
@@ -113,6 +118,7 @@ extension TorrentPeerManager: TorrentPeerDelegate {
}
func peer(_ sender: TorrentPeer, gotPieceAtIndex index: Int, piece: Data) {
downloadSpeedTracker.increase(by: piece.count)
delegate?.torrentPeerManager(self, downloadedPieceAtIndex: index, piece: piece)
requestNextPiece(from: sender)
// TODO: send have to peers
@@ -282,4 +282,20 @@ class TorrentPeerManagerTests: XCTestCase {
// Then
XCTAssertFalse(peer.downloadPieceCalled)
}
func test_downloadSpeedRecordedOnGettingPiece() {
// Given
let peerInfo = TorrentPeerInfo(ip: "127.0.0.1", port: 123, peerId: nil)
sut.addPeers(withInfo: [peerInfo])
guard let peer = peers.first else { return }
let pieceSize = 100
// When
sut.peer(peer, gotPieceAtIndex: 0, piece: Data(repeating: 0, count: pieceSize))
// Then
XCTAssertEqual(sut.downloadSpeedTracker.totalNumberOfBytes, pieceSize)
}
}
+4
View File
@@ -38,7 +38,10 @@ class TorrentPeer {
private(set) var peerInterested: Bool = false
private(set) var amChokedToPeer: Bool = true
private(set) var amInterestedInPeer: Bool = false
private(set) var currentProgress: BitField
private(set) var downloadSpeedTracker = NetworkSpeedTracker()
private(set) var uploadSpeedTracker = NetworkSpeedTracker()
private var downloadPieceRequests: [Int: TorrentPieceDownloadBuffer] = [:]
private var numberOfPendingBlockRequests = 0
@@ -205,6 +208,7 @@ extension TorrentPeer: TorrentPeerCommunicatorDelegate {
}
func peer(_ sender: TorrentPeerCommunicator, sentPiece index: Int, begin: Int, block: Data) {
downloadSpeedTracker.increase(by: block.count)
guard let downloadPieceBuffer = downloadPieceRequests[index] else { return }
numberOfPendingBlockRequests -= 1
downloadPieceBuffer.gotBlock(block, begin: begin)
+12
View File
@@ -352,4 +352,16 @@ class TorrentPeerTests: XCTestCase {
}
waitForExpectations(timeout: 0.1)
}
// MARK: - Speed trackers
func test_gotPieceRecordedInSpeedTracker() {
communicator.sendRequestParameters = []
communicator.delegate?.peer(communicator,
sentPiece: pieceIndex,
begin: 0,
block: Data(repeating: 0, count: pieceSize))
XCTAssertEqual(sut.downloadSpeedTracker.totalNumberOfBytes, pieceSize)
}
}
@@ -31,12 +31,12 @@ public class TorrentClient {
public let metaInfo: TorrentMetaInfo
public var progress: TorrentProgress {
return progressManager.progress
}
public private(set) var status: Status = .stopped
public var progress: TorrentProgress { return progressManager.progress }
public var numberOfConnectedPeers: Int { return peerManager.numberOfConnectedPeers }
public var numberOfConnectedSeeds: Int { return peerManager.numberOfConnectedPeers }
public var downloadSpeedTracker: NetworkSpeedTracker { return peerManager.downloadSpeedTracker }
let progressManager: TorrentProgressManager
let peerManager: TorrentPeerManager
let trackerManager: TorrentTrackerManager
@@ -0,0 +1,62 @@
//
// NetworkSpeedTracker.swift
// BitTorrent
//
// Created by Ben Davis on 13/08/2017.
// Copyright © 2017 Ben Davis. All rights reserved.
//
import Foundation
extension Collection {
func firstWhere(_ predicate: (Element) -> Bool) -> Element? {
return first(where: predicate)
}
}
public struct NetworkSpeedDataPoint: Equatable, Comparable {
var numberOfBytes: Int
var dateRecorded: Date
init(_ numberOfBytes: Int, dateRecorded: Date = Date()) {
self.numberOfBytes = numberOfBytes
self.dateRecorded = dateRecorded
}
public static func ==(_ lhs: NetworkSpeedDataPoint, _ rhs: NetworkSpeedDataPoint) -> Bool {
return (
lhs.numberOfBytes == rhs.numberOfBytes &&
lhs.dateRecorded == rhs.dateRecorded
)
}
public static func <(lhs: NetworkSpeedDataPoint, rhs: NetworkSpeedDataPoint) -> Bool {
return lhs.dateRecorded.compare(rhs.dateRecorded) == .orderedAscending
}
}
public struct NetworkSpeedTracker {
var totalNumberOfBytes: Int = 0
private var dataPoints: [NetworkSpeedDataPoint] = [NetworkSpeedDataPoint(0)]
mutating func increase(by bytes: Int) {
totalNumberOfBytes += bytes
addDataPoint(NetworkSpeedDataPoint(totalNumberOfBytes))
}
private mutating func addDataPoint(_ dataPoint: NetworkSpeedDataPoint) {
dataPoints = [dataPoint] + dataPoints
}
public func numberOfBytesDownloaded(since date: Date) -> Int {
guard let previouslyDataPoint = dataPoints.firstWhere({ $0.dateRecorded < date }) else {
return totalNumberOfBytes
}
return totalNumberOfBytes - previouslyDataPoint.numberOfBytes
}
public func numberOfBytesDownloaded(over timeInterval: TimeInterval) -> Int {
return numberOfBytesDownloaded(since: Date(timeIntervalSinceNow: -timeInterval))
}
}
@@ -0,0 +1,53 @@
//
// NetworkSpeedTrackerTests.swift
// BitTorrentTests
//
// Created by Ben Davis on 13/08/2017.
// Copyright © 2017 Ben Davis. All rights reserved.
//
import XCTest
@testable import BitTorrent
class NetworkSpeedTrackerTests: XCTestCase {
func test_increaseBytes() {
var sut = NetworkSpeedTracker()
sut.increase(by: 10)
XCTAssertEqual(sut.totalNumberOfBytes, 10)
}
func test_canGetBytesDownloadedSinceDate() {
var sut = NetworkSpeedTracker()
sut.increase(by: 2)
let date = Date()
sut.increase(by: 10)
sut.increase(by: 5)
XCTAssertEqual(sut.numberOfBytesDownloaded(since: date), 15)
}
func test_0BytesIfNoDataRecrodedSince() {
var sut = NetworkSpeedTracker()
sut.increase(by: 2)
let date = Date()
XCTAssertEqual(sut.numberOfBytesDownloaded(since: date), 0)
}
func test_canGetBytesOverAllTime() {
let date = Date()
var sut = NetworkSpeedTracker()
sut.increase(by: 10)
sut.increase(by: 5)
XCTAssertEqual(sut.numberOfBytesDownloaded(since: date), 15)
}
func test_canGetBytesDownloadedOverTimePeriod() {
var sut = NetworkSpeedTracker()
sut.increase(by: 2)
usleep(2000)
sut.increase(by: 10)
sut.increase(by: 5)
let timePeriod: TimeInterval = 0.002
XCTAssertEqual(sut.numberOfBytesDownloaded(over: timePeriod), 15)
}
}
+63
View File
@@ -0,0 +1,63 @@
//
// StringUtilities.swift
// BitTorrentExample
//
// Created by Ben Davis on 13/08/2017.
// Copyright © 2017 Ben Davis. All rights reserved.
//
import Foundation
extension TimeInterval {
var milliseconds: Int {
return Int((self.truncatingRemainder(dividingBy: 1)) * 1000)
}
var seconds: Int {
return Int(self.remainder(dividingBy: 60))
}
var minutes: Int {
return Int((self/60).remainder(dividingBy: 60))
}
var hours: Int {
return Int(self / (60*60))
}
var stringTime: String {
if self.hours != 0 {
return "\(self.hours)h \(self.minutes)m \(self.seconds)s"
} else if self.minutes != 0 {
return "\(self.minutes)m \(self.seconds)s"
} else if self.milliseconds != 0 {
return "\(self.seconds)s \(self.milliseconds)ms"
} else {
return "\(self.seconds)s"
}
}
}
public let bytesInKB = 1024
public let bytesInMB = bytesInKB*1024
public let bytesInGB = bytesInMB*1024
public func twoDecimalPlaceFloat(_ float: Float) -> String {
return String(format: "%.2f", float)
}
public func bytesToString(_ numberOfBytes: Int) -> String {
if (numberOfBytes > bytesInGB) {
let numberOfGBs: Float = Float(numberOfBytes) / Float(bytesInGB)
return "\(twoDecimalPlaceFloat(numberOfGBs)) GB"
} else if (numberOfBytes > bytesInMB) {
let numberOfMBs: Float = Float(numberOfBytes) / Float(bytesInMB)
return "\(twoDecimalPlaceFloat(numberOfMBs)) MB"
} else if (numberOfBytes > bytesInKB) {
let numberOfKBs: Float = Float(numberOfBytes) / Float(bytesInKB)
return "\(twoDecimalPlaceFloat(numberOfKBs)) KB"
} else {
return "\(numberOfBytes) Bytes"
}
}
@@ -0,0 +1,88 @@
//
// TorrentInfoRowData.swift
// BitTorrentExample
//
// Created by Ben Davis on 13/08/2017.
// Copyright © 2017 Ben Davis. All rights reserved.
//
import BitTorrent
enum TorrentInfoRowData: Int {
case name = 0
case size, percentageComplete, status, seeds, peers, downloadSpeed, uploadSpeed, eta, uploaded
static var numberOfRows: Int = 10
var titleText: String {
switch self {
case .name:
return "Name"
case .size:
return "Size"
case .percentageComplete:
return "Completed"
case .status:
return "Status"
case .seeds:
return "Seeds"
case .peers:
return "Peers"
case .downloadSpeed:
return "↓ Speed"
case .uploadSpeed:
return "↑ Speed"
case .eta:
return "ETA"
case .uploaded:
return "Uploaded"
}
}
func value(using client: TorrentClient) -> String {
let speedSampleSize: TimeInterval = 5
switch self {
case .name:
return client.metaInfo.info.name
case .size:
return bytesToString(client.metaInfo.info.length)
case .percentageComplete:
let percentageComplete = client.progress.percentageComplete
let progressString = twoDecimalPlaceFloat(percentageComplete * 100)
return "\(progressString)%"
case .status:
return client.status.toString
case .seeds:
return "\(client.numberOfConnectedSeeds)"
case .peers:
return "\(client.numberOfConnectedPeers)"
case .downloadSpeed:
let speed = client.downloadSpeedTracker.numberOfBytesDownloaded(over: speedSampleSize)
return bytesToString(speed / Int(speedSampleSize)) + "/s"
case .uploadSpeed:
return "????"
case .eta:
let speed = client.downloadSpeedTracker.numberOfBytesDownloaded(over: speedSampleSize)
guard speed > 0 else { return "" }
let remaining = client.progress.remaining * client.metaInfo.info.pieceLength
guard speed > 0 else { return "n/a" }
return TimeInterval(remaining / speed).stringTime
case .uploaded:
return bytesToString(client.progress.uploaded * client.metaInfo.info.pieceLength)
}
}
}
+62 -62
View File
@@ -11,9 +11,19 @@ import BitTorrent
class TorrentViewController: UIViewController {
static let refreshRate: TimeInterval = 1
let tableView = UITableView()
let torrentClient: TorrentClient
lazy var refreshTimer: Timer = {
return Timer.scheduledTimer(timeInterval: TorrentViewController.refreshRate,
target: self,
selector: #selector(TorrentViewController.timerFired),
userInfo: nil,
repeats: true)
}()
init(torrentClient: TorrentClient) {
self.torrentClient = torrentClient
super.init(nibName: nil, bundle: nil)
@@ -26,6 +36,7 @@ class TorrentViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
view.addSubview(tableView)
}
@@ -37,77 +48,44 @@ class TorrentViewController: UIViewController {
tableView.contentOffset = .zero
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
refreshTimer.fire()
}
@objc private func timerFired() {
tableView.reloadData()
}
}
extension TorrentViewController: UITableViewDataSource {
enum TableViewRow: Int {
case name = 0
case size, percentageComplete, status, seeds, peers, downloadSpeed, uploadSpeed, eta, uploaded
static var numberOfRows: Int = 10
var titleText: String {
switch self {
case .name:
return "Name"
case .size:
return "Size"
case .percentageComplete:
return "Completed"
case .status:
return "Status"
case .seeds:
return "Seeds"
case .peers:
return "Peers"
case .downloadSpeed:
return "↓ Speed"
case .uploadSpeed:
return "↑ Speed"
case .eta:
return "ETA"
case .uploaded:
return "Uploaded"
}
}
func value(using client: TorrentClient) -> String {
switch self {
case .name:
return client.metaInfo.info.name
case .size:
return bytesToString(client.metaInfo.info.length)
case .percentageComplete:
let percentageComplete = client.progress.percentageComplete
let progressString = twoDecimalPlaceFloat(percentageComplete * 100)
return "\(progressString)%"
case .status:
return client.status.toString
// case .seeds:
// return "Seeds"
// case .peers:
// return "Peers"
// case .downloadSpeed:
// return " Speed"
// case .uploadSpeed:
// return " Speed"
// case .eta:
// return "ETA"
// case .uploaded:
// return "Uploaded"
default:
return "????"
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return torrentClient.status == .stopped ? 2 : 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TableViewRow.numberOfRows
if section == 0 {
return TorrentInfoRowData.numberOfRows
} else {
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
return cellForTorrentInfoSection(at: indexPath, tableView: tableView)
} else {
let startCell = UITableViewCell(style: .default, reuseIdentifier: nil)
startCell.textLabel?.text = "Start"
startCell.textLabel?.textAlignment = .center
startCell.textLabel?.textColor = .blue
return startCell
}
}
func cellForTorrentInfoSection(at indexPath: IndexPath, tableView: UITableView) -> UITableViewCell {
let cellReuseIdentifier = "Cell"
let cell: UITableViewCell
@@ -123,8 +101,30 @@ extension TorrentViewController: UITableViewDataSource {
}
func setupCell(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
guard let row = TableViewRow(rawValue: indexPath.row) else { return }
guard let row = TorrentInfoRowData(rawValue: indexPath.row) else { return }
cell.textLabel?.text = row.titleText
cell.detailTextLabel?.text = row.value(using: torrentClient)
}
}
extension TorrentViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 25
} else {
return 44
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) else { return }
cell.setSelected(false, animated: false)
if indexPath.section == 1 {
torrentClient.start()
tableView.reloadData()
}
}
}