diff --git a/BitTorrent/Models/TorrentMetaInfo.swift b/BitTorrent/Models/TorrentMetaInfo.swift index 10e046a..5bc8c7f 100644 --- a/BitTorrent/Models/TorrentMetaInfo.swift +++ b/BitTorrent/Models/TorrentMetaInfo.swift @@ -16,7 +16,7 @@ class TorrentMetaInfo { let info: TorrentInfoDictionary let announce: URL - let announceList: [[String]]? + let announceList: [[URL]]? let creationDate: Date? let comment: String? let createdBy: String? @@ -70,15 +70,15 @@ class TorrentMetaInfo { } } - fileprivate class func parseAnnounceList(_ announceListData: [[Data]]) -> [[String]]? { + fileprivate class func parseAnnounceList(_ announceListData: [[Data]]) -> [[URL]]? { - var result: [[String]] = [] + var result: [[URL]] = [] for trackersArray in announceListData { - var currentArray: [String] = [] + var currentArray: [URL] = [] for trackerData in trackersArray { - if let tracker = urlCompatibleStringFromAsciiData(trackerData) { + if let tracker = urlFromAsciiData(trackerData) { currentArray.append(tracker) } else { return nil @@ -90,14 +90,9 @@ class TorrentMetaInfo { return result } - fileprivate class func urlCompatibleStringFromAsciiData(_ asciiData: Data) -> String? { - let result = String(asciiData: asciiData) - - if result == nil || URL(string: result!) == nil { - return nil - } - - return result + fileprivate class func urlFromAsciiData(_ asciiData: Data) -> URL? { + guard let result = String(asciiData: asciiData) else { return nil } + return URL(string: result) } } @@ -224,20 +219,17 @@ class TorrentFileInfo { convenience init?(dictionary: [ String : AnyObject ]) { - let pathData = dictionary["path"] as? Data - let path = String(asciiData: pathData) - - let length = dictionary["length"] as? Int - - if let length = length, let path = path { - - let md5sumData = dictionary["md5sum"] as? Data - let md5sum = String(asciiData: md5sumData) - - self.init(path: path, length: length, md5sum: md5sum) - - } else { - return nil + guard let length = dictionary["length"] as? Int, + let pathData = dictionary["path"] as? [Data], + let pathComponents = pathData.map(String.init(asciiData:)) as? [String] else { + return nil } + + let path = pathComponents.reduce("") { $0.count == 0 ? $1 : $0 + "/" + $1 } + + let md5sumData = dictionary["md5sum"] as? Data + let md5sum = String(asciiData: md5sumData) + + self.init(path: path, length: length, md5sum: md5sum) } } diff --git a/BitTorrent/Models/TorrentMetaInfoTests.swift b/BitTorrent/Models/TorrentMetaInfoTests.swift index a290c36..7892e79 100644 --- a/BitTorrent/Models/TorrentMetaInfoTests.swift +++ b/BitTorrent/Models/TorrentMetaInfoTests.swift @@ -35,10 +35,16 @@ class TorrentMetaInfoTests: XCTestCase { let multipleFile2MD5 = "e2ddfed708ea34db57b4a1ef7691776db5e24dff813706e4c695a6668f8fbabf" let multipleFileLength1 = 116 let multipleFileLength2 = 115 - let multipleFilePath1 = "/test/path" - let multipleFilePath2 = "/test/path2" + let multipleFilePath1 = ["test", "path"] + let multipleFilePath1String = "test/path" + let multipleFilePath2 = ["test", "path2"] + let multipleFilePath2String = "test/path2" let announceURL = URL(string: "announceURL.com")! - let announceList: [[String]] = [ [ "tracker1", "tracker2" ], ["backup1"] ] + let announceList: [[String]] = [ [ "http://tracker1.com", "udp://tracker2.com:123/announce" ], + ["udp://backup1.com:321/announce"] ] + let announceListURLs: [[URL]] = [ [ URL(string: "http://tracker1.com")!, + URL(string: "udp://tracker2.com:123/announce")! ], + [URL(string: "udp://backup1.com:321/announce")!] ] let creationDateInt: Int = 123456789 let creationDate = Date(timeIntervalSince1970: 123456789) let comment = "Comment" @@ -83,7 +89,7 @@ class TorrentMetaInfoTests: XCTestCase { ] } - func testFileDictionary(_ length: Int, path: String, md5sum: String?) -> [ String : AnyObject ] { + func testFileDictionary(_ length: Int, path: [String], md5sum: String?) -> [ String : AnyObject ] { var result: [ String : AnyObject ] = [ "length" : length as AnyObject, "path" : path as AnyObject, @@ -95,7 +101,7 @@ class TorrentMetaInfoTests: XCTestCase { } func exampleMetaInfoDictionary() -> Data { - return self.metaInfoDictionaryWithInfoDictionary(singleFileInfoDictionary()) + return metaInfoDictionaryWithInfoDictionary(singleFileInfoDictionary()) } func metaInfoDictionaryWithInfoDictionary(_ infoDictionary: [String : AnyObject]) -> Data { @@ -120,16 +126,16 @@ class TorrentMetaInfoTests: XCTestCase { // MARK: - func testCanInitialiseWithDictionary() { - let metaInfo = TorrentMetaInfo(data: self.exampleMetaInfoDictionary())! + let metaInfo = TorrentMetaInfo(data: exampleMetaInfoDictionary())! XCTAssertEqual(metaInfo.announce, announceURL) - XCTAssertEqual(metaInfo.announceList! as NSArray, announceList as NSArray) + XCTAssertEqual(metaInfo.announceList!, announceListURLs) XCTAssertEqual(metaInfo.creationDate!, creationDate) XCTAssertEqual(metaInfo.comment!, comment) XCTAssertEqual(metaInfo.createdBy!, createdBy) } func testInfoDictionarySplitsPiecesInto20ByteChecksums() { - let metaInfo = TorrentMetaInfo(data: self.exampleMetaInfoDictionary())! + let metaInfo = TorrentMetaInfo(data: exampleMetaInfoDictionary())! let info = metaInfo.info XCTAssertEqual(info.pieces![0], singleFilePiece) XCTAssertEqual(info.pieces![1], singleFilePiece) @@ -147,17 +153,17 @@ class TorrentMetaInfoTests: XCTestCase { } func testInitializerReturnsNilOnInvalidFields() { - var metainfoDictionary = self.unEncodedMetaInfoWithInfoDictionary(singleFileInfoDictionary()) + var metainfoDictionary = unEncodedMetaInfoWithInfoDictionary(singleFileInfoDictionary()) metainfoDictionary.removeValue(forKey: "announce") var metaInfo = TorrentMetaInfo(data: metaInfoDictionaryWithDictionary(metainfoDictionary)) XCTAssertNil(metaInfo) - metainfoDictionary = self.unEncodedMetaInfoWithInfoDictionary(singleFileInfoDictionary()) + metainfoDictionary = unEncodedMetaInfoWithInfoDictionary(singleFileInfoDictionary()) metainfoDictionary.removeValue(forKey: "info") metaInfo = TorrentMetaInfo(data: metaInfoDictionaryWithDictionary(metainfoDictionary)) XCTAssertNil(metaInfo) - metainfoDictionary = self.unEncodedMetaInfoWithInfoDictionary(singleFileInfoDictionary()) + metainfoDictionary = unEncodedMetaInfoWithInfoDictionary(singleFileInfoDictionary()) let exampleInvalidField = [[Data(bytes: [255])]] metainfoDictionary.updateValue(exampleInvalidField as AnyObject, forKey: "announce-list") metaInfo = TorrentMetaInfo(data: metaInfoDictionaryWithDictionary(metainfoDictionary)) @@ -167,7 +173,7 @@ class TorrentMetaInfoTests: XCTestCase { // MARK: - Test info dictionary func testProducesCorrectCommonInfoDictionaryProperties() { - let metaInfo = TorrentMetaInfo(data: self.exampleMetaInfoDictionary())! + let metaInfo = TorrentMetaInfo(data: exampleMetaInfoDictionary())! let info = metaInfo.info XCTAssertEqual(info.name, singleFileName) @@ -200,7 +206,7 @@ class TorrentMetaInfoTests: XCTestCase { // MARK: Single file info dictionary func testInfoDictionaryForSingleFileTorrent() { - let metaInfo = TorrentMetaInfo(data: self.exampleMetaInfoDictionary())! + let metaInfo = TorrentMetaInfo(data: exampleMetaInfoDictionary())! let info = metaInfo.info XCTAssertEqual(info.length, singleFileLength) @@ -277,11 +283,11 @@ class TorrentMetaInfoTests: XCTestCase { let file1 = info.files[0] XCTAssertEqual(file1.length, multipleFileLength1) - XCTAssertEqual(file1.path, multipleFilePath1) + XCTAssertEqual(file1.path, multipleFilePath1String) let file2 = info.files[1] XCTAssertEqual(file2.length, multipleFileLength2) - XCTAssertEqual(file2.path, multipleFilePath2) + XCTAssertEqual(file2.path, multipleFilePath2String) } func testInfoDictionaryContainsMD5ChecksumIfPresentInMultipleFiles() {