/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    PutInGrp.C

Abstract:

    Reads the command line to find a file spec (or wildcard spec) and a
    program manager group name and then for each file in the current dir
    that matches the file spec, creates a program item in the specified
    group. If an error occurs and a program item cannot be created, then
    the app returns non-zero (DOS ERRORLEVEL 1) otherwise 0 is returned if
    all matching files are put into the group. The icon name used for the
    program item is the filename (minus extension) of the file found.

Author:

    Bob Watson (a-robw)

    
Revision History:

    12 Jun 1994     Created

--*/
//
//  System include files    
//
#include <windows.h>        // windows definitions
#include <tchar.h>          // unicode data and function definitions

#include <stdio.h>          // printf's etc.       
#include <stdlib.h>
#include <ddeml.h>          // DDEML interface definitions

#include "putingrp.h"       // application definitions

//
//  DDEML constants that depend on  UNICODE/ANSI data format
//
#if _UNICODE 
#define STRING_CODEPAGE CP_WINUNICODE
#define APP_TEXT_FORMAT CF_UNICODETEXT
#else
#define STRING_CODEPAGE CP_WINANSI
#define APP_TEXT_FORMAT CF_TEXT
#endif
//
//      other application constants
//
//  time to wait for a dde command to complete (10 sec.)
//
#define APP_DDE_TIMEOUT         10000
//
//  Small buffer size used for general purpose temporary buffers
//
#define SMALL_BUFFER_SIZE       1024
//
//  Large buffer size used for general purpose temporary buffers
//
#define BIG_BUFFER_SIZE         (16 * SMALL_BUFFER_SIZE)
//
//  number of times to retry a DDE command before giving up
//
#define APP_RETRY_COUNT         5
//
//  number of buffers in GetStringResource function. These buffers
//  are used sequentially to allow up to this many calls before a 
//  buffer is overwritten.
//
#define RES_STRING_BUFFER_COUNT 4
//
//  time delay between DDE EXECUTE calls
//      500 = .5 sec
//
#define PAUSE_TIME              500

LPCTSTR
GetFileName (
    IN  LPCTSTR  szFileName
)
/*++

Routine Description:
    
    returns a buffer that contains the filename without the
        period or extension. The filename returned has the
        first character upper-cased and all other characters are
        kept the same.

Arguments:

    IN  LPCTSTR szFileName
        pointer to a filename string. This is assumed to be just
        a filename with no path information.

Return Value:

    pointer to a buffer containing the filename.    

--*/
{
    static  TCHAR   szReturnBuffer[MAX_PATH];   // buffer for result
    LPCTSTR szSrc;                  // pointer into source string
    LPTSTR  szDest;                 // pointer into destination string
    BOOL    bFirst = TRUE;          // used to tell when 1st char has been UC'd

    szSrc = szFileName;
    szDest = &szReturnBuffer[0];
    *szDest = 0;    // clear old contents
    // go through source until end or "." whichever comes first.
    while ((*szSrc != TEXT('.')) && (*szSrc != 0)) {
        *szDest++ = *szSrc++;
        // uppercase first letter
        if (bFirst) {
            *szDest = 0;
            _tcsupr (&szReturnBuffer[0]);
            bFirst = FALSE;
        }
    }
    *szDest = 0;
    return (LPCTSTR)szReturnBuffer;
}

LPCTSTR
GetStringResource (
    IN  UINT    nId
)
/*++

Routine Description:
    
    Used to load a string resource for this app so it can be used as a
        string constant. NOTE the resulting string should be copied into
        a local buffer since the contents of the buffer returned by this
        routine may change in subsequent calls.

Arguments:

    IN  UINT    nId
        Resource ID to return.

Return Value:

    pointer to string referenced by Resource ID value.    

--*/
{
    static  TCHAR   szStringBuffer[RES_STRING_BUFFER_COUNT][SMALL_BUFFER_SIZE];
    static  DWORD   dwBufferNdx;    // current buffer in use    
    LPTSTR  szBuffer;               // pointer to current buffer
    int     nLength;                // length of string found

    // select new buffer
    dwBufferNdx++;                              // go to next index
    dwBufferNdx %= RES_STRING_BUFFER_COUNT;     // keep within bounds
    szBuffer = &szStringBuffer[dwBufferNdx][0]; // set pointer

    // get buffer
    nLength = LoadString (
        (HINSTANCE)GetModuleHandle(NULL),
        nId,
        szBuffer,
        SMALL_BUFFER_SIZE);

    // return pointer to buffer in use
    return (LPCTSTR)szBuffer; 
}

