/*++

Copyright (c) 1998 Microsoft Corporation

Module Name:

    sfspace.c

Abstract:

    sfspace calculates the amount of space required for shell folders in a clean install of
    Windows 2000 and outputs the results in a form which can be copied into win95upg.inf.

Author:

    Marc R. Whitten (marcw) 24-Mar-1999

Revision History:

    <full name> (<alias>) <date> <comments>

--*/

#include "pch.h"
#include "shlobj.h"

HANDLE g_hHeap;
HINSTANCE g_hInst;

BOOL WINAPI MigUtil_Entry (HINSTANCE, DWORD, PVOID);
//BOOL WINAPI MemDb_Entry (HINSTANCE, DWORD, PVOID);



#define SFLIST \
    DEFMAC(AppData, CSIDL_APPDATA) \
    DEFMAC(Cache, CSIDL_INTERNET_CACHE) \
    DEFMAC(Cookies, CSIDL_COOKIES) \
    DEFMAC(Desktop, CSIDL_DESKTOPDIRECTORY) \
    DEFMAC(Favorites, CSIDL_FAVORITES) \
    DEFMAC(History, CSIDL_HISTORY) \
    DEFMAC(Local AppData, CSIDL_LOCAL_APPDATA) \
    DEFMAC(Local Settings, CSIDL_LOCAL_APPDATA) \
    DEFMAC(My Pictures, CSIDL_MYPICTURES) \
    DEFMAC(NetHood, CSIDL_NETHOOD) \
    DEFMAC(Personal, CSIDL_PERSONAL) \
    DEFMAC(PrintHood, CSIDL_PRINTHOOD) \
    DEFMAC(Programs, CSIDL_PROGRAMS) \
    DEFMAC(Recent, CSIDL_RECENT) \
    DEFMAC(SendTo, CSIDL_SENDTO) \
    DEFMAC(Start Menu, CSIDL_STARTMENU) \
    DEFMAC(StartUp, CSIDL_STARTUP) \
    DEFMAC(Templates, CSIDL_TEMPLATES) \
    DEFMAC(Common AppData, CSIDL_COMMON_APPDATA) \
    DEFMAC(Common Desktop, CSIDL_COMMON_DESKTOPDIRECTORY) \
    DEFMAC(Common Personal, CSIDL_COMMON_DOCUMENTS) \
    DEFMAC(Common Favorites, CSIDL_COMMON_FAVORITES) \
    DEFMAC(Common Programs, CSIDL_COMMON_PROGRAMS) \
    DEFMAC(Common Start Menu, CSIDL_COMMON_STARTMENU) \
    DEFMAC(Common StartUp, CSIDL_COMMON_STARTUP) \
    DEFMAC(Common Templates, CSIDL_COMMON_TEMPLATES) \


enum {
    CS_512 = 0,
    CS_1024,
    CS_2048,
    CS_4096,
    CS_8192,
    CS_16384,
    CS_32768,
    CS_65536,
    CS_131072,
    CS_262144,
    LAST_CLUSTER_SIZE
};

typedef struct {

    PCTSTR RegKey;
    UINT Csidl;
    PCTSTR Path;
    LONG TableOffset;
    LONG SpaceNeeded[LAST_CLUSTER_SIZE];
    LONG RawSize;
    UINT FileCount;
    UINT DirectoryCount;
} SFDATA, *PSFDATA;

#define DEFMAC(regName, csidl) {TEXT(#regName),(csidl)},

SFDATA g_Data[] = {SFLIST /*, */ {NULL,0}};
HASHTABLE g_Table;
POOLHANDLE g_Pool;
BOOL g_Verbose = FALSE;
UINT g_ClusterTable[LAST_CLUSTER_SIZE] =
    {512,1024,2048,4096,8192,16384,32768,65536,131072,262144};


#define DIRECTORY_SIZE 512

