/// <summary> /// Gets one stream that allows reading the whole resource. /// </summary> /// <param name="transferId">Identifies the transfer and resource.</param> /// <returns>A stream that provides read access to the requested resource's data.</returns> /// <remarks>This implementation piggy-backs on the capabilities to read file in blocks. /// This causes overhead (we could just return the stream), but has the advantage that /// if the transfer is aborted (e.g. because a file lock expires), the underlying resource /// is immediately unlocked, because the returned stream does not directly access the /// resource data, but merely reads block after block via <see cref="ReadBlock"/>.</remarks> public Stream ReadResourceStreamed(string transferId) { // //TODO in providers stream getting method - just forward here // IDownloadTransferService srv = this...; // var token = srv.RequestDownloadToken(resourceId, false); // return srv.ReadResourceStreamed(token.TransferId); DownloadTransfer transfer = GetCachedTransfer(transferId, true); var stream = new StreamedBlockInputStream(blockNumber => ReadBlockStreamed(transferId, blockNumber), transfer.File.Length); return(stream); }
public void Updating_Transfer_Should_Replace_Transfer_Instance() { foreach (var transfer in transfers) { store.AddTransfer(transfer); } var original = transfers[5]; DownloadTransfer<FileItem> copy = new DownloadTransfer<FileItem>(original.Token, original.FileItem); store.UpdateTransferState(copy); Assert.AreNotSame(original, store.TryGetTransfer(original.TransferId)); Assert.AreSame(copy, store.TryGetTransfer(original.TransferId)); }
/// <summary> /// Requests a download token for a given resource. /// </summary> /// <param name="resourceIdentifier"></param> /// <param name="includeFileHash">Whether a file hash for the /// requested resource should be calculated and assigned to the /// <see cref="DownloadToken.Md5FileHash"/> property of the returned /// <see cref="DownloadToken"/>.</param> /// <returns></returns> public DownloadToken RequestDownloadToken(string resourceIdentifier, bool includeFileHash) { FileInfo fileInfo = FileResolveFunc(resourceIdentifier); if (!fileInfo.Exists) { string msg = String.Format("Resource [{0}] not found.", resourceIdentifier); throw new VirtualResourceNotFoundException(msg); } string transferId = Guid.NewGuid().ToString(); DownloadToken dt = new DownloadToken { TransferId = transferId, ResourceIdentifier = resourceIdentifier, CreationTime = SystemTime.Now(), ContentType = ContentUtil.ResolveContentType(fileInfo.Extension), DownloadBlockSize = 512 * 1024, //TODO configure block size and expiration ResourceName = fileInfo.Name, ResourceLength = fileInfo.Length, ExpirationTime = SystemTime.Now().AddHours(24), Status = TransferStatus.Starting }; //calculate number of blocks long count = dt.ResourceLength / dt.DownloadBlockSize.Value; if (dt.ResourceLength % dt.DownloadBlockSize != null) { count++; } dt.TotalBlockCount = count; if (includeFileHash) { dt.Md5FileHash = fileInfo.CalculateMd5Hash(); } var transfer = new DownloadTransfer(dt) { File = fileInfo }; Transfers.Add(transferId, transfer); return(dt); }
/// <summary> /// Closes a given transfer, and discards the <see cref="DownloadToken"/> that /// was maintained. /// </summary> private void CloseTransferInternal(string transferId, TransferStatus status, AbortReason?abortReason) { DownloadTransfer transfer = GetCachedTransfer(transferId, true); lock (transfer.SyncRoot) { if (transfer.Stream != null && transfer.Status == TransferStatus.Running) { //dispose stream transfer.Stream.Dispose(); transfer.Stream = null; } transfer.Token.Status = status; transfer.Token.AbortReason = abortReason; } Transfers.Remove(transferId); }
/// <summary> /// Tells the transfer service that transmission is being /// paused for an unknown period of time. This should keep /// the transfer enabled, but gives the service time to /// free or unlock resources. /// </summary> /// <param name="transferId">Identifies the transfer and resource.</param> public void PauseTransfer(string transferId) { DownloadTransfer transfer = GetCachedTransfer(transferId, true); lock (transfer.SyncRoot) { if (!transfer.Status.Is(TransferStatus.Starting, TransferStatus.Running)) { string msg = "Only active transfers can be paused. Current status is: [{0}]."; msg = String.Format(msg, transfer.Status); throw new TransferStatusException(msg); } if (transfer.Stream != null) { //close stream transfer.Stream.Dispose(); transfer.Stream = null; } transfer.Token.Status = TransferStatus.Paused; } }
/// <summary> /// Requests a download token for a given resource. /// </summary> /// <param name="virtualFilePath">Identifies the resource to be downloaded.</param> /// <param name="maxBlockSize">The maximum size of a read block. This property must be /// equal or lower to the <see cref="ITransferHandler.MaxBlockSize"/>, if there is an /// upper limit for blocks.</param> /// <param name="includeFileHash">Whether a file hash for the /// requested resource should be calculated and assigned to the /// <see cref="DownloadToken.Md5FileHash"/> property of the returned /// <see cref="DownloadToken"/>.</param> /// <returns>A token that represents a granted resource download, optionally /// limited to a given time frame (<see cref="TransferToken.ExpirationTime"/>).</returns> /// <exception cref="VirtualResourceNotFoundException">If the submitted <paramref name="virtualFilePath"/> /// does not match an existing resource.</exception> /// <exception cref="ResourceAccessException">If the request was not authorized.</exception> /// <exception cref="ResourceLockedException">If a lock to access the /// resource was not granted.</exception> public virtual DownloadToken RequestDownloadToken(string virtualFilePath, bool includeFileHash, int maxBlockSize) { DownloadTransfer <TFile> transfer = InitTransfer(virtualFilePath, maxBlockSize, includeFileHash); return(transfer.Token); }
/// <summary> /// Handles locking, validation and stream preparation of a given transfer in order to read /// a given block of data. The actual reading (either into a buffer, or a returned /// stream) is being delegated via the <paramref name="dataReaderFunc"/>. /// </summary> /// <typeparam name="T">The type of <see cref="IDataBlockInfo"/> that is being returned.</typeparam> /// <param name="transferId">The transfer token ID.</param> /// <param name="blockNumber">The block number to be read.</param> /// <param name="dataReaderFunc">A function that receives the <see cref="DownloadTransfer"/> and /// the designated stream offset, and returns the required <see cref="IDataBlockInfo"/> that /// provides the block's data.</param> /// <returns>The <see cref="IDataBlockInfo"/> that is being created by the <paramref name="dataReaderFunc"/>.</returns> private T PrepareBlockReading <T>(string transferId, long blockNumber, Func <DownloadTransfer, long, T> dataReaderFunc) where T : IDataBlockInfo { DownloadTransfer transfer = GetCachedTransfer(transferId, true); DownloadToken token = transfer.Token; if (!File.Exists(transfer.File.FullName)) { string msg = "Resource [{0}] of transfer [{1}] was not found."; msg = String.Format(msg, transfer.Token.ResourceName, transferId); throw new VirtualResourceNotFoundException(msg); } lock (transfer.SyncRoot) { //make sure the transfer is active if (!transfer.Status.Is(TransferStatus.Starting, TransferStatus.Running, TransferStatus.Paused)) { string msg = String.Format("Transfer is not active anymore - status is [{0}].", transfer.Status); throw new TransferStatusException(msg); } long position = blockNumber * token.DownloadBlockSize.Value; //in case of an invalid position, throw error if (position > token.ResourceLength) { string msg = "Cannot deliver block {0} - invalid block number."; msg = String.Format(msg, blockNumber); throw new DataBlockException(msg); } if (transfer.Stream == null) { //open stream, share read access transfer.Stream = transfer.File.Open(FileMode.Open, FileAccess.Read, FileShare.Read); } //reposition stream if necessary if (position != transfer.Stream.Position) { transfer.Stream.Seek(position, SeekOrigin.Begin); } T dataBlock = dataReaderFunc(transfer, position); if (dataBlock.IsLastBlock) { //assume the last block will be processed successfully and //already close the stream (don't wait for the transfer to be //close by client) transfer.Stream.Close(); transfer.Stream.Dispose(); transfer.Stream = null; } //update status transfer.Token.Status = TransferStatus.Running; //maintain local (unshared) copy of the block info without the data transfer.Token.LastTransmittedBlockInfo = DataBlockInfo.FromDataBlock(dataBlock); return(dataBlock); } }