/* OdmPending100.cpp 0.00            UTF-8                   dh:2007-01-15
 *
 *             OdmPending100 CLASSES AND INTERFACE IMPLEMENTATIONS
 *             ***************************************************
 *
 * This is the implementation of the OdmPending100 class and the COM
 * object it delivers.
 *
 * This module is defined for separate compilation and inclusion as library
 * code as part of OdmNative100.lib.  It is not expected that the factory
 * function, OdmBindPending100, be usable as a public function.  Because of
 * complex preconditions, it should only be called from OdmNative100 or
 * another private module of the OdmNative implementation.
 *
 *
 *                       Copyright © 2006-2007 NuovoDoc
 *                           <http://NuovoDoc.com>
 *
 *              This software is released under the Open
 *              Document Management API License 1.0, an open-
 *              source software license modeled on the BSD
 *              License template.
 *
 *              A copy of the license should accompany the
 *              source code that includes this file.  If the
 *              license has has been separated from the code,
 *              obtain a copy at <http://DMware.info/license/>.
 */

#define OdmPending100_C_Version_ "0.00 " __FILE__ " " __TIMESTAMP__

    /* A consistent format for building descriptive texts that
       identify specific implementations.
       */


#include <windows.h>

#include <new>
    /* For std::nothrow, preventing exceptions being thrown from
       OdmBindPending100.
       */

#include "odma32types100.h"
    /* For data types and constants applicable to ODMA 1.0 access and
       important for Odma32api.h too.
       */

#include "Odma32api100.h"
    /* For entry points into the ODMA Connection Manager for ODMA 1.0
       functions.
       */

#include "OdmNative100i.hpp"
    /* For the private IodmNative100i interface used in operation
       of OdmWorking100. */


#include "OdmPending100.hpp"
    /* For the interfaces and functions that are defined in this module. */

#include "OdmBindPending100.hpp"
    /* To confirm against the factory function defined here. */




/*               ODMPENDING CLASS DEFINITION AND IMPLEMENTATION
                 ********************************************** */

class OdmPending : public IodmPending100
{ /* Class implemented internal to OdmPending100.cpp
     This class is defined privately and used for the
     current implementation of the IodmPending100
     COM object and interface.
     */

  private:
    volatile LONG refCount;
        /* Used to count the number of outstanding references to
           interfaces of the object */

    IodmApplication100i *pIodmApp;
        /* The interface of the OdmNative100 application object that
           is the connection of this object to the ODMA API and the
           DMS handle to be used in all of our ODMA operations.
           The hConMan and hOdmWorking values here are only valid so
           long as this interface is held.  It should not be given up
           until IodmPending100::release() or the constructor fails.
           */

    HINSTANCE hConMan;
        /* Cached value from pIodmApp->hConManLib()
           The constructor for this class will fail if this value is
           NULL, the same as for hWorking being NULL.*/

    ODMHANDLE hWorking;
        /* Cached value from pIodmApp->hWorkingDMS().  If there is
           no such value, the constructor will fail such that we are
           not holding any resources and our QueryInterface will fail for
           all entries.  This causes the factory function to delete
           the newly-constructed instance.
           */

    char curDocId[ODM_DOCID_MAX];
        /* The ODMA Document ID for the already-opened ODMA Document that
           is being populated for the first time from this OdmPending
           object.

              XXX: This data is assumed to have been validated before
                   ever being offered to the constructor.  There is
                   minimal defensive code here.
           */

    char curDocLocation[ODM_FILENAME_MAX];
        /* The Document Location for the location in which the
           application is expected to deliver initial content to the DMS.

           The ODMA definition of ODM_FILENAME_MAX is slightly less than
           the Win32 definition.
           */

    HRESULT okToCommit;
        /* Value used to indicate whether we can perform a commit (S_OK)
           or else return the HRESULT that is here.
           */


  public:
    OdmPending(    IUnknown  *pUnkApp,    /* Interface to the Application
                                             object that is logically our
                                             container and that we must not
                                             outlive. */

                 const char  pszDocId[],  /* The initial Document ID that
                                             applies up until commitChanges
                                             is performed. */

                 const char  pszDocLoc[] /* The already-"opened" Document
                                             Location that is the file-
                                             system destination for
                                             delivery of initial content
                                             to the DMS. */

                 );
        /* This constructor is appropriate following a startNewDoc and an
           openDoc that obtains a document location for access to the
           content document file.  This can also be initiated after an
           ODMSaveAs and an openDoc to get a transferToNewDocument off
           the ground.

           Until a successful OdmPending::QueryInterface, this class is
           not holding any references to resources, has reference count 0,
           and can be deleted at will.
          */