BOOL
pCallEntryPoints (
    DWORD Reason
    )
{
    HINSTANCE Instance;

    //
    // Simulate DllMain
    //

    Instance = g_hInst;

    //
    // Initialize the common libs
    //

    if (!MigUtil_Entry (Instance, Reason, NULL)) {
        return FALSE;
    }
/*
    if (!MemDb_Entry (Instance, Reason, NULL)) {
        return FALSE;
    }
*/


    return TRUE;
}


BOOL
Init (
    VOID
    )
{
    g_hHeap = GetProcessHeap();
    g_hInst = GetModuleHandle (NULL);

    return pCallEntryPoints (DLL_PROCESS_ATTACH);
}


VOID
Terminate (
    VOID
    )
{
    pCallEntryPoints (DLL_PROCESS_DETACH);
}

BOOL
pInitShellFolderData (
    VOID
    )
{
    PSFDATA sf;
    TCHAR buffer[MAX_TCHAR_PATH];
    PTSTR p;

    sf = g_Data;

    while (sf->RegKey) {


        if (SHGetFolderPath (NULL, sf->Csidl, NULL, 0, buffer) != S_OK) {
            _ftprintf (stderr, TEXT("sfspace: Unable to retrieve folder path for %s.\n"), sf->RegKey);
            return FALSE;

        }

        sf->Path = PoolMemDuplicateString (g_Pool, buffer);

        //
        // We don't have a CSIDL for the local settings directory. We need to hack it.
        //
        if (StringIMatch (sf->RegKey, TEXT("Local Settings"))) {

            p = _tcsrchr (sf->Path, TEXT('\\'));
            MYASSERT (p);

            *p = 0;
            if (g_Verbose) {
                _tprintf (TEXT("sfspace: Hacked path of local settings to %s.\n"), sf->Path);
            }
        }


        sf->TableOffset = HtAddString (g_Table, sf->Path);

        if (g_Verbose) {
            _tprintf (TEXT("sfspace: Shell folder %s has path %s.\n"), sf->RegKey, sf->Path);
        }
        sf++;


    }


    return TRUE;
}

BOOL
pGatherSpaceRequirements (
    VOID
    )
{
    PSFDATA sf;
    UINT i;
    TREE_ENUM e;
    LONG offset;

    sf = g_Data;


    while (sf->RegKey) {

        if (EnumFirstFileInTree (&e, sf->Path, NULL, FALSE)) {

            do {

                if (e.Directory) {

                    //
                    // Check to see if this is a different shell folder.
                    //
                    offset = HtFindString (g_Table, e.FullPath);
                    if (offset && offset != sf->TableOffset) {

                        //
                        // This is actually another shell folder. Don't enumerate
                        // it.
                        //
                        if (g_Verbose) {

                            _tprintf (TEXT("sfspace: %s is handled by another shell folder.\n"), e.FullPath);
                        }
                        AbortEnumCurrentDir (&e);
                    }
                    else {

                        //
                        // Increment directory count for this shell folder.
                        //
                        sf->DirectoryCount++;
                    }
                }
                else {

                    //
                    // this is a file. Add its data to our structure.
                    //
                    sf->FileCount++;
                    sf->RawSize += e.FindData->nFileSizeLow;
                    for (i=0; i<LAST_CLUSTER_SIZE; i++) {

                        //
                        // We assume NT doesn't install any massively large files by default.
                        //
                        MYASSERT (!e.FindData->nFileSizeHigh);
                        sf->SpaceNeeded[i] += ((e.FindData->nFileSizeLow / g_ClusterTable[i]) * g_ClusterTable[i]) + g_ClusterTable[i];
                    }
                }

            } while (EnumNextFileInTree (&e));
        }


        //
        // Add the space for all of the directories we found in this shell folder.
        //
        for (i=0; i<LAST_CLUSTER_SIZE; i++) {

            sf->SpaceNeeded[i] += (((sf->DirectoryCount * DIRECTORY_SIZE) / g_ClusterTable[i]) * g_ClusterTable[i]) + g_ClusterTable[i];
        }

        if (g_Verbose) {
            _tprintf (
                TEXT("sfspace: %u files and %u directories enumerated for shell folder %s. Space needed (512k cluster size): %u Raw Space: %u\n"),
                sf->FileCount,
                sf->DirectoryCount,
                sf->RegKey,
                sf->SpaceNeeded[CS_512],
                sf->RawSize
                );
        }

        sf++;
    }

    return TRUE;
}

