/// <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);
            }
        }