    /* These method declarations identify those virtual methods that
       are overloaded for instances of this class.  The methods
       are defined out-of-line with the OdmWorking::<<method>>
       definitions following the class declaration, below. */


    /*  IUnknown Methods */
    virtual HRESULT WINAPI QueryInterface(REFIID rIID, void** ppv);
    virtual ULONG WINAPI AddRef(void);
    virtual ULONG WINAPI Release(void);

    /*  IodmPending100 Methods */
    virtual LPCSTR WINAPI interfaceImplementation(void);
    virtual LPCSTR WINAPI docLocation(void);
    virtual HRESULT WINAPI commitContent(IUnknown **pIUnk);


    }; /* class OdmPending */



static const IID IID_IodmApplication100i = IID_IodmApplication100i_ ;
    /* IodmApplication100i IID of the interface we want to hold in our
       *pIodmApp */



OdmPending::OdmPending
              (    IUnknown  *pUnkApp,    /* Interface to the Application
                                             object that is logically our
                                             container and that we must not
                                             outlive. */

                 const char  pszDocId[],  /* The initial Document ID with
                                             which this document instance is
                                             to be associated. */

                 const char  pszDocLoc[]  /* The docLocation for the
                                             already-opened Document ID. */
                 )
              : refCount(0),
                pIodmApp(NULL), hConMan(NULL), hWorking(NULL),
                okToCommit(S_OK)

{   /* If we fail, hWorking is NULL and there are no resources held onto.
       If we succeed, hWorking is non-NULL but pIodmApp is not held.

       When QueryInterface succeeds completely for the first time, and only
       the first time, pIodmApp will be held.  Otherwise, it will never be
       held.   That way, a delete of the unreferenced object will not result
       in a memory leak caused by an unreleased pIodmApp.

       No QueryInterface that finds a NULL hWorking will ever succeed.
       */


    if(pUnkApp == NULL || pszDocId == NULL || pszDocLoc == NULL) return;
        /* If any of our parameters are ill-formed, leave now, letting our
           object be still-born. */


    char *dest = &curDocId[0];
    int  docGuard = sizeof(curDocId);

    while ( *dest++ = *pszDocId++)
          if (!--docGuard) return;
        /* Save the Document ID, copying up to and including the '\0'.
           If we fill curDocId and haven't seen the '\0', bail out.
           */

    dest = &curDocLocation[0];
    docGuard = sizeof(curDocLocation);

    while ( *dest++ = *pszDocLoc++)
          if (!--docGuard) return;
        /* Save the Document Location, copying up to and including the '\0'.
           And if we fill curDocLocation without seeing the '\0' here, we
           will also bail out.
           */

    /* We perform just enough defense to preserve the integrity of our
       instance data.  Other than that, we expect our factory to always
       protect us and its caller from ever being delivered an interface
       to an ill-constructed OdmWorking instance.
       */


    if (  S_OK != pUnkApp->QueryInterface( IID_IodmApplication100i,
                                               (void**) &pIodmApp )
          )
         return;
            /* No interface, so nothing to do at all because we don't
               have any way to set hConMan and hWorking.  Fail safe. */

    pIodmApp -> Release();
        /* We have the interface, but we can't hold it until we
           have produced an interface for someone to hold onto us
           with.  Even though we have not reserved it, we can safely
           use it until we exit from this constructor.
           */


    hConMan = pIodmApp->hConManLib();
    if (  hConMan == NULL  )
         return;
            /* We're busted without a Connection Manager.  Fail safe. */

    hWorking = pIodmApp->hWorkingDMS();
    if (  hWorking == NULL )
         return;
            /* And crushed without a Working DMS.  Fail safe here too. */

    /* hWorking is now non-null and we are good to go.  It is left to
       the factory function to either provide a good Working::QueryInterface
       result for its caller or else delete this constructed instance.
       */

    } /* OdmPending::OdmPending */



static const IID IID_IodmUnknown = IID_IodmUnknown_ ;
    /* Standard IUnknown IID uniquelly named to avoid conflicts. */

static const IID IID_IodmPending100 = IID_IodmPending100_ ;
    /* IodmPending100 IID of the current header level for this object's
       interface.  These are the only IIDs recognized by the OdmPending::
       QueryInterface.  */


HRESULT WINAPI
    OdmPending::QueryInterface(REFIID rIID, void **ppIface)

