#include <pch.cxx>
#pragma hdrstop

#define TRKDATA_ALLOCATE
#include "trkcom.hxx"
#include "trklib.hxx"

#if !defined(_UNICODE) || defined(OLE2ANSI)
#error This ILinkTrack implementation is only compatible on a Unicode build
#endif

//+----------------------------------------------------------------------------
//
//  Method:     CTrackFile/~CTrackFile
//
//  Synopsis:   Construction/Destruction
//
//  Arguments:  None
//
//  Returns:    None
//
//+----------------------------------------------------------------------------

CTrackFile::CTrackFile()
{
    _cRefs = 0;
    _fDirty = FALSE;
    _fLoaded = FALSE;
    memset( &_PersistentState, 0, sizeof(_PersistentState) );
}

CTrackFile::~CTrackFile()
{
}


//+----------------------------------------------------------------------------
//
//  Method:     IUnknown methods
//
//  Synopsis:   IUnknown
//
//+----------------------------------------------------------------------------

ULONG
CTrackFile::AddRef()
{
    long cNew;
    cNew = InterlockedIncrement( &_cRefs );
    return( cNew );
}

ULONG
CTrackFile::Release()
{
    long cNew;
    cNew = InterlockedDecrement( &_cRefs );
    if( 0 == cNew )
        delete this;

    return( cNew >= 0 ? cNew : 0 );
}

HRESULT
CTrackFile::QueryInterface( REFIID iid, void ** ppvObject )
{
    HRESULT hr = E_NOINTERFACE;

    // Parameter validation

    if( NULL == ppvObject )
    {
        hr = E_INVALIDARG;
        goto Exit;
    }


    *ppvObject = NULL;

    if( IID_IUnknown == iid
        ||
        IID_ITrackFile == iid )
    {
        AddRef();
        *ppvObject = (void*) (IUnknown*) (ITrackFile*) this;
        hr = S_OK;
    }
    else if( IID_ITrackFileRestricted == iid )
    {
        AddRef();
        *ppvObject = (void*) (ITrackFileRestricted*) this;
        hr = S_OK;
    }
    else if( IID_IPersistMemory == iid )
    {
        AddRef();
        *ppvObject = (void*) (IPersistMemory*) this;
        hr = S_OK;
    }
    else if( IID_IPersistStreamInit == iid )
    {
        AddRef();
        *ppvObject = (void*) (IPersistStreamInit*) this;
        hr = S_OK;
    }

Exit:

    return( hr );

}


//+----------------------------------------------------------------------------
//
//  Method:     CreateFromPath (ITrack*)
//
//  Synopsis:   Create a link client for a link source file.
//
//  Arguments:  [poszPath] (in)
//                  The file to which to link.
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------  

HRESULT
CTrackFile::CreateFromPath( const OLECHAR * poszPath )
{
    HRESULT hr = S_OK;
    NTSTATUS status = STATUS_SUCCESS;
    CDomainRelativeObjId droidCurrent, droidBirth;

    // Parameter validation

    if( NULL == poszPath )
    {
        hr = E_INVALIDARG;
        goto Exit;
    }

    __try
    {
        status = GetDroids( poszPath, &droidCurrent, &droidBirth, RGO_GET_OBJECTID );
        if( !NT_SUCCESS(status) )
        {
            hr = HRESULT_FROM_NT(status);
            goto Exit;
        }
    }
    __except( BreakOnDebuggableException() )
    {
        hr = GetExceptionCode();
    }
    if( FAILED(hr) ) goto Exit;

    _fLoaded = FALSE;
    InitNew();

    _fDirty = TRUE;
    _PersistentState.droidCurrent = droidCurrent;
    _PersistentState.droidBirth = droidBirth;

Exit:

    if( SUCCEEDED(hr) )
        TrkLog(( TRKDBG_CREATE, TEXT("Link created to %s"), poszPath ));

    hr = MapTR2HR( hr );

    return( hr );
}



