/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */


//
// Editor save stuff. LWT 06/01/95
//
// Added all ITapeFileSystem stuff, hardts 10/96

#ifdef EDITOR

#include "editor.h"
#include "streams.h"
#include "shist.h"
#include "xpgetstr.h"
#include "net.h"
#include "mime.h"
#include "prefapi.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_pImageData( edt_DupImageData( pImageData ) ),
        m_bReplaceImage( bReplaceImage )
        {
    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;
    XP_ObserverList image_obs_list; // List of image observers.

    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 );

    image_obs_list = lo_NewImageObserverList(m_pBuffer->m_pContext,
                                             m_pLoImage);
    if (!image_obs_list) {
        XP_ASSERT(FALSE);        // Out of memory.
        return;
    }
    lo_GetImage(m_pBuffer->m_pContext, m_pBuffer->m_pContext->img_cx,
                m_pLoImage, image_obs_list, NET_DONT_RELOAD);
}


// The image library is telling us the size of the image.  Actually do the 
//  insert now.
void CEditImageLoader::SetImageInfo(int32 width, int32 height){
    if( m_pImageData->iHeight == 0 || m_pImageData->iWidth == 0 ){
        m_pImageData->iHeight = height;
        m_pImageData->iWidth = width;    
        m_pImageData->iOriginalHeight = height;
        m_pImageData->iOriginalWidth = 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
//-----------------------------------------------------------------------------
#if defined(XP_OS2)
extern "C"
#else
PRIVATE
#endif
unsigned int edt_file_save_stream_write_ready( NET_StreamClass * /* stream */ ){
    return MAX_WRITE_READY;
}

#if defined(XP_OS2)
extern "C"
#else
PRIVATE
#endif
int edt_FileSaveStreamWrite (NET_StreamClass *stream, const char *block, int32 length) {
    CFileSaveObject *pSaver = (CFileSaveObject*)stream->data_object;	
    return pSaver->NetStreamWrite( block, length );
}

#if defined(XP_OS2)
extern "C"
#else
PRIVATE
#endif
void edt_file_save_stream_complete( NET_StreamClass * /* stream */ ){	
}

#if defined(XP_OS2)
extern "C"
#else
PRIVATE
#endif
void edt_file_save_stream_abort (NET_StreamClass * /* stream */, int /* status */ ) {	
}


#if defined(XP_OS2)
extern "C"
#else
PRIVATE
#endif
void edt_UrlExit( URL_Struct *pURL, int status, MWContext * /* context */ )
{
    // hardts, changed from CEditSaveObject to CFileSaveObject
    ((CFileSaveObject*)(pURL->fe_data))->NetFetchDone(status);

    NET_FreeURLStruct(pURL);
}

#if defined(XP_OS2)
extern "C"
#else
PRIVATE
#endif
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);
    }
 
 	// ### mwelch Relay the information stored in this URL. We do it here instead of
 	//            in the url exit routine because edt_MakeFileSaveStream could
 	//            have been invoked by an inferior URL which represents a parsed
 	//            piece of a mail or news message. If this URL is what was originally
 	//            invoked, the correct mime type should still be present here.	
	if (url)
 		pSaver->CopyURLInfo(url);
   
    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;

#ifdef MOZ_MAIL_NEWS
extern "C" 
{
	NET_StreamClass *MIME_MessageConverter (int format_out, void *closure,
                                               URL_Struct *url,
                                               MWContext *context);
}
#endif

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

CFileSaveObject::CFileSaveObject( MWContext *pContext, 
                                  ITapeFileSystem *tapeFS, XP_Bool bAutoSave,
                                  CEditSaveToTempData *pSaveToTempData) : 
        m_pContext( pContext ) ,
        m_bOverwriteAll( FALSE ),
        m_bDontOverwriteAll( FALSE ),
        m_bDontOverwrite( FALSE ),
        m_iCurFile(0),
        m_pOutStream(0),
        m_bOpenOutputHandledError(FALSE),
        m_tapeFS(tapeFS),
        m_status( ED_ERROR_NONE ),
        m_bFromAutoSave(bAutoSave),
        m_pSaveToTempData(pSaveToTempData)
{
    if( !bNetRegistered ){
#ifdef MOZ_MAIL_NEWS
    	// ### mwelch Need to pass mail/news messages through
    	//     MIME_MessageConverter since we're likely to be
    	//     extracting a part of the message.
    	NET_RegisterContentTypeConverter( MESSAGE_RFC822, FO_EDT_SAVE_IMAGE,
    										NULL, MIME_MessageConverter);
    	NET_RegisterContentTypeConverter( MESSAGE_NEWS, FO_EDT_SAVE_IMAGE,
    										NULL, MIME_MessageConverter);
#endif

    	// For everything else, just write to the tapefs.
        NET_RegisterContentTypeConverter( "*", FO_EDT_SAVE_IMAGE,
                        NULL, edt_MakeFileSaveStream );
//// Why is this duplicated? hardts
#if defined(XP_WIN) || defined(XP_OS2)
        NET_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 a flag so we know when we are saving a file
    // (also set in CEditBuffer::SaveFile() - may not be needed here, but its safe to set again)
    m_pContext->edit_saving_url = TRUE;

    // set base URL for the tape file system.
    char *pBaseURL = edt_GetDocRelativeBaseURL(m_pContext);
    if (pBaseURL) {
      m_tapeFS->SetSourceBaseURL(pBaseURL);
      XP_FREE(pBaseURL);
    }
}

CFileSaveObject::~CFileSaveObject(){
    // Success message for publishing.  Moved from WinFE to back end.
    if (m_tapeFS->GetType() == ITapeFileSystem::Publish && 
        m_status == ED_ERROR_NONE) {
      char *msg = NULL;
      // Different message for 1 or multiple files.
      if (m_tapeFS->GetNumFiles() > 1) {
        char *tmplate = XP_GetString(XP_EDT_PUBLISH_REPORT_MSG);
        if (tmplate) {
          msg = PR_smprintf(tmplate,m_tapeFS->GetNumFiles());
        }
      }
      else {
        msg = XP_STRDUP(XP_GetString(XP_EDT_PUBLISH_REPORT_ONE_FILE));
      }
      if (msg) {
        char * pBrowse = NULL, *pPub = NULL, *pLastPub = NULL;
        PREF_CopyCharPref("editor.publish_browse_location",&pBrowse);
        PREF_CopyCharPref("editor.publish_location",&pPub);
        PREF_CopyCharPref("editor.publish_last_loc",&pLastPub);

#if defined(XP_WIN) || defined(XP_OS2)
        /* FE_LoadUrl needs to be implemented in X */
    
        if( EDT_IsSameURL(pPub, pLastPub,0,0) && pBrowse && *pBrowse ){
          // We just saved to the default home location
          StrAllocCat(msg, XP_GetString(XP_EDT_BROWSE_TO_DEFAULT));
          if( FE_Confirm(m_pContext,msg) ){
            // User said Yes - load location into a BROWSER window (FALSE param)
            FE_LoadUrl(pBrowse, FALSE );
          }
        } else {
          FE_Alert(m_pContext,msg);
        }
        XP_FREEIF(pBrowse);
        XP_FREEIF(pPub);
#else
		// mac and unix
		FE_Message(m_pContext,msg);
#endif
        XP_FREE(msg);
      }
    }

    // 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"));
    }

    ////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;
    
#ifdef XP_WIN    
    // Tell front end we finished autosave
    //   so it can gray the Save toolbar button
    if( m_bFromAutoSave )
        FE_UpdateEnableStates(m_pContext);
#endif

    // Tape File system should not be deleted yet.
    if (m_tapeFS && 
        m_tapeFS->GetType() == ITapeFileSystem::Publish &&
        m_status == ED_ERROR_NONE) {
      // Call publishComplete event.
      char *pDestURL = m_tapeFS->GetDestAbsURL();
      EDT_PerformEvent(m_pContext,"publishComplete",pDestURL,TRUE,FALSE,0,0);
      XP_FREEIF(pDestURL);
    }

    ///// This code will have to change if we implement a "saveComplete" event for
    ///// composer plugins.
    // Call callback for saving file to temporary location.
    if (m_tapeFS &&
        m_tapeFS->GetType() == ITapeFileSystem::File &&
        m_pSaveToTempData && m_pSaveToTempData->doneFn) {
      if (m_status == ED_ERROR_NONE) {
        (*m_pSaveToTempData->doneFn)(m_pSaveToTempData->pFileURL,m_pSaveToTempData->hook);
      }
      else {
        // Failed, pass in NULL as the temporary file name.
        (*m_pSaveToTempData->doneFn)(NULL,m_pSaveToTempData->hook);
      }
    }
    delete m_pSaveToTempData;
}

