/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2005 Phex Development Group
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *  Created on 01.04.2006
 *  --- CVS Information ---
 *  $Id: MemoryFile.java 3859 2007-07-01 20:15:19Z gregork $
 */
package phex.download;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import phex.common.FileHandlingException;
import phex.common.file.ManagedFile;
import phex.common.file.ManagedFileException;
import phex.download.ThexVerificationData.ThexData;
import phex.download.strategy.ScopeSelectionStrategy;
import phex.download.strategy.ScopeSelectionStrategyProvider;
import phex.download.swarming.SWDownloadConstants;
import phex.download.swarming.SWDownloadFile;
import phex.download.swarming.SwarmingManager;
import phex.prefs.core.DownloadPrefs;
import phex.thex.TTHashCalcUtils;
import phex.utils.NLogger;
import phex.xml.sax.downloads.DDownloadFile;
import phex.xml.sax.downloads.DDownloadScope;

/**
 * A memory representation of the file data that is downloaded. It keeps track
 * of the download scopes and maintains a small memory cache of downloaded data
 * before it is written to disk.
 * Download scopes are hold in the following lists and live cycle:
 * 
 * 1) missingScope List
 *      gets allocated -> blockedScopeList (2)
 * 2) blockedScopeList
 *      gets released 
 *           missing parts -> missingScopeList (1)
 *           finished parts -> bufferedDataScopeList (3)
 * 3) bufferedDataScopList
 *      gets written to disk -> unverifiedScopeList (4)
 * 4) unverifiedScopeList
 *      gets verified against THEX -> finishedScopeList (5)
 *      download finished and no THEX -> finishedScopeList (5)
 * 5) finishedScopeList
 *      used to verify if download is complete...
 */
public class MemoryFile
{
    private ScopeSelectionStrategy scopeSelectionStrategy;
    
    /**
     * A list of missing download scopes.
     */
    private DownloadScopeList missingScopeList;
    
    /**
     * A list of download scopes currently blocked in downloads.
     */
    private DownloadScopeList blockedScopeList;
    
    /**
     * Contains DataDownloadScope with the downloaded data. No DownloadScopeList
     * is used since we dont like to mess with the merging of DirectByteBuffers
     * that are part of the DataDownloadScope. The bufferedDataScopeList is 
     * written to disk in regular intervals.
     */
    private List<DataDownloadScope> bufferedDataScopeList;
    
    /**
     * Counts the number of buffered bytes and ensures that buffer size
     * is limited to configured value.
     * Access needs no locking.
     */
    private BufferVolumeTracker bufferedVolume;
    
    /**
     * Additionally to bufferedDataScopeList we store an extra merged
     * DownloadScopeList list containing the buffered scopes. This can be used
     * for UI purposes. 
     */
    //private DownloadScopeList bufferedScopeList;
    
    /**
     * A list of unverified and written to disc download scopes.
     */
    private DownloadScopeList unverifiedScopeList;
    
    /**
     * A list of scopes ready to be verified.
     */
    private DownloadScopeList toBeVerifiedScopeList;
    
    /**
     * A list of finished download scopes.
     */
    private DownloadScopeList finishedScopeList;
    
    /**
     * This list contains rated download scopes representing the availability
     * of scopes from candidates.
     */
    private RatedDownloadScopeList ratedScopeList;
    
    /**
     * The last time the rated download scope list was build.
     */
    private long ratedScopeListBuildTime;
    
    /**
     * If true indicates that the buffers should be writting to disk 
     * from the DownloadDataWriter.
     */
    private boolean isBufferWritingRequested;
    
    private SWDownloadFile downloadFile;
    