//+----------------------------------------------------------------------------
//
//  Method:     Resolve (ITrack*)
//  
//  Synopsis:   Determine the current path of a link source.
//
//  Arguments:  [pcbPath] (in/out)
//                  In:  The size of the poszPath buffer
//                  Out: The actual path length (including the null terminator)
//              [poszPath] (out)
//                  The link source's current path.
//              [dwMillisecondTimeout] (in)
//                  A suggestion as to when this method should give up
//                  and return if it hasn't yet found the file.
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

// BUGBUG P1:  Optionally return the GetFileAttributesEx info, so that the
// shell doesn't have to re-open the file.

HRESULT
CTrackFile::Resolve( DWORD *pcbPath, OLECHAR * poszPath, DWORD dwMillisecondTimeout )
{
    return( Resolve( pcbPath, poszPath, dwMillisecondTimeout, TRK_MEND_DEFAULT ));
}


HRESULT
CTrackFile::Resolve( DWORD *pcbPath, OLECHAR * poszPath, DWORD dwMillisecondTimeout,
                     DWORD Restrictions )
{
    HRESULT hr = E_FAIL;
    CMachineId mcidLocal( MCID_LOCAL );
    CRpcClientBinding rc;
    CDomainRelativeObjId droidNew;
    CDomainRelativeObjId droidBirth;
    CDomainRelativeObjId droidCurrent;
    OLECHAR oszPathActual[ MAX_PATH + 1 ];
    DWORD cbPathActual = 0;
    CFILETIME cftDue;

    // Parameter validation

    if( NULL == pcbPath
        ||
        NULL == poszPath )
    {
        hr = E_INVALIDARG;
        goto Exit;
    }
        else if( !_fLoaded )
        {
                hr = E_UNEXPECTED;
                goto Exit;
        }

        __try
        {
                cftDue.IncrementMilliseconds( dwMillisecondTimeout );

                droidBirth = _PersistentState.droidBirth;
                droidCurrent = _PersistentState.droidCurrent;

                rc.RcInitialize( mcidLocal, s_tszTrkWksLocalRpcProtocol, s_tszTrkWksLocalRpcEndPoint );

                RpcTryExcept
                {
                        CMachineId mcidLast, mcidCurrent;
                        ULONG cbFileName = (MAX_PATH + 1) * sizeof(TCHAR);
                        CDomainRelativeObjId droidBirthNew;

                        hr = LnkMendLink( rc,
                                          cftDue,
                                          Restrictions,
                                          &droidBirth,
                                          &droidCurrent,
                                          &mcidLast,
                                          &droidBirthNew,
                                          &droidNew,
                                          &mcidCurrent,
                                          &cbFileName,
                                          oszPathActual );
                }
                RpcExcept( BreakOnDebuggableException() )
                {
                        hr = HRESULT_FROM_WIN32( RpcExceptionCode() );
                }
                RpcEndExcept;

                if( FAILED(hr) ) goto Exit;


                // Compare droidBirth and droidCurrent with the ones in _PersistentState.
                // If the same, do not set _fDirty.
                if(droidBirth != _PersistentState.droidBirth)
                {
                        _PersistentState.droidBirth = droidBirth;
                        _fDirty = TRUE;
                }
                if(droidNew != _PersistentState.droidCurrent)
                {
                        _PersistentState.droidCurrent = droidNew;
                        _fDirty = TRUE;
                }

                cbPathActual = ( ocslen(oszPathActual) + 1 ) * sizeof(OLECHAR);
                if( cbPathActual > *pcbPath )
                        hr = HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW);
                else
                        ocscpy( poszPath, oszPathActual );

                *pcbPath = cbPathActual;
                if( FAILED(hr) ) goto Exit;
        }
        __except( EXCEPTION_EXECUTE_HANDLER )
        {
                hr = GetExceptionCode();
                goto Exit;
        }


Exit:

    return( hr );

}