// Callback from tape file system.
//
//** Don't make this function static because it is a friend of CFileSaveObject
//** so it must be extern.  (at least on MAC)
void edt_CFileSaveObjectDone(XP_Bool success,void *arg) {
    CFileSaveObject *saveObject = (CFileSaveObject *)arg;
    if (!saveObject) {
        XP_ASSERT(0);
        return;
    }        
    
    if (!success) {
        saveObject->m_status = ED_ERROR_TAPEFS_COMPLETION;
    }
    // Tape File system should not be deleted yet.
    delete saveObject;
}

void
CFileSaveObject::CopyURLInfo(const URL_Struct *pURL)
{
	XP_ASSERT((m_tapeFS) && (m_iCurFile <= m_tapeFS->GetNumFiles()));
	
	if ((m_tapeFS) && (m_iCurFile <= m_tapeFS->GetNumFiles()))
		m_tapeFS->CopyURLInfo(m_iCurFile-1, pURL);
}

ED_FileError CFileSaveObject::FileFetchComplete() {
    XP_ASSERT(m_tapeFS->GetNumFiles());

    if( !m_bFromAutoSave && // Don't want dialogs from autosave, bug 43878
        m_tapeFS->GetType() != ITapeFileSystem::MailSend ){  // Suppress dialog for mail send.
        FE_SaveDialogDestroy( m_pContext, m_status, 0);
    }
    if (!m_tapeFS) {
        XP_ASSERT(0);
        return ED_ERROR_BLOCKED;  // is this the right thing to return?
    }

    ED_FileError statusResult = m_status;

    m_tapeFS->Complete( m_status == ED_ERROR_NONE, 
                                        edt_CFileSaveObjectDone, (void *)this );

    // "this" may already be deleted at this point, so we can't just return m_status.
    return statusResult;
}

ED_FileError CFileSaveObject::SaveFiles(){
    XP_ASSERT(m_tapeFS && m_tapeFS->GetNumFiles());

    //     Next param indicates if we are downloading (FALSE)
    //     or uploading (TRUE), so this can be used for publishing
    if( !m_bFromAutoSave && // Don't want dialogs from autosave, bug 43878
        m_tapeFS->GetType() != ITapeFileSystem::MailSend ){ // Suppress dialog for mail send.
      ED_SaveDialogType saveType;
      switch (m_tapeFS->GetType()) {
        case ITapeFileSystem::File:
          saveType = ED_SAVE_DLG_SAVE_LOCAL;
          break;
        case ITapeFileSystem::Publish:
          saveType = ED_SAVE_DLG_PREPARE_PUBLISH;
          break;
        default:
          XP_ASSERT(0);
      }
      FE_SaveDialogCreate( m_pContext, m_tapeFS->GetNumFiles(), saveType );
    }

    if (SaveFirstFile()) {
        // Child of CFileSaveObject took care of the first file.
        m_iCurFile++;
    }

    // loop through the tree getting all the image elements...
    return FetchNextFile();
}

// Just a wrapper around the Tape File System.
intn CFileSaveObject::AddFile( char *pSrcURL, char *pMIMEType, int16 iDocCharSetID){
    XP_ASSERT(m_tapeFS);
    return m_tapeFS->AddFile(pSrcURL,pMIMEType,iDocCharSetID);
}

//
// open the file and get the Netlib to fetch it from the cache.
//
ED_FileError CFileSaveObject::FetchNextFile(){
    XP_ASSERT(m_tapeFS);    

    if( m_status != ED_ERROR_NONE || m_iCurFile >= m_tapeFS->GetNumFiles() ){
        return FileFetchComplete();
    }    
    // m_iCurFile is 1-based, so i is 0-based.
    int i = m_iCurFile++;
    
    char *srcURL = m_tapeFS->GetSourceURL(i);
    if (!srcURL) {
        XP_ASSERT(0);
        return ED_ERROR_FILE_READ;
    }
    URL_Struct *pUrl;
    pUrl= NET_CreateURLStruct( srcURL, NET_DONT_RELOAD );
    if (!pUrl) {
        XP_ASSERT(0);
        XP_FREE(srcURL);
        return ED_ERROR_FILE_READ;    
    }    
    XP_FREE(srcURL);    
    // Store file save object so we can access OpenOutputFile etc.
    pUrl->fe_data = this;
   /* EXTREME HACK ALERT!
       Special signal for Composer (Editor)
       This will be used in IL_ViewStream to tell we are an editor
       and use IL_NewStream instead (as we did in 4.06 code) 
       IL_NewStream triggers tag parsing on images, which is bad! */
    pUrl->owner_id = 0x000000ED;
    //TODO: Should we be setting the referer field as well?
    NET_GetURL( pUrl, FO_EDT_SAVE_IMAGE, m_pContext, edt_UrlExit );
    return m_status;
}

