/*++

Copyright (c) 1995-97 Microsoft Corporation
All rights reserved.

Module Name:

    Monitor.c

Abstract:

    Routines for installing monitors

Author:

    Muhunthan Sivapragasam (MuhuntS) 30-Nov-1995

Revision History:

--*/

#include "precomp.h"


//
// Keys to search INF files
//
TCHAR   cszOptions[]                = TEXT("Options");
TCHAR   cszPortMonitorSection[]     = TEXT("PortMonitors");
TCHAR   cszPortMonitorDllKey []     = TEXT("PortMonitorDll");
TCHAR   cszMonitorInf[]             = TEXT("*.inf");


typedef struct _MON_INFO {
    LPTSTR  pszName;
    LPTSTR  pszDllName;
    BOOL    bInstalled;
} MON_INFO, *PMON_INFO;

typedef struct _MONITOR_SETUP_INFO {
    PMON_INFO  *ppMonInfo;
    DWORD       dwCount;
    LPTSTR      pszInfFile;         // Valid only for OEM disk INF
    LPTSTR      pszServerName;
} MONITOR_SETUP_INFO, *PMONITOR_SETUP_INFO;


VOID
FreeMonInfo(
    PMON_INFO   pMonInfo
    )
/*++

Routine Description:
    Free memory for a MON_INFO structure and the strings in it

Arguments:
    pMonInfo    : MON_INFO structure pointer

Return Value:
    Nothing

--*/
{
    if ( pMonInfo ) {

        LocalFreeMem(pMonInfo->pszName);
        LocalFreeMem(pMonInfo->pszDllName);

        LocalFreeMem(pMonInfo);
    }
}


PMON_INFO
AllocMonInfo(
    IN  LPTSTR  pszName,
    IN  LPTSTR  pszDllName,     OPTIONAL
    IN  BOOL    bInstalled,
    IN  BOOL    bAllocStrings
    )
/*++

Routine Description:
    Allocate memory for a MON_INFO structure and create strings

Arguments:
    pszName         : Monitor name
    pszDllName      : Monitor DLL name
    bAllocStrings   : TRUE if routine should allocated memory and create string
                      copies, else just assign the pointers

Return Value:
    Pointer to the created MON_INFO structure. NULL on error.

--*/
{
    PMON_INFO   pMonInfo;

    pMonInfo    = (PMON_INFO) LocalAllocMem(sizeof(*pMonInfo));

    if ( !pMonInfo )
        return NULL;

    if ( bAllocStrings ) {

        pMonInfo->pszName    = AllocStr(pszName);
        pMonInfo->pszDllName = AllocStr(pszDllName);

        if ( !pMonInfo->pszName ||
             (pszDllName && !pMonInfo->pszDllName) ) {

            FreeMonInfo(pMonInfo);
            return NULL;

        }
    } else {

        pMonInfo->pszName       = pszName;
        pMonInfo->pszDllName    = pszDllName;
    }

    pMonInfo->bInstalled = bInstalled;

    return pMonInfo;
}


VOID
PSetupDestroyMonitorInfo(
    IN OUT HANDLE h
    )
/*++

Routine Description:
    Free memory allocated to a MONITOR_SETUP_INFO structure and its contents

Arguments:
    h   : A handle got by call to PSetupCreateMonitorInfo

Return Value:
    Nothing

--*/
{
    PMONITOR_SETUP_INFO pMonitorSetupInfo = (PMONITOR_SETUP_INFO) h;
    DWORD   Index;

    if ( pMonitorSetupInfo ) {

        if ( pMonitorSetupInfo->ppMonInfo ) {

            for ( Index = 0 ; Index < pMonitorSetupInfo->dwCount ; ++Index )
                FreeMonInfo(pMonitorSetupInfo->ppMonInfo[Index]);

            LocalFreeMem(pMonitorSetupInfo->ppMonInfo);
        }

        LocalFreeMem(pMonitorSetupInfo->pszInfFile);
        LocalFreeMem(pMonitorSetupInfo->pszServerName);
        LocalFreeMem(pMonitorSetupInfo);
    }
}


