IPERSTOR.CPP

/* 
* IPERSTOR.CPP
* Cosmo Chapter 23
*
* Implementation of the IPersistStorage interface that we expose on
* the CFigure compound document object. This ties into the
* functionality of CPolyline.
*
* Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
*
* Kraig Brockschmidt, Microsoft
* Internet : kraigb@microsoft.com
* Compuserve: >INTERNET:kraigb@microsoft.com
*/


#include "cosmo.h"


/*
* CImpIPersistStorage:CImpIPersistStorage
* CImpIPersistStorage::~CImpIPersistStorage
*
* Constructor Parameters:
* pObj PCFigure associated with this object.
* pUnkOuter LPUNKNOWN of the controlling unknown.
*/

CImpIPersistStorage::CImpIPersistStorage(PCFigure pObj
, LPUNKNOWN pUnkOuter)
{
m_cRef=0;
m_pObj=pObj;
m_pUnkOuter=pUnkOuter;
m_psState=PSSTATE_UNINIT;

m_fConvert=FALSE;
return;
}


CImpIPersistStorage::~CImpIPersistStorage(void)
{
return;
}




/*
* CImpIPersistStorage::QueryInterface
* CImpIPersistStorage::AddRef
* CImpIPersistStorage::Release
*/

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
, PPVOID ppv)
{
return m_pUnkOuter->QueryInterface(riid, ppv);
}

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
{
++m_cRef;
return m_pUnkOuter->AddRef();
}

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
{
--m_cRef;
return m_pUnkOuter->Release();
}





/*
* CImpIPersistStorage::GetClassID
*
* Purpose:
* Returns the CLSID of the object represented by this interface.
*
* Parameters:
* pClsID LPCLSID in which to store our CLSID.
*
* Return Value:
* HRESULT NOERROR or a general error value.
*/

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
{
if (PSSTATE_UNINIT==m_psState)
return ResultFromScode(E_UNEXPECTED);

*pClsID=m_pObj->m_clsID;
return NOERROR;
}





/*
* CImpIPersistStorage::IsDirty
*
* Purpose:
* Tells the caller if we have made changes to this object since
* it was loaded or initialized new.
*
* Parameters:
* None
*
* Return Value:
* HRESULT Contains S_OK if we ARE dirty, S_FALSE if
* NOT dirty.
*/

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
{
if (PSSTATE_UNINIT==m_psState)
return ResultFromScode(E_UNEXPECTED);

//CFigure::FIsDirty returns the document's dirty flag.
return ResultFromScode(m_pObj->FIsDirty() ? S_OK : S_FALSE);
}







/*
* CImpIPersistStorage::InitNew
*
* Purpose:
* Provides the object with the IStorage to hold on to while the
* object is running. Here we initialize the structure of the
* storage and AddRef it for incremental access. This function will
* only be called once in the object's lifetime in lieu of Load.
*
* Parameters:
* pIStorage LPSTORAGE for the object.
*
* Return Value:
* HRESULT NOERROR or a general error value.
*/

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
{
HRESULT hr;

if (PSSTATE_UNINIT!=m_psState)
return ResultFromScode(E_UNEXPECTED);

if (NULL==pIStorage)
return ResultFromScode(E_POINTER);

/*
* The rules of IPersistStorage mean we hold onto the IStorage
* and pre-create anything we'd need in Save(...,TRUE) for
* low-memory situations. For us this means creating our
* "CONTENTS" stream and holding onto that IStream as
* well as the IStorage here (requiring an AddRef call).
*/

hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
| STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
, 0, 0, &m_pObj->m_pIStream);

if (FAILED(hr))
return hr;

//We expect that the client has called WriteClassStg
WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
, (*m_pObj->m_pST)[IDS_USERTYPE]);

pIStorage->AddRef();
m_pObj->m_pIStorage=pIStorage;

m_psState=PSSTATE_SCRIBBLE;
return NOERROR;
}