void CFileSaveObject::CheckFinishedSave(intn oneBased,ED_FileError iError) // one-based index.
{
  if (m_tapeFS->IsLocalPersistentFile(oneBased - 1)) {
    char *pAbsURL = GetDestAbsoluteURL(oneBased - 1);
    if (pAbsURL) {
      // Tell the front end that a new local file has been created.
      // We use number to tell between Main image (1) and Low-Res image (2)
      FE_FinishedSave( m_pContext, iError, pAbsURL, oneBased );
      XP_FREE(pAbsURL);
    }
  }
}

void CFileSaveObject::NetFetchDone( int status ){
    // close the file in any case.
    if( m_pOutStream ){
        m_tapeFS->CloseStream(m_iCurFile-1);
        m_pOutStream = 0;
    }
    // Default: No error, show destination name for most errors
    ED_FileError iError = ED_ERROR_NONE;

    // 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;
        } 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_READ;
        }
    }

    // ED_ERROR_FILE_OPEN was already handled in CFileSaveObject::OpenOutputFile()
    if (!m_bOpenOutputHandledError) {
      // No longer uses FE_SaveErrorContinueDialog()
      if (!SaveErrorContinueDialog(iError) ) {
        Cancel();
      }
      CheckFinishedSave(m_iCurFile,iError);
    }

    FetchNextFile();
}

intn CFileSaveObject::OpenOutputFile(){
    m_bOpenOutputHandledError = FALSE;

    // Return if we already have an open stream
    if( m_pOutStream != NULL){
        return 0;
    }
    // Current number incremented before comming here,
    //  or think of it as 1-based counting
    int i = m_iCurFile-1;
    
    XP_Bool bWriteThis = TRUE;
    char *pHumanName = m_tapeFS->GetHumanName(i);

    // Tell front end the filename even if we are not writing the file
    //
    if (!m_bDontOverwriteAll && 
        !m_bFromAutoSave && // Don't want dialogs from autosave, bug 43878
        m_tapeFS->GetType() != ITapeFileSystem::MailSend) { // Suppress dialog for mail send.
        FE_SaveDialogSetFilename( m_pContext, pHumanName );
    } 

    m_bDontOverwrite = m_bDontOverwriteAll;

    if( !m_bOverwriteAll){
        if( m_tapeFS->FileExists(i) ){
            // file exists.  
            if( m_bDontOverwriteAll ){
                bWriteThis = FALSE;
                CheckFinishedSave(m_iCurFile,ED_ERROR_FILE_EXISTS);
            }
            else {
                ED_SaveOption e;
                e = FE_SaveFileExistsDialog( m_pContext, pHumanName);

                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;

                    CheckFinishedSave(m_iCurFile,ED_ERROR_FILE_EXISTS);
                    break;
                case ED_SAVE_CANCEL:
                    Cancel();
                    bWriteThis = FALSE;
                }
            }
        }
    }

    if( bWriteThis ){
       m_pOutStream = m_tapeFS->OpenStream( i );
    }
    else {
        // We are returning, not doing the save, but not an error condition
        XP_FREE(pHumanName);
        return -1;
    }

    XP_TRACE(("Opening stream number %d gives %d.", i, (int)m_pOutStream));
    
    if ( m_pOutStream == NULL ) {
        // No longer uses FE_SaveErrorContinueDialog()
        if(!SaveErrorContinueDialog( ED_ERROR_FILE_OPEN ) ){
            Cancel();
        }
        XP_FREE(pHumanName);

        CheckFinishedSave(m_iCurFile,ED_ERROR_FILE_OPEN);

        // So we don't report error twice, once on opening file and once
        // when netlib is unable to write to it.
        m_bOpenOutputHandledError = TRUE;

        return -1;
    }
    XP_FREE(pHumanName);
    return 0; // success
}

int CFileSaveObject::NetStreamWrite( const char *block, int32 length ){
    XP_TRACE(("NetStreamWrite: length = %lu", length));
    XP_ASSERT(m_pOutStream);
    m_pOutStream->Write((char *)block,length);
    return m_pOutStream->Status() == IStreamOut::EOS_NoError;
}

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 (!m_tapeFS) {
        XP_ASSERT(0);
        return NULL;
    }    
    return m_tapeFS->GetDestURL(index);
}

char *CFileSaveObject::GetDestAbsoluteURL( intn index ){ 
    if (!m_tapeFS) {
        XP_ASSERT(0);
        return NULL;
    }    
    char *pDestPathURL = m_tapeFS->GetDestPathURL();
    char *pDestRelativeURL = m_tapeFS->GetDestURL(index);
    if (!pDestRelativeURL) {
      XP_ASSERT(0);
      XP_FREEIF(pDestPathURL);
      return NULL;
    }

    // Maybe should use NET_MakeAbsolute() instead of concatenation.
    char *pDestAbsURL = pDestPathURL ? 
        PR_smprintf( "%s%s", pDestPathURL,  pDestRelativeURL ) :
        XP_STRDUP(pDestRelativeURL);
    XP_FREEIF(pDestPathURL);
    XP_FREE(pDestRelativeURL);

    return pDestAbsURL;
}

char *CFileSaveObject::GetSrcName( intn index ){ 
    if (!m_tapeFS) {
        XP_ASSERT(0);
        return NULL;
    }    
    return m_tapeFS->GetSourceURL(index);

    /*if( index >= m_srcImageURLs.Size() ){
        return 0;
    }
    else {
        return XP_STRDUP( m_srcImageURLs[index] );
    } */
}

// Convert file:// URLs into local file names for display to user.
PRIVATE void edt_url_make_human(char **pURL) {
  if (NET_IsLocalFileURL(*pURL)) {
    char *pTemp;
    XP_ConvertUrlToLocalFile(*pURL,&pTemp);
    XP_FREEIF(*pURL);
    *pURL = pTemp;
  }
}