BOOL
IsMonitorFound(
    IN  LPVOID  pBuf,
    IN  DWORD   dwReturned,
    IN  LPTSTR  pszName
    )
/*++

Routine Description:
    Find out if the given monitor name is found in the buffer returned from
    an EnumMonitors call to spooler

Arguments:
    pBuf        : Buffer used on a succesful EnumMonitor call to spooler
    dwReturned  : Count returned by spooler on EnumMonitor
    pszMonName  : Monitor name we are searching for

Return Value:
    TRUE if monitor is found, FALSE else

--*/
{
    PMONITOR_INFO_2     pMonitor2;
    DWORD               Index;

    for ( Index = 0, pMonitor2 = (PMONITOR_INFO_2) pBuf ;
          Index < dwReturned ;
          ++Index, (LPBYTE)pMonitor2 += sizeof(MONITOR_INFO_2) ) {

        if ( !lstrcmpi(pszName, pMonitor2->pName) )
            return TRUE;
    }

    return FALSE;

}


PMONITOR_SETUP_INFO
CreateMonitorInfo(
    LPCTSTR     pszServerName
    )
/*++

Routine Description:
    Finds all installed and installable monitors.

Arguments:
    pSelectedDrvInfo    : Pointer to the selected driver info (optional)

Return Value:
    A pointer to MONITOR_SETUP_INFO on success,
    NULL on error

--*/
{
    PMONITOR_SETUP_INFO     pMonitorSetupInfo = NULL;
    PMON_INFO               *ppMonInfo;
    PMONITOR_INFO_2         pMonitor2;
    LONG                    Index, Count = 0;
    BOOL                    bFail = TRUE;
    DWORD                   dwNeeded, dwReturned;
    LPBYTE                  pBuf = NULL;
    LPTSTR                  pszMonName;

    //
    // First query spooler for installed monitors. If we fail let's quit
    //
    if ( !EnumMonitors((LPTSTR)pszServerName, 2, NULL,
                       0, &dwNeeded, &dwReturned) ) {

        if ( GetLastError() != ERROR_INSUFFICIENT_BUFFER ||
             !(pBuf = LocalAllocMem(dwNeeded)) ||
             !EnumMonitors((LPTSTR)pszServerName,
                           2,
                           pBuf,
                           dwNeeded,
                           &dwNeeded,
                           &dwReturned) ) {

            goto Cleanup;
        }
    }

    //
    // We know how many monitors we have to display now
    //
    pMonitorSetupInfo = (PMONITOR_SETUP_INFO) LocalAllocMem(sizeof(*pMonitorSetupInfo));

    if ( !pMonitorSetupInfo )
        goto Cleanup;

    ZeroMemory(pMonitorSetupInfo, sizeof(*pMonitorSetupInfo));

    //
    // pMonitorSetupInfo->dwCount could be adjusted later not to list duplicate
    // entries. We are allocating max required buffer here
    //
    pMonitorSetupInfo->dwCount = dwReturned;

    pMonitorSetupInfo->ppMonInfo = (PMON_INFO *)
                        LocalAllocMem(pMonitorSetupInfo->dwCount*sizeof(PMON_INFO));

    ppMonInfo = pMonitorSetupInfo->ppMonInfo;

    if ( !ppMonInfo )
        goto Cleanup;

    for ( Index = 0, pMonitor2 = (PMONITOR_INFO_2) pBuf ;
          Index < (LONG) dwReturned ;
          ++Index, (LPBYTE)pMonitor2 += sizeof(MONITOR_INFO_2) ) {

        *ppMonInfo++ = AllocMonInfo(pMonitor2->pName,
                                    pMonitor2->pDLLName,
                                    TRUE,
                                    TRUE);
    }

    bFail = FALSE;

Cleanup:
    if ( pBuf )
        LocalFreeMem(pBuf);

    if ( bFail ) {

        PSetupDestroyMonitorInfo(pMonitorSetupInfo);
        pMonitorSetupInfo = NULL;
    }

    return pMonitorSetupInfo;
}


BOOL
AddPrintMonitor(
    IN  LPCTSTR     pszName,
    IN  LPCTSTR     pszDllName
    )
