QTFileTransfer.c

//////////
//
//  File:       QTFileTransfer.c
//
//  Contains:   Sample code for transferring a file asynchronously from a web server.
//
//  Written by: Tim Monroe
//
//  Copyright:  © 1998-1999 by Apple Computer, Inc., all rights reserved.
//
//  Change History (most recent first):
//
//     <8>      07/20/00    rtm     changed return type of completion routines to PASCAL_RTN void
//     <7>      03/21/00    rtm     made changes to run under CarbonLib
//     <6>      10/01/99    rtm     made gDataBuffer global, so we can dispose of it in the
//                                  QTFileTrans_CloseDownHandlers function; added calls to
//                                  DisposeRoutineDescriptor
//     <5>      03/03/99    rtm     switched to using DataHReadAsync; removed unnecessary NOTES
//     <4>      12/25/99    rtm     added NOTES concerning DataHReadAsync and DataHGetFileSize
//     <3>      11/30/98    rtm     modified code to use GetDataHandler instead of FindNextComponent;
//                                  moved call to QTFileTrans_CloseDownHandlers out of completion proc
//     <2>      11/16/98    rtm     got asynchronous ftp and http file transfer working
//     <1>      11/11/98    rtm     first file
//   
//  QuickTime Streaming has ftp and http data handlers, which you can use to transfer files
//  synchronously or asynchronously from a web server. This sample code illustrates how to
//  perform asynchronous transfers. In all likelihood, you'll want to transfer asynchronously,
//  since your application can continue to operate while the transfer is underway.
//  
//  The basic idea is to instantiate the URL data handler and the HFS data handler; the URL
//  data handler will be reading data from a remote ftp or http file into a buffer, and the
//  HFS data handler will be writing data from that buffer into a local file. This reading and
//  writing continues until the file is completely transferred.
//
//  To transfer a remote file to the local machine, call the QTFileTrans_CopyRemoteFileToLocalFile
//  function defined here. It does all the necessary set-up and schedules the first read request;
//  all subsequent write and read requests are scheduled by the read and write completion routines.
//  Note that, when doing asynchronous transfers, you need to give time to the data handlers by
//  calling DataHTask periodically; on the Mac, you can put code like this in your main event loop:
//
//      // if we're done, close down the data handlers
//      if (gDoneTransferring)
//          QTFileTrans_CloseDownHandlers();
//
//      // give the data handlers some time, if they are still active
//      if (gDataReader != NULL)
//          DataHTask(gDataReader);
//      
//      if (gDataWriter != NULL)
//          DataHTask(gDataWriter);
//  
//  On Windows, you could install a timer that calls this code at a specified interval. (On either
//  platform, you should probably also implement some way of making sure that the user doesn't quit
//  the application while a transfer is underway.)
//
//  NOTES:
//
//  *** (1) ***
//  For information about the main routines used here, see the chapter "Data Handler Components" in
//  the document QT3.0Reference.pdf.
//
//  *** (2) ***
//  The code for implementing synchronous transfers is actually much simpler: you don't need any
//  completion routines, and the "scheduling" is much easier. Here's an outline of all you need to do
//  to transfer a file synchronously:
//
//      DataHGetData(gDataReader, gDataBuffer, 0L, 0L, kDataBufferSize);                    
//      DataHCloseForRead(gDataReader);
//      DataHPutData(gDataWriter, gDataBuffer, 0L, NULL, kDataBufferSize);
//      DataHCloseForWrite(gDataWriter);    
//
//  In this case, however, gDataBuffer is a handle, not a pointer. (Also, we've assumed that the file
//  being transferred fits completely into the buffer; you could easily fix that assumption.)
//
//  *** (3) ***
//  You'll notice that we use our completion routines to schedule subsequent data reads and writes.
//  This is okay because data handler completion routines are never called at interrupt time.
//
//  *** (4) ***
//  In some instances, DataHGetFileSize is not able to determine the size of the file to be downloaded
//  (for example, an FTP server might not support the SIZE command). A more general strategy therefore
//  would be to download a file until you get eofErr. Implementing this strategy is left as an exercise
//  for the reader.
//
//////////
 
 
#include "QTFileTransfer.h"
 