//+----------------------------------------------------------------------------
//
//  Method:     Open (ITrackFile)
//  
//  Synopsis:   Open the referent file ensure that its object ID is 
//              correct.  If the object ID is not correct, or the
//              file could not be found, then perform a Resolve.
//
//  Arguments:  [pcbPathHint] (in/out)
//                  In:  The size of the poszPathHint buffer
//                  Out: The actual path length (including the null terminator)
//              [poszPathHint] (in/out)
//                  The suggested path to the file.  If the path turns out not
//                  to be correct, an updated path is returned.
//              [dwMillisecondTimeout] (in)
//                  A suggestion as to when this method should give up
//                  and return if it hasn't yet found the file.
//              [dwDesiredAccess] (in)
//                  Access mode for the open file (see Win32 CreateFile)
//              [dwShareMode] (in)
//                  Sharing for the open file (see Win32 CreateFile)
//              [dwFlags] (in)
//                  Specifies the flags for the file (see the FILE_FLAG_*
//                  values in the Win32 CreateFile).
//              [phFile] (out)
//                  The open file handle.  It is because of this parameter
//                  That the ITrackFile interface is [local].
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

STDMETHODIMP CTrackFile::Open( /*in, out*/ DWORD * pcbPathHint,
                               /*in, out, size_is(*pcbPathHint), string*/ OLECHAR * poszPathHint,
                               /*in*/ DWORD dwMillisecondTimeout,
                               /*in*/ DWORD dwDesiredAccess,    // access (read-write) mode 
                               /*in*/ DWORD dwShareMode,        // share mode 
                               /*in*/ DWORD dwFlags,
                               /*out*/ HANDLE * phFile )
{
    return E_NOTIMPL;

/*

    HRESULT hr = S_OK;
    BOOL fTimeout = TRUE;
    NTSTATUS status = STATUS_SUCCESS;
    HANDLE hFile = INVALID_HANDLE_VALUE;
    FILE_OBJECTID_BUFFER fobOID;
    IO_STATUS_BLOCK Iosb;
    CObjId cobjidFile;
    DWORD dwTickCountTimeout, dwTickCountNow;

    //  ----------
    //  Initialize
    //  ----------

    *phFile = INVALID_HANDLE_VALUE;

    // Ensure we have an ObjectID to check against.

    if( !_fLoaded )
    {
        hr = E_UNEXPECTED;
        goto Exit;
    }

    // Calculate the absolute deadline.

    dwTickCountNow = GetTickCount();
    dwTickCountTimeout = dwTickCountNow + dwMillisecondTimeout;
    if( dwTickCountTimeout < dwTickCountNow )
    {
        // Bad dwMillisecondTimeout value
        hr = E_INVALIDARG;
        goto Exit;
    }

    //  -------------
    //  Open the File
    //  -------------

    do
    {
        // Open the file

        hFile = CreateFile( poszPathHint, dwDesiredAccess, dwShareMode, NULL,
                            OPEN_EXISTING, dwFlags, NULL );
        if( INVALID_HANDLE_VALUE == hFile )
        {
            hr = HRESULT_FROM_WIN32( GetLastError() );
            goto Exit;
        }

        // Get the object ID

        status = NtFsControlFile(
                     hFile,
                     NULL,
                     NULL,
                     NULL,
                     &Iosb,
                     FSCTL_GET_OBJECT_ID,
                     NULL,                  // In buffer
                     0,                     // In buffer size
                     &fobOID,               // Out buffer
                     sizeof(fobOID) );      // Out buffer size

        if( !NT_SUCCESS(status) )
        {
            hr = HRESULT_FROM_NT( status );
            goto Exit;
        }

        // Verify the object ID

        cobjidFile.Load( fobOID, LINK_TYPE_FILE );

        if( cobjidFile == _PersistentState.droidCurrent.GetObjId() )
        {
            // We found a good file and we're done.
            fTimeout = FALSE;
            break;
        }
        else
        {
            // Try to find the correct file.
            hr = Resolve( pcbPathHint, poszPathHint, dwTickCountTimeout - GetTickCount() );
            if( FAILED(hr) ) goto Exit;
        }
    
    }   while( GetTickCount() < dwTickCountTimeout );

    // Did the previous loop end because of a timeout?

    if( fTimeout )
    {
        hr = HRESULT_FROM_WIN32( ERROR_TIMEOUT );
        goto Exit;
    }

    // We completed successfully.

    *phFile = hFile;
    hFile = INVALID_HANDLE_VALUE;
    hr = S_OK;

    //  ----
    //  Exit
    //  ----

Exit:

    if( INVALID_HANDLE_VALUE != hFile )
        CloseHandle( hFile );

    return( hr );
*/
}   // CTrackFile::Open()