/*++

Routine Description:
    Add a print monitor by calling AddMonitor to spooler

Arguments:
    pszName     : Name of the monitor
    pszDllName  : Monitor dll name

Return Value:
    TRUE if monitor was succesfully added or it is already installed,
    FALSE on failure

--*/
{
    MONITOR_INFO_2  MonitorInfo2;

    MonitorInfo2.pName          = (LPTSTR) pszName;
    MonitorInfo2.pEnvironment   = NULL;
    MonitorInfo2.pDLLName       = (LPTSTR) pszDllName;

    //
    // Call is succesful if add returned TRUE, or monitor is already installed
    //
    if ( AddMonitor(NULL, 2, (LPBYTE) &MonitorInfo2) ||
         GetLastError() == ERROR_PRINT_MONITOR_ALREADY_INSTALLED ) {

        return TRUE;
    } else {

        return FALSE;
    }
}


PMON_INFO
MonInfoFromName(
    IN PMONITOR_SETUP_INFO  pMonitorSetupInfo,
    IN LPCTSTR              pszMonitorName
    )
{
    PMON_INFO   pMonInfo;
    DWORD       dwIndex;

    if ( !pMonitorSetupInfo ) {

        return NULL;
    }

    for ( dwIndex = 0 ; dwIndex < pMonitorSetupInfo->dwCount ; ++dwIndex ) {

        pMonInfo = pMonitorSetupInfo->ppMonInfo[dwIndex];
        if ( !lstrcmp(pszMonitorName, pMonInfo->pszName) ) {

            return pMonInfo;
        }
    }

    return NULL;
}

BOOL
InstallOnePortMonitor(HWND hwnd, 
                      HINF hInf, 
                      LPTSTR pMonitorName, 
                      LPTSTR pSectionName, 
                      LPTSTR pSourcePath)
/*++

Routine Description:
    Install one port monitor by copying files and calling spooler to add it

Arguments:
    hwnd                : Window handle of current top-level window
    hInf                : handle to the INF file
    pMonitorName        : port monitor display name
    pSectionName        : install section within the INF for the port monitor 

Return Value:
    TRUE if a port monitor was successfully installed
    FALSE if not

--*/

{
    DWORD  NameLen;
    BOOL   bSuccess = FALSE;
    HSPFILEQ InstallQueue = {0};
    PVOID  pQueueContext = NULL;
    LPTSTR  pMonitorDllName;

    NameLen = MAX_PATH;
    if ((pMonitorDllName = LocalAllocMem(NameLen * sizeof(TCHAR))) == NULL)
    {
        goto Cleanup;
    }
    
    //
    // Find the port monitor DLL name
    //
    if (!SetupGetLineText(NULL, hInf, pSectionName, cszPortMonitorDllKey, pMonitorDllName, NameLen, NULL))
    {
        goto Cleanup;
    }

    //
    // perform the installation
    //
    
    if ((InstallQueue = SetupOpenFileQueue()) == INVALID_HANDLE_VALUE)
    {
        goto Cleanup;
    }

    if (!SetupInstallFilesFromInfSection(hInf, NULL, InstallQueue, pSectionName, pSourcePath, 
                                         SP_COPY_IN_USE_NEEDS_REBOOT | SP_COPY_NOSKIP))
    {
        goto Cleanup;
    }

    //
    // Commit the file queue. This gets all files copied over.
    //
    pQueueContext = SetupInitDefaultQueueCallback(hwnd);
    if ( !pQueueContext ) 
    {
        goto Cleanup;
    }

    bSuccess = SetupCommitFileQueue(hwnd,
                                  InstallQueue,
                                  SetupDefaultQueueCallback,
                                  pQueueContext);


    if ( !bSuccess )
        goto Cleanup;

    bSuccess = AddPrintMonitor(pMonitorName, pMonitorDllName);

Cleanup:
    if (pQueueContext)
    {
        SetupTermDefaultQueueCallback(pQueueContext);
    }

    if (pMonitorDllName)
    {
        LocalFreeMem(pMonitorDllName);
    }
    
    SetupCloseFileQueue(InstallQueue);

    if (!bSuccess)
    {
        LPTSTR pszFormat = NULL, pszPrompt = NULL, pszTitle = NULL;

        pszFormat   = GetStringFromRcFile(IDS_ERROR_INST_PORT_MONITOR);
        pszTitle    = GetStringFromRcFile(IDS_INSTALLING_PORT_MONITOR);

        if ( pszFormat && pszTitle)
        {
            pszPrompt = LocalAllocMem((lstrlen(pszFormat) + lstrlen(pMonitorName) + 2)
                                                * sizeof(TCHAR));

            if ( pszPrompt )
            {
                wsprintf(pszPrompt, pszFormat, pMonitorName);

                MessageBox(hwnd, pszPrompt, pszTitle, MB_OK);

                LocalFreeMem(pszPrompt);
            }

        }
        LocalFreeMem(pszFormat);
        LocalFreeMem(pszTitle);
    
    }

    return bSuccess;
}