    public MemoryFile( SWDownloadFile downloadFile )
    {
        this.downloadFile = downloadFile;
        isBufferWritingRequested = false;
        missingScopeList = new DownloadScopeList();
        blockedScopeList = new DownloadScopeList();
        bufferedDataScopeList = new ArrayList<DataDownloadScope>();
        //bufferedScopeList = new DownloadScopeList();
        unverifiedScopeList = new DownloadScopeList();
        toBeVerifiedScopeList = new DownloadScopeList();
        finishedScopeList = new DownloadScopeList();
        
        SwarmingManager swMgr = SwarmingManager.getInstance();
        bufferedVolume = new BufferVolumeTracker(
            swMgr.getDownloadWriteBufferTracker(),
            DownloadPrefs.MaxWriteBufferPerDownload.get().intValue() );
        
        long fileSize = downloadFile.getTotalDataSize();
        if ( fileSize == SWDownloadConstants.UNKNOWN_FILE_SIZE )
        {
            missingScopeList.add( new DownloadScope( 0, Long.MAX_VALUE ) );
        }
        else
        {
            missingScopeList.add( new DownloadScope( 0, fileSize - 1 ) );
        }
        
        scopeSelectionStrategy = ScopeSelectionStrategyProvider.getAvailBeginRandSelectionStrategy();
    }
    
    public void setScopeSelectionStrategy( ScopeSelectionStrategy strategy )
    {
        scopeSelectionStrategy = strategy;
    }
    
    public ScopeSelectionStrategy getScopeSelectionStrategy()
    {
        return scopeSelectionStrategy;
    }
    
    public void updateFileSize(  )
    {
        long fileSize = downloadFile.getTotalDataSize();
        synchronized ( missingScopeList )
        {
            missingScopeList.remove( new DownloadScope( fileSize, Long.MAX_VALUE) );
        }
        synchronized ( blockedScopeList )
        {
            blockedScopeList.remove( new DownloadScope( fileSize, Long.MAX_VALUE) );
        }
    }
    
    /**
     * For user interface... no modifications to the scope list should be done.
     * @return
     */
    public DownloadScopeList getBlockedScopeList()
    {
        return blockedScopeList;
    }
    
    /**
     * For user interface... no modifications to the scope list should be done.
     * @return
     */
    public DownloadScopeList getUnverifiedScopeList()
    {
        return unverifiedScopeList;
    }
    
    /**
     * For user interface... no modifications to the scope list should be done.
     * @return
     */
    public DownloadScopeList getToBeVerifiedScopeList()
    {
        return toBeVerifiedScopeList;
    }
    
    /**
     * For user interface... no modifications to the scope list should be done.
     * @return
     */
    public DownloadScopeList getFinishedScopeList()
    {
        return finishedScopeList;
    }
    
    public List<DownloadScope> getFinishedScopeListCopy()
    {
        synchronized ( finishedScopeList )
        {
            return finishedScopeList.getScopeListCopy();
        }
    }
    
    public List<DownloadScope> getUnverifiedScopeListCopy()
    {
        synchronized ( unverifiedScopeList )
        {
            return unverifiedScopeList.getScopeListCopy();
        }
    }
    
    /**
     * Returns the number of downloaded fragments.
     * @return the number of downloaded fragments.
     */
    public int getDownloadedFragmentCount()
    {
        return finishedScopeList.size() + unverifiedScopeList.size() + toBeVerifiedScopeList.size();
    }
    
    /**
     * The aggregated length of finished download fragments.
     * @return aggregated length of finished download fragments.
     */
    public long getFinishedLength()
    {
        synchronized( finishedScopeList )
        {
            return finishedScopeList.getAggregatedLength();
        }
    }
    
    /**
     * The aggregated length of missing download fragments.
     * @return aggregated length of missing download fragments.
     */
    public long getMissingLength()
    {
        synchronized( missingScopeList )
        {
            return missingScopeList.getAggregatedLength();
        }
    }
    
    private boolean isComplete()
    {
        synchronized( finishedScopeList )
        {
            return finishedScopeList.getAggregatedLength() == downloadFile.getTotalDataSize();
        }
    }
    
    public boolean isFileBeginningAvailable( )
    {
        if ( finishedScopeList.size() == 0 )
        {// nothing downloaded yet.
            return false;
        }
        DownloadScope scope = finishedScopeList.getScopeAt( 0 );
        if ( scope.getStart() != 0 )
        {// file beginning not available.
            return false;
        }
        return true;
    }
    
    public long getFileBeginningScopeLength()
    {
        DownloadScope scope = finishedScopeList.getScopeAt( 0 );
        if ( scope == null || scope.getStart() != 0 )
        {// file beginning not available.
            return 0;
        }
        else
        {
            return scope.getEnd();
        }
    }
    
