Milán Major

netscape-revival

1 branch
Code

lib/layout/edtsave.cpp

/* -*- Mode: C++; tab-width: 4; tabs-indent-mode: nil -*- */

//
// Editor save stuff. LWT 06/01/95
//

#ifdef EDITOR

#include "editor.h"

// Other error values are created from ALLXPSTR.RC during compiling
extern "C" int MK_UNABLE_TO_LOCATE_FILE;
extern "C" int MK_MALFORMED_URL_ERROR;

//-----------------------------------------------------------------------------
// CEditImageLoader
//-----------------------------------------------------------------------------
CEditImageLoader::CEditImageLoader( CEditBuffer *pBuffer, EDT_ImageData *pImageData, 
                XP_Bool bReplaceImage ): 
        m_pBuffer( pBuffer ),
        m_bReplaceImage( bReplaceImage ),
        m_pImageData( edt_DupImageData( pImageData ) )
        {
    char *pStr;
    if( !pBuffer->m_pContext->is_new_document ){
        intn retVal = NET_MakeRelativeURL( LO_GetBaseURL( m_pBuffer->m_pContext ), 
                    m_pImageData->pSrc, 
                    &pStr );
        
        if( retVal != NET_URL_FAIL ){
            XP_FREE( m_pImageData->pSrc );
            m_pImageData->pSrc = pStr;
        }

        retVal = NET_MakeRelativeURL( LO_GetBaseURL( m_pBuffer->m_pContext ), 
                    m_pImageData->pLowSrc, 
                    &pStr );
        
        if( retVal != NET_URL_FAIL ){
            XP_FREE( m_pImageData->pLowSrc );
            m_pImageData->pLowSrc = pStr;
        }

    }
}

CEditImageLoader::~CEditImageLoader(){
    m_pBuffer->m_pLoadingImage = 0;
    FE_ImageLoadDialogDestroy( m_pBuffer->m_pContext );
    FE_EnableClicking( m_pBuffer->m_pContext );
    edt_FreeImageData( m_pImageData );
}

void CEditImageLoader::LoadImage(){
    char *pStr;

    m_pLoImage = LO_NewImageElement( m_pBuffer->m_pContext );
    
    // convert the URL to an absolute path
    pStr = NET_MakeAbsoluteURL( LO_GetBaseURL( m_pBuffer->m_pContext ), 
                m_pImageData->pSrc );
    m_pLoImage->image_url = PA_strdup( pStr );
    XP_FREE( pStr );

    // convert the URL to an absolute path
    pStr = NET_MakeAbsoluteURL( LO_GetBaseURL( m_pBuffer->m_pContext ), 
                m_pImageData->pLowSrc );
    m_pLoImage->lowres_image_url = PA_strdup( pStr );
    XP_FREE( pStr );

    // tell layout to  pass the size info back to us.
    m_pLoImage->ele_id = ED_IMAGE_LOAD_HACK_ID;  
    FE_ImageLoadDialog( m_pBuffer->m_pContext );
    FE_GetImageInfo(m_pBuffer->m_pContext, m_pLoImage, NET_DONT_RELOAD );
}


// The image library is telling us the size of the image.  Actually do the 
//  insert now.
void CEditImageLoader::SetImageInfo(int32 ele_id, int32 width, int32 height){
    if( m_pImageData->iHeight == 0 || m_pImageData->iWidth == 0 ){
        m_pImageData->iHeight = height;
        m_pImageData->iWidth = width;    
    }
    if( m_bReplaceImage ){
        // whack this in here...
        m_pBuffer->m_pCurrent->Image()->SetImageData( m_pImageData );
        // we could be doing a better job here.  We probably repaint too much...
        CEditElement *pContainer = m_pBuffer->m_pCurrent->FindContainer();
        m_pBuffer->Relayout( pContainer, 0, pContainer->GetLastMostChild(), 
                    RELAYOUT_NOCARET );
        m_pBuffer->SelectCurrentElement();
    }
    else {
        m_pBuffer->InsertImage( m_pImageData );
    }
    delete this;
}

//-----------------------------------------------------------------------------
// CEditSaveObject
//-----------------------------------------------------------------------------

unsigned int edt_file_save_stream_write_ready( void *closure ){
    return MAX_WRITE_READY;
}

int edt_FileSaveStreamWrite (void *closure, const char *block, int32 length) {
    CFileSaveObject *pSaver = (CFileSaveObject*)closure;
    return pSaver->NetStreamWrite( block, length );
}

void edt_file_save_stream_complete( void *closure ){
}

void edt_file_save_stream_abort (void *closure, int status) {
}


void edt_UrlExit( URL_Struct *pUrl, int status, MWContext *context )
{
    ((CEditSaveObject*)(pUrl->fe_data))->NetFetchDone( pUrl, status, context );
}



static NET_StreamClass * edt_MakeFileSaveStream (int format_out, void *closure,
    URL_Struct *url, MWContext *context ) {

    NET_StreamClass *stream;
    CFileSaveObject *pSaver = (CFileSaveObject*)url->fe_data;
    // OpenOutputFile will return 0 if OK
    if( !pSaver || pSaver->OpenOutputFile() != 0  ){
        return (NULL);
    }
    
    TRACEMSG(("Setting up attachment stream. Have URL: %s\n", url->address));

    stream = XP_NEW (NET_StreamClass);
    if (stream == NULL) 
        return (NULL);

    XP_MEMSET (stream, 0, sizeof (NET_StreamClass));

    stream->name           = "editor file save";
    stream->complete       = edt_file_save_stream_complete;
    stream->abort          = edt_file_save_stream_abort;
    stream->put_block      = edt_FileSaveStreamWrite;
    stream->is_write_ready = edt_file_save_stream_write_ready;
    stream->data_object    = url->fe_data;
    stream->window_id      = context;

    return stream;
}