XP_Bool CFileSaveObject::SaveErrorContinueDialog(ED_FileError iError) {
  // No user interaction for these conditions.
  if (iError == ED_ERROR_NONE ||
      iError == ED_ERROR_CANCEL ||
      iError == ED_ERROR_FILE_EXISTS) {
    return TRUE;
  }

  char *tmplate = NULL;
  char *msg = NULL;
  // m_iCurFile is 1-based.
  char *pAbsDest = GetDestAbsoluteURL(m_iCurFile-1);
  char *pRelDest = m_tapeFS->GetHumanName(m_iCurFile-1);
  char *pAbsSrc = GetSrcName(m_iCurFile-1);
  char *pRootAbsDest = GetDestAbsoluteURL(0);

  edt_url_make_human(&pAbsDest);
  edt_url_make_human(&pAbsSrc);
  edt_url_make_human(&pRootAbsDest);

  // Two level switch, first on different types of file systems,
  // second on different classes of errors.
  switch (m_tapeFS->GetType()) {
    case ITapeFileSystem::File:
      switch (iError) {
        case ED_ERROR_FILE_OPEN:
        case ED_ERROR_FILE_WRITE:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_SAVE_FILE_WRITE));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_SAVE_CONTINUE));
          msg = PR_smprintf(tmplate,pRelDest,pRootAbsDest,pRelDest);
          break;
        case ED_ERROR_SRC_NOT_FOUND:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_SAVE_SRC_NOT_FOUND));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_SAVE_CONTINUE));
          msg = PR_smprintf(tmplate,pAbsSrc,pAbsSrc);
          break;
        case ED_ERROR_FILE_READ:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_SAVE_FILE_READ));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_SAVE_CONTINUE));
          msg = PR_smprintf(tmplate,pRelDest,pRelDest);
          break;
      }
      break;

    case ITapeFileSystem::Publish:
      switch (iError) {
        case ED_ERROR_FILE_OPEN:
        case ED_ERROR_FILE_WRITE:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_PUBLISH_FILE_WRITE));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_PUBLISH_CONTINUE));
          msg = PR_smprintf(tmplate,pRelDest,pRelDest);
          break;
        case ED_ERROR_SRC_NOT_FOUND:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_PUBLISH_SRC_NOT_FOUND));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_PUBLISH_CONTINUE));
          msg = PR_smprintf(tmplate,pAbsSrc,pAbsSrc);
          break;
        case ED_ERROR_FILE_READ:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_PUBLISH_FILE_READ));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_PUBLISH_CONTINUE));
          msg = PR_smprintf(tmplate,pRelDest,pRelDest);
          break;
      }
      break;

    case ITapeFileSystem::MailSend:
      switch (iError) {
        case ED_ERROR_FILE_OPEN:
        case ED_ERROR_FILE_WRITE:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_MAIL_FILE_WRITE));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_MAIL_CONTINUE));
          msg = PR_smprintf(tmplate,pRelDest,pRelDest);
          break;
        case ED_ERROR_SRC_NOT_FOUND:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_MAIL_SRC_NOT_FOUND));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_MAIL_CONTINUE));
          msg = PR_smprintf(tmplate,pAbsSrc,pAbsSrc);
          break;
        case ED_ERROR_FILE_READ:
          tmplate = XP_STRDUP(XP_GetString(XP_EDT_ERR_MAIL_FILE_READ));
          StrAllocCat(tmplate,XP_GetString(XP_EDT_ERR_MAIL_CONTINUE));
          msg = PR_smprintf(tmplate,pRelDest,pRelDest);
          break;
      }
      break;
  }

  XP_Bool retVal = FALSE;
  if (msg) {
    retVal = FE_Confirm(m_pContext,msg);
  }
  else {
    XP_ASSERT(0);
  }

  XP_FREEIF(tmplate);
  XP_FREEIF(msg);
  XP_FREEIF(pAbsDest);
  XP_FREEIF(pRelDest);
  XP_FREEIF(pAbsSrc);
  XP_FREEIF(pRootAbsDest);

  return retVal;
}


char * CEditSaveObject::m_pFailedPublishUrl = NULL;

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

CEditSaveObject::CEditSaveObject( CEditBuffer *pBuffer, 
                                    ED_SaveFinishedOption finishedOpt,
                                    char *pSrcURL, 
                                    ITapeFileSystem *tapeFS,
                                    XP_Bool, // bSaveAs is ignored. 
                                    XP_Bool bKeepImagesWithDoc, 
                                    XP_Bool bAutoAdjustLinks,
                                    XP_Bool bAutoSave,
                                    CEditSaveToTempData *pSaveToTempData ) :
        CFileSaveObject( pBuffer->m_pContext,tapeFS,bAutoSave, pSaveToTempData ),
        m_pBuffer( pBuffer ),
        m_pDocStateSave( 0 ),
        m_pSrcURL( XP_STRDUP( pSrcURL ) ),
        m_finishedOpt( finishedOpt ),
        m_bKeepImagesWithDoc( bKeepImagesWithDoc ),
        m_bAutoAdjustLinks( bAutoAdjustLinks ),
        m_backgroundIndex(IgnoreImage) {
}

CEditSaveObject::~CEditSaveObject(){
    XP_Bool bRevertBuffer = FALSE;
    XP_Bool bRelayout = FALSE;

    /* 
     * possible values for m_finishedOpt:
     * ED_FINISHED_GOTO_NEW,       Point the editor to the location of the 
     *                             newly saved document if successful, otherwise revert buffer.
     * ED_FINISHED_REVERT_BUFFER,  Revert the buffer to the state before
     *                             the save operation began, regardless.
     * ED_FINISHED_SAVE_DRAFT      Like ED_FINISHED_REVERT_BUFFER, except clears the dirty flag
     *                             on success.
     * ED_FINISHED_MAIL_SEND       If we succeed we're going to throw the buffer
     *                             away, so don't revert it, but don't bother gotoNew.  
     *                             If failure, revert the buffer.
     *                             Used for mail compose, we don't 
     *                             want the editor to start any operation that 
     *                             causes problems when libmsg destroys the editor
     *                             context. 
     */


    if ( m_finishedOpt == ED_FINISHED_GOTO_NEW && m_status == ED_ERROR_NONE )
    {
        //// Go to new location.

        m_pBuffer->m_pContext->is_new_document = FALSE;

        // Set the document title and history entry to the new location.

        char *pDestFileURL = GetDestAbsoluteURL(0);
        if ( !pDestFileURL )
        {        
            XP_ASSERT( 0 );
            m_status = ED_ERROR_FILE_WRITE; // Not really the right thing to set.
            return;        
        }


        // We used to do the ftp://username@path here, but it's moved to earlier in
        // the save process, in EDT_PublishFile.

        // Set this first - FE_SetDocTitle will get URL from it
        LO_SetBaseURL( m_pBuffer->m_pContext, 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, XP_GetString(XP_EDIT_NEW_DOC_NAME) ) && 
                    0 != XP_STRCMP(hist_ent->title, hist_ent->address) )
                {
                    pTitle = XP_STRDUP(hist_ent->title);
                } else {
                    pTitle = XP_STRDUP(pDestFileURL);
                    XP_FREE(hist_ent->title);
                    hist_ent->title = XP_STRDUP(pDestFileURL);
                }

            } else {
                // Use the URL if no Document title
                pTitle = XP_STRDUP(pDestFileURL);
                XP_FREEIF(hist_ent->title);
                hist_ent->title = XP_STRDUP(pDestFileURL);
            }
            // Test if new URL is same as current before we replace the latter
            XP_Bool bSameURL = EDT_IsSameURL( hist_ent->address, pDestFileURL, NULL, NULL);
            
            // Set history entry address to new URL
            XP_FREEIF(hist_ent->address);
            hist_ent->address = XP_STRDUP(pDestFileURL);

            // We may inherit this URL (used for faster reloading in browser)
            //  when saving a remote URL to local disk,
            //  so always clear it since Editor never uses it.
            XP_FREEIF(hist_ent->wysiwyg_url);

            // This changes doc title, window caption, history title,
            ///  but not history address
            if (pTitle)
            {
              FE_SetDocTitle(m_pBuffer->m_pContext, pTitle);
              XP_FREE(pTitle);
            }  
            
            if( !bSameURL )
            {
                // Make the new URL (now in current history entry) 
                //   as the most-recently-used URL in prefs. list
                //   (Note: We should never be here in mail message composer)
                EDT_SyncEditHistory(m_pBuffer->m_pContext);
            }

        } // if (hist_ent)
        XP_FREE(pDestFileURL);

        // Saved successfully.
        m_pBuffer->DocumentStored();

        // 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();
        // Some of the links may have changed, so relayout
        bRelayout = TRUE;
    } 
    else if ( m_finishedOpt == ED_FINISHED_MAIL_SEND && m_status == ED_ERROR_NONE )
    {
      //// Do nothing.
      // Don't relayout, we're throwing the buffer away.
    }
    else if (m_pDocStateSave) {
      //// Don't go to the new document, revert to m_pDocStateSave.
      bRevertBuffer = TRUE;        
      // Don't relayout, reverting the buffer will take care of it.
    }

    if ( m_finishedOpt == ED_FINISHED_SAVE_DRAFT && m_status == ED_ERROR_NONE )
    {
        // Note that we are still reverting the buffer.
        
        // Saved successfully.
        m_pBuffer->DocumentStored();
    }


    // Originally, this was only called if m_status == ED_ERROR_NONE.
    m_pBuffer->FileFetchComplete(m_status);
    if (bRelayout)
    {
      // Don't always relayout because we may have "cid:" links which cause an error.
      // reverting the buffer will take care of it.
      m_pBuffer->RefreshLayout();
    }
    
    
    // Already cleared above if ED_FINISHED_GOTO_NEW and success,
    // but it must be cleared eventually in all cases.
    m_pContext->edit_saving_url = FALSE;

    m_pBuffer->m_pSaveObject = 0;

    if (bRevertBuffer) {
        // Must be after CEditBuffer::FileFetchComplete(), because it deletes CEditBuffer.
        m_pBuffer->RestoreState(m_pDocStateSave);
    }
    
    if( m_tapeFS->GetType() == ITapeFileSystem::Publish ){
        XP_FREEIF(m_pFailedPublishUrl);
    
        if( m_status == ED_ERROR_NONE ){
            // Save the last-used location into the history list
            EDT_SyncPublishingHistory();
        } else {
            // Save this URL so we can supply the "bad" location
            //   to user if they try to publish that URL again
            m_pFailedPublishUrl = XP_STRDUP(m_pSrcURL);
        }
    }
    XP_FREE( m_pSrcURL );
    
    delete m_pDocStateSave;
}