BOOL
InstallAllPortMonitorsFromInf(HWND hwnd, 
                              HINF hInfFile, 
                              LPTSTR pSourcePath)
/*++

Routine Description:
    Install all port monitors listed in one INF

Arguments:
    hwnd                : Window handle of current top-level window
    hInfFile            : handle of the INF file
    pSourcePath         : path to the INF file (without the name of the INF)

Return Value:
    TRUE if at least one port monitor was successfully installed
    FALSE if not

--*/

{
    LPTSTR pMonitorName = NULL, pSectionName= NULL;
    DWORD  NameLen;
    BOOL   bSuccess = FALSE;
    INFCONTEXT Context = {0};

    NameLen = MAX_PATH;
    if (((pMonitorName = LocalAllocMem(NameLen * sizeof(TCHAR))) == NULL) ||
        ((pSectionName = LocalAllocMem(NameLen * sizeof(TCHAR))) == NULL))
    {
        goto Cleanup;
    }

    //
    // Go through the list of port monitors
    //
    if (!SetupFindFirstLine(hInfFile, cszPortMonitorSection, NULL, &Context))
    {
        goto Cleanup;
    }

    do 
    {
        //
        // get the key name
        //
        if (!SetupGetStringField(&Context, 0, pMonitorName, NameLen, NULL))
        {
            goto Cleanup;
        }
        //
        // get the section name
        //
        if (!SetupGetStringField(&Context, 1, pSectionName, NameLen, NULL))
        {
            goto Cleanup;
        }
        
        bSuccess = InstallOnePortMonitor(hwnd, hInfFile, pMonitorName, pSectionName, pSourcePath) ||
                   bSuccess;

    } while (SetupFindNextLine(&Context, &Context));

Cleanup:
    if (pMonitorName)
    {
        LocalFreeMem(pMonitorName);
    }
    if (pSectionName)
    {
        LocalFreeMem(pSectionName);
    }

    return bSuccess;
}

BOOL
PSetupInstallMonitor(
    IN  HWND                hwnd
    )