VOID
DisplayUsage (
    VOID
)
{
    UINT    nString;

    for (nString = APP_USAGE_START; nString <= APP_USAGE_END; nString++){
        _tprintf (GetStringResource(nString));
    }
}

BOOL
IsProgmanWindow (
    IN  HWND    hWnd,
    IN  LPARAM  lParam
)
/*++

Routine Description:
    
    Function called by EnumWindows function to tell if the
        Program Manger window has been found. A match is determined
        by comparing the window caption (so it's not fool proof).
        

Arguments:

    IN  HWND    hWnd
        handle of window to test

    IN  LPARAM  lParam
        address of window handle variable to be loaded when program
        manager window is found. set to NULL if this window is NOT
        the Program Manager

Return Value:

    TRUE if this is NOT the Program Manager window
    FALSE if this IS the Program Manager window
        (this is to accomodate the EnumWindows logic)

--*/
{
    static  TCHAR   szWindowName[MAX_PATH]; // buffer to write this window's title
    DWORD   dwProgmanTitleLen;              // length of "Program Manager"
    LPTSTR  szProgmanTitle;                 // pointer to title string
    HWND    *hwndReturn;                    // return window handle pointer

    hwndReturn = (HWND *)lParam;            // cast LPARAM to HWND *
    if (hwndReturn != NULL) {
        *hwndReturn = NULL;                 // initialize to NULL handle
    }

    if (IsWindow (hWnd)) {
        // only check windows
        //
        // get title string to match against
        szProgmanTitle = (LPTSTR)GetStringResource (APP_PROGMAN_TITLE);
        dwProgmanTitleLen = lstrlen(szProgmanTitle);
        //
        // get title of this window
        GetWindowText (hWnd, &szWindowName[0], MAX_PATH);

        // check the length
        if ((DWORD)lstrlen(&szWindowName[0]) < dwProgmanTitleLen) {
            // this is too short to match
            return TRUE; // not Program Manager, get next window
        } else {
            // make window name same length as program manager string
            szWindowName[dwProgmanTitleLen] = 0;
            // compare window name to match title string
            if (lstrcmpi(&szWindowName[0], szProgmanTitle) == 0) {
                // it's a match
                if (hwndReturn != NULL) {
                    *hwndReturn = hWnd;
                }
                return FALSE;   // found it so leave
            } else {
                return TRUE;    // not this one, so keep going
            }
        }
    } else {
        return TRUE;    // not this one, so keep going
    }
}

BOOL
RestoreProgmanWindow (
    VOID
)
/*++

Routine Description:
    
    Activates and Restores the Program Manager window and makes it the
        foreground app.

Arguments:

    None    

Return Value:

    TRUE if window restored
    FALSE if window not found

--*/
{
    HWND    hwndProgman;

    // find progman and restore it

    EnumWindows (IsProgmanWindow, (LPARAM)&hwndProgman);

    if (IsWindow (hwndProgman)) {
        // if iconic, then restore
        if (IsIconic(hwndProgman)) {
            ShowWindow (hwndProgman, SW_RESTORE);
        }
        // make the foreground app.
        SetForegroundWindow (hwndProgman);
        return TRUE;
    } else {
        return FALSE;
    }
}