// Add all images in document that are also in ppIncludedFiles.
//
// If ppIncludedFiles is NULL, this should add exactly those files that would be returned by
// CEditBuffer::GetAllDocumentFiles() with the selected flag set TRUE.
// i.e. Regular Saving should save exactly those files that are published by default in remote publishing.
XP_Bool CEditSaveObject::AddAllFiles(char **ppIncludedFiles){
//////// WARNING: if you change this function, fix CEditBuffer::GetAllDocumentFiles and CEditSaveObject::FixupLinks() also.
    EDT_ImageData *pData;
    EDT_PageData *pPageData;

    // Make sure all URLs in ppIncludedFiles are absolute
    if (ppIncludedFiles) {
      char **ppFile = ppIncludedFiles;
      while (*ppFile) {
        char *pAbs = NET_MakeAbsoluteURL(m_pSrcURL,*ppFile);
        if (pAbs) {
          // If making *ppFile absolute changed it, replace it with absolute URL.
          if (XP_STRCMP(pAbs,*ppFile)) {
            XP_FREE(*ppFile);
            *ppFile = pAbs;
          }
          else {
            XP_FREE(pAbs);
          }
        }
        
        ppFile++;
      }
    }

    // Add actual HTML document.    
    intn firstIndex = AddFile(m_pSrcURL,TEXT_HTML,m_pBuffer->GetDocCharSetID());
    if (firstIndex != 0) {
        m_status = ED_ERROR_BAD_URL;
        FreeList(ppIncludedFiles);
        return FALSE;
    }


    CEditElement *pImage = m_pBuffer->m_pRoot->FindNextElement( 
                                             &CEditElement::FindImage, 0 );

    // If there is a background Image, make it the first image.
    pPageData = m_pBuffer->GetPageData();
    if( pPageData ) {
        if ( pPageData->pBackgroundImage && *pPageData->pBackgroundImage){
            m_backgroundIndex = CheckAddFile( pPageData->pBackgroundImage, NULL, ppIncludedFiles, 
                m_bKeepImagesWithDoc && !pPageData->bBackgroundNoSave );
        }
        // We now support > 1 FontDefURL per page
        int iFontDefCount = m_pBuffer->m_FontDefURL.Size();
        for( int i = 0; i < iFontDefCount; i++ )
        {
            intn iIndex = CheckAddFile( m_pBuffer->m_FontDefURL[i], NULL, ppIncludedFiles, 
                                        m_bKeepImagesWithDoc && !m_pBuffer->m_FontDefNoSave[i] );
            m_FontDefIndex.Add(iIndex);
        }
#if 0
        if ( pPageData->pFontDefURL && *pPageData->pFontDefURL ){
            m_fontDefIndex = CheckAddFile( pPageData->pFontDefURL, NULL, ppIncludedFiles, 
                m_bKeepImagesWithDoc && !pPageData->bFontDefNoSave );
        }
#endif
    }
    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 = IgnoreImage;
        pImage->Image()->m_iSaveLowIndex = IgnoreImage;
        pData = pImage->Image()->GetImageData();
        if( pData )
        {
            // Only consider images that are not server-generated
            if( EDT_IsImageURL(pData->pSrc) )
            {
                pImage->Image()->m_iSaveIndex = CheckAddFile( pData->pSrc, NULL, ppIncludedFiles, 
                    m_bKeepImagesWithDoc && !pData->bNoSave );
            }
            if( EDT_IsImageURL(pData->pLowSrc) )
            {
                pImage->Image()->m_iSaveLowIndex = CheckAddFile( pData->pLowSrc, NULL,ppIncludedFiles, 
                    m_bKeepImagesWithDoc && !pData->bNoSave );
            }
            edt_FreeImageData( pData );
        }
        pImage = pImage->FindNextElement( &CEditElement::FindImage, 0 );
    }


    //// Sure would be nice to abstract all the different types of table
    //// backgrounds.
    // table backgrounds <table>
    CEditElement *pNext = m_pBuffer->m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTable, 0 )) ){
      EDT_TableData *pData = ((CEditTableElement *)pNext)->GetData();
      if (pData) {
        if ( pData->pBackgroundImage && *pData->pBackgroundImage) {
          ((CEditTableElement *)pNext)->m_iBackgroundSaveIndex = CheckAddFile( pData->pBackgroundImage, NULL, ppIncludedFiles, 
              m_bKeepImagesWithDoc && !pData->bBackgroundNoSave);
        }
        CEditTableElement::FreeData(pData);
      }
    }
    // table row backgrounds <tr>
    pNext = m_pBuffer->m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTableRow, 0 )) ){
      EDT_TableRowData *pData = ((CEditTableRowElement *)pNext)->GetData();
      if (pData) {
        if ( pData->pBackgroundImage && *pData->pBackgroundImage) {
          ((CEditTableRowElement *)pNext)->m_iBackgroundSaveIndex = CheckAddFile( pData->pBackgroundImage, NULL, ppIncludedFiles, 
              m_bKeepImagesWithDoc && !pData->bBackgroundNoSave);
        }
        CEditTableRowElement::FreeData(pData);
      }
    }
    // table cell backgrounds <td> <th>
    pNext = m_pBuffer->m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTableCell, 0 )) ){
      EDT_TableCellData *pData = ((CEditTableCellElement *)pNext)->GetData(0);
      if (pData) {
        if ( pData->pBackgroundImage && *pData->pBackgroundImage) {
          ((CEditTableCellElement *)pNext)->m_iBackgroundSaveIndex = CheckAddFile( pData->pBackgroundImage, NULL, ppIncludedFiles, 
              m_bKeepImagesWithDoc && !pData->bBackgroundNoSave);
        }
        CEditTableCellElement::FreeData(pData);
      }
    }


    // UnknownHTML tags with LOCALDATA attribute.
    pNext = m_pBuffer->m_pRoot;
    if ( pNext ) {
        while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindUnknownHTML, 0 )) ){
          CEditIconElement *pIcon = CEditIconElement::Cast(pNext);
          if (pIcon) {
            char **pMimeTypes;
            char **pURLs;
            int count = pIcon->ParseLocalData(&pMimeTypes,&pURLs);
            
            // Remember indices into save object.
            if (count) {
              XP_FREEIF(pIcon->m_piSaveIndices);
              // allocate memory.            
              pIcon->m_piSaveIndices = new intn[count];
            }

            // Maybe should make a check that pURLs[n] is a relative URL
            // in current directory.
            for (int n = 0; n < count; n++) {
              pIcon->m_piSaveIndices[n] = CheckAddFile(pURLs[n],pMimeTypes[n],ppIncludedFiles,TRUE);
            }
            CEditIconElement::FreeLocalDataLists(pMimeTypes,pURLs,count);
          }
        } // while
    }


    // Go through ppIncludedFiles once more to add anything we missed.
    //
    // This is for the "add all files in directory" button in publishing,
    // here ppIncludedFiles may contain files that CEditSaveObject knows
    // nothing about.
    //
    // Doesn't hurt to add files twice to tape file system, it'll just 
    // return the previous index.
    if (ppIncludedFiles) {
      char **pFile = ppIncludedFiles;
      while (*pFile) {
        // Ignore returned index, we're not going to update any links here.
        AddFile(*pFile,NULL,m_pBuffer->GetDocCharSetID());
        pFile++;
      }
    }


    FreeList(ppIncludedFiles);
    return TRUE;
}