/*++

Routine Description:
    Install a print monitor by copying files, and calling spooler to add it

Arguments:
    hwnd                : Window handle of current top-level window

Return Value:
    TRUE if at least one port monitor was successfully installed
    FALSE if not

--*/
{
    PMONITOR_SETUP_INFO     pMonitorSetupInfo = NULL;
    PMON_INFO              *ppMonInfo, pMonInfo;
    HINF                    hInf = INVALID_HANDLE_VALUE;
    INFCONTEXT              InfContext;
    TCHAR                   szInfPath[MAX_PATH];
    LPTSTR                  pszTitle, pszPrintMonitorPrompt;
    WIN32_FIND_DATA         FindData ={0};
    HANDLE                  hFind;
    size_t                  PathLen;
    BOOL                    bRet = FALSE;
    

    pszTitle              = GetStringFromRcFile(IDS_INSTALLING_PORT_MONITOR);
    pszPrintMonitorPrompt = GetStringFromRcFile(IDS_PROMPT_PORT_MONITOR);

    if (!pszTitle || ! pszPrintMonitorPrompt) 
    {
        goto Cleanup;
    }

    //
    // Ask the user where the inf file with the port monitor info resides
    //
    GetCDRomDrive(szInfPath);

    if ( !PSetupGetPathToSearch(hwnd,
                                pszTitle,
                                pszPrintMonitorPrompt,
                                cszMonitorInf,
                                TRUE,
                                szInfPath) ) {

        goto Cleanup;
    }

    //
    // find the INF(s) in the path. There must be one else SetupPromptForPath would've complained
    //
    PathLen = _tcslen(szInfPath);
    if (PathLen > MAX_PATH - _tcslen(cszMonitorInf) - 2) // -2 for terminating zero and backslash
    {
        DBGMSG(DBG_WARN, ("PSetupInstallMonitor: Path too long\n"));
        SetLastError(ERROR_BUFFER_OVERFLOW);
        goto Cleanup;
    }

    ASSERT(PathLen);

    if (szInfPath[PathLen-1] != _T('\\'))
    {
        szInfPath[PathLen++] = _T('\\');
        szInfPath[PathLen] = 0;
    }

    _tcscat(szInfPath, cszMonitorInf);

    hFind = FindFirstFile(szInfPath, &FindData);

    if (hFind != INVALID_HANDLE_VALUE)
    {
        HANDLE hInfFile;

        do
        {
            if (PathLen + _tcslen(FindData.cFileName) >= MAX_PATH)
            {
                DBGMSG(DBG_WARN, ("PSetupInstallMonitor: Path for %s%s too long - file skipped\n", szInfPath, FindData.cFileName));
                SetLastError(ERROR_BUFFER_OVERFLOW);
                continue;
            }

            _tcscpy(&(szInfPath[PathLen]), FindData.cFileName);

            hInfFile = SetupOpenInfFile(szInfPath, _T("Printer"), INF_STYLE_WIN4, NULL);

            if (hInfFile != INVALID_HANDLE_VALUE)
            {
                //
                // if the file has a section on port monitors, install it
                //
                if ( SetupGetLineCount(hInfFile, cszPortMonitorSection) > 0 )
                {
                    //
                    // cut off the INF name from the path
                    //
                    szInfPath[PathLen -1] = 0;

                    //
                    // bRet should be TRUE if there was at least one print monitor successfully installed
                    //
                    bRet = InstallAllPortMonitorsFromInf(hwnd, hInfFile, szInfPath) || bRet;                    
                    
                    //
                    // Put the trailing backslash back on
                    //
                    szInfPath[PathLen -1] = _T('\\');
                
                }

                SetupCloseInfFile(hInfFile);
            }
        } while ( FindNextFile(hFind, &FindData) );

        FindClose(hFind);
    }

Cleanup:
    if (pszTitle)
    {
        LocalFreeMem(pszTitle);
    }
    if (pszPrintMonitorPrompt)
    {
        LocalFreeMem(pszPrintMonitorPrompt);
    }

    return bRet;
}


HANDLE
PSetupCreateMonitorInfo(
    IN  HWND        hwnd,
    IN  LPCTSTR     pszServerName
    )
{
    return (HANDLE) CreateMonitorInfo(pszServerName);
}


BOOL
PSetupEnumMonitor(
    IN     HANDLE   h,
    IN     DWORD    dwIndex,
    OUT    LPTSTR   pMonitorName,
    IN OUT LPDWORD  pdwSize
    )
{
    PMONITOR_SETUP_INFO     pMonitorSetupInfo = (PMONITOR_SETUP_INFO) h;
    PMON_INFO               pMonInfo;
    DWORD                   dwNeeded;

    if ( dwIndex >= pMonitorSetupInfo->dwCount ) {

        SetLastError(ERROR_NO_MORE_ITEMS);
        return FALSE;
    }

    pMonInfo = pMonitorSetupInfo->ppMonInfo[dwIndex];

    dwNeeded = lstrlen(pMonInfo->pszName) + 1;
    if ( dwNeeded > *pdwSize ) {

        *pdwSize = dwNeeded;
        SetLastError(ERROR_INSUFFICIENT_BUFFER);
        return FALSE;
    }

    lstrcpy(pMonitorName, pMonInfo->pszName);
    return TRUE;
}