HDDEDATA CALLBACK
DdeCallback (
    IN  UINT        wType,
    IN  UINT        wFmt,
    IN  HCONV       hConv,
    IN  HSZ         hsz1,
    IN  HSZ         hsz2,
    IN  HDDEDATA    hData,
    IN  DWORD       lData1,
    IN  DWORD       lData2
)
/*++

Routine Description:
    
    Generic Callback function required by DDEML calls    

Arguments:

    See WinHelp    

Return Value:

    See WinHelp    

--*/
{
    switch (wType) {
        case XTYP_REGISTER:
        case XTYP_UNREGISTER:
            return (HDDEDATA)NULL;

        case XTYP_ADVDATA:
            // received when new data is available
            return (HDDEDATA)DDE_FACK;

        case XTYP_XACT_COMPLETE:
            // received when an async transaction is complete
            return (HDDEDATA)NULL;

        case XTYP_DISCONNECT:
            // connection termination has been requested
            return (HDDEDATA)NULL;

        default:
            return (HDDEDATA)NULL;
    }
}

HCONV
ConnectToProgman (
    IN  DWORD   dwInst
)
/*++

Routine Description:
    
    Establishes a DDE connection to the program manager's DDE
        server.

Arguments:

    IN  DWORD   dwInst
        DDEML Instance as returned by DdeInitialize call

Return Value:

    Handle to conversation if successful,
    0 if not    

--*/
{
    HSZ     hszProgman1;
    HSZ     hszProgman2;
    HCONV   hConversation;
    CONVCONTEXT ccConversation;

    // init conversation context buffer
    ccConversation.cb = sizeof(CONVCONTEXT);
    ccConversation.wFlags = 0;
    ccConversation.wCountryID = 0;
    ccConversation.iCodePage = 0;
    ccConversation.dwLangID = 0L;
    ccConversation.dwSecurity = 0L;
    ccConversation.qos.Length = sizeof(SECURITY_QUALITY_OF_SERVICE);
    ccConversation.qos.ImpersonationLevel = SecurityImpersonation;
    ccConversation.qos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
    ccConversation.qos.EffectiveOnly = TRUE;
    
    // get server name
    hszProgman1 = DdeCreateStringHandle (dwInst,
        (LPTSTR)GetStringResource (APP_SERVER),
        STRING_CODEPAGE);

    if (hszProgman1 != 0) {
        // get topic name
        hszProgman2 = DdeCreateStringHandle (dwInst,
            (LPTSTR)GetStringResource (APP_TOPIC),
            STRING_CODEPAGE);

        if (hszProgman2 != 0) {
            // connect to server
            hConversation = DdeConnect (
                dwInst,
                hszProgman1,
                hszProgman2,
                &ccConversation);

            // free string handle
            DdeFreeStringHandle (dwInst, hszProgman2);
        }
        // free string handle
        DdeFreeStringHandle (dwInst, hszProgman1);
    }
    
    return hConversation;        // return handle
}

