/// <summary>
        /// Gets information about all blocks that have been transferred so far.
        /// This information can be used to resume a transfer that spawns several
        /// sessions on the client side.<br/>
        /// In case of retransmissions of blocks, this method returns only the
        /// <see cref="DataBlockInfo"/> instance per block number that corresponds
        /// to the most recent transfer.
        /// </summary>
        /// <param name="transferId">Identifies the transfer and resource.</param>
        /// <returns>All transferred blocks (without data).</returns>
        /// <exception cref="UnknownTransferException">In case no such transfer
        /// is currently maintained.</exception>
        /// <remarks>Implementing classes might decide to discard cached block information
        /// once a transfer was completed, and throw exceptions or just return an empty list.</remarks>
        public IEnumerable <DataBlockInfo> GetTransferredBlocks(string transferId)
        {
            var context = IsUploadService
                ? FileSystemTask.UploadedBlockInfosRequest
                : FileSystemTask.DownloadedBlockInfosRequest;

            TTransfer transfer = GetCachedTransfer(transferId, true, context);
            var       blocks   = transfer.TransferredBlocks;

            Auditor.AuditTransferOperation(context, AuditEvent.TransferredBlockInfoRequest, transferId, transfer.FileItem);

            return(blocks);
        }
        private Stream DownloadFileImpl(string transferId)
        {
            const FileSystemTask context = FileSystemTask.StreamedFileDownloadRequest;

            //get the cached transfer
            TTransfer transfer = GetCachedTransfer(transferId, true, context);


            if (transfer.Token.TotalBlockCount == 0)
            {
                //if we don't have any blocks, return a null stream
                Auditor.AuditTransferOperation(context, AuditEvent.FileDataDownloaded, transfer.TransferId, transfer.FileItem);
                return(Stream.Null);
            }

            //we don't expose the underlying stream directly, but rather use blocks again,
            //which disconnects the exposed stream from the underlying resource and allows for simple
            //aborting
            long resourceLength = transfer.FileItem.ResourceInfo.Length;

            // ReSharper disable UseObjectOrCollectionInitializer
            var stream = new StreamedBlockInputStream(blockNumber => ReadBlockStreamed(transferId, blockNumber),
                                                      resourceLength);

            // ReSharper restore UseObjectOrCollectionInitializer

            //assign the stream the transfer object in order so it can query the status during reading
            stream.Transfer = transfer;

            //reading the last block closes the transfer automatically..
            transfer.AutoCloseAfterLastBlockDelivery = true;

            //...and so does disposing the stream at any time
            var closingStream = new ClosingActionStream(stream, () => CompleteTransfer(transferId));

            Auditor.AuditTransferOperation(context, AuditEvent.FileDataDownloaded, transfer.TransferId, transfer.FileItem);
            return(closingStream);
        }
        /// <summary>
        /// Orchestrates common block reading procedures for the <see cref="ReadBlock"/> and
        /// <see cref="ReadBlockStreamed"/> methods.
        /// </summary>
        protected virtual T OrchestrateBlockReading <T>(TTransfer transfer, long blockNumber,
                                                        Func <TTransfer, long, DataBlockInfo, T> readerImplFunc) where T : IDataBlock
        {
            const FileSystemTask context = FileSystemTask.DataBlockDownloadRequest;

            if (blockNumber < 0)
            {
                string msg = "Invalid block number [{0}] requested - block numbers cannot be negative.";
                msg = String.Format(msg, blockNumber);
                throw new DataBlockException(msg);
            }

            if (blockNumber >= transfer.Token.TotalBlockCount)
            {
                string msg = "Invalid block number [{0}] requested - the total number of blocks is [{1}].";
                msg = String.Format(msg, blockNumber, transfer.Token.TotalBlockCount);
                throw new DataBlockException(msg);
            }

            //make sure the file still exists
            if (!transfer.FileItem.Exists)
            {
                Auditor.AuditRequestedFileNotFound(transfer.FileItem, context);

                string msg = "Resource [{0}] of transfer [{1}] was not found.";
                msg = String.Format(msg, transfer.Token.ResourceName, transfer.TransferId);
                throw new VirtualResourceNotFoundException(msg)
                      {
                          IsAudited = true
                      };
            }

            lock (transfer.SyncRoot)
            {
                //make sure the transfer is active
                if (!transfer.Status.Is(TransferStatus.Starting, TransferStatus.Running, TransferStatus.Paused))
                {
                    string msg = String.Format("Transfer [{0}] is not active anymore - status is [{1}].", transfer.TransferId,
                                               transfer.Status);

                    if (transfer.AbortReason.HasValue)
                    {
                        msg += String.Format(" Transfer abort reason: [{0}].", transfer.AbortReason);
                    }

                    Auditor.AuditDeniedOperation(context, AuditEvent.DownloadNoLongerActive, transfer.FileItem, msg);
                    throw new TransferStatusException(msg)
                          {
                              IsAudited = true, EventId = (int)AuditEvent.DownloadNoLongerActive
                          };
                }

                //check whether we already transferred this block once
                DataBlockInfo transferredBlock = transfer.TryGetTransferredBlock(blockNumber);

                T dataBlock = readerImplFunc(transfer, blockNumber, transferredBlock);

                //update status
                transfer.Status = TransferStatus.Running;

                //store copy of the downloaded block that doesn't contain any data
                transfer.RegisterBlock(DataBlockInfo.FromDataBlock(dataBlock));

                //audit download
                Auditor.AuditTransferOperation(context, AuditEvent.FileBlockDownloaded, transfer.TransferId, transfer.FileItem);

                return(dataBlock);
            }
        }