//+----------------------------------------------------------------------------
//
//  Method:     GetClassID (IPersistMemory & IPersistStreamInit)
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::GetClassID( CLSID *pClassID )
{
    if(NULL == pClassID)
    {
        return E_POINTER;
    }
    *pClassID = IID_ITrackFile;
    return( S_OK );
}


//+----------------------------------------------------------------------------
//
//  Method:     IsDirty (IPersistMemory & IPersistStreamInit)
//
//  Returns:    HRESULT
//                  S_OK => Dirty, S_FALSE => clean
//
//+----------------------------------------------------------------------------


STDMETHODIMP
CTrackFile::IsDirty()
{
    return( _fDirty ? S_OK : S_FALSE );
}


//+----------------------------------------------------------------------------
//
//  Method:     Load (IPersistMemory)
//
//  Synopsis:   Load the TrackFile object from persistent state.
//
//  Arguments:  [pvMem]
//                  Points to the serialization buffer.
//              [cbSize]
//                  Size of the serialization buffer.
//  
//  Returns:    None
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::Load( void * pvMem, ULONG cbSize )
{
    LinkTrackPersistentState PersistentState;

    if( NULL == pvMem )
        return( E_POINTER );

    else if( _fLoaded )
        return( E_UNEXPECTED );

    else if( sizeof(_PersistentState) > cbSize )
        return( E_INVALIDARG );

    else if( ( (LinkTrackPersistentState*) pvMem )->clsid != IID_ITrackFile )
        return( E_INVALIDARG );

    else if( ( (LinkTrackPersistentState*) pvMem )->cbSize < sizeof(_PersistentState) )
        return( E_INVALIDARG );

    else
    {
        _PersistentState = *(LinkTrackPersistentState*) pvMem;
        _fLoaded = TRUE;
        return( S_OK );
    }
        
}


//+----------------------------------------------------------------------------
//
//  Method:     Save(IPersistMemory)
//
//  Synopsis:   Save the persistent state to a memory buffer.
//
//  Arguments:  [pvMem]
//                  The buffer to which we'll save.
//              [fClearDirty]
//                  TRUE => we'll set _fDirty to FALSE on a successful save.
//              [cbSize]
//                  The available buffer in pvMem.
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::Save( void* pvMem, BOOL fClearDirty, ULONG cbSize )
{
    if( NULL == pvMem )
        return( E_POINTER );

    else if( !_fLoaded )
        return( E_UNEXPECTED );

    else if( sizeof(_PersistentState) > cbSize )
        return( E_INVALIDARG );

    else
    {
        *(LinkTrackPersistentState*) pvMem = _PersistentState;

        if( fClearDirty )
            _fDirty = FALSE;

        return( S_OK );
    }
}


//+----------------------------------------------------------------------------
//
//  Method:     InitNew
//
//  Synopsis:   Initialize the TrackFile object.
//
//  Arguments:  None
//
//  Returns:    None
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::InitNew()
{
    if( _fLoaded )
        return( E_UNEXPECTED );
    else
    {
        memset( &_PersistentState, 0, sizeof(_PersistentState) );
        _PersistentState.cbSize = sizeof(_PersistentState);
        _PersistentState.clsid = IID_ITrackFile;
        _fLoaded = TRUE;
        return( S_OK );
    }
}