BOOL
CreateAndShowGroup (
    IN  DWORD   dwInst,
    IN  HCONV   hConversation,
    IN  LPCTSTR szGroupName
)
/*++

Routine Description:
    
    creates and activates the program manager group specified
        in the argument  list

Arguments:

    IN  DWORD   dwInst
        Instance ID returned from DdeInitialize

    IN  HCONV   hConversation
        Handle to the current DDE conversation

    IN  LPCTSTR szGroupName
        Pointer to string containing name of program manager group to
        create and/or activate

Return Value:
    
    TRUE if operation succeeded
    FALSE if not

--*/
{
    LPTSTR  szCmdBuffer;        // DDE command string to send
    LPTSTR  szCmdFmt;           // DDE command format for sprintf
    DWORD   dwCmdLength;        // size of command string
    BOOL    bResult;            // result of function calls
    DWORD   dwTransactionResult; // result of Command 

    // allocate temporary memory buffers
    szCmdFmt = GlobalAlloc (GPTR, (SMALL_BUFFER_SIZE * sizeof(TCHAR)));
    szCmdBuffer = GlobalAlloc (GPTR, (SMALL_BUFFER_SIZE * sizeof(TCHAR)));

    if ((szCmdBuffer != NULL) &&
        (szCmdFmt != NULL)) {

        // get command format string
        lstrcpy (szCmdFmt, GetStringResource(APP_CREATE_AND_SHOW_FMT));

        // format command to include desired group name
        dwCmdLength = _stprintf (szCmdBuffer, szCmdFmt, szGroupName) + 1;
        dwCmdLength *= sizeof(TCHAR) ;

        // create group or activate group if it already exists
        // send command to server
        Sleep (PAUSE_TIME);
        bResult = DdeClientTransaction (
            (LPBYTE)szCmdBuffer,
            dwCmdLength,
            hConversation,
            0L,
            APP_TEXT_FORMAT,
            XTYP_EXECUTE,
            APP_DDE_TIMEOUT,
            &dwTransactionResult);

#if DEBUG_OUT
        if (!bResult) {
            _tprintf (GetStringResource (APP_DDE_EXECUTE_ERROR_FMT),
                DdeGetLastError(dwInst),
                szCmdBuffer);
        }
#endif

        // now activate the group
        // get the command format string
        lstrcpy (szCmdFmt, GetStringResource(APP_RESTORE_GROUP_FMT));

        // create the command that includes the group name
        dwCmdLength = _stprintf (szCmdBuffer, szCmdFmt, szGroupName) + 1;
        dwCmdLength *= sizeof(TCHAR);

        // send command to server
        Sleep (PAUSE_TIME);
        bResult = DdeClientTransaction (
            (LPBYTE)szCmdBuffer,
            dwCmdLength,
            hConversation,
            0L,
            APP_TEXT_FORMAT,
            XTYP_EXECUTE,
            APP_DDE_TIMEOUT,
            &dwTransactionResult);

#if DEBUG_OUT
        if (!bResult) {
            _tprintf (GetStringResource (APP_DDE_EXECUTE_ERROR_FMT),
                DdeGetLastError(dwInst),
                szCmdBuffer);
        }
#endif

        // free global memory buffers
        GlobalFree (szCmdBuffer);
        GlobalFree (szCmdFmt);
        return bResult;
    } else {
        // unable to allocate memory buffers so return error
        SetLastError (ERROR_OUTOFMEMORY);
        return FALSE;
    }
}