    public RatedDownloadScopeList getRatedScopeList()
    {
        long now = System.currentTimeMillis();
        if ( ratedScopeListBuildTime + SWDownloadConstants.RATED_SCOPE_LIST_TIMEOUT > now )
        {
            return ratedScopeList;
        }
        if ( ratedScopeList == null )
        {
            ratedScopeList = new RatedDownloadScopeList();
        }
        else
        {
            ratedScopeList.clear();
        }
        ratedScopeList.addAll( missingScopeList );
        downloadFile.rateDownloadScopeList( ratedScopeList );
        ratedScopeListBuildTime = System.currentTimeMillis();
        
        return ratedScopeList;
    }
    
    /**
     * Used to check if a scope is allocateable. This check is done early
     * before a connection to a candidate is opend. The actual allocation happens
     * after the connection to the candidate is established. Though it can happen
     * that all scops are already allocated until then.
     * @param candidateScopeList the ranges that are wanted for this download if
     *        set to null all ranges are allowed.
     * @return true if there is a scope available. false otherwise.
     */
    public boolean isMissingScopeAllocateable( DownloadScopeList candidateScopeList )
    {
        synchronized( missingScopeList )
        {
            if ( missingScopeList.size() == 0 )
            {
                return false;
            }
            if (  downloadFile.getTotalDataSize() != SWDownloadConstants.UNKNOWN_FILE_SIZE 
                 && candidateScopeList != null )
            {
                DownloadScopeList wantedScopeList = 
                    (DownloadScopeList) missingScopeList.clone();
                wantedScopeList.retainAll( candidateScopeList );
                return wantedScopeList.size() > 0;
            }
            else
            {
                return true;
            }
        }        
    }
    
    /**
     * Allocates a missing scope.
     * @return a missing scope ready to download.
     */
    public DownloadScope allocateMissingScope( long preferredSize )
    {
        synchronized( missingScopeList )
        {
            if ( missingScopeList.isEmpty() )
            {
                return null;
            }
            
            DownloadScope scope = missingScopeList.getScopeAt( 0 );
            missingScopeList.remove( scope );
            synchronized( scope )
            {
                // ignore prefered size if fileSize is unknown
                if ( downloadFile.getTotalDataSize() != SWDownloadConstants.UNKNOWN_FILE_SIZE && 
                     scope.getLength() > preferredSize )
                {
                    DownloadScope beforeScope = new DownloadScope( 
                        scope.getStart(), scope.getStart() + preferredSize - 1 );
                    DownloadScope afterScope = new DownloadScope( 
                        scope.getStart() + preferredSize, scope.getEnd() );
                    missingScopeList.add( afterScope );
                    scope = beforeScope;
                }
            }
            synchronized( blockedScopeList )
            {
                blockedScopeList.add( scope );
            }
            return scope;
        }
    }
    
    public DownloadScope allocateMissingScopeForCandidate( DownloadScopeList candidateScopeList,
            long preferredSize )
    {
        synchronized( missingScopeList )
        {
            DownloadScopeList wantedScopeList = (DownloadScopeList)missingScopeList.clone();
            wantedScopeList.addAll( missingScopeList );
            wantedScopeList.retainAll( candidateScopeList );
            if ( wantedScopeList.size() == 0 )
            {
                return null;
            }
            
            DownloadScope scope = scopeSelectionStrategy.selectDownloadScope( 
                downloadFile, wantedScopeList, preferredSize );
            if ( scope == null )
            {
                return null;
            }
            missingScopeList.remove( scope );
            synchronized( blockedScopeList )
            {
                blockedScopeList.add( scope );
            }
            return scope;
        }
    }
    