static XP_Bool bNetRegistered = FALSE;

//-----------------------------------------------------------------------------
// CFileSaveObject
//-----------------------------------------------------------------------------

CFileSaveObject::CFileSaveObject( MWContext *pContext ) :
        m_pContext( pContext ) ,
        m_bOverwriteAll( FALSE ),
        m_iCurFile(0),
        m_outFile(0),
        m_status( ED_ERROR_NONE ),
        m_bDontOverwriteAll( FALSE ),
        m_bDontOverwrite( FALSE ),
        m_iExtraFiles(0),
        m_pDestPathURL(0)
{
    if( !bNetRegistered ){
        NET_RegisterContentTypeConverter( "*", FO_EDT_SAVE_IMAGE,
                        NULL, edt_MakeFileSaveStream );
#ifdef XP_WIN
        RealNET_RegisterContentTypeConverter( "*", FO_EDT_SAVE_IMAGE,
                        NULL, edt_MakeFileSaveStream );
#else
NET_RegisterContentTypeConverter( "*", FO_EDT_SAVE_IMAGE,
				NULL, edt_MakeFileSaveStream );
#endif /* XP_WIN */
        bNetRegistered = TRUE;
    }

    // MUST DISABLE WINDOW INTERACTION HERE!
    // (It will always be cleared in the GetURLExitRoutine)
    // (This is normally set TRUE by front end before calling NET_GetURL)
    m_pContext->waitingMode = TRUE;
    // Set file save state
    m_pContext->edit_saving_url = TRUE;
}

CFileSaveObject::~CFileSaveObject(){
    if( m_srcImageURLs.Size() ){
        FE_SaveDialogDestroy( m_pContext, m_status, 0);
    }

    // LOU: What should I do here.  There appear to be connections on the 
    //  window that are still open....
    if( NET_AreThereActiveConnectionsForWindow( m_pContext ) ){
        //XP_ASSERT(FALSE);
        //NET_SilentInterruptWindow( m_pContext );
        XP_TRACE(("NET_AreThereActiveConnectionsForWindow is TRUE in ~CFileSaveObject"));
    }

    int i = 0;
    while( i < m_srcImageURLs.Size() ){
        XP_FREE( m_srcImageURLs[i] );
        i++;
    }

    i = 0;
    while( i < m_destImageFileBaseName.Size() ){
        if( m_destImageFileBaseName[i] ){
            XP_FREE( m_destImageFileBaseName[i] );
        }
        i++;
    }

    XP_FREE( m_pDestPathURL );

    // Allow front end interaction
    m_pContext->waitingMode = FALSE;
    // Clear flag (also done in CEditBuffer::FinishedLoad2)
    m_pContext->edit_saving_url = FALSE;
}

void CFileSaveObject::SetDestPathURL( char* pDestURL ){
    m_pDestPathURL = XP_STRDUP( pDestURL );
    char * pLastSlash = XP_STRRCHR( m_pDestPathURL, '/' );
    pLastSlash++;
    *pLastSlash = 0;
}

static ED_FileError edt_StatusResult = ED_ERROR_NONE;

ED_FileError CFileSaveObject::FileFetchComplete() {
    edt_StatusResult = m_status;
    delete this;
    return edt_StatusResult; // ED_ERROR_NONE;
}

ED_FileError CFileSaveObject::SaveFiles(){
    //  Supply count of images + 1 for parent file
    //     Next param indicates if we are downloading (FALSE)
    //     or uploading (TRUE), so this can be used for publishing
    if( m_srcImageURLs.Size() ){
        FE_SaveDialogCreate( m_pContext, m_srcImageURLs.Size()+m_iExtraFiles, FALSE );
    }
    // loop through the tree getting all the image elements...
    return FetchNextFile();
}

intn CFileSaveObject::AddFile( char *pSrcURL, char *pDestURL ){

    int i = 0;
    while( i < m_srcImageURLs.Size() ){
        if( XP_STRCMP( pSrcURL, m_srcImageURLs[i]) == 0 ){
            return i;
        }
        i++;
    }

    m_destImageFileBaseName.Add( XP_STRDUP(pDestURL) );
    return m_srcImageURLs.Add( XP_STRDUP(pSrcURL) );
}



//
// open the file and get the Netlib to fetch it from the cache.
//
ED_FileError CFileSaveObject::FetchNextFile(){
    if( m_status != ED_ERROR_NONE || m_iCurFile >= m_srcImageURLs.Size() ){
        return FileFetchComplete();
    }    
    int i = m_iCurFile++;
    // lou said we shouldn't open file here -- 
    //     should be in edt_MakeFileSaveStream
#if 0
    if( OpenOutputFile() != 0  ){
        // some kind of error opening the file... Punt for now.
        // we should probably make the url absolute or something.
        return FetchNextFile();
    }
#endif
    URL_Struct *pUrl;

    pUrl= NET_CreateURLStruct( m_srcImageURLs[i], NET_DONT_RELOAD );
    // Store file save object so we can access OpenOutputFile etc.
    pUrl->fe_data = this;
    NET_GetURL( pUrl, FO_EDT_SAVE_IMAGE, m_pContext, edt_UrlExit );
    return m_status;
}



