/******************************************************************************

Copyright (c) 2000 Microsoft Corporation

Module Name:
    PurgeEngine.cpp

Abstract:
    This file contains the implementation of the MPCPurgeEngine class,
    that controls the cleaning of the temporary directories.

Revision History:
    Davide Massarenti   (Dmassare)  07/12/99
        created

******************************************************************************/

#include "stdafx.h"


HRESULT MPCPurgeEngine::Process()
{
    __ULT_FUNC_ENTRY("MPCPurgeEngine::Process");

    HRESULT                  hr;
    HRESULT                  hr2;
    CISAPIconfig::Iter       itInstanceBegin;
    CISAPIconfig::Iter       itInstanceEnd;
    CISAPIinstance::PathIter itPathBegin;
    CISAPIinstance::PathIter itPathEnd;
    double                   dblNow = MPC::GetSystemTime();


    //
    // Enumerate all the instances.
    //
    __MPC_EXIT_IF_METHOD_FAILS(hr, g_Config.GetInstances( itInstanceBegin, itInstanceEnd ));
    for(;itInstanceBegin != itInstanceEnd; itInstanceBegin++)
    {
        __MPC_EXIT_IF_METHOD_FAILS(hr, itInstanceBegin->get_URL               ( m_szURL                ));
        __MPC_EXIT_IF_METHOD_FAILS(hr, itInstanceBegin->get_QueueSizeMax      ( m_dwQueueSizeMax       ));
        __MPC_EXIT_IF_METHOD_FAILS(hr, itInstanceBegin->get_QueueSizeThreshold( m_dwQueueSizeThreshold ));
        __MPC_EXIT_IF_METHOD_FAILS(hr, itInstanceBegin->get_MaximumJobAge     ( m_dwMaximumJobAge      ));

        m_dblMaximumJobAge = dblNow - m_dwMaximumJobAge;


		MPCServer mpcsServer( NULL, m_szURL.c_str(), NULL );
		m_mpcsServer = &mpcsServer;

        //
        // For each instance, enumerate all the temporary directories.
        //
        __MPC_EXIT_IF_METHOD_FAILS(hr, itInstanceBegin->GetLocations( itPathBegin, itPathEnd ));
        for(;itPathBegin != itPathEnd; itPathBegin++)
        {
            MPC::FileSystemObject fso( itPathBegin->c_str() );

            if(SUCCEEDED(hr2 = fso.Scan( true )))
            {
                DWORD dwTotalSize = 0;

                m_lstClients.clear();

                __MPC_EXIT_IF_METHOD_FAILS(hr, AnalyzeFolders( &fso, dwTotalSize ));

                __MPC_EXIT_IF_METHOD_FAILS(hr, RemoveOldJobs( dwTotalSize ));

                if(dwTotalSize > m_dwQueueSizeMax)
                {
                    while(dwTotalSize && dwTotalSize > m_dwQueueSizeThreshold)
                    {
                        __MPC_EXIT_IF_METHOD_FAILS(hr, RemoveOldestJob   ( dwTotalSize ));
                        __MPC_EXIT_IF_METHOD_FAILS(hr, RemoveEmptyClients( dwTotalSize ));
                    }
                }
            }
        }
    }

    hr = S_OK;


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(hr);
}

/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////

static bool MatchExtension( const MPC::wstring& szPath ,
                            LPCWSTR             szExt  )
{
    MPC::wstring::size_type iPos;

    iPos = szPath.find( szExt, 0 );
    if(iPos != MPC::wstring::npos && iPos + wcslen( szExt ) == szPath.length())
    {
        return true;
    }

    return false;
}