BOOL
LoadFilesToGroup (
    IN  DWORD   dwInst,
    IN  HCONV   hConversation,
    IN  LPCTSTR szFileSpec,
    IN  LPCTSTR szGroupName
)
/*++

Routine Description:
    
    Searches the current directory for the file(s) that match the
        fileSpec argument and creates program items the program
        manager group specified by szGroupName.

Arguments:

    IN  DWORD   dwInst
        DDEML instance handle returned by DdeInitialize

    IN  HCONV   hConversation
        Handle to current conversation with DDE Server

    IN  LPCTSTR szFileSpec
        file spec to look up for program items

    IN  LPCTSTR szGroupName
        program manager group to add items to

Return Value:

    TRUE if all items loaded successfully
    FALSE of one or more items did not get installed in Program Manager

--*/
{
    WIN32_FIND_DATA fdSearchData;   // search data struct for file search
    HANDLE          hFileSearch;    // file search handle
    BOOL            bSearchResult;  // results of current file lookup
    BOOL            bResult;        // function return
    BOOL            bReturn = TRUE; // value returned to calling fn.
    LPTSTR          szCmdBuffer;    // buffer for one command    
    LPTSTR          szCmdFmt;       // command format buffer for sprintf
    DWORD           dwCmdLength;    // length of command buffer
    DWORD           dwBufferUsed;   // chars in DdeCmd that have been used
    DWORD           dwTransaction;  // returned by DdeClientTransaction
    DWORD           dwTransactionResult;    // result code

    // Allocate global memory
    szCmdBuffer = GlobalAlloc (GPTR, (SMALL_BUFFER_SIZE * sizeof(TCHAR)));
    szCmdFmt = GlobalAlloc (GPTR, (SMALL_BUFFER_SIZE * sizeof(TCHAR)));

    if ((szCmdBuffer != NULL)  &&
        (szCmdFmt != NULL)) {
        // get single entry command format for sprintf
        lstrcpy (szCmdFmt, GetStringResource(APP_ADD_PROGRAM_FMT));

        // start file search
        hFileSearch = FindFirstFile (szFileSpec, &fdSearchData);

        if (hFileSearch != INVALID_HANDLE_VALUE) {
            // file search initialized OK so start processing files
            dwBufferUsed = 0;
            bSearchResult = TRUE;
            while (bSearchResult) {
                // make sure it's a real file and not a dir or a 
                // temporary file

                if (!((fdSearchData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
                      (fdSearchData.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY))) {

                    // make a command for this file
                    dwCmdLength = _stprintf (szCmdBuffer, szCmdFmt,
                        fdSearchData.cFileName,
                        GetFileName(fdSearchData.cFileName)) + 1;
                    dwCmdLength *= sizeof(TCHAR);
                    dwTransactionResult = 0;
                    Sleep (PAUSE_TIME);
                    dwTransaction = DdeClientTransaction (
                        (LPBYTE)szCmdBuffer,
                        dwCmdLength,
                        hConversation,
                        0L,
                        APP_TEXT_FORMAT,
                        XTYP_EXECUTE,
                        APP_DDE_TIMEOUT,
                        &dwTransactionResult);

                    if (dwTransaction > 0) {
                        _tprintf (GetStringResource (APP_ADD_SUCCESS_FMT),
                            fdSearchData.cFileName, szGroupName);
                        bResult = TRUE;
                    } else {
                        _tprintf (GetStringResource (APP_ADD_ERROR_FMT),
                            fdSearchData.cFileName, szGroupName);
                        bResult = FALSE;
                    }
                    if (!bResult) {
#if DEBUG_OUT
                        _tprintf (GetStringResource (APP_DDE_EXECUTE_ERROR_FMT),
                            DdeGetLastError(dwInst),
                            szDdeCmd);
#endif
                        // at least one entry didn't work so set return value
                        bReturn = FALSE;
                    }
                }
                // get next matching file
                bSearchResult = FindNextFile (hFileSearch, &fdSearchData);
            }
        }
        // free global memory
        GlobalFree (szCmdBuffer);
        GlobalFree (szCmdFmt);
        return bReturn;
    } else {
        SetLastError (ERROR_OUTOFMEMORY);
        return FALSE;
    }
}

BOOL
SaveNewGroup (
    IN  DWORD   dwInst,
    IN  HCONV   hConversation,
    IN  LPCTSTR szGroupName
)
/*++

Routine Description:
    
    Sends the Reload command to save and reload the new group. This will
        save the information.

Arguments:

    IN  DWORD   dwInst
        DDEML instance handle returned by DdeInitialize

    IN  HCONV   hConversation
        Handle to current conversation with DDE Server

    IN  LPCTSTR szGroupName
        program manager group to add items to

Return Value:

    TRUE if successful
    FALSE if not

--*/
{
    LPTSTR  szCmdBuffer;        // DDE command string to send
    LPTSTR  szCmdFmt;           // DDE command format for sprintf
    DWORD   dwCmdLength;        // size of command string
    BOOL    bResult;            // result of function calls
    DWORD   dwTransactionResult; // result of Command 

    // allocate temporary memory buffers
    szCmdFmt = GlobalAlloc (GPTR, (SMALL_BUFFER_SIZE * sizeof(TCHAR)));
    szCmdBuffer = GlobalAlloc (GPTR, (SMALL_BUFFER_SIZE * sizeof(TCHAR)));

    if ((szCmdBuffer != NULL) &&
        (szCmdFmt != NULL)) {

        // get command format string
        lstrcpy (szCmdFmt, GetStringResource(APP_RELOAD_GROUP_FMT));

        // format command to include desired group name
        dwCmdLength = _stprintf (szCmdBuffer, szCmdFmt, szGroupName) + 1;
        dwCmdLength *= sizeof(TCHAR);

        // create group or activate group if it already exists
        // send command to server
        Sleep (PAUSE_TIME);
        bResult = DdeClientTransaction (
            (LPBYTE)szCmdBuffer,
            dwCmdLength,
            hConversation,
            0L,
            APP_TEXT_FORMAT,
            XTYP_EXECUTE,
            APP_DDE_TIMEOUT,
            &dwTransactionResult);

        return bResult;
    } else {
        SetLastError (ERROR_OUTOFMEMORY);
        return FALSE;
    }
}

int
_CRTAPI1 main (
    int     argc,
    char    **argv
)
/*++

Routine Description:
    
    Main entry point for command line    

Arguments:

    IN  int argc
        count of arguments passed in from command line

    IN  char *argv[]
        array of pointers to each command line argument
            argv[0] = this program'e .exe path
            argv[1] = file(s) to put in group
            argv[2] = group to create/load

Return Value:

    0 if all files matching the path are successfully loaded into progman
    non-zero if one or more files did not have a progman item created

--*/
{
    DWORD   dwInstId = 0;   // DDEML Instance
    UINT    nReturn;        // return value
    BOOL    bResult;        // function return value
    HCONV   hConversation;  // handle to DDE conversation
    LPTSTR  szFiles;        // file path read from command line
    LPTSTR  szGroup;        // group name read from command line

    if (argc < 3) {
        // check for correct command line arg count
        DisplayUsage();
        return ERROR_BAD_COMMAND;
    }
    
    // allocate buffers for command line arguments
    szFiles = GlobalAlloc (GPTR, (strlen(argv[1]) + 1) * sizeof(TCHAR));
    szGroup = GlobalAlloc (GPTR, (strlen(argv[2]) + 1) * sizeof(TCHAR));

    if ((szFiles == NULL) || (szGroup == NULL)) {
        return ERROR_OUTOFMEMORY;
    }

    // read in command line arguments using appropriate function
#if _UNICODE
    mbstowcs (szFiles, argv[1], lstrlenA(argv[1]));
    mbstowcs (szGroup, argv[2], lstrlenA(argv[2]));
#else
    lstrcpyA (szFiles, argv[1]);
    lstrcpyA (szGroup, argv[2]);
#endif

    // make Program Manager window the foreground app and restore it's size
    RestoreProgmanWindow ();

    // begin DDEML session
    nReturn = DdeInitialize (&dwInstId,
        (PFNCALLBACK)DdeCallback,
        APPCMD_CLIENTONLY,
        0L);

    if (nReturn == DMLERR_NO_ERROR) {
        // connect to Program Manager DDE server
        hConversation = ConnectToProgman (dwInstId);

        if (hConversation != 0) {
            bResult = DdeEnableCallback (dwInstId, hConversation,
                EC_ENABLEALL);

            // create program group
            if (CreateAndShowGroup (dwInstId, hConversation, szGroup)) {
                // load selected files into group
                if (!LoadFilesToGroup (dwInstId, hConversation, szFiles, szGroup)) {
                    // 1 or more files did not get a program item
                    nReturn = ERROR_CAN_NOT_COMPLETE;
                } else {
                    SaveNewGroup (dwInstId, hConversation, szGroup);
                    // all files were loaded into program manager successfully
                    nReturn = ERROR_SUCCESS;
                }
                // that's it so close conversation handle
                DdeDisconnect (hConversation);
            } else {
                // unable to create program group
                nReturn = ERROR_CAN_NOT_COMPLETE;
            }
        } else {
            // unablet to establish conversation
            nReturn = ERROR_CAN_NOT_COMPLETE;
        }

        // terminate DDEML session    
        if (!DdeUninitialize (dwInstId)) {
            nReturn = ERROR_CAN_NOT_COMPLETE;
        }
    }

    // free global buffers
    GlobalFree (szFiles);
    GlobalFree (szGroup);

    // return value to command shell 
    return nReturn;
}