void CFileSaveObject::NetFetchDone( URL_Struct *pUrl, int status, MWContext *pContext ){
    // close the file in any case.
    if( m_outFile ){
        XP_FileClose( m_outFile );
        m_outFile = 0;
    }
    // Default: No error, show destination name for most errors
    int   iError = ED_ERROR_NONE;
    char *pFilename = NULL;

    // NOTE: This is the NETLIB error code: (see merrors.h) 
    //   < 0 is an error
    if( status < 0 ){
        // As we become familiar with typical errors,
        //  add specialized error types here
        //  Use " extern MK_..." to access the error value
        if( status == MK_UNABLE_TO_LOCATE_FILE ||
            status == MK_MALFORMED_URL_ERROR ) {
            // Couldn't find source -- use source filename
            //   for error report
            iError = ED_ERROR_SRC_NOT_FOUND;
            if( m_iCurFile) {
                // m_iCurFile is 1-based
                pFilename = GetSrcName(m_iCurFile-1);
            }
        } else if( m_bDontOverwrite ) {
            // We get status = MK_UNABLE_TO_CONVERT 
            //  if user said NO to overwriting an existing file:
            //  edt_MakeFileSaveStream returns 0,
            //  making NET_GetURL think it failed,
            //   but its not really an error
            iError = ED_ERROR_NONE;
        } else if( m_status == ED_ERROR_CANCEL ) {
            // User selected CANCEL at overwrite warning dialog,
            //  thus stream was not created and returns error status,
            //  but its not really an error
            iError = ED_ERROR_CANCEL;
        } else {
            // "Unknown" error default
            iError = ED_ERROR_FILE_WRITE;
        }
        if( m_fileBackup.InTransaction() ){
            m_fileBackup.Rollback();
        }
    }
    else {
        if( m_fileBackup.InTransaction() ){
            m_fileBackup.Commit();
        }
    }

    if( !pFilename ){
        pFilename = GetDestName(m_iCurFile-1);
    }

    // Tell the front end that the file save is finished
    // We use number to tell between Main image (1) and Low-Res image (2)
    FE_FinishedSave( m_pContext, iError, pFilename, m_iCurFile );
    
    if( pFilename ) XP_FREE(pFilename);

    FetchNextFile();
}

intn CFileSaveObject::OpenOutputFile(){
    // Return if we already have a handle
    if( m_outFile ){
        return 0;
    }
    // Current number incremented before comming here,
    //  or think of it as 1-based counting
    int i = m_iCurFile-1;
    char *pDestFileName = 0;
    
    // compute the name of the file.
    if( !m_destImageFileBaseName[i] ){
        // create a temporary name?
        XP_TRACE(("m_destImageFileBaseName is NULL"));
        return 0;
    }
    // create the base name URL...
    char *pDestURL = PR_smprintf( "%s%s", m_pDestPathURL, m_destImageFileBaseName[i] );

    // Must have "file:" URL type and at least 1 character after "///"
    // LTNOTE: this probably only works on windows...
    if ( pDestURL == NULL || !NET_IsLocalFileURL((char*)pDestURL) || XP_STRLEN(pDestURL) <= 8 ) {
        XP_ASSERT(FALSE);
    }

    // Extract file path from URL: e.g. "/c|/foo/file.html"
    char *szPath = NET_ParseURL( pDestURL, GET_PATH_PART);
    if ( szPath == NULL ) {
        if( !FE_SaveErrorContinueDialog( m_pContext, pDestURL, ED_ERROR_FILE_OPEN ) ){
            Cancel();
        }
        XP_FREE(pDestURL);
        return -1;
    }

#if defined(XP_MAC) || defined(XP_UNIX)
    // Get filename - convert it to a local filesystem name.
    pDestFileName = XP_STRDUP(szPath);
#else
    // Get filename - skip over initial "/", convert it to a local filesystem
    //  name.
    pDestFileName = WH_FileName(szPath+1, xpURL);
#endif
    XP_TRACE(("Ready to open file %s to write URL %s", pDestFileName, pDestURL));
    XP_FREE(szPath);
    
    if( !pDestFileName ) {
        XP_TRACE(("Failed to create destination filename"));
        if( !FE_SaveErrorContinueDialog( m_pContext, pDestURL, ED_ERROR_FILE_OPEN ) ){
            Cancel();
        }
        XP_FREE(pDestURL);
        return -1;
    }
    XP_FREE(pDestURL);

    XP_Bool bWriteThis = TRUE;

    // Tell front end the filename even if we are not writing the file
    FE_SaveDialogSetFilename( m_pContext, pDestFileName );
 
    m_bDontOverwrite = m_bDontOverwriteAll;

    if( !m_bOverwriteAll){
        XP_StatStruct statinfo;
        
        if( -1 != XP_Stat(pDestFileName, &statinfo, xpURL) && statinfo.st_mode & S_IFREG ){
            // file exists.  
            if( m_bDontOverwriteAll ){
                bWriteThis = FALSE;
                // Tell front end the filename and that we didn't copy the file
                FE_FinishedSave( m_pContext, ED_ERROR_FILE_EXISTS, 
                                 m_destImageFileBaseName[i], m_iCurFile );
            }
            else {
                ED_SaveOption e;
                e = FE_SaveFileExistsDialog( m_pContext, pDestFileName );

                switch( e ){
                case ED_SAVE_OVERWRITE_THIS:
                    break;
                case ED_SAVE_OVERWRITE_ALL:
                    m_bOverwriteAll = TRUE;
                    break;
                case ED_SAVE_DONT_OVERWRITE_ALL:
                    m_bDontOverwriteAll = TRUE;
                    // intentionally fall through
                case ED_SAVE_DONT_OVERWRITE_THIS:
                    // This flag prevents us from reporting
                    //  "failed" file stream creation as an error
                    m_bDontOverwrite = TRUE;
                    bWriteThis = FALSE;
                    // Tell front end the filename and that we didn't copy the file
                    FE_FinishedSave( m_pContext, ED_ERROR_FILE_EXISTS, 
                                     m_destImageFileBaseName[i], m_iCurFile );
                    break;
                case ED_SAVE_CANCEL:
                    Cancel();
                    bWriteThis = FALSE;
                }
            }
        }
    }

    if( bWriteThis ){

#if 0
        m_fileBackup.Reset();
        ED_FileError status = m_fileBackup.BeginTransaction( pDestURL );
        if( status != ED_ERROR_NONE ){
            if( !FE_SaveErrorContinueDialog( m_pContext, pDestFileName, 
                    status ) ){
                Cancel();
            }
            return -1;
        }
#endif

#ifdef XP_MAC
        m_outFile = XP_FileOpen(pDestFileName, xpURL, XP_FILE_TRUNCATE_BIN );
#else
        m_outFile = XP_FileOpen(pDestFileName, xpTemporary, XP_FILE_TRUNCATE_BIN );
#endif
    }
    else {
        // We are returning, not doing the save, but not an error condition
        return -1;
    }

    XP_TRACE(("XP_FileOpen handle = %d", m_outFile));
    
    if( m_outFile == 0 ){
        XP_TRACE(("Failed to opened file %s", pDestFileName));

        if( !FE_SaveErrorContinueDialog( m_pContext, pDestFileName, 
                ED_ERROR_FILE_OPEN ) ){
            Cancel();
        }
        // file error...
        return -1;
    }
    else {
        FE_SaveErrorContinueDialog( m_pContext, pDestFileName, 
                ED_ERROR_NONE );
    }
    XP_FREE(pDestFileName);
    return 0;
}