    /**
     * Releases the missing part of the download scope. The downloaded part
     * is handled through the buffered data writer.
     * @param downloadScope
     * @param downloadSegment
     */
    public void releaseAllocatedScope( DownloadScope downloadScope, 
        long transferredSize )
    {
        if ( downloadScope.getEnd() == Long.MAX_VALUE 
          && downloadFile.getTotalDataSize() != SWDownloadConstants.UNKNOWN_FILE_SIZE )
        {
            downloadScope = new DownloadScope( downloadScope.getStart(),
                downloadFile.getTotalDataSize() - 1 );
        }
        
        DownloadScope missingScope = null;

        if ( transferredSize == 0 )
        {// just give back the scope to missing.
            missingScope = downloadScope;
        }
        else if ( transferredSize < downloadScope.getLength() )
        {
            missingScope = new DownloadScope(
                downloadScope.getStart() + transferredSize,
                downloadScope.getEnd() );
        }
        synchronized( blockedScopeList )
        {
            blockedScopeList.remove( downloadScope );
        }
        synchronized ( missingScopeList )
        {            
            if ( missingScope != null )
            {
                missingScopeList.add( missingScope );
            }
        }
    }
    
    public long getDownloadedLength()
    {
        long length = 0;
        synchronized( unverifiedScopeList )
        {
            length += unverifiedScopeList.getAggregatedLength();
        }
        synchronized( toBeVerifiedScopeList )
        {
            length += toBeVerifiedScopeList.getAggregatedLength();
        }
        return length + getFinishedLength() + getBufferedDataLength();
    }
    
    public int getBufferedDataLength()
    {
        return bufferedVolume.getUsedBufferSize();
    }
    
    public void bufferDataScope( DataDownloadScope dataScope )
    {
        synchronized( bufferedDataScopeList )
        {
            bufferedDataScopeList.add( dataScope );
        }
        assert dataScope.getLength() < Integer.MAX_VALUE;
        bufferedVolume.addBufferedSize( (int)dataScope.getLength() );
    }
    
    public boolean isBufferWritingRequested()
    {
        return isBufferWritingRequested;
    }
    
    /**
     * Requests that the buffers of this MemoryFile are written to disk from the 
     * DownloadDataWriter thread.
     */
    public void requestBufferWriting()
    {
        NLogger.debug( MemoryFile.class, "MemoryFile requesting buffer write." );
        isBufferWritingRequested = true;
    }
    
    /**
     * This method should only be called by the single DownloadDataWriter thread,
     * to ensure integrity. This method is not able to handle multiple thread access
     * to its data scopes.
     */
    public void writeBuffersToDisk()
    {
        if ( bufferedDataScopeList.isEmpty() )
        {
            return;
        }
        NLogger.debug( MemoryFile.class, "MemoryFile write buffers to disk." );
        isBufferWritingRequested = false;
        try
        {
            ManagedFile destFile = downloadFile.getIncompleteDownloadFile();
            List<DataDownloadScope> list;
            synchronized( bufferedDataScopeList )
            {
                list = new ArrayList<DataDownloadScope>( bufferedDataScopeList );
            }

            for( DataDownloadScope dataScope : list )
            {
                destFile.write( dataScope.getDataBuffer(), dataScope.getStart() );
                synchronized( bufferedDataScopeList )
                {
                    bufferedDataScopeList.remove( dataScope );
                }
                assert dataScope.getLength() < Integer.MAX_VALUE;
                bufferedVolume.reduceBufferedSize( (int)dataScope.getLength() );
                
                // release scope buffer. After releasing we can use the
                // DataDownloadScope like a simple DownloadScope.
                dataScope.releaseDataBuffer();
                
                synchronized ( unverifiedScopeList )
                {
                    unverifiedScopeList.add( dataScope );
                }
//                synchronized( bufferedScopeList )
//                {
//                    bufferedScopeList.remove( dataScope );
//                }
            }
            
            findScopesToVerify();
            
            if ( !downloadFile.isFileCompletedOrMoved() && isComplete() )
            {
                downloadFile.setStatus( SWDownloadConstants.STATUS_FILE_COMPLETED );
                downloadFile.moveToDestinationFile();
            }
        }
        catch ( FileHandlingException exp )
        {
            // this exp stops the download in download file...
            NLogger.error( MemoryFile.class, exp, exp );
        }
        catch ( ManagedFileException exp )
        {
            NLogger.error( MemoryFile.class, exp, exp );
        }
    }
    