//global variables
Ptr                             gDataBuffer = NULL;         // buffer that holds data being transferred
ComponentInstance               gDataReader = NULL;         // the data handler that reads data from the URL
ComponentInstance               gDataWriter = NULL;         // the data handler that writes data to an HFS file
DataHCompletionUPP              gReadDataHCompletionUPP = NULL;
DataHCompletionUPP              gWriteDataHCompletionUPP = NULL;
long                            gBytesToTransfer = 0L;      // the number of bytes to transfer
long                            gBytesTransferred = 0L;     // the number of bytes already transferred
Boolean                         gDoneTransferring = false;  // are we done transferring data?
 
 
//////////
//
// QTFileTrans_CopyRemoteFileToLocalFile
// Copy a remote file (located at the specified URL) into a local file.
//
//////////
 
OSErr QTFileTrans_CopyRemoteFileToLocalFile (char *theURL, FSSpecPtr theFSSpecPtr)
{
    Handle                      myReaderRef = NULL;         // data reference for the remote file
    Handle                      myWriterRef = NULL;         // data reference for the local file
    Size                        mySize = 0;
    ComponentResult             myErr = badComponentType;
 
    //////////
    //
    // create a data reference for the remote file
    //
    //////////
    
    // get the size of the URL, plus the terminating null byte
    mySize = (Size)strlen(theURL) + 1;
    if (mySize == 0)
        goto bail;
    
    // allocate a new handle
    myReaderRef = NewHandleClear(mySize);
    if (myReaderRef == NULL)
        goto bail;
 
    // copy the URL into the handle
    BlockMove(theURL, *myReaderRef, mySize);
 
    //////////
    //
    // create a data reference for the local file
    //
    //////////
    
    // delete the target local file, if it already exists;
    // if it doesn't exist yet, we'll get an error (fnfErr), which we just ignore
    FSpDelete(theFSSpecPtr);
    
    myWriterRef = NewHandleClear(sizeof(Handle));
    if (myWriterRef == NULL)
        goto bail;
 
    // create the local file
    myErr = FSpCreate(theFSSpecPtr, kTransFileCreator, kTransFileType, smSystemScript);
    if (myErr != noErr)
        goto bail;
 
    myErr = QTNewAlias(theFSSpecPtr, (AliasHandle *)&myWriterRef, true);
    if (myErr != noErr)
        goto bail;
 
    //////////
    //
    // find and open the Apple URL and HFS data handlers; connect the data references to them
    //
    //////////
    
    gDataReader = OpenComponent(GetDataHandler(myReaderRef, URLDataHandlerSubType, kDataHCanRead));
    if (gDataReader == NULL)
        goto bail;
 
    gDataWriter = OpenComponent(GetDataHandler(myWriterRef, rAliasType, kDataHCanWrite));
    if (gDataWriter == NULL)
        goto bail;
        
    // set the data reference for the URL data handler
    myErr = DataHSetDataRef(gDataReader, myReaderRef);
    if (myErr != noErr)
        goto bail;
    
    // set the data reference for the HFS data handler
    myErr = DataHSetDataRef(gDataWriter, myWriterRef);
    if (myErr != noErr)
        goto bail;
    
    //////////
    //
    // allocate a data buffer; the URL data handler copies data into this buffer,
    // and the HFS data handler copies data out of it
    //
    //////////
    
    gDataBuffer = NewPtrClear(kDataBufferSize);
    myErr = MemError();
    if (myErr != noErr)
        goto bail;
        
    //////////
    //
    // connect to the remote and local files
    //
    //////////
    
    // open a read-only path to the remote data reference
    myErr = DataHOpenForRead(gDataReader);
    if (myErr != noErr)
        goto bail;
 
    // get the size of the remote file
    myErr = DataHGetFileSize(gDataReader, &gBytesToTransfer); 
    if (myErr != noErr)
        goto bail;
    
    // open a write-only path to the local data reference
    myErr = DataHOpenForWrite(gDataWriter);
    if (myErr != noErr)
        goto bail;
        
    //////////
    //
    // start reading and writing data
    //
    //////////
    
    gDoneTransferring = false;
    gBytesTransferred = 0L;
    
    gReadDataHCompletionUPP = NewDataHCompletionUPP(QTFileTrans_ReadDataCompletionProc);
    gWriteDataHCompletionUPP = NewDataHCompletionUPP(QTFileTrans_WriteDataCompletionProc);
        
    // start retrieving the data; we do this by calling our own write completion routine,
    // pretending that we've just successfully finished writing 0 bytes of data
    QTFileTrans_WriteDataCompletionProc(gDataBuffer, 0L, noErr);
 
bail:
    // if we encountered any error, close the data handler components
    if (myErr != noErr)
        QTFileTrans_CloseDownHandlers();
    
    return((OSErr)myErr);
}
 
 
//////////
//
// QTFileTrans_ReadDataCompletionProc
// This procedure is called when the data handler has completed a read operation.
//
// The theRefCon parameter contains the number of bytes just read.
//
//////////
 
