//=--------------------------------------------------------------------------=
// inseng.cpp
//=--------------------------------------------------------------------------=
// Copyright 1995-1996 Microsoft Corporation.  All Rights Reserved.
//
//
#include "inspch.h"
#include "regstr.h"
#include "globals.h"
#include "insobj.h"
#include "resource.h"
#include "diskspac.h"

#define GRPCONV  "grpconv -o"

#define BUFFERSIZE 4096



//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

CInstallEngine::CInstallEngine(IUnknown **punk)
{
   DWORD dwThreadID;
   HANDLE hThread;
   HKEY hKey = NULL;
   char szBuf[16];
   DWORD dwType;

   GetWindowsDirectory(g_szWindowsDir, sizeof(g_szWindowsDir));
   if(g_szWindowsDir[0] >= 'a' && g_szWindowsDir[0] <= 'z')
      g_szWindowsDir[0] -= 32;

   hThread = CreateThread(NULL, 0, CleanUpAllDirs, NULL, 0, &dwThreadID);
   CloseHandle(hThread);
   _chInsDrive = g_szWindowsDir[0];
   
   // Decide whether we are in stepping mode or not
   _uCommandMode = 0;
   _fSteppingMode = FALSE;
   _fResetTrust = TRUE;
   _fIgnoreTrust = FALSE;
   if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, ACTIVESETUP_KEY,0, KEY_READ | KEY_WRITE, &hKey) == ERROR_SUCCESS)
   {
      // Get Steeping mode value. OK if not present
      DWORD dwSize = sizeof(szBuf);
      if(RegQueryValueEx(hKey, STEPPING_VALUE, NULL, &dwType, (LPBYTE) szBuf, &dwSize) == ERROR_SUCCESS)
      {
         if(szBuf[0] == 'y' || szBuf[0] == 'Y')
            _fSteppingMode = TRUE;
      }
 
      // Get CommandMode value. OK if not present
      dwSize = sizeof(szBuf);
      if(RegQueryValueEx(hKey, COMMAND_VALUE, NULL, &dwType, (LPBYTE) szBuf, &dwSize) == ERROR_SUCCESS)
      {
         _uCommandMode = AtoL(szBuf);
         // Once we read it, set it to zero
         // BUGBUG: beware of hardcoded "0" and 2 below (2 includes null terminator)
         RegSetValueEx(hKey, COMMAND_VALUE, 0, REG_SZ, (BYTE *) "0", 2 ); 
      }

      if(RegQueryValueEx(hKey, CHECKTRUST_VALUE, NULL, &dwType, (LPBYTE) szBuf, &dwSize) == ERROR_SUCCESS)
      {
         if(szBuf[0] == 'Y' || szBuf[0] == 'y')
         {
            _fIgnoreTrust = TRUE;
            _fResetTrust = FALSE;
         }
         // Once we read it, set it to zero
         // BUGBUG: beware of hardcoded "0" and 2 below (2 includes null terminator)
         RegDeleteValue(hKey, CHECKTRUST_VALUE); 
      }
 
 
      RegCloseKey(hKey);
   }
    
   _hwndForUI = NULL;
   _pStmLog = NULL;
   _fIgnoreDownloadError = FALSE;
   _enginestatus = ENGINESTATUS_NOTREADY;
   _dwStatus = 0;
   _pcb = NULL;
   _cRef = 0;
   _dwDLRemaining = 0;
   _dwInstallRemaining = 0;
   _dwInstallOld = 0;
   _dwDLOld = 0;
   _fUseCache = FALSE;
   _dwInstallOptions = INSTALLOPTIONS_DOWNLOAD | INSTALLOPTIONS_INSTALL;
   _hContinue = NULL;
   _hAbort = NULL;
   _fCleanUpDir = FALSE;
   //init CCifFile
   _pCif = new CCifFile();
   _pCif->AddRef();
   _pCif->SetInstallEngine(this);
   // init downloader
   _pDL = new CDownloader();
   _pIns = new CInstaller(this);

   _szBaseUrl[0] = 0;

   _fSRLiteAvailable = IsPatchableIEVersion() && IsCorrectAdvpExt() && InitSRLiteLibs();
   if (!_fSRLiteAvailable)
       WriteToLog("Install engine failed to initialize the advpack extension DLL\r\n", FALSE);
   
   _pPDL = new CPatchDownloader(_fSRLiteAvailable);

   AddRef();
   *punk = (IInstallEngine *) this;
}

//=--------------------------------------------------------------------------=
// CInstallEngine::~CInstallEngine
//=--------------------------------------------------------------------------=
// Destructor for InstallEngine class
//
// Parameters:
//   
// Returns:
//
// Notes:
//

CInstallEngine::~CInstallEngine()
{
   char szBuf[MAX_PATH];

   WriteToLog("Install Engine - object destroyed\r\n", TRUE);
   
   if(_fCleanUpDir)
   {
      lstrcpy(szBuf, _pCif->GetDownloadDir());
   }

   if(_hAbort)
      CloseHandle(_hAbort);
   
   if(_hContinue)
      CloseHandle(_hContinue);
      
   if(_pStmLog)
      _pStmLog->Release();

   _pcb = NULL;
                               
   _pCif->Release();

   _pDL->Release();

   delete _pPDL;

   _pIns->Release();

   if(_fCleanUpDir)
   {
      CleanUpTempDir(szBuf);
   }

   FreeSRLiteLibs();

   DllRelease();
}