int CFileSaveObject::NetStreamWrite( const char *block, int32 length ){
    XP_TRACE(("NetStreamWrite: length = %lu", length));
    return XP_FileWrite( block, length, m_outFile ) != 0;
}

void CFileSaveObject::Cancel(){
    // tell net URL to stop...
    m_status = ED_ERROR_CANCEL;
    // Above alone does seem to work! Don't we need this?
    // NET_InterruptWindow causes a "reentrant interrupt" error message now!
    NET_SilentInterruptWindow( m_pContext );
}

char *CFileSaveObject::GetDestName( intn index ){ 
    if( index >= m_destImageFileBaseName.Size() ){
        return 0;
    }
    else {
        return XP_STRDUP( m_destImageFileBaseName[index] );
    }
}

char *CFileSaveObject::GetSrcName( intn index ){ 
    if( index >= m_srcImageURLs.Size() ){
        return 0;
    }
    else {
        return XP_STRDUP( m_srcImageURLs[index] );
    }
}

//-----------------------------------------------------------------------------
// CEditSaveObject
//-----------------------------------------------------------------------------

CEditSaveObject::CEditSaveObject( CEditBuffer *pBuffer, 
                                    char *pSrcURL, 
                                    char* pDestFileURL,
                                    XP_Bool bSaveAs, 
                                    XP_Bool bKeepImagesWithDoc, 
                                    XP_Bool bAutoAdjustLinks ):
        CFileSaveObject( pBuffer->m_pContext ),
        m_pBuffer( pBuffer ),
        m_pSrcURL( XP_STRDUP( pSrcURL ) ),
        m_bSaveAs( bSaveAs ),
        m_bKeepImagesWithDoc( bKeepImagesWithDoc ),
        m_bAutoAdjustLinks( bAutoAdjustLinks ),
        m_backgroundIndex(-1),
        m_bOverwriteAll( FALSE ),
        m_bFixupImageLinks( FALSE ),
        m_bDontOverwriteAll( FALSE ),
        m_pDestFileURL( XP_STRDUP( pDestFileURL ) )
    {
    // Set a flag so we know when we are saving a file
    m_pContext->edit_saving_url = TRUE;
    // take the destination filename and whack what follows the training slash.
    //  The URL is supposed to be of the form file:///fdslkjfdslkj/fdsfd.html
    //  we don't check.
    //
    m_iExtraFiles = 1;
    SetDestPathURL( m_pDestFileURL );
}

CEditSaveObject::~CEditSaveObject(){
    // This flag must be cleared before GetFileWriteTime,
    //  else the write time will not be recorded
    m_pContext->edit_saving_url = FALSE;
    // Get the current file time
    m_pBuffer->GetFileWriteTime();

    m_pBuffer->m_pSaveObject = 0;
    XP_FREE( m_pDestFileURL );
    XP_FREE( m_pSrcURL );
}