/*
* CImpIPersistStorage::Load
*
* Purpose:
* Instructs the object to load itself from a previously saved
* IStorage that was handled by Save in another object lifetime.
* This function will only be called once in the object's lifetime
* in lieu of InitNew. The object should hold on to pIStorage here
* for incremental access and low-memory saves in Save.
*
* Parameters:
* pIStorage LPSTORAGE from which to load.
*
* Return Value:
* HRESULT NOERROR or a general error value.
*/

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
{
HRESULT hr;
LONG lRet;
LPSTREAM pIStream;

if (PSSTATE_UNINIT!=m_psState)
return ResultFromScode(E_UNEXPECTED);

if (NULL==pIStorage)
return ResultFromScode(E_POINTER);

//This tells us if we're coming from another class storage.
m_fConvert=(NOERROR==GetConvertStg(pIStorage));

//This is the type of storage we're really messing with in Treat As
ReadClassStg(pIStorage, &m_pObj->m_clsID);

hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

//We might be looking for OLE 1 streams as well.
if (FAILED(hr))
{
hr=pIStorage->OpenStream(SZOLE1STREAM, 0, STGM_DIRECT
| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

if (FAILED(hr))
return ResultFromScode(STG_E_READFAULT);

m_pObj->m_pPL->m_fReadFromOLE10=TRUE;
}

if (FAILED(hr))
return ResultFromScode(STG_E_READFAULT);

lRet=m_pObj->m_pPL->ReadFromStream(pIStream);

if (lRet < 0)
return ResultFromScode(STG_E_READFAULT);


/*
* We don't call pIStream->Release here because we may need
* it for a low-memory save in Save. We also need to
* hold onto a copy of pIStorage, meaning AddRef.
*/
m_pObj->m_pIStream=pIStream;

pIStorage->AddRef();
m_pObj->m_pIStorage=pIStorage;

m_psState=PSSTATE_SCRIBBLE;
return NOERROR;
}





/*
* CImpIPersistStorage::Save
*
* Purpose:
* Purpose:
* Saves the data for this object to an IStorage which may
* or may not be the same as the one previously passed to
* Load, indicated with fSameAsLoad. After this call we may
* not write into the storage again until SaveCompleted is
* called, although we may still read.
*
* Parameters:
* pIStorage LPSTORAGE in which to save our data.
* fSameAsLoad BOOL indicating if this is the same pIStorage
* that was passed to Load. If TRUE, then the
* object should write whatever it has *without
* *using any extra memory* as this may be a low
* memory save attempt. That means that you must
* not try to open or create streams. If FALSE
* you need to regenerate your whole storage
* structure, being sure to also release any
* pointers held from InitNew and Load.
*
* Return Value:
* HRESULT NOERROR or a general error value.
*/

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
, BOOL fSameAsLoad)
{
LONG lRet;
HRESULT hr;
LPSTREAM pIStream;
LONG lVer=VERSIONCURRENT;

//Have to come here from scribble state.
if (PSSTATE_SCRIBBLE!=m_psState)
return ResultFromScode(E_UNEXPECTED);

//Must have an IStorage if we're not in SameAsLoad
if (NULL==pIStorage && !fSameAsLoad)
return ResultFromScode(E_POINTER);

/*
* If this was read from an OLE 1.0 storage, but there is no
* convert bit, then we have to save the 1.0 format to the
* "\1Ole10Native" stream. Otherwise if we were converting
* from OLE 1.0, we should nuke the stream since it's no longer
* useful. To handle this, we call WriteToStorage with
* VERSIONCURRENT in any convert case. WriteToStorage will
* remove the OLE 1.0 stream if it previously read from one, or
* it will just save normally.
*
* The Polyine allows us to look at it's m_fReadFromOLE10 which
* tells us to pass it 0x00010000 if we're not converting, that
* is, we're doing Treat As on the OLE 1.0 object and so we
* need to write the new data in the Ole10Native stream.
*/

if (!m_fConvert && m_pObj->m_pPL->m_fReadFromOLE10)
lVer=0x00010000;


/*
* If we're saving to a new storage, create a new stream.
* If fSameAsLoad it TRUE, then we write to the
* stream we already allocated. We should NOT depends on
* pIStorage with fSameAsLoad is TRUE.
*
* If we're converting an OLE 1 storage to an OLE 2 storage,
* then we have to create a new stream (conversion is not
* guaranteed to succeed in low memory) and delete the old
* one, so we ignore fSameAsLoad if we're converting.
*/

if (fSameAsLoad
&& !(m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert))
{
LARGE_INTEGER li;

/*
* Use pre-allocated streams to avoid failures due
* to low-memory conditions. Be sure to reset the
* stream pointer if you used this stream before!!
*/
pIStream=m_pObj->m_pIStream;
LISet32(li, 0);
pIStream->Seek(li, STREAM_SEEK_SET, NULL);

//This matches the Release below.
pIStream->AddRef();
}
else
{
hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
| STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
, 0, 0, &pIStream);

if (FAILED(hr))
return hr;

WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
, (*m_pObj->m_pST)[IDS_USERTYPE]);
}