    private void findScopesToVerify()
    {
        synchronized ( unverifiedScopeList )
        {
            List<DownloadScope> verifyableScopes = new ArrayList<DownloadScope>();
            ThexData thexData = downloadFile.getThexVerificationData().getThexData();
            if ( thexData == null )
            {
                if ( unverifiedScopeList.getAggregatedLength() == downloadFile.getTotalDataSize() )
                {// the download has completed without having verification data available...
                 // move all scopes to finished...
                    List<DownloadScope> scopes = unverifiedScopeList.getScopeListCopy();
                    synchronized ( finishedScopeList )
                    {
                        for ( DownloadScope scope : scopes )
                        {
                            unverifiedScopeList.remove( scope );
                            finishedScopeList.add( scope );
                        }
                    }
                    if ( !downloadFile.isFileCompletedOrMoved() && isComplete() )
                    {
                        downloadFile.setStatus( SWDownloadConstants.STATUS_FILE_COMPLETED );
                        downloadFile.moveToDestinationFile();
                    }
                }
                return;
            }
            long totalFileSize = downloadFile.getTotalDataSize();
            int nodeSize = thexData.getNodeSize();
            
            long lastNodeStart = totalFileSize - ( totalFileSize % nodeSize );
            if ( lastNodeStart == totalFileSize )
            {
                lastNodeStart -= nodeSize;
            }

            for ( DownloadScope scope : unverifiedScopeList )
            {
                boolean isLastScope = scope.getEnd()+1 == totalFileSize;
                
                if ( !isLastScope && scope.getLength() < nodeSize  )
                {
                    continue;
                }

                // calc start offset of thex node
                long nodeStart;
                if ( scope.getStart() % nodeSize == 0 )
                {
                    nodeStart = scope.getStart();
                }
                else if ( isLastScope )
                {// special handling for start of last node since it might not be a full nodeSize large.
                    nodeStart = totalFileSize - ( totalFileSize % nodeSize );
                    if ( nodeStart == totalFileSize )
                    {
                        nodeStart -= nodeSize;
                    }
                }
                else
                {
                    nodeStart = scope.getStart() + nodeSize - scope.getStart() % nodeSize; 
                }
                long nodeEnd;
                if ( (scope.getEnd()+1) % nodeSize == 0 || isLastScope )
                {
                    nodeEnd = scope.getEnd();
                }
                else
                {
                    nodeEnd = scope.getEnd() - 1 - (scope.getEnd() % nodeSize);
                }
                if ( nodeEnd - nodeStart + 1 >= nodeSize && (nodeEnd - nodeStart+1)%nodeSize == 0 )
                { 
                    long rangeStart = nodeStart;
                    long rangeEnd;
                    do
                    {
                        rangeEnd = rangeStart + nodeSize - 1;
                        verifyableScopes.add( new DownloadScope( rangeStart, rangeEnd ) );
                        rangeStart = rangeEnd + 1;
                    }
                    while( rangeStart < nodeEnd );
                }
                else if ( isLastScope && scope.getStart() <= nodeStart )
                {
                    verifyableScopes.add( new DownloadScope( nodeStart, nodeEnd ) );
                }
            }
            if ( verifyableScopes.size() > 0 )
            {
                SwarmingManager swMgr = SwarmingManager.getInstance();
                synchronized( toBeVerifiedScopeList )
                {
                    for ( DownloadScope scope : verifyableScopes )
                    {
                        unverifiedScopeList.remove( scope );
                        toBeVerifiedScopeList.add( scope );
                        swMgr.getDownloadVerifyRunner().add( 
                            new DownloadVerificationWorker( scope ) );
                    }
                }
            }
        }
    }
    
    private void verifyScope( DownloadScope scope )
    {
        try
        {
            ManagedFile destFile = downloadFile.getIncompleteDownloadFile();
            boolean succ = TTHashCalcUtils.verifyTigerTreeHash( downloadFile.getThexVerificationData().getThexData(), 
                destFile, scope.getStart(), scope.getLength() );
            if ( succ )
            {
                synchronized( toBeVerifiedScopeList )
                {
                    toBeVerifiedScopeList.remove( scope );
                }
                synchronized( finishedScopeList )
                {
                    finishedScopeList.add( scope );
                }
            }
            else
            {
                synchronized( toBeVerifiedScopeList )
                {
                    toBeVerifiedScopeList.remove( scope );
                }
                synchronized( missingScopeList )
                {
                    missingScopeList.add( scope );
                }
            }
        }
        catch ( FileHandlingException exp )
        {
            // this exp should stop the download in download file...
            NLogger.error( MemoryFile.class, exp, exp );
        }
        catch ( ManagedFileException exp )
        {
            NLogger.error( MemoryFile.class, exp, exp );
        }
        
        if ( !downloadFile.isFileCompletedOrMoved() && isComplete() )
        {
            downloadFile.setStatus( SWDownloadConstants.STATUS_FILE_COMPLETED );
            downloadFile.moveToDestinationFile();
        }
    }
    