// Returns the URL that should be used as the base for relative URLs in 
// the document.  Similar to just calling LO_GetBaseURL(), except returns
// the document temp directory for untitled documents.
// Must free returned memory.
char *edt_GetDocRelativeBaseURL(MWContext *pContext) {
  char *pDocURL = NULL;

  // Interpret URLs relative to temp directory if a new document.
  if (EDT_IS_NEW_DOCUMENT(pContext)) {
    char *pxpURL = EDT_GetDocTempDir(pContext);
    if (!pxpURL) {
      return pDocURL;
    }
    // prepend "file://" to make a URL.
    StrAllocCat(pDocURL,"file://");
    StrAllocCat(pDocURL,pxpURL);
    XP_FREE(pxpURL);
  }
  else {
    pDocURL = edt_StrDup(LO_GetBaseURL(pContext));
  }
  return pDocURL;
}


// Mostly a wrapper for CFileSaveObject::AddFile, except deals with adjusting links.
//
// Analogous to AddToBufferUnique() in edtbuf.cpp
intn  CEditSaveObject::CheckAddFile( char *pSrc, char *pMIMEType, 
                      char **ppIncludedFiles, XP_Bool bSaveDefault ){
    intn retVal = IgnoreImage;
    if( EDT_IS_LIVEWIRE_MACRO( pSrc ) ){
        return IgnoreImage;  
    }

    char *pDocURL = edt_GetDocRelativeBaseURL(m_pContext);
    if (!pDocURL) {
      return IgnoreImage;
    }

    char *pAbsoluteSrc = NET_MakeAbsoluteURL(pDocURL,pSrc);
    XP_FREE(pDocURL);
    if (!pAbsoluteSrc) {
      return IgnoreImage;
    }

    // If ppIncludedFiles is passed in, use it to decide whether to add file.
    // Otherwise, use bSaveDefault to decide.
    XP_Bool saveMe;
    if (ppIncludedFiles) {
      saveMe = URLInList(ppIncludedFiles,pAbsoluteSrc);
    }
    else {
      saveMe = bSaveDefault;
    }
    
    if (saveMe) {
      retVal = AddFile(pSrc,pMIMEType,m_pBuffer->GetDocCharSetID());        
      if (retVal == ITapeFileSystem::Error || retVal == ITapeFileSystem::SourceDestSame) {
          // couldn't make a good local name, so try to fix it.
          retVal = AttemptAdjust;
      }
    }
    else if ( m_bAutoAdjustLinks ) { 
        retVal = AttemptAdjust;
    }

    XP_FREE(pAbsoluteSrc);
    return retVal;
}

void CEditSaveObject::FreeList(char **ppList) {
  if (!ppList) 
    return;

  int n = 0;
  while (ppList[n]) {
    XP_FREE(ppList[n]);
    n++;
  }
  
  XP_FREE(ppList);  
}

// ppList and pURL must be absolute URLs.
XP_Bool CEditSaveObject::URLInList(char **ppList, char *pURL ) {
  for (int  n = 0; ppList[n]; n++) {
    if (EDT_IsSameURL(ppList[n],pURL,NULL,NULL))
      return TRUE;
  }
  return FALSE;
}

// Should be called no matter what, even if error or cancel occurs.
ED_FileError CEditSaveObject::FileFetchComplete(){
    XP_ASSERT(XP_IsContextInList(m_pBuffer->m_pContext));

    return CFileSaveObject::FileFetchComplete();
}

