// (c) 2006-2007 Electronic Arts Inc.

#if 0
/*
const char *SPIT_history[] =
{
"------------------------------------------------------------------",
"                                                                  ",
"             FileStat - File access Statistics v1.01              ",
"                                                                  ",
"            by Max Burke and Frank Barchard, Criterion            ",
"                                                                  ",
"                   Main program - Feb 6, 2006                     ",
"                                                                  ",
"------------------------------------------------------------------",
"                                                                  ",
" Version Date   SE History:                                       ",
" ------- ------ -- --------                                       ",
"    1.00 060202 MB initial version                                ",
"    1.01 070907 FB fix filename truncation, add gda output        ",
"                                                                  ",
"------------------------------------------------------------------",
"\0"
};
*/
#endif

/*
example output from xbox360 emulator (Game Disc Authoring Tool)
Time (ms) Operation  Seek(us)  Read(us)  Lyr Start LBA   #Blocks Actual(us)    File(s) Accessed
000001536   REQUEST   0082021   0004156   0        32         2   00086168     <Volume Descriptor>
000001629   REQUEST   0145376   0002058   0   1783934         2   00147436     \, \data\big
*/

/*
;
; ABSTRACT
;
; filestat - file access monitor
;
; SUMMARY
;
; filestat [options]
;     -h             This message.
;     -d <directory> Monitor activity on a certain directory.
;     -nosubs        Do not monitor sub directories.
;
; DESCRIPTION
;
; This utility monitors file activity on the specified directory.  When an access even occurs, it is
; printed to stdout.
; if you access a file 'filestatstop' the tool will exit
;
; RETURNS
;
; none
;
; SEE ALSO
;
; Spit
;
; EXAMPLE
;
; >filestat -d e:\
; C:\env\egami\util\dev\spit>filestat -d e:\
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources
; Added       : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\CheckBoxNotChecked.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\CheckBoxNotChecked.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\CheckBoxNotChecked.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources
; Added       : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\LeftArrow.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\LeftArrow.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\LeftArrow.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources
; Added       : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\RightArrow.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\RightArrow.bmp
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\Resources\RightArrow.bmp
; Added       : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\RwArrayEditor.cs
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\RwArrayEditor.cs
; Modified    : dev\rw4\stable\studio\src\GUI\Csl.Rws.UITypeEditors\RwArrayEditor.cs
;
; END ABSTRACT
;
*/



#define _WIN32_WINNT 0x0500

#include <windows.h>
#include <winioctl.h>
#include <wchar.h>
#include <conio.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if defined(_MSC_VER)
#pragma warning(disable: 4996)  // warning C4996: 'fscanf' was declared deprecated
#endif
#if defined(__ICL)
#pragma warning(disable: 1478)  // (icl) warning #1478: function "strcpy" was declared "deprecated"
#endif

#if _MSC_VER >= 1400
#define WCSNCPY(A,B,C) wcsncpy_s(A,C,B,_TRUNCATE)
#define STRCPY(A,B) strcpy_s(A,_TRUNCATE,B)
#elif _MSC_VER >= 1310
#define WCSNCPY(A,B,C) wcsncpy(A,B,C)
#define STRCPY(A,B) strcpy(A,B)
#else
#error Only VS2005 and VS2003 are supported
#endif

const char *g_ProgName = "filestat";
char *g_WatchName = NULL;

bool g_WatchSubs = true;
bool g_GameDiscStyle = false;
HANDLE WatchHandle;
USN_JOURNAL_DATA g_JournalData;

#define FILESTATERROR(p,e) fprintf(stderr, "%s (errno: %d line: %d)\n", p, e, __LINE__)

void PrintHelp();
bool Initialize();
inline void InterpretReason(char *status, int k, DWORD Reason);
bool TrackChanges();

void PrintHelp()
{
    printf("Usage: %s [options]\n", g_ProgName);
    printf("Options:\n");
    printf("  -h             This message.\n");
    printf("  -d <directory> Monitor activity on a certain directory.\n");
    printf("  -g             Game Disc Authoring tool output style.\n");
    printf("  -nosubs        Do not monitor sub directories.\n");
}

bool Initialize()
{
    // May need to remove the GENERIC_READ access specifier
    WatchHandle = CreateFile(g_WatchName,
        FILE_LIST_DIRECTORY | GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);

    if(INVALID_HANDLE_VALUE == WatchHandle)
    {
        FILESTATERROR("CreateFile",GetLastError());
        return false;
    }

    return true;
}


/****************************************************************/
/*  Time function for -time options                             */
/*  returns milliseconds                                        */
/****************************************************************/

#ifdef _MSC_VER
static double i64tod(__int64 it)
{
    return (double) it;
    //t = ((double)(unsigned int)(t1.HighPart)*4294967296.0) + ((double)(unsigned int)(t1.LowPart));
}
#endif

double SPIT_gettime(void)
{
    double t=0;

#ifdef _MSC_VER

    t = i64tod(GetTickCount());

#elif defined(__unix__) || defined(__BEOS__) || defined(__APPLE__)
    {
        struct timeval tv;
        struct timezone tz; // not used
        gettimeofday(&tv,&tz);
        t = (double)tv.tv_sec*1000.0+(double)tv.tv_usec/1000.0;
    }
#endif
    return(t);
}