    ////////////////////////// XJB stuff ///////////////////////////////////////
    
    public void createDownloadScopes( DDownloadFile dFile )
    {
        //clean scops
        missingScopeList.clear();
        blockedScopeList.clear();
        unverifiedScopeList.clear();
        toBeVerifiedScopeList.clear();
        finishedScopeList.clear();
        
        setScopeSelectionStrategy( ScopeSelectionStrategyProvider.getByClassName( 
            dFile.getScopeSelectionStrategy() ) );
        
        Iterator<DDownloadScope> iterator = dFile.getUnverifiedScopesList().getSubElementList().iterator();
        while( iterator.hasNext() )
        {
            DDownloadScope dScope = iterator.next();
            DownloadScope downloadScope = new DownloadScope( dScope.getStart(),
                dScope.getEnd() );
            unverifiedScopeList.add( downloadScope );
        }
        
        iterator = dFile.getFinishedScopesList().getSubElementList().iterator();
        while( iterator.hasNext() )
        {
            DDownloadScope dScope = iterator.next();
            DownloadScope downloadScope = new DownloadScope( dScope.getStart(),
                dScope.getEnd() );
            finishedScopeList.add( downloadScope );
        }
        
        // build missing scope list.
        long fileSize = downloadFile.getTotalDataSize();
        if ( fileSize == SWDownloadConstants.UNKNOWN_FILE_SIZE )
        {
            missingScopeList.add( new DownloadScope( 0, Long.MAX_VALUE ) );
        }
        else
        {
            missingScopeList.add( new DownloadScope( 0, fileSize - 1 ) );
        }
        // remove finished scopes...
        missingScopeList.removeAll( unverifiedScopeList );
        missingScopeList.removeAll( finishedScopeList );
    }
    
    public void createXJBFinishedScopes( DDownloadFile dFile )
    {
        dFile.setScopeSelectionStrategy( scopeSelectionStrategy.getClass().getName() );
        
        List<DDownloadScope> list = dFile.getUnverifiedScopesList().getSubElementList();
        
        // copy scope list to prevent ConcurrentModificationException during iteration.
        List<DownloadScope> unvScopeListCopy = getUnverifiedScopeListCopy();
        for( DownloadScope scope : unvScopeListCopy )
        {
            DDownloadScope dScope = new DDownloadScope( 
                DDownloadScope.UNVERIFIED_SCOPE_ELEMENT_NAME );
            dScope.setStart( scope.getStart() );
            dScope.setEnd( scope.getEnd() );
            list.add( dScope );
        }
        
        list = dFile.getFinishedScopesList().getSubElementList();
        
        // copy finished scope list to prevent ConcurrentModificationException during iteration.
        List<DownloadScope> finScopeListCopy = getFinishedScopeListCopy();
        for( DownloadScope scope : finScopeListCopy )
        {
            DDownloadScope dScope = new DDownloadScope( 
                DDownloadScope.FINISHED_SCOPE_ELEMENT_NAME );
            dScope.setStart( scope.getStart() );
            dScope.setEnd( scope.getEnd() );
            list.add( dScope );
        }
    }
    
    public class DownloadVerificationWorker implements Runnable
    {
        private DownloadScope scope;
        
        public DownloadVerificationWorker( DownloadScope scope )
        {
            this.scope = scope;
        }
        
        public void run()
        {
            try
            {
                verifyScope( scope );
            }
            catch ( Throwable th )
            {
                // this is a very bad error situation...
                // it could break the download scope consistency!
                NLogger.error( MemoryFile.class, "Download scope consistency in danger!" );
                NLogger.error( MemoryFile.class, th, th );
            }
        }
    }
}