lRet=m_pObj->m_pPL->WriteToStream(pIStream, lVer);
pIStream->Release();

/*
* If we are overwriting an OLE 1 storage, delete the old
* Ole10Native stream if writing our CONTENTS worked.
*/
if (m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert && (lRet >= 0))
pIStorage->DestroyElement(SZOLE1STREAM);

//Clear the convert bit if it was set
if (m_fConvert)
{
UINT cf;

cf=RegisterClipboardFormat((*m_pObj->m_pST)[IDS_FORMAT]);
WriteFmtUserTypeStg(pIStorage, cf
, (*m_pObj->m_pST)[IDS_USERTYPE]);

SetConvertStg(pIStorage, FALSE);
m_fConvert=FALSE;
}

if (lRet >= 0)
{
m_psState=PSSTATE_ZOMBIE;
return NOERROR;
}

return ResultFromScode(STG_E_WRITEFAULT);
}






/*
* CImpIPersistStorage::SaveCompleted
*
* Purpose:
* Notifies the object that the storage in pIStorage has been
* completely saved now. This is called when the user of this
* object wants to save us in a completely new storage, and if
* we normally hang on to the storage we have to reinitialize
* ourselves here for this new one that is now complete.
*
* Parameters:
* pIStorage LPSTORAGE of the new storage in which we live.
*
* Return Value:
* HRESULT NOERROR or a general error value.
*/

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
{
HRESULT hr;
LPSTREAM pIStream;

//Must be called in no-scribble or hands-off state
if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
return ResultFromScode(E_UNEXPECTED);

//If we're coming from Hands-Off, we'd better get a storage
if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
return ResultFromScode(E_UNEXPECTED);

/*
* If pIStorage is NULL, then we don't need to do anything
* since we already have all the pointers we need for Save.
* Otherwise we have to release any held pointers and
* reinitialize them from pIStorage.
*/

if (NULL!=pIStorage)
{
hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
| STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
, &pIStream);

if (FAILED(hr))
return hr;

if (NULL!=m_pObj->m_pIStream)
m_pObj->m_pIStream->Release();

m_pObj->m_pIStream=pIStream;

if (NULL!=m_pObj->m_pIStorage)
m_pObj->m_pIStorage->Release();

m_pObj->m_pIStorage=pIStorage;
m_pObj->m_pIStorage->AddRef();
}

m_pObj->SendAdvise(OBJECTCODE_SAVED);
m_psState=PSSTATE_SCRIBBLE;
return NOERROR;
}





/*
* CImpIPersistStorage::HandsOffStorage
*
* Purpose:
* Purpose:
* Instructs the object that another agent is interested in having
* total access to the storage we might be hanging on to from
* InitNew or SaveCompleted. In this case we must release our hold
* and await another call to SaveCompleted before we have a hold
* again. Therefore we cannot read or write after this call until
* SaveCompleted.
*
* Situations where this might happen arise in compound document
* scenarios where this object might be in-place active but the
* application wants to rename and commit the root storage.
* Therefore we are asked to close our hold, let the container
* party on the storage, then call us again later to tell us the
* new storage we can hold.
*
* Parameters:
* None
*
* Return Value:
* HRESULT NOERROR or a general error value.
*/

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
{
/*
* Must come from scribble or no-scribble. A repeated call
* to HandsOffStorage is an unexpected error (bug in client).
*/
if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
return ResultFromScode(E_UNEXPECTED);

//Release held pointers
if (NULL!=m_pObj->m_pIStream)
{
m_pObj->m_pIStream->Release();
m_pObj->m_pIStream=NULL;
}

if (NULL!=m_pObj->m_pIStorage)
{
m_pObj->m_pIStorage->Release();
m_pObj->m_pIStorage=NULL;
}

m_psState=PSSTATE_HANDSOFF;
return NOERROR;
}