XP_Bool CEditSaveObject::FindAllDocumentImages(){
    EDT_ImageData *pData;
    EDT_PageData *pPageData;
    XP_Bool bFound = FALSE;

    if( m_bKeepImagesWithDoc || MakeImagesAbsolute() ){
        m_bFixupImageLinks = TRUE;
        CEditElement *pImage = m_pBuffer->m_pRoot->FindNextElement( 
                                                 &CEditElement::FindImage, 0 );

        // If there is a background Image, make it the first one.
        pPageData = m_pBuffer->GetPageData();
        if( pPageData->pBackgroundImage ){
            m_backgroundIndex = AddImage( pPageData->pBackgroundImage );        
            bFound = TRUE;
        }
        else {
            m_backgroundIndex = -1;
        }
        m_pBuffer->FreePageData( pPageData );

        while( pImage ){
            // If this assert fails, it is probably because FinishedLoad
            // didn't get called on the image. And that's probably because
            // of some bug in the recursive FinishedLoad code.
            // If the size isn't known, the relayout after the save will block,
            // and the fourth .. nth images in the document will get zero size.
            // XP_ASSERT(pImage->Image()->SizeIsKnown());
            pImage->Image()->m_iSaveIndex = -1;
            pImage->Image()->m_iSaveLowIndex = -1;
            pData = pImage->Image()->GetImageData();
            if( pData->pSrc ){
                pImage->Image()->m_iSaveIndex = AddImage( pData->pSrc );
                bFound = TRUE;
            }
            if( pData->pLowSrc ){
                pImage->Image()->m_iSaveLowIndex = AddImage( pData->pLowSrc );
                bFound = TRUE;
            }
            edt_FreeImageData( pData );
            pImage = pImage->FindNextElement( &CEditElement::FindImage, 0 );
        }
    }
    return bFound;
}

//
// create a full path.  Search for it in our src Image Array.  If we find it
//  return the index of the image we found.
//
intn  CEditSaveObject::AddImage( char *pSrc ){
    intn retVal = -1;
    if( EDT_IS_LIVEWIRE_MACRO( pSrc ) ){
        // ignore this image
        return -1;  
    }

    char *pFullPath = NET_MakeAbsoluteURL( m_pSrcURL, pSrc );
    char *pLocalName;
    int i = 0;

    // this can return 0 if it couldn't generate a good name.
    if( m_bKeepImagesWithDoc ){
        pLocalName = edt_LocalName( pFullPath );
        if( pLocalName != 0 ){  
            if( !IsSameURL( pFullPath, pLocalName ) ){
                retVal = AddFile( pFullPath, pLocalName );
            }
            else {
                retVal = -2;
            }
        }
        else {
            retVal =  -2;       // couldn't make a good local name, make it abs.
        }
        XP_FREE( pFullPath );
        if( pLocalName ) XP_FREE( pLocalName );
    }
    else if( MakeImagesAbsolute() ){
        retVal = -2;
    }
    return retVal;
}

//
// INTL:
// check to see if the source URL is the same as the destPathURL+LocalName
//  probably has international issues on MAC and WIN...
//
XP_Bool CEditSaveObject::IsSameURL( char* pSrcURL, char* pLocalName ){
    char *pDestURL = PR_smprintf( "%s%s", m_pDestPathURL, pLocalName );
#if defined(XP_WIN)                                   
    XP_Bool bRetVal = (strcmpi( pSrcURL, pDestURL ) == 0);
#else
    XP_Bool bRetVal = (XP_STRCMP( pSrcURL, pDestURL ) == 0 );
#endif
    XP_FREE( pDestURL );
    return bRetVal;
}

ED_FileError CEditSaveObject::FileFetchComplete(){
    XP_ASSERT(XP_IsContextInList(m_pBuffer->m_pContext));
    
    // We're done saving all the images, now save Edit buffer to HTML file
    if( m_status == ED_ERROR_NONE ){
        FixupLinks();
        m_status = m_pBuffer->WriteBufferToFile( m_pDestFileURL );

        if( m_status == ED_ERROR_NONE ){
            m_pBuffer->m_pContext->is_new_document = FALSE;
            
            // Set this first - FE_SetDocTitle will get URL from it
            LO_SetBaseURL( m_pBuffer->m_pContext, m_pDestFileURL );
    		History_entry * hist_ent =
    		    SHIST_GetCurrent(&(m_pBuffer->m_pContext->hist));
            char * pTitle = NULL;
	    	if(hist_ent) {
                if ( hist_ent->title && XP_STRLEN(hist_ent->title) > 0 ){
                    // Note: hist_ent->title should always = m_pContext->title?
                    // Change "file:///Untitled" into URL
                    // Use the new address if old title == old address
                    if( 0 != XP_STRCMP(hist_ent->title, "file:///Untitled") && 
                        0 != XP_STRCMP(hist_ent->title, hist_ent->address) ){
                        pTitle = hist_ent->title;
                    } else {
                        pTitle = m_pDestFileURL;
                    }

                } else {
                    // Use the URL if no Document title
                    pTitle = m_pDestFileURL;
                }
                if( pTitle == m_pDestFileURL ){
                    XP_FREE(hist_ent->title);
                    hist_ent->title = XP_STRDUP(m_pDestFileURL);
                }

                // Set history entry address to new URL
	    	    if (hist_ent->address) {
                    XP_FREE(hist_ent->address);
                }
                hist_ent->address = XP_STRDUP(m_pDestFileURL);
                // This changes doc title, window caption, history title,
                ///  but not history address
                FE_SetDocTitle(m_pBuffer->m_pContext, pTitle);
            }
        }
        else {
            FE_SaveErrorContinueDialog( m_pContext, m_pDestFileURL, 
                m_status );
        }
        // Pass current status to CEditBuffer
        m_pBuffer->FileFetchComplete(m_status);
    }
    return CFileSaveObject::FileFetchComplete();
}

