private void Load(BEncoding.Dictionary torrentInfo) { ClearInfo(); BEncoding.List announceList; if (torrentInfo.TryGetList("announce-list", out announceList)) { int announceCount = announceList.Count; var newAnnounceList = new List <AnnounceItem>(announceCount); for (int i = 0; i < announceCount; i++) { var urlList = (announceList[i] as BEncoding.List); if (urlList != null) { int urlCount = urlList.Count; var newUrlList = new List <string>(urlCount); for (int j = 0; j < urlCount; j++) { string url = urlList.GetString(j); if (!string.IsNullOrEmpty(url)) { newUrlList.Add(url); } } if (newUrlList.Count > 0) { newAnnounceList.Add(new AnnounceItem(newUrlList.ToArray())); } } } announces = newAnnounceList.ToArray(); } else { string announceUrl; if (torrentInfo.TryGetString("announce", out announceUrl)) { announces = new AnnounceItem[] { new AnnounceItem(announceUrl) }; } } long creationDateTimestamp; if (!torrentInfo.TryGetString("comment", out comment)) { comment = null; } if (!torrentInfo.TryGetString("created by", out createdBy)) { createdBy = null; } if (torrentInfo.TryGetInteger("creation date", out creationDateTimestamp)) { creationDate = TimeHelper.GetDateFromUnixTimestamp(creationDateTimestamp); } else { creationDate = DateTime.MinValue; } BEncoding.Dictionary info; if (!torrentInfo.TryGetDictionary("info", out info)) { throw new InvalidDataException("The info dictionary is missing in the torrent meta-data."); } infoHash = ComputeInfoHash(info); long pieceSize; if (!info.TryGetInteger("piece length", out pieceSize)) { throw new InvalidDataException("The piece length is missing in the torrent meta-data. But it is required for all torrents."); } byte[] pieceHashData; if (!info.TryGetByteArray("pieces", out pieceHashData)) { throw new InvalidDataException("The piece hashes are missing in the torrent meta-data. But it is required for all torrents."); } else if ((pieceHashData.Length % 20) != 0) { throw new InvalidDataException("The piece hashes are invalid in the torrent meta-data. It must be a multiple of 20 (for SHA1 hashes)."); } int pieceCount = (pieceHashData.Length / 20); pieceHashes = new PieceHash[pieceCount]; for (int i = 0; i < pieceCount; i++) { byte[] pieceHashBytes = new byte[20]; Buffer.BlockCopy(pieceHashData, (i * 20), pieceHashBytes, 0, 20); pieceHashes[i] = new PieceHash(pieceHashBytes); } long isPrivate; if (info.TryGetInteger("private", out isPrivate) && isPrivate == 1) { this.isPrivate = true; } if (!info.TryGetString("source", out source)) { source = null; } if (!info.TryGetString("name", out name) || string.IsNullOrEmpty(name)) { throw new InvalidDataException("The name string is missing in the torrent meta-data. But it is required for all torrents."); } this.pieceSize = (int)pieceSize; BEncoding.List fileList; if (info.TryGetList("files", out fileList)) { int fileCount = fileList.Count; files = new FileItem[fileCount]; for (int i = 0; i < fileCount; i++) { var fileItem = fileList[i] as BEncoding.Dictionary; if (fileItem == null) { throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a dictionary of file information."); } long fileSize; if (!fileItem.TryGetInteger("length", out fileSize)) { throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a file size."); } string fileMD5HashHex; byte[] fileMD5Hash = null; if (fileItem.TryGetString("md5sum", out fileMD5HashHex) && fileMD5HashHex.Length == 32) { fileMD5Hash = HexHelper.HexToBytes(fileMD5HashHex); } BEncoding.List pathList; if (!fileItem.TryGetList("path", out pathList)) { throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a file path."); } int pathPartCount = pathList.Count; string[] pathParts = new string[pathPartCount]; for (int j = 0; j < pathPartCount; j++) { string pathPart = pathList.GetString(j); if (string.IsNullOrEmpty(pathPart)) { throw new InvalidDataException("The format of the torrent meta-data is invalid. Expected a file path."); } pathParts[j] = pathPart; } string filePath = string.Join("/", pathParts); files[i] = new FileItem(filePath, fileSize, fileMD5Hash); } } else { long fileSize; if (!info.TryGetInteger("length", out fileSize)) { throw new InvalidDataException("The file length is missing in the torrent meta-data. But it is required for single-file torrents."); } string fileMD5HashHex; byte[] fileMD5Hash = null; if (info.TryGetString("md5sum", out fileMD5HashHex) && fileMD5HashHex.Length == 32) { fileMD5Hash = HexHelper.HexToBytes(fileMD5HashHex); } files = new FileItem[] { new FileItem(name, fileSize, fileMD5Hash) }; } totalSize = ComputeTotalSize(); int expectedPieceCount = (int)((totalSize - 1) / pieceSize) + 1; if (expectedPieceCount != pieceHashes.Length) { throw new InvalidOperationException(string.Format("The calculated number of pieces is {0} while there are {1} piece hashes.", expectedPieceCount, pieceHashes.Length)); } }
/// <summary> /// Assigns the files for this torrent. /// </summary> /// <param name="rootPath">The path to the root directory of where the files can be found.</param> /// <param name="files">The files of the torrent.</param> /// <param name="calculateMD5Hashes">If MD5 hashes for the files should be calculated.</param> public void SetFiles(string rootPath, FileItem[] files, bool calculateMD5Hashes = false) { if (rootPath == null) { throw new ArgumentNullException("rootPath"); } else if (!Directory.Exists(rootPath)) { throw new DirectoryNotFoundException(string.Format("The directory could not be found: {0}", rootPath)); } else if (files == null) { throw new ArgumentNullException("files"); } else if (files.Length == 0) { throw new ArgumentException("There must be at least one file.", "files"); } long totalSize = 0L; for (int i = 0; i < files.Length; i++) { if (files[i].Path == null) { throw new ArgumentException(string.Format("The file path at index {0} is null.", i), "files"); } string fileFullPath = IOHelper.GetTorrentFilePath(rootPath, files[i]); var fileInfo = new FileInfo(fileFullPath); if (!fileInfo.Exists) { throw new FileNotFoundException(string.Format("Unable to find the file: {0}", fileFullPath), fileFullPath); } long fileSize = fileInfo.Length; files[i].Size = fileSize; totalSize += fileSize; if (calculateMD5Hashes) { byte[] md5Hash = HashHelper.ComputeFileMD5(fileFullPath); files[i].MD5Hash = md5Hash; } } this.totalSize = totalSize; if (totalSize == 0L) { throw new InvalidOperationException("Unable to create a torrent with the total size of zero."); } if (pieceSize == 0) { pieceSize = SizeHelper.GetRecommendedPieceSize(totalSize); } int pieceCount = (int)((totalSize - 1) / pieceSize) + 1; pieceHashes = new PieceHash[pieceCount]; int lastPieceSize = (int)(totalSize % pieceSize); if (lastPieceSize == 0) { lastPieceSize = pieceSize; } byte[] pieceData = new byte[pieceSize]; int pieceDataOffset = 0; int pieceIndex = 0; int currentPieceSize = (pieceCount > 1 ? pieceSize : lastPieceSize); for (int i = 0; i < files.Length; i++) { string fileFullPath = IOHelper.GetTorrentFilePath(rootPath, files[i]); using (var fileStream = File.OpenRead(fileFullPath)) { while (pieceIndex < pieceCount) { int pieceRemaining = currentPieceSize - pieceDataOffset; if (pieceRemaining <= 0) { byte[] pieceHash = HashHelper.ComputeSHA1(pieceData, 0, pieceDataOffset); pieceHashes[pieceIndex] = new PieceHash(pieceHash); ++pieceIndex; pieceDataOffset = 0; currentPieceSize = (pieceIndex < (pieceCount - 1) ? pieceSize : lastPieceSize); pieceRemaining = currentPieceSize; if (pieceIndex >= pieceCount) { break; } } int readBytes = fileStream.Read(pieceData, pieceDataOffset, pieceRemaining); if (readBytes <= 0) { break; } pieceDataOffset += readBytes; } } } if (pieceDataOffset > 0) { byte[] pieceHash = HashHelper.ComputeSHA1(pieceData, 0, pieceDataOffset); pieceHashes[pieceIndex] = new PieceHash(pieceHash); ++pieceIndex; } if (pieceIndex < pieceCount) { throw new InvalidOperationException(string.Format("Something went wrong when calculating piece hashes. Expected {0} hashes but only calculated {1}.", pieceCount, pieceIndex)); } this.files = files; }