HRESULT MPCPurgeEngine::AnalyzeFolders( /*[in]*/ MPC::FileSystemObject* fso         ,
                                        /*[in]*/ DWORD&                 dwTotalSize )
{
    __ULT_FUNC_ENTRY("MPCPurgeEngine::AnalyzeFolders");

    HRESULT                     hr;
    HRESULT                     hr2;
    MPC::FileSystemObject::List lst;
    MPC::FileSystemObject::Iter it;


    //
    // Process all folders.
    //
    __MPC_EXIT_IF_METHOD_FAILS(hr, fso->EnumerateFolders( lst ));
    for(it = lst.begin(); it != lst.end(); it++)
    {
        __MPC_EXIT_IF_METHOD_FAILS(hr, AnalyzeFolders( *it, dwTotalSize ));
    }

    //
    // Process all files.
    //
    __MPC_EXIT_IF_METHOD_FAILS(hr, fso->EnumerateFiles( lst ));
    for(it = lst.begin(); it != lst.end(); it++)
    {
        MPC::wstring szPath;

        __MPC_EXIT_IF_METHOD_FAILS(hr, (*it)->get_Path( szPath ));

        if(MatchExtension( szPath, CLIENT_CONST__DB_EXTENSION ))
        {
            __MPC_EXIT_IF_METHOD_FAILS(hr, AddClient( szPath, dwTotalSize ));
        }
        else if(MatchExtension( szPath, SESSION_CONST__IMG_EXTENSION ))
        {
            ;
        }
        else
        {
            //
            // Any other file should be deleted.
            //
            (void)(*it)->Delete();
        }
    }

    hr = S_OK;


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(hr);
}

/////////////////////////////////////////////////////////////////////////////

HRESULT MPCPurgeEngine::AddClient( /*[in]*/     const MPC::wstring& szPath      ,
                                   /*[in/out]*/ DWORD&              dwTotalSize )
{
    __ULT_FUNC_ENTRY("MPCPurgeEngine::AddClient");

    HRESULT   hr;
    HRESULT   hr2;
    MPCClient mpccClient( m_mpcsServer, szPath );
    Iter      itClient = m_lstClients.insert( m_lstClients.end(), MPCPurge_ClientSummary( szPath ) );

    if(SUCCEEDED(hr2 = mpccClient.InitFromDisk( false )))
    {
        MPCClient::Iter itBegin;
        MPCClient::Iter itEnd;

        //
        // Adjust total count with size of the Directory File.
        //
        __MPC_EXIT_IF_METHOD_FAILS(hr, mpccClient.GetFileSize( itClient->m_dwFileSize ));
        dwTotalSize += itClient->m_dwFileSize;


        __MPC_EXIT_IF_METHOD_FAILS(hr, mpccClient.GetSessions( itBegin, itEnd ));
        while(itBegin != itEnd)
        {
            MPCPurge_SessionSummary pssSession;

            itBegin->get_JobID       ( pssSession.m_szJobID         );
            itBegin->get_LastModified( pssSession.m_dblLastModified );
            itBegin->get_CurrentSize ( pssSession.m_dwCurrentSize   );

            //
            // Don't count "committed" jobs in total size, because the file has already been moved.
            //
            if(itBegin->get_Committed())
            {
                pssSession.m_dwCurrentSize = 0;
            }

            itClient->m_lstSessions.push_back( pssSession );

            dwTotalSize += pssSession.m_dwCurrentSize;
            itBegin++;
        }
    }

    hr = S_OK;


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(hr);
}

/////////////////////////////////////////////////////////////////////////////

HRESULT MPCPurgeEngine::RemoveOldJobs( /*[in/out]*/ DWORD& dwTotalSize )
{
    __ULT_FUNC_ENTRY("MPCPurgeEngine::RemoveOldJobs");

    HRESULT hr;
    HRESULT hr2;
    Iter    it;

    for(it = m_lstClients.begin(); it != m_lstClients.end(); it++)
    {
        MPCPurge_ClientSummary::Iter itSession;
        MPCClient                    mpccClient( m_mpcsServer, it->m_szPath );
        bool                         fInitialized = false;

        while(it->GetOldestSession( itSession ))
        {
            //
            // If the oldest session is younger than the limit, leave the loop.
            //
            if(itSession->m_dblLastModified > m_dblMaximumJobAge)
            {
                break;
            }

            __MPC_EXIT_IF_METHOD_FAILS(hr, RemoveSession( mpccClient, fInitialized, it, itSession, dwTotalSize ));
        }
    }

    hr = S_OK;


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(hr);
}

/////////////////////////////////////////////////////////////////////////////

