///*
// *  PHEX - The pure-java Gnutella-servent.
// *  Copyright (C) 2001 - 2006 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
// * 
// *  --- CVS Information ---
// *  $Id: DownloadEngine.java 3362 2006-03-30 22:27:26Z gregork $
// */
//package phex.download;
//
//import java.io.IOException;
//import java.io.InputStream;
//import java.io.OutputStreamWriter;
//import java.net.SocketException;
//import java.util.*;
//
//import org.apache.commons.collections.CollectionUtils;
//import org.apache.commons.httpclient.ChunkedInputStream;
//
//import phex.common.*;
//import phex.common.address.*;
//import phex.common.bandwidth.BandwidthController;
//import phex.common.file.ManagedFile;
//import phex.common.file.ManagedFileException;
//import phex.connection.ConnectionFailedException;
//import phex.connection.NetworkManager;
//import phex.download.swarming.*;
//import phex.host.UnusableHostException;
//import phex.http.*;
//import phex.net.connection.Connection;
//import phex.net.connection.OIOSocketFactory;
//import phex.net.repres.SocketFacade;
//import phex.net.repres.PresentationManager;
//import phex.utils.*;
//
///**
// * This class is responsible to download a file using a HTTP connection.
// * The DownloadEngine is usually managed by a SWDownloadWorker.
// */
//public class ThexDownloadEngine
//{
//    private static final int BUFFER_LENGTH = 16 * 1024;
//    private SWDownloadCandidate candidate;
//    private SWDownloadSegment segment;
//
//    /**
//     * The download file object the big parent of all the download stuff of this
//     * file.
//     */
//    private SWDownloadFile downloadFile;
//
//    private Connection connection;
//    private SocketFacade socket;
//    private InputStream inStream;
//    private boolean isKeepAliveSupported;
//    private boolean isDownloadSuccessful;
//    
//    /**
//     * Indicates if the download was stoped from externally.
//     * Usually per user request.
//     */
//    private boolean isDownloadStopped;
//    
//    private long replyContentLength;
//
//    /**
//     * Create a download engine
//     * @param aDownloadFile the file to download
//     * @param aCandidate the candidate to download the file from.
//     */
//    public ThexDownloadEngine( SWDownloadFile aDownloadFile,
//        SWDownloadCandidate aCandidate )
//    {
//        downloadFile = aDownloadFile;
//        candidate = aCandidate;
//    }
//    
//    /**
//     * Sets a already available socket connection for the download engine and
//     * prepares the DownloadEngine to use it.
//     * @param socket
//     * @throws IOException
//     */
//    public void setSocket( SocketFacade socket )
//        throws IOException
//    {
//        assert this.socket != null;
//        this.socket = socket;
//        prepareConnection();
//    }
//
//    public void exchangeHTTPHandshake( SWDownloadSegment aSegment )
//        throws IOException, UnusableHostException, HTTPMessageException
//    {
//        OutputStreamWriter writer = new OutputStreamWriter(
//            connection.getOutputStream() );
//        // reset to default input stream
//        inStream = connection.getInputStream();
//
//        String requestUri = candidate.getThexRequestUri();
//        
//        HTTPRequest request = new HTTPRequest( "GET", requestUri, true );
//        request.addHeader( new HTTPHeader( HTTPHeaderNames.HOST,
//            candidate.getHostAddress().getFullHostName() ) );
//        request.addHeader( new HTTPHeader( HTTPHeaderNames.CONNECTION,
//            "Keep-Alive" ) );
//        String httpRequestStr = request.buildHTTPRequestString();
//
//        NLogger.debug(NLoggerNames.Download_Engine,
//            "HTTP Request to: " + candidate.getHostAddress() + "\n" + httpRequestStr );
//        candidate.addToCandidateLog( "HTTP Request:\n" + httpRequestStr );
//        // write request...
//        writer.write( httpRequestStr );
//        writer.flush();
//
//        HTTPResponse response = HTTPProcessor.parseHTTPResponse( connection );
//        if ( NLogger.isDebugEnabled( NLoggerNames.Download_Engine ) )
//        {
//            NLogger.debug(NLoggerNames.Download_Engine,
//                "HTTP Response from: " + candidate.getHostAddress() + "\n" 
//                + response.buildHTTPResponseString() );
//        }
//        if ( ServiceManager.sCfg.downloadCandidateLogBufferSize > 0 )
//        {
//            candidate.addToCandidateLog( "HTTP Response:\n" 
//                + response.buildHTTPResponseString() );
//        }
//
//        replyContentLength = -1;
//        HTTPHeader header = response.getHeader( HTTPHeaderNames.CONTENT_LENGTH );
//        if ( header != null )
//        {
//            try
//            {
//                replyContentLength = header.longValue();
//            }
//            catch ( NumberFormatException exp )
//            { //unknown 
//            }
//        }
//        
//        URN downloadFileURN = downloadFile.getFileURN();
//        ArrayList contentURNHeaders = new ArrayList();
//        
//        int httpCode = response.getStatusCode();
//                
//        updateKeepAliveSupport( response );
//
//        if ( httpCode >= 200 && httpCode < 300 )
//        {// code accepted
//            // connection successfully finished
//            NLogger.debug(NLoggerNames.Download_Engine,
//                "HTTP Handshake successfull.");
//            return;
//        }
//        // check error type
//        else if ( httpCode == 503 )
//        {// 503 -> host is busy (this can also be returned when remotly queued)
//            header = response.getHeader( GnutellaHeaderNames.X_QUEUE );
//            XQueueParameters xQueueParameters = null;
//            if ( header != null )
//            {
//                xQueueParameters = XQueueParameters.parseHTTPRangeSet( header.getValue() );
//            }
//            // check for persistent connection (gtk-gnutella uses queuing with 'Connection: close')
//            if ( xQueueParameters != null && isKeepAliveSupported )
//            {
//                throw new RemotelyQueuedException( xQueueParameters );
//            }
//            else
//            {
//                header = response.getHeader( HTTPHeaderNames.RETRY_AFTER );
//                if ( header != null )
//                {
//                    int delta = HTTPRetryAfter.parseDeltaInSeconds( header );
//                    if ( delta > 0 )
//                    {
//                        throw new HostBusyException( delta );
//                    }
//                }
//                throw new HostBusyException();
//            }
//        }
//    }
//
//    public void startDownload( ) throws IOException
//    {
//        String snapshotOfSegment;
//        NLogger.debug(NLoggerNames.Download_Engine,
//            "Download Engine starts download.");
//        
//        DirectByteBuffer directByteBuffer = null;
//        LengthLimitedInputStream downloadStream = null;
//        try
//        {
//            ManagedFile destFile = downloadFile.getIncompleteDownloadFile();
//            directByteBuffer = DirectByteBufferProvider.requestBuffer(
//                DirectByteBufferProvider.BUFFER_SIZE_64K );
//            
//            segment.downloadStartNotify();
//            snapshotOfSegment = segment.toString();
//            
//            // determine the length to download, we start with the MAX
//            // which would cause a download until the stream ends.
//            long downloadLengthLeft = Long.MAX_VALUE;
//            // maybe we know the file size
//            if ( replyContentRange != null && replyContentRange.totalLength != -1 )
//            {
//                downloadLengthLeft = replyContentRange.totalLength;
//            }
//            // maybe we know a reply content length
//            if ( replyContentLength != -1 )
//            {
//                downloadLengthLeft = Math.min( replyContentLength, downloadLengthLeft );
//            }
//            // maybe the segment has a smaler length (usually not the case)
//            long segmentDataSizeLeft = segment.getTransferDataSizeLeft();
//            if ( segmentDataSizeLeft != -1 )
//            {
//                downloadLengthLeft = Math.min( segmentDataSizeLeft, downloadLengthLeft );
//            }
//            
//            downloadStream = new LengthLimitedInputStream( 
//                inStream, downloadLengthLeft );
//            long fileOffset = segment.getStart() + segment.getTransferredDataSize();
//            long lengthDownloaded = segment.getTransferredDataSize();
//            int len;
//            byte[] buffer = new byte[BUFFER_LENGTH];
//            while( (len = downloadStream.read( buffer, 0, BUFFER_LENGTH )) > 0 )
//            {
//                synchronized (segment)
//                {
//                    long tmpCheckLength = lengthDownloaded + len;
//                    if ( tmpCheckLength < segment.getTransferredDataSize() )
//                    {
//                        NLogger.error(NLoggerNames.Download_Engine, 
//                            "TransferredDataSize would be going down! " + 
//                            " ll " + downloadLengthLeft + " l " + len + " ld "
//                            + lengthDownloaded + " gtds "
//                            + segment.getTransferredDataSize()
//                            + " seg: " + segment
//                            + " originally: " + snapshotOfSegment);
//                        throw new IOException( "TransferredDataSize would be going down!" );
//                    }
//                    else if (segment.getTransferDataSize() > -1 
//                        && tmpCheckLength > segment.getTransferDataSize())
//                    {
//                        NLogger.error(NLoggerNames.Download_Engine, 
//                            "TransferredDataSize would be larger then segment! " +
//                            " ll " + downloadLengthLeft + " l " + len + " ld "
//                            + lengthDownloaded + " gtds "
//                            + segment.getTransferredDataSize()
//                            + " seg: " + segment
//                            + " originally: " + snapshotOfSegment);
//                        throw new IOException( "TransferredDataSize would be larger then segment!" );
//                    }
//                    
//                    directByteBuffer.put( buffer, 0, len );
//                    directByteBuffer.flip();
//                    destFile.write( directByteBuffer, fileOffset );
//                    fileOffset += directByteBuffer.limit();
//                    directByteBuffer.clear();
//
//                    lengthDownloaded += len;
//                    segment.setTransferredDataSize( lengthDownloaded );
//                    candidate.incTotalDownloadSize( len );
//
//                    // get transfer size since it might have changed in the meantime.
//                    segmentDataSizeLeft = segment.getTransferDataSizeLeft();
//                    if ( segmentDataSizeLeft != -1 )
//                    {
//                        downloadLengthLeft = Math.min( segmentDataSizeLeft, downloadLengthLeft );
//                        downloadStream.setLengthLimit(downloadLengthLeft);
//                    }
//                }
//            }
//            isDownloadSuccessful = true;
//            // if we successful downloaded and we still dont know the total file size,
//            // we can assume that the file was completly downloaded.
//            if ( downloadFile.getTotalDataSize() == SWDownloadConstants.UNKNOWN_FILE_SIZE )
//            {
//                // we have a file with an unknown data size. For aditional check assert
//                // certain parameters
//                assert( segment.getTotalDataSize() == -1 );
//                downloadFile.setFileSize( segment.getTransferredDataSize() );
//            }
//        }
//        catch ( FileHandlingException exp )
//        {
//            NLogger.error(NLoggerNames.Download_Engine, exp, exp);
//            IOException ioExp = new IOException( exp.getMessage() );
//            ioExp.initCause(exp);
//            throw ioExp;
//        }
//        catch ( ManagedFileException exp )
//        {
//            if ( Thread.currentThread().isInterrupted() )
//            {
//                return;
//            }
//            NLogger.error(NLoggerNames.Download_Engine, exp, exp);
//            IOException ioExp = new IOException( exp.getMessage() );
//            ioExp.initCause(exp);
//            throw ioExp;
//        }
//        finally
//        {// dont close managed file since it might be used by parallel threads.
//            if ( directByteBuffer != null )
//            {
//                directByteBuffer.release();
//            }
//            
//            boolean isAcceptingNextSegment = isAcceptingNextSegment();
//            candidate.addToCandidateLog( "Is accepting next segment: " + isAcceptingNextSegment );
//            // this is for keep alive support...
//            if ( isAcceptingNextSegment )
//            {
//                // only need to close and consume left overs if we plan to
//                // continue using this connection.
//                downloadStream.close();
//            }
//            else
//            {
//                stopDownload();
//            }
//        }
//    }
//
//    public void stopDownload()
//    {
//        NLogger.debug(NLoggerNames.Download_Engine,
//            "Closing pipe and socket and telling segment we've stopped.");
//        isDownloadStopped = true;
//        candidate.addToCandidateLog( "Stop download." );
//        IOUtil.closeQuietly(inStream);
//
//        if ( segment != null )
//        {
//            segment.downloadStopNotify();
//        }
//        // dont close managed file since it might be used by parallel threads.
//        if ( connection != null )
//        {
//            connection.disconnect();
//        }
//        IOUtil.closeQuietly(socket);
//    }
//
//    /**
//     * Indicates whether the connection is keept alive and the next http request
//     * can be send.
//     * @return true if the next http request can be send.
//     */
//    public boolean isAcceptingNextSegment()
//    {
//        return isDownloadSuccessful && isKeepAliveSupported && replyContentLength != -1;
//    }
//    
//    private void updateKeepAliveSupport( HTTPResponse response )
//    {
//        // check if Keep-Alive connection is accepted
//        HTTPHeader header = response.getHeader( HTTPHeaderNames.CONNECTION );
//        if ( header != null )
//        {
//            if ( header.getValue().equalsIgnoreCase( "close" ) )
//            {
//                isKeepAliveSupported = false;
//                return;
//            }
//            else if ( header.getValue().equalsIgnoreCase( "keep-alive" ) )
//            {
//                isKeepAliveSupported = true;
//                return;
//            }
//        }
//        // missing or unknown connection header do the HTTP method default.
//        if ( response.getHTTPVersion().equals("HTTP/1.1") )
//        {
//            isKeepAliveSupported = true;
//        }
//        else
//        {
//            isKeepAliveSupported = false;
//        }
//    }
//}