void CEditSaveObject::FixupLinks(){
    EDT_ImageData *pData;

    // If there is a background Image, make it the first one.

    if( m_bFixupImageLinks ){

        if( m_backgroundIndex != -1 ){
            char *pNewURL = 0;

            if( m_backgroundIndex == -2 ){
                char *pAbsURL = NET_MakeAbsoluteURL( m_pSrcURL,
                        m_pBuffer->m_pBackgroundImage );
                
                NET_MakeRelativeURL( m_pDestPathURL, 
                    pAbsURL, 
                    &pNewURL );

                XP_FREE( pAbsURL );

            }
            else {
                pNewURL = GetDestName(m_backgroundIndex);
            }

            if( m_pBuffer->m_pBackgroundImage ){
                XP_FREE( m_pBuffer->m_pBackgroundImage );
            }
            m_pBuffer->m_pBackgroundImage = pNewURL;
        }

        CEditElement *pImage = m_pBuffer->m_pRoot->FindNextElement( 
                                                    &CEditElement::FindImage, 0 );
        while( pImage ){
            pData = pImage->Image()->GetImageData();

            // do the normal image
            if( pImage->Image()->m_iSaveIndex >= 0 ){
                XP_FREE( pData->pSrc );
                pData->pSrc = GetDestName( pImage->Image()->m_iSaveIndex );
            }
            else if( pImage->Image()->m_iSaveIndex == -2 ){
                char *pNewURL;
                char *pAbs = NET_MakeAbsoluteURL( m_pSrcURL, pData->pSrc );
                NET_MakeRelativeURL( m_pDestPathURL, 
                    pAbs, 
                    &pNewURL );
                XP_FREE( pData->pSrc );
                XP_FREE( pAbs );

                pData->pSrc = pNewURL;
            }

            // do the lowres image
            if( pImage->Image()->m_iSaveLowIndex >= 0 ){
                XP_FREE( pData->pLowSrc );
                pData->pLowSrc = GetDestName( pImage->Image()->m_iSaveLowIndex );
            }
            else if( pImage->Image()->m_iSaveLowIndex == -2 ){
                char *pNewURL;
                char *pAbs = NET_MakeAbsoluteURL( m_pSrcURL, pData->pLowSrc );
                NET_MakeRelativeURL( m_pDestPathURL, 
                    pAbs, 
                    &pNewURL );
                XP_FREE( pData->pLowSrc );
                XP_FREE( pAbs );

                pData->pLowSrc = pNewURL;
            }

            
            pImage->Image()->SetImageData( pData );
            edt_FreeImageData( pData );
            pImage = pImage->FindNextElement( &CEditElement::FindImage, 0 );
        }
    }
    
    // Adjust the HREFs.
    if( m_bAutoAdjustLinks ){
        m_pBuffer->linkManager.AdjustAllLinks( m_pSrcURL, m_pDestPathURL );
    }
}

//-----------------------------------------------------------------------------
// CEditImageSaveObject
//-----------------------------------------------------------------------------

CEditImageSaveObject::CEditImageSaveObject( CEditBuffer *pBuffer, 
                EDT_ImageData *pData, XP_Bool bReplaceImage ) 
    :
        CFileSaveObject( pBuffer->m_pContext ),
        m_pBuffer( pBuffer ),
        m_pData( edt_DupImageData( pData ) ),
        m_srcIndex(-1),
        m_lowSrcIndex(-1),
        m_bReplaceImage( bReplaceImage )
{
    // we are going to write the image files into
    SetDestPathURL( LO_GetBaseURL( pBuffer->m_pContext ) );
}

CEditImageSaveObject::~CEditImageSaveObject(){
    edt_FreeImageData( m_pData );
    m_pBuffer->m_pSaveObject = 0;
}

ED_FileError CEditImageSaveObject::FileFetchComplete(){

    if( m_status == ED_ERROR_NONE ){
        char *pName = 0;
        if( m_srcIndex != -1 && (pName = GetDestName(m_srcIndex)) != 0 ){
            if( m_pData->pSrc) XP_FREE( m_pData->pSrc );
            m_pData->pSrc = pName;
        }

        if( m_lowSrcIndex != -1 && (pName = GetDestName(m_lowSrcIndex)) != 0 ){
            XP_FREE( m_pData->pLowSrc );
            m_pData->pLowSrc = pName;
        }

        m_pBuffer->m_pLoadingImage = new CEditImageLoader( m_pBuffer, 
                    m_pData, m_bReplaceImage );
        m_pBuffer->m_pLoadingImage->LoadImage();
    }
    return CFileSaveObject::FileFetchComplete();
}


//-----------------------------------------------------------------------------
// CEditBackgroundImageSaveObject
//-----------------------------------------------------------------------------

CEditBackgroundImageSaveObject::CEditBackgroundImageSaveObject( CEditBuffer *pBuffer)
    :
        CFileSaveObject( pBuffer->m_pContext ),
        m_pBuffer( pBuffer )
{
    // we are going to write the image files into
    SetDestPathURL( LO_GetBaseURL( pBuffer->m_pContext ) );
}

CEditBackgroundImageSaveObject::~CEditBackgroundImageSaveObject(){
    m_pBuffer->m_pSaveObject = 0;
}

ED_FileError CEditBackgroundImageSaveObject::FileFetchComplete(){
    // Note: If user canceled in overwrite dialog, then we don't change the background
    if( m_status == ED_ERROR_NONE ){
        if( m_pBuffer->m_pBackgroundImage ) XP_FREE(m_pBuffer->m_pBackgroundImage);

        // Get the one and only image file (without local or URL path)
        m_pBuffer->m_pBackgroundImage = GetDestName(0);

        // This will set background image
        m_pBuffer->RefreshLayout();
        // LO_SetBackgroundImage( m_pBuffer->m_pContext, m_pBuffer->m_pBackgroundImage );
    }
    return CFileSaveObject::FileFetchComplete();
}