{   /* Simple single-inheritance implementation. */

    if (ppIface != NULL)
         *ppIface = NULL;
    else return E_POINTER;

    if (&rIID == NULL)
         return E_POINTER;
            /* Never store into a null pointer.  Never reference
               through a null pointer.  We are not cycling these
               QueryInterfaces so hard that we are pained by this
               amount of defensive programming.

               The OdmPending constructor and the OdmBindPending100
               factory function depend on this code providing a meaningful
               defense against misadventures.
               */

    if (hWorking == NULL)
         return E_FAIL;

    if (  !(rIID == IID_IodmPending100)
             && !(rIID == IID_IodmUnknown)
          )
        return E_NOINTERFACE;

    /* We are about to succeed. */

    if (refCount == 0)
         pIodmApp->AddRef();
            /* We are in business.  Hold pIodmApp in a vice-like grip
               until we are released. */

    *ppIface = (IodmPending100 *) this;
        /* relying on the fact of single inheritance to satisfy all
           requests with the same binary interface.
           */

    ((IUnknown *)(*ppIface)) -> AddRef();
        /* Count the interface reference we have just given out */

    return S_OK;

    } /* OdmPending::QueryInterface */



ULONG WINAPI
    OdmPending::AddRef(void)

{   /* Simple reference-count operation.
       XXX: If this were to wrap around, it's not clear
         that there is anything useful to be done about it.
       */

    return InterlockedIncrement(&refCount);
        /* Maintain thread-safety even though most
           operations should all be done on a single
           GUI thread.
           */
    } /* OdmPending::AddRef */



ULONG WINAPI
    OdmPending::Release(void)

{   /* This method is never called unless an interface to us has
       been issued.  That means the constructor succeeded and
       QueryInterface reserved pI0dmApp.
       */

    if (InterlockedDecrement(&refCount))
         return 1;
            /* Note that the object can be deleted by another
               release before this one returns.  No accesses
               to anything are assured following a decrement that
               did not reach 0.
               */

    /* This thread has just reduced the reference count to 0.  Our
       this pointer is good until we delete our own instance.
       */


    if (hWorking != NULL)
         { /* Close the document with the DMS.
             XXX: We set hWorking = NULL if there is a successful
                  commitContent operation and we have handed the
                  open document off to the working document instance.
             */

           /* Get the entry point for OdmCloseDoc.
              This is the only place where we need this entry.
              */

           pfnODMCLOSEDOC
                pOdmCloseDoc
                    = (pfnODMCLOSEDOC)
                            GetProcAddress(hConMan, "ODMCloseDoc");
                            /* Entry point for ODMCloseDoc */

           if (pOdmCloseDoc == NULL)
                { /* We have a document we can't close.
                     XXX: We are in a world of hurting and can't do
                          much at this point.  This would appear to be a
                          loggable exception, but we really don't want to
                          get into anything that could have this release
                          fail.

                          The last hope is that the Application's
                          UnRegisterApp will provoke the DMS into cleaning
                          up for us.
                     */
                  }

           else { /* Close the Document
                     */

                  ODMSTATUS rc = pOdmCloseDoc
                                    (hWorking,
                                     curDocId,
                                     0xFFFFFFFF, /* Unknown editing time */
                                     0xFFFFFFFF, /* Unknown pages printed */
                                     NULL,       /* No application-specific
                                                    data record */
                                     0
                                     );

                      /* XXX: There is nothing for us to do if the Close
                              fails.  It is too late and there is no way
                              to let the application know.  There should
                              be no lost work because the commitChanges
                              either succeeded or it failed and the
                              application took whatever fall-back measures
                              it is designed for.
                         */
                  }

           }



    if(pIodmApp != NULL)
         { /* Release the OdmApplication interface */

           pIodmApp -> Release();

           }


    /* Con Te Partirò */

    delete this;
        /* When we are the final release, there can be no
           others and *this is ours to delete.  Afterwards,
           no references to the object fields and members
           are valid.
           */

    return 0;

    } /* OdmPending::Release */



LPCSTR WINAPI
    OdmPending::interfaceImplementation(void)

{   /* Identify the interface definitions followed by the implementation.
       */

    return "defined in " OdmPending100_H_Version_ ",\n"
           "implemented in " OdmPending100_C_Version_ ",\n"
           #if defined(_MSC_EXTENSIONS)
                " MS-extended"
           #endif
           #if defined(__cplusplus)
                " C++"
           #elif defined(__STDC__)
                " Standard C"
           #elif defined(_MSC_VER)
                " Visual C"
           #else
                " C Language"
           #endif

           #if defined(_DLL)
                " DLL"
           #endif
           #if defined(_WIN32)
                " (Win32)"
           #endif
           " compiled " __DATE__ " at " __TIME__
           ;

    } /* OdmPending::interfaceImplementation */



LPCSTR WINAPI
    OdmPending::docLocation(void)

{   /* Give out our address for the current docLocation.  The requester
       must not hold that pointer for any longer than the validity of its
       reference to this instance's interface.
       */

    if (hWorking != NULL)
         return &curDocLocation[0];
    else return NULL;

    } /* OdmPending::docLocation */