// Always return TRUE.
XP_Bool CEditSaveObject::SaveFirstFile() {
    if ( m_status != ED_ERROR_NONE ) {
        XP_ASSERT(0);
        return TRUE;    
    }

    XP_ASSERT(!m_pDocStateSave);
    m_pDocStateSave = m_pBuffer->RecordState();

    char *pBadLinks = FixupLinks();
    PRBool needEncrypt = EDT_EncryptState( m_pContext );

    // Tell user some links may be broken by this operation, give choice
    // of continuing or not.
    if (pBadLinks) {
      XP_Bool bCancel = FALSE;
      char *tmplate = XP_GetString(XP_EDT_BREAKING_LINKS);
      char *msg = NULL;
      if (tmplate) {
        msg = PR_smprintf(tmplate,pBadLinks);
        bCancel = !FE_Confirm(m_pContext,msg);
      }

      XP_FREEIF(msg);
      XP_FREEIF(pBadLinks);
      if (bCancel) {
          Cancel();
          return TRUE;
      }
    }

    // Write root HTML document
    char *pHumanName = m_tapeFS->GetHumanName(0);
    if (pHumanName == NULL) {
        XP_ASSERT(0);
        return TRUE;
    }    
    
    // Tell front end the filename.
    if( !m_bFromAutoSave && // Don't want dialogs from autosave, bug 43878
        m_tapeFS->GetType() != ITapeFileSystem::MailSend ){// Suppress dialog for mail send.
      FE_SaveDialogSetFilename( m_pContext, pHumanName );
    } 

    IStreamOut *out = NULL;
    URL_Struct *URL_s = NULL;

    if (needEncrypt) { 
	    // by the time the file gets to the tapeFS, it will be binary.
	    m_tapeFS->SetFirstBinary();
	    out = new CNetStreamToTapeFS(m_pContext,m_tapeFS);
    }
    else {
        out = m_tapeFS->OpenStream(0);
    }
    
    if (out == NULL) {
        m_status = ED_ERROR_FILE_OPEN;
    }
    else {
      m_pBuffer->WriteToStream( out );
      if ( out->Status() != IStreamOut::EOS_NoError ) {
          m_status = ED_ERROR_FILE_WRITE;
      }
      if (!needEncrypt) {
          m_tapeFS->CloseStream(0);    
      }
      else {
         delete out;
      }
    }
    
    if( m_status != ED_ERROR_NONE ){
      char *tmplate = NULL;
      char *msg = NULL;
      char *pAbsDest = GetDestAbsoluteURL(0);
      char *pAbsSrc = GetSrcName(0);
      edt_url_make_human(&pAbsDest);
      edt_url_make_human(&pAbsSrc);

      switch (m_tapeFS->GetType()) {
        case ITapeFileSystem::File:
          tmplate = XP_GetString(XP_EDT_ERR_SAVE_WRITING_ROOT);
          if (tmplate) {
            char *pRelDest = m_tapeFS->GetHumanName(0);
            msg = PR_smprintf(tmplate,pRelDest,pAbsDest);
            XP_FREEIF(pRelDest);
          }
          break;
        case ITapeFileSystem::Publish:
          tmplate = XP_GetString(XP_EDT_ERR_PUBLISH_PREPARING_ROOT);
          if (tmplate) {
            msg = PR_smprintf(tmplate,pAbsSrc);
            StrAllocCat(msg,XP_GetString(XP_EDT_ERR_CHECK_DISK));
          }
          break;
        case ITapeFileSystem::MailSend:
          msg = XP_STRDUP(XP_GetString(XP_EDT_ERR_MAIL_PREPARING_ROOT));
          break;
      }
      if (msg) {
        FE_Alert(m_pContext,msg);
      }

      XP_FREEIF(msg);
      XP_FREEIF(pAbsDest);
      XP_FREEIF(pAbsSrc);
    }

    // We know this is file number 1,  CFileSaveObject::m_iCurFile is 1-based.
    CheckFinishedSave(1,m_status);

    XP_FREE(pHumanName);
    return TRUE;
}

void CEditSaveObject::FixupLink(intn iIndex, // index to tape file system
                                char **ppImageURL,   // Value to fixup.
                                char *pDestPathURL,
                                ED_HREFList *badLinks) {
  if( iIndex == IgnoreImage ){
    return;
  }

  if( iIndex == AttemptAdjust ){
    CEditLinkManager::AdjustLink(ppImageURL,m_pSrcURL,pDestPathURL,badLinks);
  }
  else {
    // Look it up from tape file system.
    char *pNewURL = GetDestName(iIndex);
    if (pNewURL) {
      XP_FREEIF(*ppImageURL);
      *ppImageURL = pNewURL;
    }
  }          
}