//-----------------------------------------------------------------------------
//  Streams, Implementation
//-----------------------------------------------------------------------------

//
// LTNOTE:
// There is some uglyness on different platforms to make this work.  We'll 
//  cross that bridge when we come to it.
//

static char* stream_buffer = 0;
int IStreamOut::Printf( char * pFormat, ... ){
    va_list stack;
    int32 len;

    // if the buffer has been allocated, resize it.
    if( stream_buffer ){
        stream_buffer[0] = 0;
    }
    va_start (stack, pFormat);
    stream_buffer = PR_vsprintf_append( stream_buffer, pFormat, stack );
    va_end (stack);

    len = XP_STRLEN( stream_buffer ); 
    Write( stream_buffer, len );
    return len;
}

void IStreamOut::WriteZString(char* pString){
    if( pString ){
        int32 iLen = XP_STRLEN( pString )+1;
        WriteInt( iLen );
        Write( pString, iLen );
    }
    else {
        WriteInt(0);
    }
}

void IStreamOut::WritePartialZString(char* pString, ElementIndex start, ElementIndex end){
    int32 iLen = end - start;
    XP_ASSERT(iLen >= 0);
    if( pString && iLen > 0 ){
        WriteInt( iLen + 1 ); // Account for the '\0'
        Write( pString + start, iLen );
        Write( "", 1 ); // write the '\0'
    }
    else {
        WriteInt(0);
    }
}

char* IStreamIn::ReadZString(){
    char *pRet = 0;
    int32 iLen = ReadInt();
    if( iLen ){
        pRet = (char*)XP_ALLOC(iLen);
        Read( pRet, iLen );
    }
    return pRet;
}

//-----------------------------------------------------------------------------
// NetStream
//-----------------------------------------------------------------------------
CStreamOutNet::CStreamOutNet( MWContext* pContext )
{
    URL_Struct * URL_s;
    Chrome chrome;

    XP_BZERO( &chrome, sizeof( Chrome ) );
    chrome.allow_close = TRUE;
    chrome.allow_resize = TRUE;
    chrome.show_scrollbar = TRUE;

    //
    // LTNOTE: Ownership of the 'chrome' struct isn't documented in the interface.
    //  The windows implementation doesn't appear to keep pointers to the struct.
    //
    MWContext *pNewContext = FE_MakeNewWindow(pContext, NULL, "view-source",
                    &chrome );
    pNewContext->edit_view_source_hack = TRUE;

    URL_s = NET_CreateURLStruct("View Document Source", NET_DONT_RELOAD);  

    URL_s->content_type = XP_STRDUP(TEXT_PLAIN);

    m_pStream = NET_StreamBuilder(FO_PRESENT, URL_s, pNewContext);

    if(!m_pStream){
        XP_ASSERT( FALSE );
    }
}

CStreamOutNet::~CStreamOutNet(){
    (*m_pStream->complete)(m_pStream->data_object );
}

void CStreamOutNet::Write( char *pBuffer, int32 iCount ){

    // Buffer the output.
    // pBuffer may be a const string, even a ROM string.
    // (e.g. string constants on a Mac with VM.)
    // But networking does in-place transformations on the
    // data to convert it into other character sets.
    
    const int32 kBufferSize = 128;
    char buffer[kBufferSize];
    while ( iCount > 0 ) {
        int32 iChunkSize = iCount;
        if ( iChunkSize > kBufferSize ) {
            iChunkSize = kBufferSize;
        }
        XP_MEMCPY(buffer, pBuffer, iChunkSize);

        int status = (*m_pStream->put_block)(m_pStream->data_object, buffer, iChunkSize );
    
        if(status < 0){
            (*m_pStream->abort)(m_pStream->data_object, status);
        }
        // status??

        iCount -= iChunkSize;
    }
}

//-----------------------------------------------------------------------------
// File Stream
//-----------------------------------------------------------------------------
//CM: For better cross-platform use, call with file handle already open
CStreamOutFile::CStreamOutFile( XP_File hFile, XP_Bool bBinary ){
    m_status = EOS_NoError;
    m_outputFile = hFile;
    m_bBinary = bBinary;
}

CStreamOutFile::~CStreamOutFile(){
    XP_FileClose( m_outputFile );
}

void CStreamOutFile::Write( char *pBuffer, int32 iCount ){

    if( m_status != EOS_NoError ){
        return;
    }

    // this code doesn't work and it probably should be done at the other end
    //  it is designed to fix CR's showing up in the text.

    int iWrote;

    if( !m_bBinary ){
        int i = 0;
        int iLast = 0;
        int iWrite;
        while( i < iCount ){
            if( pBuffer[i] == '\n' ){
                iWrite = i - iLast;
                if( iWrite ){
                    iWrote = XP_FileWrite( &pBuffer[iLast], iWrite, m_outputFile );
                    if( iWrote != iWrite ){ m_status = EOS_DeviceFull; }
                }
#ifdef XP_MAC
				iWrote = XP_FileWrite("\r", 1, m_outputFile );
#else
				iWrote = XP_FileWrite("\n", 1, m_outputFile );
#endif
                if( iWrote != 1 ){ m_status = EOS_DeviceFull; }
                iLast = i+1;
            }
            i++;
        }
        iWrite = i - iLast;
        if( iWrite ){
            iWrote = XP_FileWrite( &pBuffer[iLast], iWrite, m_outputFile );
            if( iWrote != iWrite ){ m_status = EOS_DeviceFull; }
        }
        return;
    }
    else {
        iWrote = XP_FileWrite( pBuffer, iCount, m_outputFile );
        if( iWrote != iCount ){ m_status = EOS_DeviceFull; }
    }
}