//************ IUnknown implementation ***************

//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP_(ULONG) CInstallEngine::AddRef()                      
{
   return(_cRef++);
}

//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP_(ULONG) CInstallEngine::Release()
{
   ULONG temp = --_cRef;

   if(temp == 0)
      delete this;
   return temp;
}

//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::QueryInterface(REFIID riid, void **ppv)
{
   *ppv = NULL;

   if((riid == IID_IUnknown) || (riid == IID_IInstallEngine))
      *ppv = (IInstallEngine *)this;
   else if(riid == IID_IInstallEngineTiming)
      *ppv = (IInstallEngineTiming *)this;
   else if(riid == IID_IInstallEngine2)
      *ppv = (IInstallEngine2 *)this;

   
   if(*ppv == NULL)
      return E_NOINTERFACE;
   
   AddRef();
   return NOERROR;
}

//************* IInstallEngine interface ************

//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::SetLocalCif(LPCSTR pszCifPath)
{
   HCURSOR hNew = NULL;
   HCURSOR hOld = NULL;
   
   OnEngineStatusChange(ENGINESTATUS_LOADING, 0);
   
   hNew = LoadCursor(NULL, IDC_WAIT);
   hOld = SetCursor(hNew);
   
   HRESULT hr = _pCif->SetCifFile(pszCifPath, FALSE);
   if(SUCCEEDED(hr))
      OnEngineStatusChange(ENGINESTATUS_READY, 0);
   else 
      OnEngineStatusChange(ENGINESTATUS_NOTREADY, hr);
   
   SetCursor(hOld);
   
   return hr;
}

STDMETHODIMP CInstallEngine::GetICifFile(ICifFile **pic)
{
   *pic = (ICifFile *) _pCif;
   (*pic)->AddRef();
   return NOERROR;
}

STDMETHODIMP CInstallEngine::GetEngineStatus(DWORD * theenginestatus)
{
  *theenginestatus = _enginestatus;
   return(NOERROR);
}

//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::Abort(DWORD lFlag)
{
   // If we are NOT downLOADING or INSTALLING, an abort command makes no sense.
   if( !(_enginestatus == ENGINESTATUS_INSTALLING || _enginestatus == ENGINESTATUS_LOADING) )
      return E_UNEXPECTED;

   // if we are downloading, this will cause the abort to filter thru
   _pDL->Abort();

   // this will abort an install if possible
   _pIns->Abort();
   
   // any other time, we will pick this up just as soon as we can
   SetEvent(_hAbort);

   WriteToLog("Install Engine - Abort called\r\n", FALSE);
   
   return(NOERROR);
}


//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::Suspend()
{
   HRESULT hr;

   if(_enginestatus != ENGINESTATUS_INSTALLING)
      return E_UNEXPECTED;

   WriteToLog("Install Engine - Suspend called\r\n", FALSE);
   
 
   _pDL->Suspend();

   // we only catch suspend return, because it tells us "zsafe to cancel or not"
   hr = _pIns->Suspend();
   
   ResetEvent(_hContinue);
 
   // If we cant create the resume event, we will fail this call and not pause
        
   return hr;
}

//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::Resume()
{
   if(_enginestatus != ENGINESTATUS_INSTALLING)
      return E_UNEXPECTED;

   WriteToLog("Install Engine - Resume called\r\n", FALSE);
   
   _pDL->Resume();

   _pIns->Resume();

   SetEvent(_hContinue);
   
   return NOERROR;
}
//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::SetCifFile(LPCSTR pszCabName, LPCSTR pszCifName)
{
   HRESULT hr = NOERROR;
   HANDLE hThread;
   DWORD dwThreadID;

   if(_enginestatus == ENGINESTATUS_LOADING || _enginestatus == ENGINESTATUS_INSTALLING)
      return E_UNEXPECTED;

   SETCIFARGS *p = new SETCIFARGS;

   if (p == NULL)
   {
       hr = E_OUTOFMEMORY;
       goto Cleanup;
   }

   if(_szBaseUrl[0] != 0)
   {
      if (lstrlen(_szBaseUrl) + lstrlen(pszCabName) + 2 > INTERNET_MAX_URL_LENGTH)
      {
          hr = E_INVALIDARG;
          goto Cleanup;
      }

      p->szUrl[0] = '\0';
      lstrcpy(p->szUrl, _szBaseUrl);
      lstrcat(p->szUrl, "/");
      lstrcat(p->szUrl, pszCabName);
   }
   else
   {
      lstrcpy(p->szUrl, "file://");
      lstrcat(p->szUrl, _pCif->GetDownloadDir());
      SafeAddPath(p->szUrl, pszCabName, sizeof(p->szUrl));
   }

   lstrcpyn(p->szCif, pszCifName, MAX_PATH);

   p->pCif = _pCif;
    
   // do the actual downloading of the CIF file in a separate thread
   if ((hThread = CreateThread(NULL, 0, DownloadCifFile, (LPVOID) p, 0, &dwThreadID)) != NULL)
      CloseHandle(hThread);
   else
   {
      hr = HRESULT_FROM_WIN32(GetLastError());
   }
   
Cleanup:

   if (FAILED(hr) && p != NULL)
       delete p;

   return hr;
}