PASCAL_RTN void QTFileTrans_ReadDataCompletionProc (Ptr theRequest, long theRefCon, OSErr theErr)
{
#pragma unused(theErr)
 
    // we just finished reading some data, so schedule a write operation            
    DataHWrite( gDataWriter,
                theRequest,                     // the data buffer
                gBytesTransferred,              // write from the current offset
                theRefCon,                      // the number of bytes to write
                gWriteDataHCompletionUPP,
                theRefCon);
}
 
 
//////////
//
// QTFileTrans_WriteDataCompletionProc
// This procedure is called when the data handler has completed a write operation.
//
// The theRefCon parameter contains the number of bytes just written.
//
//////////
 
PASCAL_RTN void QTFileTrans_WriteDataCompletionProc (Ptr theRequest, long theRefCon, OSErr theErr)
{
#pragma unused(theErr)
 
    long        myNumBytesToRead;
    wide        myWide;
 
    // increment our tally of the number of bytes written so far
    gBytesTransferred += theRefCon;
 
    if (gBytesTransferred < gBytesToTransfer) {
        // there is still data to read and write, so schedule a read operation
    
        // determine how big a chunk to read
        if (gBytesToTransfer - gBytesTransferred > kDataBufferSize)
            myNumBytesToRead = kDataBufferSize;
        else
            myNumBytesToRead = gBytesToTransfer - gBytesTransferred;
 
        myWide.lo = gBytesTransferred;          // read from the current offset 
        myWide.hi = 0;
        
        // schedule a read operation
        DataHReadAsync(gDataReader,
                        theRequest,             // the data buffer
                        myNumBytesToRead,
                        &myWide,
                        gReadDataHCompletionUPP,
                        myNumBytesToRead);
 
    } else {
        // we've transferred all the data, so set a flag to tell us to close down the data handlers
        gDoneTransferring = true;
    }
    
}
 
 
//////////
//
// QTFileTrans_CloseDownHandlers
// Close our read/write access to our data references and then close down the read/write data handlers.
//
//////////
 
void QTFileTrans_CloseDownHandlers (void)
{
    if (gDataReader != NULL) {
        DataHCloseForRead(gDataReader);
        CloseComponent(gDataReader);
        gDataReader = NULL;
    }
 
    if (gDataWriter != NULL) {
        DataHCloseForWrite(gDataWriter);
        CloseComponent(gDataWriter);
        gDataWriter = NULL;
    }
    
    // dispose of the data buffer
    if (gDataBuffer != NULL)
        DisposePtr(gDataBuffer);
        
    // dispose of the routine descriptors
    if (gReadDataHCompletionUPP != NULL)
        DisposeDataHCompletionUPP(gReadDataHCompletionUPP);
        
    if (gWriteDataHCompletionUPP != NULL)
        DisposeDataHCompletionUPP(gWriteDataHCompletionUPP);
}