char *CEditSaveObject::FixupLinks(){
//////// WARNING: if you change this function, fix CEditSaveObject::AddAllFiles() and CEditBuffer::GetAllDocumentFiles() also.
    ED_HREFList badLinks;

    EDT_ImageData *pData;

    char *pDestPathURL = m_tapeFS->GetDestPathURL();
    // Note: pDestPathURL may legally be NULL.

    // If there is a background Image, make it the first one.
    FixupLink(m_backgroundIndex,&m_pBuffer->m_pBackgroundImage,pDestPathURL,&badLinks);

    // We now support > 1 FontDefURLs
    int iFontDefCount = m_pBuffer->m_FontDefURL.Size();
    for( int i=0; i < iFontDefCount; i++ )
        FixupLink(m_FontDefIndex[i], &m_pBuffer->m_FontDefURL[i], pDestPathURL, &badLinks);

    // regular images.
    CEditElement *pImage = m_pBuffer->m_pRoot->FindNextElement( &CEditElement::FindImage, 0 );
    while( pImage )
    {
        pData = pImage->Image()->GetImageData();
        if( pData )
        {        
            // Only consider images that are not server-generated
            if( EDT_IsImageURL(pData->pSrc) )
            {
                // do the normal image
                FixupLink(pImage->Image()->m_iSaveIndex,&pData->pSrc,pDestPathURL,&badLinks);
            }            
            if( EDT_IsImageURL(pData->pLowSrc) )
            {
                // do the lowres image
                FixupLink(pImage->Image()->m_iSaveLowIndex,&pData->pLowSrc,pDestPathURL,&badLinks);
            }
            pImage->Image()->SetImageData( pData );
            edt_FreeImageData( pData );
        }
        pImage = pImage->FindNextElement( &CEditElement::FindImage, 0 );
    }


    //// Sure would be nice to abstract all the different types of table
    //// backgrounds.
    // table backgrounds <table>
    CEditElement *pNext = m_pBuffer->m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTable, 0 )) ){
      CEditTableElement *pTable = (CEditTableElement *)pNext;
      EDT_TableData *pData = pTable->GetData();
      if (pData) {
        if ( pData->pBackgroundImage && *pData->pBackgroundImage) {
          FixupLink(pTable->m_iBackgroundSaveIndex,&pData->pBackgroundImage,pDestPathURL,&badLinks);
          pTable->SetData(pData);
        }
        CEditTableElement::FreeData(pData);
      }
    }
    // table row backgrounds <tr>
    pNext = m_pBuffer->m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTableRow, 0 )) ){
      CEditTableRowElement *pTable = (CEditTableRowElement *)pNext;
      EDT_TableRowData *pData = pTable->GetData();
      if (pData) {
        if ( pData->pBackgroundImage && *pData->pBackgroundImage) {
          FixupLink(pTable->m_iBackgroundSaveIndex,&pData->pBackgroundImage,pDestPathURL,&badLinks);
          pTable->SetData(pData);
        }
        CEditTableRowElement::FreeData(pData);
      }
    }
    // table cell backgrounds <td> <th>
    pNext = m_pBuffer->m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindTableCell, 0 )) ){
      CEditTableCellElement *pTable = (CEditTableCellElement *)pNext;
      EDT_TableCellData *pData = pTable->GetData(0);
      if (pData) {
        if ( pData->pBackgroundImage && *pData->pBackgroundImage) {
          FixupLink(pTable->m_iBackgroundSaveIndex,&pData->pBackgroundImage,pDestPathURL,&badLinks);
          pTable->SetData(pData);
        }
        CEditTableCellElement::FreeData(pData);
      }
    }

    // UnknownHTML tags with LOCALDATA attribute.
    pNext = m_pBuffer->m_pRoot;
    while(NULL != (pNext = pNext->FindNextElement( &CEditElement::FindUnknownHTML, 0 )) ){
      CEditIconElement *pIcon = CEditIconElement::Cast(pNext);
      if (pIcon) {
        char **pMimeTypes;
        char **pURLs;
        int count = pIcon->ParseLocalData(&pMimeTypes,&pURLs);

        if (count) {
          if (pIcon->m_piSaveIndices) {
            // Change all occurances of pURLs[n] in the unknown tag to
            // take account of the new location.
            for (int n = 0; n < count; n++) {
              char *pPrev = XP_STRDUP(pURLs[n]); // Remember, so we can se if pURLs[n] changes.
              FixupLink(pIcon->m_piSaveIndices[n],&pURLs[n],pDestPathURL,&badLinks);
              // Note: ReplaceParamValue checks if the two are the same.
              // Also note: the LOCALDATA parameter itself will be updated.
              pIcon->ReplaceParamValues(pPrev,pURLs[n]);
              XP_FREEIF(pPrev);
            }
          } 
          else {
            XP_ASSERT(0);
          }
        }

        // kill and zero memory for saved indices.
        XP_FREEIF(pIcon->m_piSaveIndices);
        CEditIconElement::FreeLocalDataLists(pMimeTypes,pURLs,count);
      }
    } // while


    
    // Adjust the HREFs.
    if( m_bAutoAdjustLinks && pDestPathURL ){
       // No longer use CEditLinkManager::AdjustAllLinks because its ref counting is 
       // messed up and has more links than are currently in document.  It adds too 
       // many lines to badLinks (which is actually displayed to the user.)
       // m_pBuffer->linkManager.AdjustAllLinks( m_pSrcURL, pDestPathURL, &badLinks );

        // Walk the tree and find all HREFs.
        CEditElement *pLeaf = m_pBuffer->m_pRoot->FindNextElement( 
                                      &CEditElement::FindLeafAll,0 );
        // First sweep, mark all HREFs as not adjusted.
        while (pLeaf) {
          m_pBuffer->linkManager.SetAdjusted(pLeaf->Leaf()->GetHREF(),FALSE);
          pLeaf = pLeaf->FindNextElement(&CEditElement::FindLeafAll,0 );
        }
        // Second sweep, actually adjust the HREFs.
        pLeaf = m_pBuffer->m_pRoot->FindNextElement( 
                &CEditElement::FindLeafAll,0 );
        while (pLeaf) {
          ED_LinkId linkId = pLeaf->Leaf()->GetHREF();
          if (linkId && !m_pBuffer->linkManager.GetAdjusted(linkId)) {
            // linkManager can deal with NULL HREF.
            m_pBuffer->linkManager.AdjustLink(linkId,m_pSrcURL, pDestPathURL, &badLinks );          
            m_pBuffer->linkManager.SetAdjusted(linkId,TRUE);
          }
          pLeaf = pLeaf->FindNextElement(&CEditElement::FindLeafAll,0 );
        }
    }
    
    XP_FREEIF(pDestPathURL);

    // Package up a string of all the bad links found.
    char *pRet = NULL;
    if (badLinks.Size() > 0) {
      int n;
      for (n = 0; n < badLinks.Size(); n++) {
        StrAllocCat(pRet,badLinks[n]);
        StrAllocCat(pRet,"\n");
        XP_FREEIF(badLinks[n]);
      }
    }

    return pRet;
}


#if 0 //// Now obsolete
//-----------------------------------------------------------------------------
// 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();
}
#endif

#if 0 // now obsolete
//-----------------------------------------------------------------------------
// 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();
}
#endif

//
// Sigh, why did the editor invent a new streaming type? This function
// allows the preencrypted file standard streamer to point back to the
// Editor's stream without turning the C++ virus loose on the security
// library.
//

typedef struct edtPrivStructStr {
     ITapeFileSystem *tapeFS;
	 IStreamOut *out;
} edtPrivStruct;

static unsigned int
edt_TapeIsReady(NET_StreamClass * /* stream */ ) {	
    return MAX_WRITE_READY;
}

static void
edt_TapeComplete(NET_StreamClass *stream) {
    edtPrivStruct *obj = (edtPrivStruct *)stream->data_object;	
    obj->tapeFS->CloseStream(0);
    XP_FREE(obj);
    return;
}

static void
edt_TapeAbort(NET_StreamClass *stream, int /* status */) {	
    edt_TapeComplete(stream);
}

static int
edt_TapeWrite(NET_StreamClass *stream, char *str, int32 len) {
	edtPrivStruct *obj= (edtPrivStruct *)stream->data_object;	
    obj->out->Write(str,len);
    if ( obj->out->Status() != IStreamOut::EOS_NoError ) {
        return -1;
    }
    return 0;
}

extern "C" NET_StreamClass *EDT_NetToTape(void *data) {
    NET_StreamClass *stream;
    edtPrivStruct *obj;

    stream = (NET_StreamClass *) XP_ALLOC(sizeof(NET_StreamClass));
    if (stream == NULL) {
        return NULL;
    }

    obj = (edtPrivStruct *)XP_ALLOC(sizeof(edtPrivStruct));

    if (obj == NULL) {
        XP_FREE(stream);
        return NULL;
    }

    stream->name = "Editor Tape FS stream";
    stream->complete = edt_TapeComplete;
    stream->abort = edt_TapeAbort;
    stream->is_write_ready = edt_TapeIsReady;
    stream->data_object = obj;
    stream->window_id = NULL; // if this were a real stream we would need a 
                              // url and a window context.
    stream->put_block  = (MKStreamWriteFunc)edt_TapeWrite;

    obj->tapeFS = (ITapeFileSystem *)data;
    obj->out = obj->tapeFS->OpenStream(0);
    
    if (obj->out == NULL) {
        XP_FREE(obj);
        XP_FREE(stream);
        return NULL;
    }

    return stream;
}
#endif