//-----------------------------------------------------------------------------
// Memory Streams
//-----------------------------------------------------------------------------

#define MEMBUF_GROW 10
#define MEMBUF_START 32
//
CStreamOutMemory::CStreamOutMemory(): m_pBuffer(0), m_bufferSize(MEMBUF_START),
        m_bufferEnd(0){
    m_pBuffer = (XP_HUGE_CHAR_PTR) XP_HUGE_ALLOC( m_bufferSize );
    m_pBuffer[m_bufferEnd] = '\0';
}
  
//
// This implementation intenttionally does not destroy the buffer.  The buffer
//  is kept and destroyed by the stream user.
//   
CStreamOutMemory::~CStreamOutMemory(){
}

//
//
void CStreamOutMemory::Write( char *pSrc, int32 iCount ){
    int32 neededSize = iCount + m_bufferEnd + 1; /* Extra byte for '\0' */

    //
    // Grow the buffer if need be.
    //
    if( neededSize  > m_bufferSize ){
        int32 iNewSize = (neededSize * 5 / 4) + MEMBUF_GROW;
        XP_HUGE_CHAR_PTR pBuf = (XP_HUGE_CHAR_PTR) XP_HUGE_ALLOC(iNewSize);
        if( pBuf ){
            XP_HUGE_MEMCPY(pBuf, m_pBuffer, m_bufferSize );
            XP_HUGE_FREE(m_pBuffer);
            m_pBuffer = pBuf;
            m_bufferSize = iNewSize;
        }
        else {
            // LTNOTE: throw an out of memory exception
            XP_ASSERT(FALSE);
            return;
        }
    }

    XP_HUGE_MEMCPY( &m_pBuffer[m_bufferEnd], pSrc, iCount );
    m_bufferEnd += iCount;
    m_pBuffer[m_bufferEnd] = '\0';
}

// class CStreamInMemory

void CStreamInMemory::Read( char *pBuffer, int32 iCount ){
    XP_HUGE_MEMCPY( pBuffer, &m_pBuffer[m_iCurrentPos], iCount );
    m_iCurrentPos += iCount;
}


void CEditFileBackup::Reset(){
    if( m_pBackupName ){
        XP_FREE( m_pBackupName );
        m_pBackupName = 0;
    }
    if( m_pFileName ){
        XP_FREE( m_pFileName );
        m_pFileName = 0;
    }
    m_bInitOk = FALSE;
}

ED_FileError CEditFileBackup::BeginTransaction( char *pDestURL ){
    XP_StatStruct statinfo;

    if ( pDestURL == NULL || XP_STRLEN(pDestURL) == 0 || 
             !NET_IsLocalFileURL(pDestURL) ) {
        return ED_ERROR_BAD_URL;
    }
    
    if (XP_ConvertUrlToLocalFile(pDestURL, &m_pFileName) ) {
        // (This must succeed if above test is true, it calls XP_STAT)
        XP_Stat(m_pFileName, &statinfo, xpURL);
        if ( XP_STAT_READONLY( statinfo ) ){
            return ED_ERROR_READ_ONLY;
        }

        // File exists - rename to backup to protect data
        m_pBackupName = XP_BackupFileName(pDestURL);
        if ( m_pBackupName == NULL ) {
            return ED_ERROR_CREATE_BAKNAME;
        }
        // Delete backup file if it exists
        if ( -1 != XP_Stat(m_pBackupName, &statinfo, xpURL) &&
              statinfo.st_mode & S_IFREG ) {
            if ( 0 != XP_FileRemove(m_pBackupName, xpURL) ) {
                return ED_ERROR_DELETE_BAKFILE;
            }
        }
        if ( 0 != XP_FileRename(m_pFileName, xpURL,
                                m_pBackupName, xpURL) ){
            return ED_ERROR_FILE_RENAME_TO_BAK;
        }
    }
    m_bInitOk = TRUE;
    return ED_ERROR_NONE;
}

void CEditFileBackup::Commit() {
    XP_ASSERT( m_bInitOk );

#ifdef XP_UNIX
    XP_StatStruct statinfo;

    if (m_pBackupName != NULL && m_pFileName != NULL &&
		XP_Stat(m_pBackupName, &statinfo, xpURL) != -1) {
		
		/*
		 *    Is there an XP_chmod()? I cannot find one. This will
		 *    work for Unix, which is probably the only place that cares.
		 *    Don't bother to check the return status, as it's too late to
		 *    do anything about, and we are not in dire straights if it fails.
		 *    Bug #28775..djw
		 */
		chmod(m_pFileName, statinfo.st_mode);
	}
#endif

    XP_FileRemove(m_pBackupName, xpURL);
}

void CEditFileBackup::Rollback(){
    XP_ASSERT( m_bInitOk );
    if ( m_pBackupName ) {
        // Restore previous file
        // If this fails, we're really messed up!
        XP_FileRemove(m_pFileName, xpURL);
        XP_FileRename(m_pBackupName, xpURL, m_pFileName, xpURL);
    }
}



#endif