HRESULT WINAPI
    OdmPending::commitContent(   IUnknown **pIUnk
                                 )

{   if (pIUnk != NULL)
         *pIUnk = NULL;
    else return E_POINTER;

    if (okToCommit != S_OK)
        return okToCommit;

    okToCommit = E_FAIL;
        /* However this goes, we never get another chance. */

    if (hWorking == NULL)
         return E_FAIL;

    pfnODMSAVEDOC pODMSaveDoc
        = (pfnODMSAVEDOC) GetProcAddress(hConMan, "ODMSaveDoc");

    if (pODMSaveDoc == NULL)
         return E_FAIL;

    char newDocId[sizeof(curDocId)+2]={'\0'};
    /* XXX: Simple use of additional guard nulls to defang buffer-overflow
            edge case.  Add more guard nulls if we are concerned about
            this as a threat.
       XXX: This function depends on the entire array being filled with
            '\0' in this initialization, in accordance with the C++
            standard.
       */

    ODMSTATUS rc = pODMSaveDoc(hWorking, curDocId, newDocId);

    if (rc != ODM_SUCCESS)
         return E_FAIL;

    if (newDocId[sizeof(curDocId)-1] !='\0' || newDocId[0] == '\0')
         return E_FAIL;

    /* We are assured that newDocId fits into curDocId, including
       the terminal '\0'.

       XXX: We know the newDocId is not an empty string but we don't know
            that it is well-formed.
       */

    char *dest = curDocId;
    char *source = newDocId;

    while (*dest++ = *source++) ;

        /* We have done the initial save and we now have the document ID
           and the content that makes for a Working Document.
           */

    HRESULT ourCode
                = pIodmApp -> makeWorkingDocument(  curDocId,
                                                    curDocLocation,
                                                    FALSE,
                                                    IID_IodmUnknown,
                                                    (void **) pIUnk
                                                    );

    if (ourCode == S_OK)
         hWorking = NULL;
            /* So we don't close the document ourselves. */

    return ourCode;

    } /* OdmPending::commitContent */



/*                     ODMBINDPENDING100 FACTORY FUNCTION
                       ********************************** */

    /* FIXME: Add validation functions for the parameters and data
              elements accepted by OdmBindPending100.  We will add
              defensive code later.
              */


/*                OdmBindPending100: BIND AN OdmPending OBJECT
                  ******************************************** */

HRESULT WINAPI
    OdmBindPending100(    void *pIodmApp,
                                    /* Reference to an IodmApplication100
                                       interface that is valid for at
                                       least until OdmBinding100 returns.
                                       */
                        LPCSTR pszDocId,
                                    /* A legitimate ODMA Document ID for
                                       an open DMS document
                                       */
                        LPCSTR pszDocLoc,
                                    /* The valid ODMA Document Location for
                                       delivering the pszDocId[] document
                                       initial content as a file.
                                       */

                           REFIID  rIID,
                                       /* The requested interface */

                             void  **ppIface
                                       /* The location to receive the
                                          Interface pointer or NULL */
                       )

{   /* Instantiate the interface implementation that we have at this
       point.  Technically, this is a method of OdmApplication, but we
       have separated it for manageability reasons.
       */

    /* FIXME: There need to be parameter validations, although we can
              count on OdmWorking and OdmWorking::QueryInterface to
              defend themselves appropriately.
              */

    HRESULT rc = S_OK;

    OdmPending *pOdmPending
        = new(std::nothrow)
            OdmPending( (IUnknown *) pIodmApp,
                        pszDocId, pszDocLoc );

    if (pOdmPending == NULL) return E_OUTOFMEMORY;

        /* We have an OdmPending object with refCount = 0 and with
           nothing created that a simple delete can't handle at this point.
           */

    rc = pOdmPending -> QueryInterface(rIID, ppIface);
            /* It is assumed that the IodmPending object QueryInterface
               defends itself and will not crash. */

    if (rc != S_OK) delete pOdmPending;
        /* If we can't produce the interface the customer wants,
           delete the OdmPending object and pass on the bad news.

           If the interface is delivered, refCount = 1 now and it is
           up to the caller to release the interface and allow the
           implementing object to clean up and delete itself.
           (See the code for OdmPending::Release.)
           */

    return rc;

    } /* OdmBindPending100 */



/* 0.00 2007-01-15-20:50 Use OdmWorking100 0.06 as the pattern to whittle
        down to form OdmPending100.

   $Header: /ODMdev/info/odma/OdmNative100/OdmPending100.cpp 1     07-01-15 22:04 Orcmid $
   */

/*                     *** end of OdmPending100.cpp ***                  */