//+----------------------------------------------------------------------------
//
//  Method:     GetSizeMax (IPersistMemory)
//
//  Synopsis:   Returns the size necessary to pass to IPersist:Save
//
//  Arguments:  [pcbSize]
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::GetSizeMax( ULONG *pcbSize )
{
    if( NULL == pcbSize )
        return( E_POINTER );
    else
    {
        *pcbSize = sizeof(_PersistentState);
        return( S_OK );
    }
}

//+----------------------------------------------------------------------------
//
//  Method:     GetSizeMax (IPersistStreamInit)
//
//  Synopsis:   Returns the size necessary.
//
//  Arguments:  [pcbSize]
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::GetSizeMax( ULARGE_INTEGER* pcbSize )
{
    if( NULL == pcbSize )
        return( E_POINTER );
    else
    {
        pcbSize->QuadPart = sizeof(_PersistentState);
        return( S_OK );
    }
}

//+----------------------------------------------------------------------------
//
//  Method:     Load (IPersistStreamInit)
//
//  Synopsis:   Load the TrackFile object from a stream.
//
//  Arguments:  [pStm]
//                  Points to the IStream interface.
//  
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::Load(IStream* pStm)
{
    HRESULT                     hr;                     // return value
    LinkTrackPersistentState    PersistentState;        // tmp storage
    ULONG                       cbRead;                 // # of bytes read
    LARGE_INTEGER               cbOffset;               // = -cbRead
    ULONG                       cbSize = sizeof(_PersistentState);

    if(NULL == pStm)
        return(E_POINTER);

    else if(_fLoaded)
        return(E_UNEXPECTED);

    // Read _PersistentState from the stream and check if the read is
    // successful. If not, revert back the seek pointer in pStm, and
    // return the HRESULT.
    hr = pStm->Read((byte*)&PersistentState, cbSize, &cbRead);
    if(FAILED(hr) || cbSize != cbRead)
    {
        cbOffset.QuadPart = -static_cast<LONGLONG>(cbRead);
        goto Exit;
    }

    // So now we successfully read the _PersistentState into memory, check to
    // see if we read garbage. If so, revert and return the error.
    // xxx What error message should be returned for this?
    if(PersistentState.clsid != IID_ITrackFile)
    {
        cbOffset.QuadPart = -static_cast<LONGLONG>(cbRead);
        hr = E_FAIL;
        goto Exit;
    }

    // Everything went well. Now we can copy _PersistentState from its
    // temporary storage to its real storage.
    _PersistentState = PersistentState;
    _fLoaded = TRUE;
    return(S_OK);

Exit:
        
    pStm->Seek(cbOffset, STREAM_SEEK_CUR, NULL);
    return(hr);
}

//+----------------------------------------------------------------------------
//
//  Method:     Save (IPersistStreamInit)
//
//  Synopsis:   Save the persistent state to a stream.
//
//  Arguments:  [pStm]
//                  The IStream interface we use to save.
//              [fClearDirty]
//                  TRUE => we'll set _fDirty to FALSE on a successful save.
//
//  Returns:    HRESULT
//
//+----------------------------------------------------------------------------

STDMETHODIMP
CTrackFile::Save(IStream* pStm, BOOL fClearDirty)
{
    HRESULT         hr;
    ULONG           cbSize = sizeof(_PersistentState);
    ULONG           cbWritten;                          // # of bytes written
    LARGE_INTEGER   cbOffset;                           // same as cbWritten

    if(NULL == pStm)
        return(E_POINTER);

    else if( !_fLoaded )
        return(E_UNEXPECTED);

    else
    {
        // Write the _PersistentState to the stream and check the return value.
        // If failed, revert the changes in IStream and return the HRESULT.
        hr = pStm->Write((byte*)&_PersistentState, cbSize, &cbWritten);
        if(FAILED(hr))
        {
            cbOffset.QuadPart = -static_cast<LONGLONG>(cbWritten);
            pStm->Seek(cbOffset, STREAM_SEEK_CUR, NULL);
            return hr;
        }

        if(fClearDirty)
            _fDirty = FALSE;

        return(S_OK);
    }
}