gecko-dev/cmd/winfe/cxsave.cpp

1636 lines
43 KiB
C++

/* -*- 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.
*/
// cxsave.cpp : implementation file
//
#include "stdafx.h"
#include "msgcom.h"
#include "cxsave.h"
#include "extgen.h"
#include "intl_csi.h"
#include "netcache.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
extern "C" int MK_DISK_FULL; // defined in allxpstr.c
extern char *FE_FindFileExt(char * path);
extern void FE_LongNameToDosName(char* dest, char* source);
extern void CheckLegalFileName(char* full_path);
/////////////////////////////////////////////////////////////////////////////
// CSaveCX dialog
BOOL CSaveCX::SaveAnchorObject(const char *pAnchor, History_entry *pHist, int16 iCSID, CWnd *pParent, char *pFileName)
{
// Indirect constructor for serializing object.
// Allocate the dialog.
CSaveCX *pSaveCX = new CSaveCX(pAnchor, NULL, pParent);
pSaveCX->m_iCSID = iCSID;
// see if we've been given a file to load
if(pFileName) {
pSaveCX->m_csFileName = pFileName;
}
// Assign over the history entry.
pSaveCX->m_pHist = pHist;
// See if we can create the dialog.
BOOL bCreate = pSaveCX->CanCreate();
if(bCreate == TRUE) {
// Create the dialog.
pSaveCX->DoCreate();
return(TRUE);
}
else {
// No need to destroy a window, none created.
return(FALSE);
}
}
// Kludge to avoid including cxsave.h in edview.cpp (freaks out Win16 compiler)
BOOL wfe_SaveAnchorAsText(const char *pAnchor, History_entry *pHist, CWnd *pParent, char *pFileName)
{
return CSaveCX::SaveAnchorAsText(pAnchor, pHist, pParent, pFileName);
}
// Similar to above, but special version for Composer
// to allow converting current HTML to text output
BOOL CSaveCX::SaveAnchorAsText(const char *pAnchor, History_entry *pHist, CWnd *pParent, char *pFileName)
{
// Allocate the dialog.
CSaveCX *pSaveCX = new CSaveCX(pAnchor, NULL, pParent);
if( !pSaveCX || !pFileName )
return FALSE;
// see if we've been given a file to load
pSaveCX->m_csFileName = pFileName;
// We already know to save as Text, so set this here
// (CanCreate will not prompt for filename - we already selected it)
pSaveCX->m_iFileType = TXT;
// Assign over the history entry.
pSaveCX->m_pHist = pHist;
// See if we can create the dialog.
BOOL bCreate = pSaveCX->CanCreate();
if(bCreate == TRUE) {
// Create the dialog.
pSaveCX->DoCreate();
return(TRUE);
}
else {
// No need to destroy a window, none created.
return(FALSE);
}
}
NET_StreamClass *CSaveCX::SaveUrlObject(URL_Struct *pUrl, CWnd *pParent, char * pFileName)
{
// Indirect constructor for serializing object.
// See if it's safe to convert the URL to this context.
if(NET_IsSafeForNewContext(pUrl) == FALSE) {
return(NULL);
}
// Allocate the dialog.
CSaveCX *pSaveCX = new CSaveCX(pUrl->address, NULL, pParent);
// see if we've been given a file to load
if(pFileName) {
pSaveCX->m_csFileName = pFileName;
}
// See if it's OK to further create the dialog.
pSaveCX->SetUrl(pUrl);
if(pSaveCX->CanCreate() == FALSE) {
return(NULL);
}
// Transfer the URL to the new context.
// This ties together the dialog and the URL.
if(0 != NET_SetNewContext(pUrl, pSaveCX->GetContext(), CFE_GetUrlExitRoutine)) {
// Couldn't transfer.
ASSERT(0);
pSaveCX->DestroyContext();
return(NULL);
}
// Manually bind the stream that will handle the download of
// the URL, and return that stream.
// This ties together the stream and dialog.
NET_StreamClass *pRetval = ContextSaveStream(FO_SAVE_AS, NULL, pUrl, pSaveCX->GetContext());
if(pRetval == NULL) {
// Couldn't create the stream.
pSaveCX->DestroyContext();
return(NULL);
}
// Create the dialog, the viewable portions.
pSaveCX->DoCreate();
return(pRetval);
}
NET_StreamClass *CSaveCX::ViewUrlObject(URL_Struct *pUrl, const char *pViewer, CWnd *pParent) {
// Indirect constructor for externally viewing object.
// See if it's safe to convert the URL to this context.
if(NET_IsSafeForNewContext(pUrl) == FALSE) {
return(NULL);
}
// Allocate the dialog.
CSaveCX *pSaveCX = NULL;
if(pViewer != NULL && strlen(pViewer)) {
pSaveCX = new CSaveCX(pUrl->address, pViewer, pParent);
}
else {
// Shell execute style.
pSaveCX = new CSaveCX(pUrl->address, "ShellExecute", pParent);
}
// See if it's OK to further create the dialog.
if(pSaveCX->CanCreate(pUrl) == FALSE) {
return(NULL);
}
// Transfer the URL to the new context.
// This ties together the dialog and the URL.
if(0 != NET_SetNewContext(pUrl, pSaveCX->GetContext(), CFE_GetUrlExitRoutine)) {
// Couldn't transfer.
ASSERT(0);
pSaveCX->DestroyContext();
return(NULL);
}
pSaveCX->SetUrl(pUrl);
// Manually bind the stream that will handle the download of
// the URL, and return that stream.
// This ties together the stream and dialog.
NET_StreamClass *pRetval = ContextSaveStream(FO_SAVE_AS, NULL, pUrl, pSaveCX->GetContext());
if(pRetval == NULL) {
// Couldn't create the stream.
pSaveCX->DestroyContext();
return(NULL);
}
// Create the dialog, the viewable portions.
pSaveCX->DoCreate();
return(pRetval);
}
NET_StreamClass *CSaveCX::OleStreamObject(NET_StreamClass *pOleStream, URL_Struct *pUrl, const char *pViewer, CWnd *pParent) {
// Indirect constructor for externally viewing object.
// See if it's safe to convert the URL to this context.
if(NET_IsSafeForNewContext(pUrl) == FALSE) {
return(NULL);
}
// Allocate the dialog.
CSaveCX *pSaveCX = new CSaveCX(pUrl->address, pViewer, pParent);
// See if it's OK to further create the dialog.
if(pSaveCX->CanCreate() == FALSE) {
return(NULL);
}
// Transfer the URL to the new context.
// This ties together the dialog and the URL.
if(0 != NET_SetNewContext(pUrl, pSaveCX->GetContext(), CFE_GetUrlExitRoutine)) {
// Couldn't transfer.
ASSERT(0);
return(NULL);
}
pSaveCX->SetUrl(pUrl);
// Speically set the secondary stream.
pSaveCX->SetSecondaryStream(pOleStream);
// Manually bind the stream that will handle the download of
// the URL, and return that stream.
// This ties together the stream and dialog.
NET_StreamClass *pRetval = ContextSaveStream(FO_SAVE_AS, NULL, pUrl, pSaveCX->GetContext());
if(pRetval == NULL) {
// Couldn't create the stream.
return(NULL);
}
// Create the dialog, the viewable portions.
pSaveCX->DoCreate();
return(pRetval);
}
CSaveCX::CSaveCX(const char *pAnchor, const char *pViewer, CWnd *pParent)
: CDialog(CSaveCX::IDD, pParent)
{
iLastPercent = 0;
tLastBarTime = tLastTime = 0;
m_iFileType = ALL;
m_pSink = NULL;
// We're not loading a URL yet.
m_pUrl = NULL;
// There's no history entry.
m_pHist = NULL;
// There's no secondary stream yet.
m_pSecondaryStream = NULL;
// We haven't been interrupted.
m_bInterrupted = FALSE;
// We haven't been abortioned.
m_bAborted = FALSE;
// We aren't saving to memory yet
m_bSavingToGlobal = FALSE;
// Set the context type.
m_cxType = Save;
GetContext()->type = MWContextSaveToDisk;
// No progress information as of yet.
m_lOldPercent = 0;
// Do not know the character set yet
m_iCSID = 0;
// Set the anchor, and our viewer if possible.
// We won't resolve any issues until later, when we can safely
// fall out if the user chooses cancel in a file dialog or something.
if(pAnchor != NULL) {
m_csAnchor = pAnchor;
}
if(pViewer != NULL) {
m_csViewer = pViewer;
}
// Set our parent window.
m_pParent = pParent;
#ifdef DEBUG
// CWnd must not be a temporary object.
if(m_pParent) {
ASSERT(FromHandlePermanent(m_pParent->GetSafeHwnd()));
}
#endif
//{{AFX_DATA_INIT(CSaveCX)
m_csAction = _T("");
m_csDestination = _T("");
m_csLocation = _T("");
m_csProgress = _T("");
m_csTimeLeft = _T("");
m_csPercentComplete = _T("");
//}}AFX_DATA_INIT
}
// m_pUrl, if it exists will have been freed in CFE_GetUrlExitRoutine()
CSaveCX::~CSaveCX()
{
// Clean up any memory that's not handled before this.
// We specifically allocated the save_as_name in the XP context.
// for file saves only.
if(GetContext()->save_as_name != NULL) {
free(GetContext()->save_as_name);
GetContext()->save_as_name = NULL; // In case someone else wants to free it, clear it.
}
// Clear back pointer.
m_pParent = NULL;
}
void CSaveCX::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CSaveCX)
DDX_Text(pDX, IDC_ACTION, m_csAction);
DDX_Text(pDX, IDC_DESTINATION, m_csDestination);
DDX_Text(pDX, IDC_LOCATION, m_csLocation);
DDX_Text(pDX, IDC_PROGRESS, m_csProgress);
DDX_Text(pDX, IDC_TIMELEFT, m_csTimeLeft);
DDX_Text(pDX, IDC_PERCENTCOMPLETE, m_csPercentComplete);
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(CSaveCX, CDialog)
//{{AFX_MSG_MAP(CSaveCX)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_SIZE()
ON_WM_SYSCOMMAND()
ON_WM_ERASEBKGND()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CSaveCX message handlers
void CSaveCX::OnCancel()
{
// TODO: Add extra cleanup here
// Mark that the user hit the cancel button, so that we can get
// rid of the temp files when everything's kosher.
// Do this before the interrupt, or other code may not know this fact.
m_bInterrupted = TRUE;
// Stop the load.
// This variable should never be invalid while this message handler can be
// called.
XP_InterruptContext(GetContext());
}
HCURSOR CSaveCX::OnQueryDragIcon() {
// Return the icon that will show up when dragged.
HICON hIcon = theApp.LoadIcon(IDR_MAINFRAME);
ASSERT(hIcon);
return((HCURSOR)hIcon);
}
BOOL CSaveCX::OnEraseBkgnd(CDC *pDC) {
if(IsIconic() == TRUE) {
return(TRUE);
}
return(CDialog::OnEraseBkgnd(pDC));
}
void CSaveCX::OnSysCommand(UINT nID, LPARAM lParam) {
// Don't maximize ourselves
if(nID == SC_MAXIMIZE) {
return;
}
CDialog::OnSysCommand(nID, lParam);
}
void CSaveCX::OnSize(UINT nType, int cx, int cy)
{
// Change any maximize request to a normal request.
if(nType == SIZE_MAXIMIZED) {
nType = SIZE_RESTORED;
}
CDialog::OnSize(nType, cx, cy);
}
void CSaveCX::OnPaint()
{
TRY {
CPaintDC dc(this);
// Check to see if we need to draw our icon.
if(IsIconic() != FALSE) {
HICON hIcon = theApp.LoadIcon(IDR_MAINFRAME);
ASSERT(hIcon);
dc.DrawIcon(2, 2, hIcon);
// Also, make sure the window title is correct.
char aTitle[64];
//CLM: Removed hard-coded strings - be kind to International folks!
if(IsSaving() == TRUE) {
sprintf(aTitle, szLoadString(IDS_SAVE_SAVINGLOCATION), m_lOldPercent);
}
else {
sprintf(aTitle, szLoadString(IDS_SAVE_VIEWLOCATION), m_lOldPercent);
}
SetWindowText(aTitle);
}
else {
// Call the progress routine.
// This will cause the painting of the progress bar to be
// correct.
Progress(m_lOldPercent);
// Also, make sure the window title is correct.
if(IsSaving() == TRUE) {
SetWindowText(szLoadString(IDS_SAVING_LOCATION));
}
else {
SetWindowText(szLoadString(IDS_VIEWING_LOCATION));
}
}
// Do not call CDialog::OnPaint() for painting messages
}
CATCH(CException, e) {
// Something went wrong.
return;
}
END_CATCH
}
BOOL CSaveCX::Creator()
{
BOOL bRetval = FALSE;
CWnd *pParent = m_pParent;
CWnd desktop;
// If no parent, use the desktop.
// Should not be temporary CWnd returned from CWnd::GetDesktopWindow
// as can theoretically be released if a URL completes and calls
// the idle code during creation.
if(!pParent) {
desktop.Attach(::GetDesktopWindow());
pParent = &desktop;
}
// Do it.
bRetval = Create(CSaveCX::IDD, pParent);
// If we used the desktop, be sure to unattach before CWnd goes out
// of scope.
if(pParent == &desktop) {
pParent->Detach();
pParent = NULL;
}
return(bRetval);
}
#ifdef MOZ_MAIL_NEWS
void CSaveCX::AddFileExtension (char *& pFileName)
{
char *ext = FE_FindFileExt(pFileName);
if (!ext) {
// get the mime content type
char * pSuggestedType = MimeGetURLContentType(GetContext(),m_csAnchor);
if (pSuggestedType && *pSuggestedType) {
// Look up an extension
char aExt[_MAX_EXT];
DWORD dwFlags = 0;
size_t stExt = 0;
aExt[0] = '\0';
#ifdef XP_WIN16
dwFlags |= EXT_DOT_THREE;
#endif
stExt = EXT_Invent(aExt, sizeof(aExt), dwFlags, pFileName, pSuggestedType);
XP_FREE(pSuggestedType);
if (stExt)
StrAllocCat (pFileName, aExt);
}
}
}
#endif // MOZ_MAIL_NEWS
// Determine if it's OK to create the dialog for the transfer.
BOOL CSaveCX::CanCreate(URL_Struct* pUrl)
{
if(m_csAnchor.IsEmpty()) {
DestroyContext();
return(FALSE);
}
// Set some information for the dialog.
m_csLocation = m_csAnchor;
WFE_CondenseURL(m_csLocation, 40, FALSE);
// Are we asking them for a file name or stream? (not externally viewing)
if(IsSaving() == TRUE) {
// Don't ask for filename stuff if saving to stream.
if (!IsSavingToGlobal()) {
// Query for a filename if we don't have one already
if(m_csFileName.IsEmpty()) {
// If not provided, try to acquire URL struct.
BOOL bAcquiredUrl = FALSE;
if ( !pUrl ) {
// Try to get URL struct from cache.
pUrl = NET_CreateURLStruct(m_csAnchor, NET_DONT_RELOAD);
if ( pUrl ) {
NET_FindURLInCache(pUrl, GetContext());
bAcquiredUrl = TRUE;
}
}
#ifdef MOZ_MAIL_NEWS
char * pSuggested = MimeGuessURLContentName(GetContext(),m_csAnchor);
// get the mime content type
if (pSuggested && *pSuggested) {
// check if the file doesn't have an extension
AddFileExtension(pSuggested);
}
if (!pSuggested)
#else
char *
#endif /* MOZ_MAIL_NEWS */
pSuggested = fe_URLtoLocalName(m_csAnchor, pUrl ? pUrl->content_type : NULL);
char *pUserName = wfe_GetSaveFileName(NULL, szLoadString(IDS_SAVE_AS), pSuggested, &m_iFileType);
if(pSuggested) {
XP_FREE(pSuggested);
}
// Free URL struct if we acquired it.
if ( bAcquiredUrl ) {
NET_FreeURLStruct(pUrl);
pUrl = 0;
}
if(pUserName == NULL) {
DestroyContext();
return(FALSE);
}
m_csFileName = pUserName;
XP_FREE(pUserName);
}
//CLM: CHECK FOR SOURCE == DESTINATION
char * pSource = NULL;
XP_ConvertUrlToLocalFile(m_csAnchor, &pSource);
if( pSource && !m_csFileName.CompareNoCase(pSource) ){
::MessageBox(0, szLoadString( IDS_SOURCE_SAMEAS_DEST),
szLoadString(IDS_SAVING_LOCATION), MB_OK | MB_ICONEXCLAMATION );
DestroyContext();
XP_FREE(pSource);
return(FALSE);
}
if( pSource) XP_FREE(pSource);
// We need to copy the file name into the XP context's save_as_name,
// so that other older code that depends on this will work (DDE).
if(GetContext()->save_as_name != NULL) {
ASSERT(FALSE); // Why isn't this NULL???
free(GetContext()->save_as_name);
GetContext()->save_as_name = NULL;
}
GetContext()->save_as_name = strdup((const char *)m_csFileName);
}
// Set some information in the dialog.
m_csAction.LoadString(IDS_SAVE_SAVING); // = "Saving:";
m_csDestination = m_csFileName;
WFE_CondenseURL(m_csDestination, 40, FALSE);
// Create the viewable portions of the dialog.
Creator();
// Set its title.
SetWindowText(szLoadString(IDS_SAVING_LOCATION));
// Only ask for a URL if we don't already have one assigned
// to us.
if(m_pUrl == NULL) {
// we've got a file name to save into.
// the type of the file is set.
// it would appear that everything is ready, ask for the URL.
// We can only do this when saving. Other methods must
// manually set the URL.
if(m_pHist == NULL) {
m_pUrl = NET_CreateURLStruct(m_csAnchor, NET_DONT_RELOAD);
} else {
// We are going to use the history entry of the other context
// instead. In this manner, we can properly get the form
// data set for the load.
m_pUrl = SHIST_CreateURLStructFromHistoryEntry(GetContext(), m_pHist);
// We need to make a copy of the form data, because we are in a
// different context
if (m_pUrl) {
SHIST_SavedData savedData;
memcpy(&savedData, &m_pUrl->savedData, sizeof(SHIST_SavedData));
memset(&m_pUrl->savedData, 0, sizeof(SHIST_SavedData));
#ifdef MOZ_NGLAYOUT
ASSERT(0);
#else
LO_CloneFormData(&savedData, GetDocumentContext(), m_pUrl);
#endif
}
}
switch(m_iFileType) {
case TXT:
#ifdef MOZ_NGLAYOUT
XP_ASSERT(0);
#else
// We need to ask the text front end to handle.
TranslateText(m_pUrl, m_csFileName);
#endif /* MOZ_NGLAYOUT */
break;
default:
// We handle ourselves.
// Will send through a particular format out stream.
// WE EXPECT NETLIB TO CALL OUR EXIT ROUTINE AND ALLCONNECTIONS COMPLETE
// IN ALL CASES.
if(-1 == GetUrl(m_pUrl, FO_CACHE_AND_SAVE_AS)) {
return(FALSE);
}
break;
}
}
}
else {
// We don't really care about the file type.
m_iFileType = ALL;
// Formulate a file name that will sink data from the net.
// Security rist to let path information in externally provided
// filename (content disposition, filename =)
char *pFormulateName;
if (pUrl && pUrl->content_name != NULL &&
strstr(pUrl->content_name, "../") == NULL &&
strstr(pUrl->content_name, "..\\") == NULL) {
pFormulateName = XP_STRDUP(pUrl->content_name);
}
else {
pFormulateName = fe_URLtoLocalName(m_csAnchor, pUrl ? pUrl->content_type : NULL);
}
char *pLocalName = NULL;
if(((CNetscapeApp *)AfxGetApp())->m_pTempDir != NULL && pFormulateName != NULL) {
StrAllocCopy(pLocalName, ((CNetscapeApp *)AfxGetApp())->m_pTempDir);
StrAllocCat(pLocalName, "\\");
CheckLegalFileName(pFormulateName);
#ifdef XP_WIN16
char dosName[13]; //8.3 with '\0' total 13 chars
FE_LongNameToDosName(dosName, pFormulateName);
StrAllocCat(pLocalName, dosName);
#else
StrAllocCat(pLocalName, pFormulateName);
#endif
// If this file exists, then we must attempt another temp file.
if(-1 != _access(pLocalName, 0)) {
int type = HTTP_TYPE_URL;
if(pUrl) {
type = NET_URL_Type(pUrl->address);
}
// bug 63751 for Mail/News attachment
if ((type == MAILBOX_TYPE_URL) || (type == NEWS_TYPE_URL) || (type == IMAP_TYPE_URL))
{
#ifdef XP_WIN16
pLocalName = GetMailNewsTempFileName(pLocalName, dosName);
#else
pLocalName = GetMailNewsTempFileName(pLocalName);
#endif
#ifdef MOZ_MAIL_NEWS
AddFileExtension( pLocalName );
#endif
}
else {
// Retain the extension.
char aExt[_MAX_EXT];
DWORD dwFlags = 0;
size_t stExt = 0;
aExt[0] = '\0';
#ifdef XP_WIN16
dwFlags |= EXT_DOT_THREE;
#endif
stExt = EXT_Invent(aExt, sizeof(aExt), dwFlags, pLocalName, pUrl ? pUrl->content_type : NULL);
if(pLocalName) {
XP_FREE(pLocalName);
pLocalName = NULL;
}
pLocalName = WH_TempFileName(xpTemporary, "V", aExt);
}
}
}
else {
// Retain the extension.
char aExt[_MAX_EXT];
DWORD dwFlags = 0;
size_t stExt = 0;
aExt[0] = '\0';
#ifdef XP_WIN16
dwFlags |= EXT_DOT_THREE;
#endif
stExt = EXT_Invent(aExt, sizeof(aExt), dwFlags, m_csAnchor, pUrl ? pUrl->content_type : NULL);
if(pLocalName) {
XP_FREE(pLocalName);
pLocalName = NULL;
}
pLocalName = WH_TempFileName(xpTemporary, "V", aExt);
}
if(pFormulateName != NULL) {
XP_FREE(pFormulateName);
pFormulateName = NULL;
}
m_csFileName = pLocalName;
// Set some information for the dialog.
m_csAction.LoadString(IDS_SAVE_VIEWER); // "Viewer:"
m_csDestination = GetViewer();
WFE_CondenseURL(m_csDestination, 40, FALSE);
// Create the viewable portions of the dialog.
Creator();
// Set its title.
SetWindowText(szLoadString(IDS_VIEWING_LOCATION));
// We must wait for the URL to be assigned in manually.
// DO NOT CREATE ONE.
}
tFirstTime = theApp.GetTime();
// made it
return(TRUE);
}
// for bug 63751
// pFileName is intended for Win16 to check the prefix length of the
// filename since pTempPath is a full path. Make sure you pass in a valid pointer
// If the prefix is shorter,
// we append the number number after the prefix, otherwise we replace the
// last char for the number
char* CSaveCX::GetMailNewsTempFileName(char* pTempPath, char *pFileName)
{
int nUniqueNo = 0;
char tempName[MAX_PATH + 3];
char extension[MAX_PATH];
char* pExt = NULL;
strcpy(tempName, pTempPath);
#ifdef XP_WIN16
if (pFileName) {
pExt = FE_FindFileExt(pFileName);
if (pExt) *pExt = '\0';
}
#endif
pExt = FE_FindFileExt(tempName);
if (pExt)
{
strcpy(extension, pExt);
*pExt = '\0';
// Get a name, If this file exists, then we attempt another temp file.
do {
char number[3];
int numLen;
nUniqueNo += 1;
numLen = sprintf(number, "%d", nUniqueNo);
#ifdef XP_WIN16
*pExt = '\0';
if (strlen(pFileName) < (8 - numLen)) {
strcat(pExt, number); //append if we can
*(pExt + numLen) = '\0';
}
else {
strncpy((pExt - numLen), number, numLen);
*pExt = '\0';
}
#else
strncpy(pExt, number, numLen);
*(pExt + numLen) = '\0';
#endif
strcat(tempName, extension);
} while (-1 != _access(tempName, 0));
}
if(pTempPath != NULL) {
XP_FREE(pTempPath);
pTempPath = NULL;
}
StrAllocCopy(pTempPath, tempName);
return pTempPath;
}
void CSaveCX::Progress(long lPercentage) {
// return if there's nothing to do.
if(lPercentage == 0) {
return;
}
// update the progress meter every second (at least)
int i;
for ( i = iLastPercent; i < lPercentage; i++ )
m_ProgressMeter.StepIt ( );
if ( lPercentage > iLastPercent )
{
time_t t;
t = theApp.GetTime();
if ( t - tLastBarTime )
{
tLastBarTime = t;
// print out the percent complete as well
char szPercent[8];
wsprintf(szPercent,"%d%%", (int) LOWORD(lPercentage));
SetDlgItemText(IDC_PERCENTCOMPLETE, szPercent );
}
}
iLastPercent = LOWORD(lPercentage);
}
void CSaveCX::SetProgressBarPercent(MWContext *pContext, int32 lPercentage) {
// Make sure there's something to do here.
if(m_lOldPercent == lPercentage || lPercentage == 0) {
return;
}
m_lOldPercent = lPercentage;
Progress(lPercentage);
if(IsIconic() == TRUE) {
char aTitle[64];
if(IsSaving() == TRUE) {
sprintf(aTitle, szLoadString(IDS_SAVE_SAVINGLOCATION), lPercentage);
}
else {
sprintf(aTitle, szLoadString(IDS_SAVE_VIEWLOCATION), lPercentage);
}
SetWindowText(aTitle);
}
}
CWnd *CSaveCX::GetDialogOwner() const {
// Return this dialog as the owner of any other dialogs that
// may be created in base classes.
// No need to call the base.
return((CWnd *)this);
}
void CSaveCX::GetUrlExitRoutine(URL_Struct *pUrl, int iStatus, MWContext *pContext) {
// Call the base.
CStubsCX::GetUrlExitRoutine(pUrl, iStatus, pContext);
// If the URL is changine context, then handle correctly by not freeing.
if(iStatus == MK_CHANGING_CONTEXT) {
m_pUrl = NULL;
}
// Just destroy the window, object gets destroyed in all connections complete.
// Fun's over.
DestroyWindow();
}
void CSaveCX::TextTranslationExitRoutine(PrintSetup *pTextFE) {
// Call the base.
CStubsCX::TextTranslationExitRoutine(pTextFE);
// Destroy this object, object doesn't get destroyed in all connections complete.
// Fun's over.
DestroyWindow();
DestroyContext();
}
void CSaveCX::AllConnectionsComplete(MWContext *pContext) {
// Go ahead and delete this context.
// Won't be doing anything else.
DestroyContext();
}
void CSaveCX::Progress(MWContext *pContext, const char *pMessage) {
// Set our progress message.
if(pMessage && !strchr(pMessage,'%') && ::IsWindow(m_hWnd)) {
SetDlgItemText(IDC_PROGRESS, pMessage);
}
}
void CSaveCX::GraphProgress(MWContext *pContext, URL_Struct *pURL, int32 lBytesReceived, int32 lBytesSinceLastTime, int32 lContentLength) {
// call the base.
CStubsCX::GraphProgress(pContext, pURL, lBytesReceived, lBytesSinceLastTime, lContentLength);
// update the progress message after at least 1 second has passed
char szMessage[50];
unsigned long lKReceived = lBytesReceived / 1024;
time_t t, tdiff;
t = theApp.GetTime();
if ( t - tLastTime )
{
tLastTime = t;
tdiff = t - tFirstTime;
if ( !tdiff )
tdiff = 1;
unsigned long bytes_sec = lBytesReceived / tdiff;
if ( !bytes_sec )
bytes_sec = 1;
if ( lContentLength == 0 )
wsprintf ( szMessage, "%ldK of Unknown (at %ld.%ldK/sec)",
(long)lKReceived,
(long)(bytes_sec / 1000L),
(long)( ( bytes_sec % 1000L ) / 100 ) );
else
wsprintf ( szMessage, "%ldK of %ldK (at %ld.%ldK/sec)",
(long)lKReceived,
(long)lContentLength / 1024L,
(long)(bytes_sec / 1000L),
(long)( ( bytes_sec % 1000L ) / 100 ) );
CFE_Progress(pContext, szMessage );
if ( lContentLength == 0 )
{
// do we know the content length?
if ( !lKReceived )
SetDlgItemText ( IDC_TIMELEFT, szLoadString(IDS_UNKNOWN_TIMELEFT));
}
else
{
char szTime[10];
time_t tleft = ( lContentLength - lBytesReceived ) / bytes_sec;
wsprintf ( szTime, "%02ld:%02ld:%02ld",
(long)(tleft / 3600L),
(long)(( tleft % 3600L ) / 60L),
(long)(( tleft % 3600L ) % 60L ));
SetDlgItemText ( IDC_TIMELEFT, szTime );
}
}
// Draw our progress bar from this information.
CFE_SetProgressBarPercent( pContext, lContentLength != 0 ? lBytesReceived * 100 / lContentLength : 0 );
}
extern "C" {
NET_StreamClass *ContextSaveStream(int iFormatOut, void *pDataObj, URL_Struct *pUrl, MWContext *pContext) {
ASSERT(iFormatOut & FO_SAVE_AS);
/* settings to enable FTP and HTTP restart of interrupted downloads */
if(pUrl->server_can_do_byteranges || pUrl->server_can_do_restart)
pUrl->must_cache = TRUE;
// If we're saving from a context not meant to save, then intro the nasty hack.
if(ABSTRACTCX(pContext)->GetContextType() != Save) {
NET_StreamClass *pStreamHack = CSaveCX::SaveUrlObject(pUrl, NULL, pContext->save_as_name);
// steal the save as name (avoid always saving to the same file if this happens many times).
if(pContext->save_as_name) {
free(pContext->save_as_name);
pContext->save_as_name = NULL;
}
return(pStreamHack);
}
// We're to save a stream to disk or a secondary stream.
// First thing to do is find our CSaveCX object.
CSaveCX *pSaveCX = VOID2CX(pContext->fe.cx, CSaveCX);
// Create the stream which will do the actual work.
NET_StreamClass *pStream = NET_NewStream("Context save THIS buddy",
ContextSaveWrite,
ContextSaveComplete,
ContextSaveAbort,
ContextSaveReady,
CX2VOID(pSaveCX, CSaveCX),
pContext);
if(pStream == NULL) {
ASSERT(0);
return(NULL);
}
// Don't create an output file if all we're doing is proxying to
// another stream.
if(pSaveCX->GetSecondaryStream() == NULL) {
// All we need to do now is save the file, the name should already be
// established inside the context class.
ASSERT(pSaveCX->GetFileName().IsEmpty() == FALSE);
// See if this is a text file.
// Leave as shared readable for DDE apps looking into the file early.
int iOpenFlags = CStdioFile::modeCreate | CStdioFile::modeWrite;
#ifdef _WIN32
iOpenFlags |= CStdioFile::shareDenyWrite;
#endif
ASSERT(pUrl->content_type != NULL);
CString csContentType = pUrl->content_type;
csContentType = csContentType.Left(5);
if(csContentType == "text/") {
iOpenFlags |= CStdioFile::typeText;
}
else {
iOpenFlags |= CStdioFile::typeBinary;
}
// See if the URL has a content length, and if so, see if the device
// we'll be writing to has enough free space.
if(FEU_ConfirmFreeDiskSpace(pSaveCX->GetContext(), pSaveCX->GetFileName(), pUrl->content_length) == FALSE) {
// Not enough space to continue.
XP_FREE(pStream);
return(NULL);
}
// Open the file
CStdioFile *pSink;
TRY {
pSink = new CStdioFile(pSaveCX->GetFileName(), iOpenFlags);
}
CATCH(CException, e) {
CString strMsg;
strMsg.LoadString(IDS_NO_OPEN_WRITE);
FE_Alert(pSaveCX->GetContext(), (LPCSTR)strMsg);
pSink = NULL;
}
END_CATCH
if(pSink == NULL) {
ASSERT(0);
XP_FREE(pStream);
return(NULL);
}
// Let the context know where the output is going.
pSaveCX->SetSink(pSink);
}
// We're done.
return(pStream);
}
unsigned int ContextSaveReady(NET_StreamClass *stream) {
void *pDataObj=stream->data_object;
// Get our save context out of the data object.
CSaveCX *pSaveCX = VOID2CX(pDataObj, CSaveCX);
// See if we need to handle specially for a secondary stream.
NET_StreamClass *pSecondary = pSaveCX->GetSecondaryStream();
if(pSecondary == NULL) {
// We should always be ready to write the maximum amount out to disk.
return(MAX_WRITE_READY);
}
else {
return(pSecondary->is_write_ready(pSecondary));
}
}
int ContextSaveWrite(NET_StreamClass *stream, const char *pWriteData, int32 iDataLength) {
void *pDataObj=stream->data_object;
// Get our save context out of the data object.
CSaveCX *pSaveCX = VOID2CX(pDataObj, CSaveCX);
CStdioFile *pSink = pSaveCX->GetSink();
int iRetval = (UINT)iDataLength;
// See if we need to handle specially for a secondary stream.
NET_StreamClass *pSecondary = pSaveCX->GetSecondaryStream();
if(pSecondary == NULL) {
// Write the data to disk.
TRY {
pSink->Write((void *)pWriteData, (UINT)iDataLength);
}
CATCH(CException, e) {
// Some type of error occurred.
pSaveCX->Interrupt();
CString strMsg;
strMsg.LoadString(IDS_CANT_WRITE);
FE_Alert(pSaveCX->GetContext(), (LPCSTR)strMsg);
iRetval = MK_DISK_FULL;
}
END_CATCH
}
else {
iRetval = pSecondary->put_block(pSecondary, pWriteData, iDataLength);
}
// Return the amount written, or error.
return(iRetval);
}
void ContextSaveComplete(NET_StreamClass *stream) {
void *pDataObj=stream->data_object;
// The save is done. Close the file.
CSaveCX *pSaveCX = VOID2CX(pDataObj, CSaveCX);
if(!pSaveCX->m_bAborted)
{
// get rid of the cache file since we don't need it for
// ftp restarts anymore since it successfully download
if(pSaveCX->m_pUrl)
NET_RemoveURLFromCache(pSaveCX->m_pUrl);
}
// See if we need to handle specially for a secondary stream.
NET_StreamClass *pSecondary = pSaveCX->GetSecondaryStream();
if(pSecondary == NULL) {
TRY {
delete(pSaveCX->GetSink());
}
CATCH(CException, e) {
// Disk full or some other error condition for Close();
// Don't do anything.
CString strMsg;
strMsg.LoadString(IDS_CANT_CLOSE);
FE_Alert(pSaveCX->GetContext(), (LPCSTR)strMsg);
}
END_CATCH
pSaveCX->ClearSink();
// If we've been interrupted, we're going to want to clean up the partial droppings
// on disk.
if(pSaveCX->m_bInterrupted) {
TRY {
CFile::Remove(pSaveCX->GetFileName());
}
CATCH(CException, e) {
// Report it to the person wanting to cancel the operation.
// Failure...
CString strMsg;
strMsg.LoadString(IDS_CANT_CLEANUP);
FE_Alert(pSaveCX->GetContext(), (LPCSTR)strMsg);
}
END_CATCH
}
// If this were going to an external viewer, we'll want to remove the file on exit.
else if(pSaveCX->IsViewing() == TRUE) {
FE_DeleteFileOnExit(pSaveCX->GetFileName(), pSaveCX->GetAnchor());
// We'll also want to start that viewer now.
// It should have previously been verified as a viewer OK to spawn prior to switching
// to this context.
FE_Progress(pSaveCX->GetContext(), szLoadString(IDS_SPAWNING_EXTERNAL_VIEWER));
if(pSaveCX->IsShellExecute()) {
FEU_Execute(pSaveCX->GetContext(), pSaveCX->GetFileName(), NULL);
}
else {
#ifdef XP_WIN32
// Pass an 8.3 filename to the helper app in case it doesn't understand long filenames
char szShortFileName[_MAX_PATH];
VERIFY(GetShortPathName(pSaveCX->GetFileName(), szShortFileName, sizeof(szShortFileName)) > 0);
FEU_Execute(pSaveCX->GetContext(), pSaveCX->GetViewer(), szShortFileName);
#else
FEU_Execute(pSaveCX->GetContext(), pSaveCX->GetViewer(), pSaveCX->GetFileName());
#endif
}
}
}
else {
pSecondary->complete(pSecondary);
XP_FREE(pSecondary);
pSaveCX->ClearSecondary();
}
}
void ContextSaveAbort(NET_StreamClass *stream, int iStatus) {
void *pDataObj=stream->data_object;
// The save is done. Close the file.
CSaveCX *pSaveCX = VOID2CX(pDataObj, CSaveCX);
pSaveCX->m_bAborted = TRUE;
// See if we need to handle specially for a secondary stream.
NET_StreamClass *pSecondary = pSaveCX->GetSecondaryStream();
if(pSecondary == NULL) {
// The load was aborted.
// Handle as a normally compeleted stream.
ContextSaveComplete(stream);
}
else {
pSecondary->abort(pSecondary, iStatus);
XP_FREE(pSecondary);
pSaveCX->ClearSecondary();
}
}
};
BOOL CSaveCX::OnInitDialog()
{
CDialog::OnInitDialog();
m_ProgressMeter.SubclassDlgItem( IDC_PROGRESSMETER, this );
// Set some information for the dialog.
m_csLocation = m_csAnchor;
CStatic * pStatic = (CStatic *)GetDlgItem(IDC_LOCATION);
int iLocWidth = 40;
if ( pStatic != NULL )
{
CRect rect;
TEXTMETRIC tm;
pStatic->GetClientRect( &rect );
CDC * pdc = pStatic->GetDC();
pdc->GetTextMetrics ( &tm );
iLocWidth = rect.Width() / tm.tmAveCharWidth;
pStatic->ReleaseDC ( pdc );
};
WFE_CondenseURL(m_csLocation, iLocWidth, FALSE);
SetDlgItemText (IDC_LOCATION, m_csLocation );
WFE_CondenseURL(m_csDestination, iLocWidth, FALSE);
SetDlgItemText (IDC_DESTINATION, m_csDestination );
#ifdef PMETER
m_ProgressCtl.SetRange ( 0, 100 );
m_ProgressCtl.SetStep ( 1 );
#endif
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
CString CSaveCX::GetViewer() const
{
CString csRetval = m_csViewer;
if(IsShellExecute()) {
char aViewer[_MAX_PATH];
memset(aViewer, 0, sizeof(aViewer));
BOOL bViewer = FEU_FindExecutable(GetFileName(), aViewer, FALSE);
if(bViewer) {
csRetval = aViewer;
}
}
return(csRetval);
}
/////////////////////////////////////////////////////////////////////
//
// CSaveAttachmentStream
//
// for attachment download for drag-and-drop
//
class CSaveAttachmentStream {
protected:
HGLOBAL m_hGlobal;
CFile *m_pFile;
int32 m_nPosition;
int32 m_nFileSize;
int32 m_nBufferSize;
BOOL *m_pbStatus;
NET_StreamClass *m_pStream;
int16 m_mail_csid;
int16 m_win_csid;
CCCDataObject m_converter;
XP_Bool m_doConvert;
public:
CSaveAttachmentStream(MWContext *pContext, HGLOBAL hGlobal, LPCTSTR lpszUrl, LPCTSTR lpszTitle, BOOL *pbStatus);
CSaveAttachmentStream(MWContext *pContext, CFile *pFile, LPCTSTR lpszUrl, LPCTSTR lpszTitle, BOOL *pbStatus);
~CSaveAttachmentStream();
NET_StreamClass *GetStream() const { return m_pStream; }
// Stream Stuff
int Write(const char *pWriteData, int32 iDataLength);
void Complete();
void Abort(int iStatus);
// Global Stuff
BOOL Grow(int32 nAdd);
BOOL SetSize(int32 nSize);
void SetupINTLConverter();
};
extern "C" {
unsigned int AttachmentSaveReady(NET_StreamClass *stream)
{
return MAX_WRITE_READY;
}
int AttachmentSaveWrite(NET_StreamClass *stream, const char *pWriteData, int32 iDataLength)
{
void *pDataObj=stream->data_object;
CSaveAttachmentStream *pSaveCX = (CSaveAttachmentStream *) pDataObj;
return pSaveCX->Write(pWriteData, iDataLength);
}
void AttachmentSaveComplete(NET_StreamClass *stream)
{
void *pDataObj=stream->data_object;
CSaveAttachmentStream *pSaveCX = (CSaveAttachmentStream *) pDataObj;
pSaveCX->Complete();
}
void AttachmentSaveAbort(NET_StreamClass *stream, int iStatus)
{
void *pDataObj=stream->data_object;
CSaveAttachmentStream *pSaveCX = (CSaveAttachmentStream *) pDataObj;
pSaveCX->Abort(iStatus);
}
}; // extern "C"
CSaveAttachmentStream::CSaveAttachmentStream(MWContext *pContext, HGLOBAL hGlobal, LPCTSTR lpszUrl, LPCTSTR lpszTitle, BOOL *pbStatus)
{
m_hGlobal = hGlobal;
m_nBufferSize = ::GlobalSize(m_hGlobal);
m_nPosition = 0;
m_nFileSize = 0;
m_pFile = 0;
m_pStream = NET_NewStream("SaveAttachment",
AttachmentSaveWrite,
AttachmentSaveComplete,
AttachmentSaveAbort,
AttachmentSaveReady,
this,
pContext);
m_pbStatus = pbStatus;
if (m_pbStatus)
*m_pbStatus = TRUE;
m_mail_csid = 0;
m_win_csid = 0;
m_converter = NULL;
m_doConvert = FALSE;
}
CSaveAttachmentStream::CSaveAttachmentStream(MWContext *pContext, CFile *pFile, LPCTSTR lpszUrl, LPCTSTR lpszTitle, BOOL *pbStatus)
{
m_hGlobal = 0;
m_nBufferSize = 0;
m_nPosition = 0;
m_nFileSize = 0;
m_pFile = pFile;
m_pStream = NET_NewStream("SaveAttachment",
AttachmentSaveWrite,
AttachmentSaveComplete,
AttachmentSaveAbort,
AttachmentSaveReady,
this,
pContext);
m_pbStatus = pbStatus;
if (m_pbStatus)
*m_pbStatus = TRUE;
m_mail_csid = 0;
m_win_csid = 0;
m_converter = NULL;
m_doConvert = FALSE;
}
CSaveAttachmentStream::~CSaveAttachmentStream()
{
if (m_converter)
{
INTL_DestroyCharCodeConverter(m_converter);
m_converter = NULL;
}
}
void CSaveAttachmentStream::SetupINTLConverter()
{
INTL_CharSetInfo csi =
LO_GetDocumentCharacterSetInfo(m_pStream->window_id);
char *mime_charset = (csi == NULL) ? NULL : INTL_GetCSIMimeCharset(csi);
if (!m_converter &&
mime_charset &&
*(mime_charset))
{
m_mail_csid = INTL_CharSetNameToID(mime_charset);
m_win_csid = INTL_DocToWinCharSetID(m_mail_csid);
m_converter = INTL_CreateCharCodeConverter();
if (m_converter)
m_doConvert = INTL_GetCharCodeConverter(m_mail_csid, m_win_csid, m_converter);
}
}
int CSaveAttachmentStream::Write(const char *lpBuf, int32 nCount)
{
if (nCount == 0)
return 0;
SetupINTLConverter();
char *newStr = NULL;
if(m_doConvert)
{
char *dupStr = (char *) XP_ALLOC(nCount+1);
if (dupStr)
{
XP_MEMCPY(dupStr, lpBuf, nCount);
*(dupStr + nCount) = 0;
newStr = (char *) INTL_CallCharCodeConverter(m_converter,
(unsigned char *)dupStr,
nCount);
if (!newStr)
newStr = dupStr;
else if (newStr != dupStr)
XP_FREE(dupStr);
if (newStr)
{
lpBuf = newStr;
nCount = INTL_GetCCCLen(m_converter);
}
}
}
if (m_pFile)
{
m_pFile->Write(lpBuf, nCount);
}
else
{
if (m_nPosition + nCount > m_nBufferSize)
Grow(m_nPosition + nCount);
ASSERT(m_nPosition + nCount <= m_nBufferSize);
// Safety net
if (m_nPosition + nCount > m_nBufferSize)
{
XP_FREEIF(newStr);
return 0;
}
BYTE *lpBuffer = (BYTE *)::GlobalLock(m_hGlobal);
memcpy((BYTE*)lpBuffer + m_nPosition, (BYTE*)lpBuf, nCount);
::GlobalUnlock(m_hGlobal);
}
m_nPosition += nCount;
if (m_nPosition > m_nFileSize)
m_nFileSize = m_nPosition;
XP_FREEIF(newStr);
return (int) nCount;
}
void CSaveAttachmentStream::Complete()
{
if (m_pFile)
{
m_pFile->SetLength(m_nFileSize);
m_pFile->SeekToBegin();
}
else
{
BOOL res = SetSize(m_nFileSize);
if (m_pbStatus)
*m_pbStatus = res;
}
}
void CSaveAttachmentStream::Abort(int iStatus)
{
if (m_pbStatus)
*m_pbStatus = FALSE;
}
BOOL CSaveAttachmentStream::Grow(int32 newLen)
{
if (newLen > m_nBufferSize)
{
// grow the buffer
int32 newBufferSize = m_nBufferSize;
// determine new buffer size
while ((int32)newBufferSize < newLen)
newBufferSize += 4096;
// allocate new buffer
HGLOBAL hNew = ::GlobalReAlloc(m_hGlobal, newBufferSize, GMEM_MOVEABLE|GMEM_ZEROINIT);
if (hNew == NULL)
return FALSE;
m_nBufferSize = newBufferSize;
}
return TRUE;
}
BOOL CSaveAttachmentStream::SetSize(int32 nSize)
{
if (nSize < 1)
return FALSE;
HGLOBAL hNew = ::GlobalReAlloc(m_hGlobal, nSize, GMEM_MOVEABLE);
return (hNew != NULL);
}
BOOL CSaveCX::SaveToGlobal(HGLOBAL *phGlobal, LPCSTR lpszUrl, LPCSTR lpszTitle)
{
// Indirect constructor for serializing object.
BOOL bRes = TRUE;
*phGlobal = ::GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, 4096);
// This would be too sad...
if (!*phGlobal)
return FALSE;
// Allocate the dialog.
CSaveCX *pSaveCX = new CSaveCX(lpszUrl, NULL, NULL);
pSaveCX->m_bSavingToGlobal = TRUE;
pSaveCX->m_csFileName = lpszTitle;
CSaveAttachmentStream *pStream =
new CSaveAttachmentStream(pSaveCX->GetContext(), *phGlobal, lpszUrl, lpszTitle, &bRes);
pSaveCX->SetSecondaryStream(pStream->GetStream());
if (pSaveCX->CanCreate()) {
pSaveCX->DoCreate();
} else {
return FALSE;
}
FEU_BlockUntilDestroyed(pSaveCX->GetContextID());
delete pStream;
if (!bRes) {
::GlobalFree(*phGlobal);
*phGlobal = NULL;
}
return bRes;
}
#ifdef MOZ_MAIL_NEWS
BOOL CSaveCX::SaveToFile(CFile *pFile, LPCSTR lpszUrl, LPCSTR lpszTitle)
{
// Indirect constructor for serializing object.
BOOL bRes = TRUE;
if (!pFile) return FALSE;
// Allocate the dialog.
CSaveCX *pSaveCX = new CSaveCX(lpszUrl, NULL, NULL);
pSaveCX->m_bSavingToGlobal = TRUE;
pSaveCX->m_csFileName = lpszTitle;
CSaveAttachmentStream *pStream =
new CSaveAttachmentStream(pSaveCX->GetContext(), pFile, lpszUrl, lpszTitle, &bRes);
pSaveCX->SetSecondaryStream(pStream->GetStream());
if (pSaveCX->CanCreate()) {
pSaveCX->DoCreate();
} else {
return FALSE;
}
// XXX Note from gab: Should use XP_IsContextInList and not block
// here since pSaveCX could already be invalid.
FEU_BlockUntilDestroyed(pSaveCX->GetContextID());
delete pStream;
return bRes;
}
#endif