bool TrackChanges()
{
    // ReadDirectoryChangesW has an issue where it cannot deal with a buffer larger than 64kb over a network
    char buf[0x1000];
    ULONG outputsize;
    int done=false;

    if (g_GameDiscStyle)
        printf("Time (ms) Operation  Seek(us)  Read(us)  Lyr Start LBA   #Blocks Actual(us)    File(s) Accessed\n");

    double startTime = SPIT_gettime();
    while (!done)
    {
//        if (GetKeyState(VK_ESCAPE)&0x8000)
//            done = true;

#if 0
        if (g_GameDiscStyle)
        {
            if(!ReadDirectoryChangesW(WatchHandle, &buf, sizeof(buf), g_WatchSubs, FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_LAST_WRITE, &outputsize, 0, 0))
            {
                FILESTATERROR("ReadDirectoryChangesW",GetLastError());
                return(false);
            }
        }
        else
#endif
        {   
            if(!ReadDirectoryChangesW(WatchHandle,
            &buf, sizeof(buf), g_WatchSubs,
            FILE_NOTIFY_CHANGE_FILE_NAME |
            FILE_NOTIFY_CHANGE_DIR_NAME |
            FILE_NOTIFY_CHANGE_ATTRIBUTES |
            FILE_NOTIFY_CHANGE_SIZE |
            FILE_NOTIFY_CHANGE_LAST_WRITE |
            FILE_NOTIFY_CHANGE_LAST_ACCESS |
            FILE_NOTIFY_CHANGE_CREATION |
            FILE_NOTIFY_CHANGE_SECURITY, &outputsize, 0, 0))
            {
                FILESTATERROR("ReadDirectoryChangesW",GetLastError());
                return(false);
            }
        }

        const char* p = buf;

        for (;;)
        {
            double currentTime = SPIT_gettime();

            const FILE_NOTIFY_INFORMATION* fni = reinterpret_cast<const FILE_NOTIFY_INFORMATION*>(p);


            char * fname = (char*)malloc((fni->FileNameLength + 1)*sizeof(char));
            int k = fni->FileNameLength/sizeof(WCHAR);
            for (int i=0; i<k; ++i)
                fname[i] = (char)fni->FileName[i];
            fname[k] = '\0';

            char pathname[512];
            sprintf(pathname,"%s\\%s",g_WatchName,fname);
#if 1
            WIN32_FIND_DATA finfo;
            HANDLE f = FindFirstFile(pathname,&finfo);
            FindClose(f);

            __int64 fsize = (((__int64)finfo.nFileSizeHigh)<<32) | finfo.nFileSizeLow;
#else
            __int64 fsize = 1;
#endif
            if (strstr(fname,"filestatstart")) // resets time to zero
                startTime = SPIT_gettime();
            if (strstr(fname,"filestatstop"))
                done = true;

            if (g_GameDiscStyle)
                printf("%09.0f   REQUEST   0000000   0000000   0   0000000 %9I64d        0        ",currentTime-startTime,(fsize+2047)/2048);
            else
            {
                printf("%09.0f   ",currentTime-startTime);

                if(FILE_ACTION_ADDED == fni->Action)
                    printf("Added       : ");
                if(FILE_ACTION_REMOVED == fni->Action)
                    printf("Removed     : ");
                if(FILE_ACTION_MODIFIED == fni->Action)
                    printf("Modified    : ");
                if(FILE_ACTION_RENAMED_OLD_NAME == fni->Action)
                    printf("Rename (old): ");
                if(FILE_ACTION_RENAMED_NEW_NAME == fni->Action)
                    printf("Rename (new): ");
                printf("%9I64d     ",fsize);
            }

            printf("%s\n", fname);
            free(fname);

            if(0 == fni->NextEntryOffset)
            {
                break;
            }
            else
            {
                p += fni->NextEntryOffset;
            }
        }
    }

    return true;
}

int main(int argc, char* argv[])
{
    // 6 is the magic number for the length of the volume string
    g_WatchName = (char *)malloc(4*sizeof(char));
    STRCPY(g_WatchName, "c:\\");

    if (argc<2)
    {
        PrintHelp();
        return 0;
    }

    for(int i=1; i<argc; i++)
    {
        if(0 == strcmp(argv[i],"-h"))
        {
            PrintHelp();
            return 0;
        }
        else if(0 == strcmp(argv[i],"-nosubs"))
        {
            g_WatchSubs = false;
        }
        else if(0 == strcmp(argv[i],"-g"))
        {
            g_GameDiscStyle = true;
        }
        else if(0 == strcmp(argv[i],"-d"))
        {
            free(g_WatchName);
            int length = (int)strlen(argv[i+1]);

            g_WatchName = (char *)malloc(length*sizeof(char) + 1);

            ++i;

            STRCPY(g_WatchName, argv[i]);

        }
        else
        {
            fprintf(stderr, "Invalid option, use \"-h\" to view a list of valid command line options\n");
            return 1;
        }
    }

    if (!Initialize())
    {
        return 1;
    }

    if (!TrackChanges())
    {
        return 1;
    }

    return 0;
}