//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::SetBaseUrl(LPCSTR pszBaseName)
{
   DWORD dwLen;

   if(_enginestatus == ENGINESTATUS_INSTALLING)
      if(!_IsValidBaseUrl(pszBaseName))
         return E_UNEXPECTED;
   
   lstrcpyn(_szBaseUrl, pszBaseName, INTERNET_MAX_URL_LENGTH);

   wsprintf(szLogBuf,"Install Engine - base url set to %s\r\n", pszBaseName);
   WriteToLog(szLogBuf, FALSE);

   return NOERROR;
}


//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::SetDownloadDir(LPCSTR pszDLDir)
{
   char szBuf[MAX_PATH];
   DWORD dwLen;
   DWORD dwVer;
   
   if(_enginestatus == ENGINESTATUS_INSTALLING)
      return E_UNEXPECTED;
   
   if(pszDLDir != NULL && lstrlen(pszDLDir) > (MAX_PATH - 20))
      return E_FAIL;

   // clean up what we have
   if(_fCleanUpDir)
   {
      DelNode(_pCif->GetDownloadDir(), 0);
      _fCleanUpDir = FALSE;
   }

   if(pszDLDir == NULL)
   {
      _fCleanUpDir = TRUE;
      if(FAILED(CreateTempDirOnMaxDrive(szBuf, sizeof(szBuf))))
         return E_FAIL;
   }
   else
   {
      _fCleanUpDir = FALSE;
      // Make sure the directory exists
      if(GetFileAttributes(pszDLDir) == 0xffffffff)
         CreateDirectory(pszDLDir, NULL);
   }

   _pCif->SetDownloadDir(pszDLDir ? pszDLDir : szBuf);

   wsprintf(szLogBuf,"Install Engine - download directory set to %s\r\n", _pCif->GetDownloadDir());
   WriteToLog(szLogBuf, FALSE);

   return NOERROR;
}



//=--------------------------------------------------------------------------=
// Function name here
//=--------------------------------------------------------------------------=
// Function description
//
// Parameters:
//   
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::IsComponentInstalled(LPCSTR pszComponentID, DWORD *lResult)
{
   DWORD dwResult = ICI_NOTINSTALLED;
   
   ICifComponent *pComp = NULL;
   
   if(SUCCEEDED(_pCif->FindComponent(pszComponentID, &pComp)))
   {
      dwResult = pComp->IsComponentInstalled();
   }
      
   *lResult = dwResult;

   return(pComp ? NOERROR : E_INVALIDARG);
}