PCTSTR
pLeftJustify (
    IN PCTSTR String,
    IN UINT FieldWidth
    )
{
    static TCHAR rBuffer[MAX_TCHAR_PATH];
    UINT length;
    UINT i;


    MYASSERT(String);
    length = CharCount (String);
    MYASSERT(FieldWidth < MAX_TCHAR_PATH && length < FieldWidth);

    StringCopy (rBuffer,String);
    for (i=length; i<FieldWidth; i++) {
        rBuffer[i] = TEXT(' ');
    }
    rBuffer[i] = 0;

    return rBuffer;
}

VOID
pOutputSpaceTable (
    VOID
    )
{

    PSFDATA sf;
    UINT i;
    TCHAR buffer[20];

    _tprintf (TEXT("[%s]\n"), S_SHELL_FOLDERS_DISK_SPACE);
    _tprintf (
        TEXT("@*:                                                                                \n")
        TEXT("@*: Disk space requirements for each shell folder.  The key name is a registry     \n")
        TEXT("@*: value name.                                                                    \n")
        TEXT("@*:                                                                                \n")
        );

    sf = g_Data;
    while (sf->RegKey) {

        _tprintf (TEXT("%s"),pLeftJustify (sf->RegKey,20));

        for (i=0; i<LAST_CLUSTER_SIZE; i++) {
            _tprintf (TEXT("%s %u"),i ? TEXT(",") : TEXT("="), sf->SpaceNeeded[i]);
        }
        _tprintf (TEXT("\n"));

        sf++;
    }
}


VOID
HelpAndExit (
    VOID
    )
{
    //
    // This routine is called whenever command line args are wrong
    //

    _ftprintf (
        stderr,
        TEXT("Command Line Syntax:\n\n")
        TEXT("  sfspace [/V]\n")
        TEXT("\nDescription:\n\n")
        TEXT("  sfspace gathers the space requirements for the default\n")
        TEXT("  shell folders installed by Windows 2000. It should be\n")
        TEXT("  run against a clean install of Windows 2000.\n")
        TEXT("\nArguments:\n\n")
        TEXT("  /V  Instructs sfspace to generate verbose output.\n")
        );

    exit (1);
}


INT
__cdecl
_tmain (
    INT argc,
    PCTSTR argv[]
    )
{
    INT i;

    for (i = 1 ; i < argc ; i++) {
        if (argv[i][0] == TEXT('/') || argv[i][0] == TEXT('-')) {
            switch (_totlower (_tcsnextc (&argv[i][1]))) {

            case TEXT('v'):
                //
                // Verbose output wanted.
                //
                g_Verbose = TRUE;
                break;

            default:
                HelpAndExit();
            }
        } else {
            //
            // Parse other args that don't require / or -
            //

            //
            // None
            //
            HelpAndExit();
        }
    }

    //
    // Begin processing
    //

    if (!Init()) {
        return 0;
    }

    //
    // Initialize data structures.
    //
    g_Table = HtAlloc ();
    g_Pool = PoolMemInitPool ();
    _try {

        if (!pInitShellFolderData ()) {
            _ftprintf (stderr, TEXT("sfspace: Unable to initialize shell folder data. Exiting.\n"));
            __leave;
        }

        if (!pGatherSpaceRequirements ()) {
            _ftprintf (stderr, TEXT("sfspace: Unable to gather space requirements for shell folders. Exiting.\n"));
            __leave;
        }

        pOutputSpaceTable ();
    }
    __finally {
        HtFree (g_Table);
        PoolMemDestroyPool (g_Pool);
    }
    //
    // End of processing
    //

    Terminate();

    return 0;
}