HRESULT MPCPurgeEngine::RemoveOldestJob( /*[in/out]*/ DWORD& dwTotalSize )
{
    __ULT_FUNC_ENTRY("MPCPurgeEngine::RemoveOldestJob");

    HRESULT hr;
    HRESULT hr2;
    Iter    it;
    Iter    itOldestClient;
    double  dblOldestClient = DBL_MAX;
    bool    fFound          = false;

    //
    // Look for the oldest job.
    //
    for(it = m_lstClients.begin(); it != m_lstClients.end(); it++)
    {
        if(it->m_dblLastModified < dblOldestClient)
        {
            itOldestClient  = it;
            dblOldestClient = it->m_dblLastModified;
            fFound          = true;
        }
    }

    if(fFound)
    {
        MPCPurge_ClientSummary::Iter itSession;
        MPCClient                    mpccClient( m_mpcsServer, itOldestClient->m_szPath );
        bool                         fInitialized = false;

        if(itOldestClient->GetOldestSession( itSession ))
        {
            __MPC_EXIT_IF_METHOD_FAILS(hr, RemoveSession( mpccClient, fInitialized, itOldestClient, itSession, dwTotalSize ));

            //
            // Update the m_dblLastModified of the MPCPurge_ClientSummary object.
            //
            itOldestClient->GetOldestSession( itSession );
        }

        if(fInitialized)
        {
            DWORD dwPost;

            __MPC_EXIT_IF_METHOD_FAILS(hr, mpccClient.SyncToDisk (        ));
            __MPC_EXIT_IF_METHOD_FAILS(hr, mpccClient.GetFileSize( dwPost ));

            //
            // Update Directory File size.
            //
            dwTotalSize                  -= itOldestClient->m_dwFileSize;
            dwTotalSize                  += dwPost;
            itOldestClient->m_dwFileSize  = dwPost;
        }
    }

    hr = S_OK;


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(hr);
}

/////////////////////////////////////////////////////////////////////////////

HRESULT MPCPurgeEngine::RemoveSession( /*[in]*/     MPCClient&                    mpccClient   ,
                                       /*[in/out]*/ bool&                         fInitialized ,
                                       /*[in]*/     Iter                          itClient     ,
                                       /*[in]*/     MPCPurge_ClientSummary::Iter& itSession    ,
                                       /*[in/out]*/ DWORD&                        dwTotalSize  )
{
    __ULT_FUNC_ENTRY("MPCPurgeEngine::RemoveSession");

    HRESULT hr;
    HRESULT hr2;


    //
    // Lock the client.
    //
    if(fInitialized == false)
    {
        if(SUCCEEDED(hr2 = mpccClient.InitFromDisk( false )))
        {
            fInitialized = true;
        }
    }

    if(fInitialized)
    {
        MPCClient::Iter itSessionReal;

        //
        // If the session exists, remove it.
        //
        if(mpccClient.Find( itSession->m_szJobID, itSessionReal ) == true)
        {
            __MPC_EXIT_IF_METHOD_FAILS(hr, itSessionReal->RemoveFile());

            mpccClient.Erase( itSessionReal );
        }

        //
        // Update the total size counter and remove the session from memory.
        //
        dwTotalSize -=                 itSession->m_dwCurrentSize;
        itClient->m_lstSessions.erase( itSession );
    }

    hr = S_OK;


    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(hr);
}

/////////////////////////////////////////////////////////////////////////////

HRESULT MPCPurgeEngine::RemoveEmptyClients( /*[in/out]*/ DWORD& dwTotalSize )
{
    __ULT_FUNC_ENTRY("MPCPurgeEngine::RemoveEmptyClients");

    HRESULT hr;
    Iter    it;

    for(it = m_lstClients.begin(); it != m_lstClients.end(); it++)
    {
        //
        // If the client has no more sessions, don't count it.
        //
        if(it->m_lstSessions.size() == 0)
        {
            dwTotalSize -= it->m_dwFileSize;

            m_lstClients.erase( it ); it = m_lstClients.begin();
        }
    }

    hr = S_OK;


    //    __ULT_FUNC_CLEANUP;

    __ULT_FUNC_EXIT(hr);
}


/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

MPCPurge_ClientSummary::MPCPurge_ClientSummary( const MPC::wstring& szPath )
{
    m_szPath          = szPath; // MPC::wstring m_szPath;
                              	// List         m_lstSessions;
    m_dwFileSize      = 0;    	// DWORD        m_dwFileSize;
    m_dblLastModified = 0;    	// double       m_dblLastModified;
}

bool MPCPurge_ClientSummary::GetOldestSession( /*[out]*/ Iter& itSession )
{
    Iter it;

    m_dblLastModified = DBL_MAX;
    itSession         = m_lstSessions.end();

    for(it = m_lstSessions.begin(); it != m_lstSessions.end(); it++)
    {
        if(it->m_dblLastModified < m_dblLastModified)
        {
            itSession = it;
            m_dblLastModified = it->m_dblLastModified;
        }
    }

    return (itSession != m_lstSessions.end());
}