STDMETHODIMP CInstallEngine::SetInstallDrive(CHAR chDrive)
{
   HRESULT hr = E_INVALIDARG;

   if(chDrive >= 'a' && chDrive <= 'z')
      chDrive -= 32;

   if(chDrive >= 'A' && chDrive <= 'Z')
   {
      hr = NOERROR;
      _chInsDrive = chDrive;
   }   
   return hr;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::SetInstallOptions(DWORD dwOptions)
{
   _fUseCache = !(dwOptions & INSTALLOPTIONS_NOCACHE);
   _dwInstallOptions = dwOptions;
   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::GetInstallOptions(DWORD *pdwOptions)
{
    if (!pdwOptions)
        return E_POINTER;
    else
    {
        *pdwOptions = _dwInstallOptions;
        return NOERROR;
    }
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::GetSizes(LPCSTR pszID, COMPONENT_SIZES *p) 
{
   if(!p)
      return E_POINTER;

   // work around bug in old versions of jobexec where it didn't init
   // this field properly

   if(p->cbSize > sizeof(COMPONENT_SIZES))
      p->cbSize = COMPONENTSIZES_SIZE_V1;

   DWORD dwSize = p->cbSize; 
   ZeroMemory(p, p->cbSize);
   p->cbSize = dwSize;
   
   if(_enginestatus != ENGINESTATUS_READY)
      return E_UNEXPECTED;
  
   if(pszID != NULL)
   {
      ICifComponent *pComp = NULL;

      if(SUCCEEDED(_pCif->FindComponent(pszID, &pComp)))
      {
         DWORD dwWin, dwApp;
         p->dwDownloadSize = pComp->GetDownloadSize();
         pComp->GetInstalledSize(&dwWin, &dwApp);
         p->dwInstallSize = dwApp;
         p->dwWinDriveSize = dwWin;
      }
      else
         return E_INVALIDARG;
   }
   else
      _GetTotalSizes(p);
   
   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//


void CInstallEngine::_GetTotalSizes(COMPONENT_SIZES *pSizes)
{
   ICifComponent *pComp;
   DriveInfo drvinfo[3];
   LPSTR pszDep = NULL;
   UINT uTempDrive;
   UINT uWinDrive = 0;
   UINT uInstallDrive = 1;
   UINT uDownloadDrive = 2;
   LPCSTR pszDownloadDir = _pCif->GetDownloadDir();
   COMPONENT_SIZES   Sizes;

   ZeroMemory(&Sizes, sizeof(COMPONENT_SIZES));
   
   // Fill in all arrays to start
   
   drvinfo[uWinDrive].InitDrive(g_szWindowsDir[0]);
   
   if(_dwInstallOptions & INSTALLOPTIONS_INSTALL)
   {
      // We know we can do a compare because these are always uppcase
      if(_chInsDrive != drvinfo[uWinDrive].Drive())
      {
         drvinfo[uInstallDrive].InitDrive(_chInsDrive);
      }
      else 
         uInstallDrive = uWinDrive;
   }

   if(_dwInstallOptions & INSTALLOPTIONS_DOWNLOAD)
   {
      if(pszDownloadDir[0] == drvinfo[uWinDrive].Drive())
         uDownloadDrive = uWinDrive;
      else if(pszDownloadDir[0] == drvinfo[uInstallDrive].Drive())
         uDownloadDrive = uInstallDrive;
      else
         drvinfo[uDownloadDrive].InitDrive(pszDownloadDir[0]);
   }

   // do space for download phase (easy part)
   if(_dwInstallOptions & INSTALLOPTIONS_DOWNLOAD)
   {
      Sizes.dwDownloadSize = _GetActualDownloadSize(FALSE);
      Sizes.dwTotalDownloadSize = _GetTotalDownloadSize();
      Sizes.dwDependancySize = 0;

      // add download to download drive
      drvinfo[uDownloadDrive].UseSpace(Sizes.dwDownloadSize + Sizes.dwDependancySize, TRUE);
   
      // if going to cache
      // BUGBUG: we still assume cache is on windows drive
      if(_fUseCache)
      {
         drvinfo[uWinDrive].UseSpace(Sizes.dwDownloadSize + Sizes.dwDependancySize, TRUE);
      }
   }

   // do space for install (hard part)
   if(_dwInstallOptions & INSTALLOPTIONS_INSTALL)
   {
      // walk the install list in order (very important)
      // do any dependancy, then original
      IEnumCifComponents *penum;
      ICifComponent *pComp = NULL;

      _pCif->EnumComponents(&penum, 0, NULL);
      for(penum->Next(&pComp); pComp; penum->Next(&pComp))
      {
         if(pComp->GetInstallQueueState() == SETACTION_INSTALL)
         {
            DWORD dwWin, dwApp;
            
            pComp->GetInstalledSize(&dwWin, &dwApp);
            // add install
            // add the install size
            Sizes.dwInstallSize += dwApp;
            // size that goes to windows dir
            Sizes.dwWinDriveSize += dwWin;
                           
            drvinfo[uInstallDrive].UseSpace(dwApp, FALSE);
            drvinfo[uWinDrive].UseSpace(dwWin, FALSE);
            // Add (and then remove) temp space
            AddTempSpace(pComp->GetDownloadSize(), pComp->GetExtractSize(), drvinfo);
                           
          
         }
      }
      penum->Release();
   }
    
   // fill in the required amounts
   Sizes.dwWinDriveReq = drvinfo[uWinDrive].MaxUsed();
   Sizes.chWinDrive = drvinfo[uWinDrive].Drive();

   if(uWinDrive != uInstallDrive)
   {
      Sizes.dwInstallDriveReq = drvinfo[uInstallDrive].MaxUsed();
      Sizes.chInstallDrive = drvinfo[uInstallDrive].Drive();
   }
   if((uDownloadDrive != uWinDrive) && (uDownloadDrive != uInstallDrive))
   {
      Sizes.dwDownloadDriveReq = drvinfo[uDownloadDrive].MaxUsed();
      Sizes.chDownloadDrive = drvinfo[uDownloadDrive].Drive();
   }

   CopyMemory((LPVOID)(&(pSizes->dwInstallSize)), (LPVOID)(&(Sizes.dwInstallSize)), 
      pSizes->cbSize - sizeof(DWORD)); 
}


//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::SetAction(LPCSTR pszComponentID, DWORD action, DWORD dwPriority)
{
   HRESULT hr = NOERROR;

   if(_enginestatus != ENGINESTATUS_READY)
      return E_UNEXPECTED;
  
   ICifComponent *pComp = NULL;  
   
   if(pszComponentID == NULL || lstrlen(pszComponentID) == 0)
   {
      _pCif->ClearQueueState();
   }
   else
   {
      if(SUCCEEDED(_pCif->FindComponent(pszComponentID, &pComp)))
      {
         if(dwPriority != 0xffffffff)
            pComp->SetCurrentPriority(dwPriority);
         hr = pComp->SetInstallQueueState(action);
      }
      else
         hr = E_INVALIDARG;
   }
   return hr;
}

// the following two are now ridiculously inefficient. New clients should use the enumerators

STDMETHODIMP CInstallEngine::EnumInstallIDs(UINT uIndex, LPSTR *ppszID)
{
   HRESULT hr;
   UINT i = 0;
   *ppszID = NULL;

   IEnumCifComponents *penum;
   ICifComponent *pComp;
   
   _pCif->EnumComponents(&penum, 0, NULL);
   for(penum->Next(&pComp); pComp; penum->Next(&pComp))
   {
      if(pComp->GetInstallQueueState())
      {
         if(uIndex == i)
            break;
         i++;
      }
   }
   penum->Release();
   if(pComp)
   {
      char szID[MAX_ID_LENGTH];
      pComp->GetID(szID, sizeof(szID));
      *ppszID = COPYANSISTR(szID);
      hr = NOERROR;
   }
   else
      hr = E_FAIL;

   return hr;
}


STDMETHODIMP CInstallEngine::EnumDownloadIDs(UINT uIndex, LPSTR *ppszID)
{
   HRESULT hr;
   UINT i = 0;
   *ppszID = NULL;

   IEnumCifComponents *penum;
   ICifComponent *pComp;
   
   _pCif->EnumComponents(&penum, 0, NULL);
   for(penum->Next(&pComp); pComp; penum->Next(&pComp))
   {
      if(pComp->GetInstallQueueState() && (pComp->IsComponentDownloaded() == S_FALSE))
      {
         if(uIndex == i)
            break;
         i++;
      }
   }
   penum->Release();
   if(pComp)
   {
      char szID[MAX_ID_LENGTH];
      pComp->GetID(szID, sizeof(szID));
      *ppszID = COPYANSISTR(szID);
      hr = NOERROR;
   }
   else
      hr = E_FAIL;

   return hr;
   
}

 
//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::DownloadComponents(DWORD lFlags)
{
   DWORD dwThreadID;
   HRESULT hr = NOERROR;
   
   WriteToLog("Install Engine - Starting download phase\r\n", TRUE);
   if(_enginestatus == ENGINESTATUS_NOTREADY || _enginestatus == ENGINESTATUS_LOADING)
      hr = E_UNEXPECTED;

   // BUGBUG - add a downloading status? it really just a "busy" indication
   if(_enginestatus == ENGINESTATUS_INSTALLING)
      hr = E_PENDING;


   if(SUCCEEDED(hr))
   {   
      OnEngineStatusChange(ENGINESTATUS_INSTALLING, 0);

      // since trust may be set globally, only turn it on, not off
      if(EXECUTEJOB_IGNORETRUST & lFlags)
         _fIgnoreTrust = TRUE;

      if(EXECUTEJOB_IGNOREDOWNLOADERROR & lFlags)
         _fIgnoreDownloadError = TRUE;
      else
         _fIgnoreDownloadError = FALSE;

      HANDLE h = CreateThread(NULL, 0, InitDownloader, this, 0, &dwThreadID);
      if(h == NULL)
      {
         // Won't be doing any downloading today.....
         hr = E_FAIL;
      }
      else
         CloseHandle(h);
   }

   return hr;
} 

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::InstallComponents(DWORD lFlags)
{
   DWORD dwThreadID;
   HRESULT hr = NOERROR;

   WriteToLog("Install Engine - Starting install phase\r\n", TRUE);

   EnterCriticalSection(&g_cs);
   if(_enginestatus == ENGINESTATUS_NOTREADY || _enginestatus == ENGINESTATUS_LOADING)
      hr = E_UNEXPECTED;

   if(_enginestatus == ENGINESTATUS_INSTALLING)
      hr = E_PENDING;
   LeaveCriticalSection(&g_cs);

   if(SUCCEEDED(hr))
   {   
      // We first check to see if all files are present
      // by seeing if we would download anything!!!
      //
      if(EXECUTEJOB_VERIFYFILES & lFlags)
      {
         WriteToLog("Checking for missing files\r\n", FALSE);
         if(_GetActualDownloadSize(TRUE) != 0)
           return E_FILESMISSING;
      }
         
      OnEngineStatusChange(ENGINESTATUS_INSTALLING, 0);

      // since trust may be set globally, only turn it on, not off
      if(EXECUTEJOB_IGNORETRUST & lFlags)
         _fIgnoreTrust = TRUE;

      HANDLE h = CreateThread(NULL, 0, InitInstaller, this, 0, &dwThreadID);
      if(h == NULL)
      {
         // Won't be doing any installing today.....
         hr = E_FAIL;
      }
      else
         CloseHandle(h);
   }

   return hr;
} 


//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

DWORD WINAPI InitInstaller(LPVOID pv)
{
   CInstallEngine *pInsEng = (CInstallEngine *) pv;
   HRESULT hr = S_OK;
   BOOL fOneInstalled = FALSE;
   
   ICifComponent *pComp;

   EnableSage(FALSE);
   EnableScreenSaver(FALSE);
   EnableDiskCleaner(FALSE);

   pInsEng->_dwStatus = 0;
   
   pInsEng->_hAbort = CreateEvent(NULL, FALSE, FALSE, NULL);
   pInsEng->_hContinue = CreateEvent(NULL, TRUE, TRUE, NULL);
   
   //BUGBUG check for failure
   
   pInsEng->AddRef();

   pInsEng->OnStartInstall(0, pInsEng->_GetTotalInstallSize());

   // check trust the Cif cab if it has not been done so
   hr = pInsEng->CheckForContinue();
  
   // this is the install pass
   if(SUCCEEDED(hr))
      hr = pInsEng->_pCif->Install(&fOneInstalled);
      
   if(fOneInstalled && FNeedGrpConv())
   {
      if(!(pInsEng->GetStatus() & STOPINSTALL_REBOOTNEEDED))
      {
         // if we dont need a reboot launch grpconv immeadiatly
         HANDLE h = NULL;
         pInsEng->WriteToLog("Install Engine - No reboot required\r\n", FALSE);
         LaunchAndWait(GRPCONV, NULL, &h, NULL, SW_SHOWMINIMIZED);
         if(h)
            CloseHandle(h);
      }
      else
      {
         HKEY hKey;
         DWORD dumb;
         // otherwise put grpconv into runonce
         if(RegCreateKeyEx(HKEY_LOCAL_MACHINE, REGSTR_PATH_RUNONCE,
            0,0,0, KEY_SET_VALUE, NULL, &hKey, &dumb) == ERROR_SUCCESS)
         {
            RegSetValueEx(hKey, "GrpConv", 0, REG_SZ, 
               (BYTE *) GRPCONV, sizeof(GRPCONV));
            RegCloseKey(hKey);
         }
      }
   }
   
   
   // reset to checking trust
   if(pInsEng->_fResetTrust)
      pInsEng->_fIgnoreTrust = FALSE;
   // Send a install all done message

   if(!(pInsEng->GetStatus() & STOPINSTALL_REBOOTNEEDED))
   {
       // If we don't need a reboot, enable the screen saver and sage.
       EnableScreenSaver(TRUE);
       EnableSage(TRUE);
   }
   EnableDiskCleaner(TRUE);

   CloseHandle(pInsEng->_hAbort);
   pInsEng->_hAbort = NULL;
   
   CloseHandle(pInsEng->_hContinue);
   pInsEng->_hContinue = NULL;
      
   
   pInsEng->OnStopInstall(hr, NULL, pInsEng->GetStatus());
   pInsEng->OnEngineStatusChange(ENGINESTATUS_READY, 0);
   
   pInsEng->Release();


   return 0;
}   

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

DWORD WINAPI InitDownloader(LPVOID pv)
{
   CInstallEngine *pInsEng = (CInstallEngine *) pv;
   HRESULT hr = S_OK;
  
   EnableSage(FALSE);
   EnableScreenSaver(FALSE);
   EnableDiskCleaner(FALSE);

   pInsEng->_hAbort = CreateEvent(NULL, FALSE, FALSE, NULL);
   pInsEng->_hContinue = CreateEvent(NULL, TRUE, TRUE, NULL);
   
   //BUGBUG check for failure
   
   pInsEng->AddRef();

   pInsEng->OnStartInstall(pInsEng->_GetActualDownloadSize(FALSE), 0);

   // check trust the Cif cab if it has not been done so
   hr = pInsEng->CheckForContinue();
  
   // this is the download pass
   if(SUCCEEDED(hr))
   {
      hr = pInsEng->_pCif->Download();
   }

   if(SUCCEEDED(hr))
   {
      pInsEng->WriteToLog("Install Engine - Download complete\r\n", FALSE);
   }
   
   // reset to checking trust
   if(pInsEng->_fResetTrust)
      pInsEng->_fIgnoreTrust = FALSE;
   

   CloseHandle(pInsEng->_hAbort);
   pInsEng->_hAbort = NULL;
   
   CloseHandle(pInsEng->_hContinue);
   pInsEng->_hContinue = NULL;
   
   EnableScreenSaver(TRUE);
   EnableSage(TRUE);
   EnableDiskCleaner(TRUE);
   // Send a install all done message
   EnterCriticalSection(&g_cs);
   pInsEng->OnStopInstall(hr, NULL, 0);
   pInsEng->OnEngineStatusChange(ENGINESTATUS_READY, 0);
   LeaveCriticalSection(&g_cs);
   
   pInsEng->Release();
   return 0;
}   


//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

DWORD CInstallEngine::_GetTotalDownloadSize()
{
   DWORD dwTotalSize = 0;
   IEnumCifComponents *penum;
   ICifComponent *pComp = NULL;

   _pCif->EnumComponents(&penum, 0, NULL);
   for(penum->Next(&pComp); pComp; penum->Next(&pComp))
   {
      if(pComp->GetInstallQueueState() == SETACTION_INSTALL)
      dwTotalSize += pComp->GetDownloadSize();
   }
   penum->Release();
   return dwTotalSize;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

DWORD CInstallEngine::_GetActualDownloadSize(BOOL bLogMissing)
{
   DWORD dwTotalSize = 0;
   IEnumCifComponents *penum;
   ICifComponent *pComp = NULL;

   _pCif->EnumComponents(&penum, 0, NULL);
   for( penum->Next(&pComp); pComp; penum->Next(&pComp))
   {
      if(pComp->GetInstallQueueState() == SETACTION_INSTALL)
         dwTotalSize += pComp->GetActualDownloadSize();
   }
   penum->Release();
   return dwTotalSize;
}


//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

DWORD CInstallEngine::_GetTotalInstallSize()
{
   DWORD dwTotalSize = 0;
   DWORD dwWin, dwApp;
   IEnumCifComponents *penum;
   ICifComponent *pComp = NULL;

   _pCif->EnumComponents(&penum, 0, NULL);
   for(penum->Next(&pComp); pComp; penum->Next(&pComp))
   {
      if(pComp->GetInstallQueueState() == SETACTION_INSTALL)
      {
         pComp->GetInstalledSize(&dwWin, &dwApp);
         dwTotalSize += (dwWin + dwApp);
      }
   }
   penum->Release();
   return dwTotalSize;
}
   

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::LaunchExtraCommand(LPCSTR pszInfName, LPCSTR pszSection)
{
   return E_NOTIMPL;
}

//=---------------------------------------------------------------------------=
// RegisterInstallEngineCallback
//=---------------------------------------------------------------------------=
// Register the callback interface
//
// Parameters:
//    IInstallEngineCallback *  - the callback interface
//    HWND - For ui
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::RegisterInstallEngineCallback(IInstallEngineCallback *pcb)
{ 
   _pcb = pcb;
   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::UnregisterInstallEngineCallback()
{ 
   _pcb = NULL;
   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::SetHWND(HWND h)
{
   _hwndForUI = h;
   return NOERROR;
}

//***********  IInstallEngineCallback implementation ************

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::OnEngineStatusChange(DWORD status,DWORD substatus)
{
   _enginestatus = status; 
   if(_pcb)
      _pcb->OnEngineStatusChange(_enginestatus, substatus);
   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::OnStartInstall(DWORD dwDLSize, DWORD dwInstallSize) 
{
   _dwDLRemaining = dwDLSize;
   _dwInstallRemaining = dwInstallSize;
   
   wsprintf(szLogBuf, "\r\nOnStartInstall:\r\n   Download: %d KB\r\n   Install %d KB\r\n", dwDLSize, dwInstallSize);
   WriteToLog(szLogBuf, TRUE);

   if(_pcb)
      _pcb->OnStartInstall(dwDLSize, dwInstallSize);

   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::OnStartComponent(LPCSTR pszID, DWORD dwDLSize, 
                                   DWORD dwInstallSize, LPCSTR pszString)
{
   wsprintf(szLogBuf, "OnStartComponent:\r\n   ID: %s\r\n   Download: %d KB\r\n   Install %d KB\r\n", 
                         pszID, dwDLSize, dwInstallSize);
   WriteToLog(szLogBuf, TRUE);

   _dwDLOld = 0;
   _dwInstallOld = 0;
   if(_pcb)
      _pcb->OnStartComponent(pszID, dwDLSize, dwInstallSize, pszString);

   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::OnEngineProblem(DWORD dwProblem, LPDWORD pdwAction)
{
   HRESULT hr = S_FALSE;

   if(_pcb)
      hr = _pcb->OnEngineProblem(dwProblem, pdwAction);

   return hr;
}


//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::OnComponentProgress(LPCSTR pszID, DWORD dwPhase,
                        LPCSTR pszString, LPCSTR pszMsgString,  ULONG prog, ULONG max)
{
   DWORD dwNew;
   
   if(dwPhase == INSTALLSTATUS_DOWNLOADING)
   {
      _dwDLOld = prog;
   }
   else if(dwPhase == INSTALLSTATUS_RUNNING)
   {
      _dwInstallOld = prog;
   }
   
   if(_pcb)
      _pcb->OnComponentProgress(pszID, dwPhase, pszString, pszMsgString, prog, max);

   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::OnStopComponent(LPCSTR pszID, HRESULT hError,
                        DWORD dwPhase, LPCSTR pszString, DWORD dwStatus)
{
   // adjust remaining
   if(_dwDLRemaining > _dwDLOld)
      _dwDLRemaining -= _dwDLOld;
   else
      _dwDLRemaining = 0;
   
   // adjust remaining
   if(_dwInstallRemaining > _dwInstallOld)
      _dwInstallRemaining -= _dwInstallOld;
   else
      _dwInstallRemaining = 0;
   

   wsprintf(szLogBuf, "Timing rates: Download: %d, Install %d\r\n", 
                _pDL->GetBytesPerSecond(), _pIns->GetBytesPerSecond());
   WriteToLog(szLogBuf, TRUE);

   
   wsprintf(szLogBuf, "OnStopComponent:\r\n   ID: %s\r\n   HRESULT: %x (%s)\r\n   Phase: %d\r\n   Status: %d\r\n", 
                         pszID, hError, SUCCEEDED(hError) ? STR_OK : STR_FAILED, dwPhase, dwStatus);
   WriteToLog(szLogBuf, TRUE);
   
   if(_pcb)
      _pcb->OnStopComponent(pszID, hError, dwPhase, pszString, dwStatus);

   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::OnStopInstall(HRESULT hrError, LPCSTR szError,
                        DWORD dwStatus)
{
   _dwDLRemaining = 0;
   _dwInstallRemaining = 0;
   
   wsprintf(szLogBuf, "\r\nOnStopInstall:\r\n   HRESULT: %x (%s)\r\n   Status: %d\r\n", 
                         hrError, SUCCEEDED(hrError) ? STR_OK : STR_FAILED, dwStatus);
   WriteToLog(szLogBuf, TRUE);

   
   if(_pcb)
      _pcb->OnStopInstall(hrError, szError, dwStatus);

   return NOERROR;
}


//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//


STDMETHODIMP CInstallEngine::SetIStream(IStream *pstm)
{
   if(_pStmLog)
      _pStmLog->Release();

   _pStmLog = pstm;
   if(_pStmLog)
      _pStmLog->AddRef();

   return NOERROR;
}

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::GetDisplayName(LPCSTR pszComponentID, LPSTR *ppszName)
{ 
   HRESULT hr = E_INVALIDARG;
   char szTitle[MAX_DISPLAYNAME_LENGTH];
   
   if(!ppszName)
      return E_POINTER;

   *ppszName = 0;

   ICifComponent *pComp = NULL;

   if(pszComponentID)
   {
      if(SUCCEEDED(_pCif->FindComponent(pszComponentID, &pComp)))
      {
         pComp->GetDescription(szTitle, sizeof(szTitle));
         *ppszName = COPYANSISTR(szTitle);
         if(!(*ppszName))
            hr = E_OUTOFMEMORY;
         else
            hr = NOERROR;
      }
   }
   else
   {
      _pCif->GetDescription(szTitle, sizeof(szTitle));
      *ppszName = COPYANSISTR(szTitle);
      if(!(*ppszName))
         hr = E_OUTOFMEMORY;
      else
         hr = NOERROR;
   }
   return hr;
}

//************** IInstallEngineTiming **********************

//=---------------------------------------------------------------------------=
// Function name here
//=---------------------------------------------------------------------------=
// Function description
//
// Parameters
//
// Returns:
//
// Notes:
//

STDMETHODIMP CInstallEngine::GetRates(DWORD *pdwDownload, DWORD *pdwInstall)
{ 
   *pdwDownload = _pDL->GetBytesPerSecond();
   *pdwInstall = _pIns->GetBytesPerSecond();
   return NOERROR;
}

STDMETHODIMP CInstallEngine::GetInstallProgress(INSTALLPROGRESS *pinsprog)
{
   if(!pinsprog)
      return E_POINTER;

   DWORD dwTemp;

   pinsprog->dwDownloadKBRemaining = _dwDLRemaining - _dwDLOld;
   pinsprog->dwInstallKBRemaining = _dwInstallRemaining - _dwInstallOld;

   dwTemp = _pDL->GetBytesPerSecond();
   if(dwTemp == 0)
      pinsprog->dwDownloadSecsRemaining = 0xffffffff;
   else
      pinsprog->dwDownloadSecsRemaining = (pinsprog->dwDownloadKBRemaining << 10)/dwTemp;

   dwTemp = _pIns->GetBytesPerSecond();
   if(dwTemp == 0)
      pinsprog->dwInstallSecsRemaining = 0xffffffff;
   else
      pinsprog->dwInstallSecsRemaining = (pinsprog->dwInstallKBRemaining << 10)/dwTemp;

   return NOERROR;
}


HRESULT CInstallEngine::CheckForContinue()
{
   HRESULT hr = S_OK;

   // Need to check Abort before AND after check for pause...
   if(_pCif->CanCancel() && (WaitForSingleObject(_hAbort, 0) == WAIT_OBJECT_0))
   {
      hr = E_ABORT;
   }
   
   if(SUCCEEDED(hr))
   {
      WaitForEvent(_hContinue, NULL);
   }

   if(_pCif->CanCancel() && (WaitForSingleObject(_hAbort, 0) == WAIT_OBJECT_0))
   {
      hr = E_ABORT;
   }
 
   return hr;
}

#define STEPPINGMODE_NO "n"

void CInstallEngine::WriteToLog(char *sz, BOOL pause)
{
   ULONG foo;
   UINT ret;
   HKEY hKey;
   
   if(_fSteppingMode && pause)
   {
      ret = MessageBox(_hwndForUI, sz, "Stepping Mode Message", MB_OKCANCEL | MB_ICONINFORMATION); 
      if(ret == IDCANCEL)
      {
         // turn off stepping mode
         _fSteppingMode = FALSE;
         // Whack the key
         if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, ACTIVESETUP_KEY,0, 
                     KEY_WRITE, &hKey) == ERROR_SUCCESS)
         {
            // I don't check for failure of delete - what would I do anyways?
            RegSetValueEx(hKey, STEPPING_VALUE, 0, REG_SZ, 
                        (BYTE *) STEPPINGMODE_NO, lstrlen(STEPPINGMODE_NO) + 1);
            RegCloseKey(hKey);
         }
      }
   }
      
   if(_pStmLog)
      _pStmLog->Write(sz, lstrlen(sz), &foo);
}

BOOL CInstallEngine::_IsValidBaseUrl(LPCSTR pszUrl)
{
   BOOL bValid = TRUE;

   if(!pszUrl)
      bValid = FALSE;

   return bValid;
}