/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    receive.c

Abstract:

    This module handles the FAX receive case.

Author:

    Wesley Witt (wesw) 6-Mar-1996


Revision History:

--*/

#include "faxsvc.h"
#pragma hdrstop


DWORD
FaxReceiveThread(
    PFAX_RECEIVE_ITEM FaxReceiveItem
    )

/*++

Routine Description:

    This function process a FAX send operation.  This runs
    asynchronously as a separate thread.  There is one
    thread for each outstanding FAX operation.

Arguments:

    FaxReceiveItem  - FAX receive packet

Return Value:

    Error code.

--*/

{
    DWORD rVal = ERROR_SUCCESS;
    PJOB_ENTRY JobEntry;
    DWORD JobId;
    PLINE_INFO LineInfo;
    PFAX_RECEIVE FaxReceive = NULL;
    PFAX_DEV_STATUS FaxStatus = NULL;
    DWORD ReceiveSize;
    DWORD StatusSize;
    BOOL Result;
    DWORD BytesNeeded;
    DWORDLONG ElapsedTime = 0;
    DWORDLONG ReceiveTime = 0;
    BOOL DoFaxRoute = FALSE;
    DWORD Attrib;
    DWORD RecoveredPages,TotalPages;
    MS_TAG_INFO MsTagInfo;
    BOOL fReceiveNoFile = FALSE;
    BOOL ReceiveFailed = FALSE;
    PJOB_QUEUE JobQueue = NULL;
    BOOL DeviceCanSend;

    __try {

        LineInfo = FaxReceiveItem->LineInfo;
        JobEntry = FaxReceiveItem->JobEntry;

        JobQueue = AddJobQueueEntry(
            JT_RECEIVE,
            FaxReceiveItem->FileName,
            NULL,
            NULL,
            FALSE,
            JobEntry
            );

        if (!JobQueue) {
            return ERROR_NOT_ENOUGH_MEMORY;
        }
        JobId = JobQueue->JobId;
        DeviceCanSend = ((LineInfo->Flags & FPF_SEND) == FPF_SEND);

        //
        // allocate memory for the receive packet
        // this is a variable size packet based
        // on the size of the strings contained
        // withing the packet.
        //

        ReceiveSize = sizeof(FAX_RECEIVE) + FAXDEVRECEIVE_SIZE;
        FaxReceive = MemAlloc( ReceiveSize );
        if (!FaxReceive) {
            RemoveJobQueueEntry( JobQueue );
            JobQueue = NULL;
            return ERROR_NOT_ENOUGH_MEMORY;
        }

        //
        // allocate memory for the status packet
        // this is a variable size packet based
        // on the size of the strings contained
        // withing the packet.
        //

        StatusSize = sizeof(FAX_DEV_STATUS) + FAXDEVREPORTSTATUS_SIZE;
        FaxStatus = (PFAX_DEV_STATUS) MemAlloc( StatusSize );
        if (!FaxStatus) {
            RemoveJobQueueEntry( JobQueue );
            JobQueue = NULL;
            MemFree( JobQueue );
            return ERROR_NOT_ENOUGH_MEMORY;
        }

        SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_CONTINUOUS);

        //
        // setup the status packet
        //

        FaxStatus->SizeOfStruct = StatusSize;

        //
        // setup the receive packet
        //

        FaxReceive->SizeOfStruct    = ReceiveSize;

        //
        // copy filename into place
        //
        FaxReceive->FileName        = (LPTSTR) ((LPBYTE)FaxReceive + sizeof(FAX_RECEIVE));
        _tcscpy( FaxReceive->FileName, FaxReceiveItem->FileName );

        FaxReceive->ReceiverName    = NULL;
        
        //
        // copy number into place right after filename
        //
        FaxReceive->ReceiverNumber  = (LPTSTR) ( (LPBYTE)FaxReceive->FileName +
                            sizeof(TCHAR)*(_tcslen(FaxReceive->FileName) + 1));
        _tcscpy( FaxReceive->ReceiverNumber, LineInfo->Csid );
                    
        FaxReceive->Reserved[0]     = 0;
        FaxReceive->Reserved[1]     = 0;
        FaxReceive->Reserved[2]     = 0;
        FaxReceive->Reserved[3]     = 0;

        

        Attrib = GetFileAttributes( FaxReceiveDir );
        if (Attrib == 0xffffffff) {
            MakeDirectory( FaxReceiveDir );
        }
        Attrib = GetFileAttributes( FaxReceiveDir );
        if (Attrib == 0xffffffff) {
            FaxLog(
                FAXLOG_CATEGORY_INBOUND,
                FAXLOG_LEVEL_MAX,
                1,
                MSG_FAX_RECEIVE_NODIR,
                FaxReceiveDir
                );
        }

        Attrib = GetFileAttributes( FaxReceive->FileName );
        if (Attrib == 0xffffffff) {
            FaxLog(
                FAXLOG_CATEGORY_INBOUND,
                FAXLOG_LEVEL_MIN,
                1,
                MSG_FAX_RECEIVE_NOFILE,
                FaxReceive->FileName
                );
            fReceiveNoFile = TRUE;
            DebugPrint(( TEXT("FaxReceive - %s does not exist"), FaxReceive->FileName ));

        } else {
            DebugPrint(( TEXT("Starting FAX receive into %s"), FaxReceive->FileName ));
        }

        //
        // do the actual receive
        //

        __try {

            Result = LineInfo->Provider->FaxDevReceive(
                    (HANDLE) JobEntry->InstanceData,
                    FaxReceiveItem->hCall,
                    FaxReceive
                    );

        } __except (EXCEPTION_EXECUTE_HANDLER) {

            Result = FALSE;
            DebugPrint(( TEXT("FaxDevReceive() failed: 0x%08x"), GetExceptionCode() ));
            ReceiveFailed = TRUE;

        }

        __try {

            LineInfo->Provider->FaxDevReportStatus(
               (HANDLE) JobEntry->InstanceData,
                FaxStatus,
                StatusSize,
                &BytesNeeded
                );

        } __except (EXCEPTION_EXECUTE_HANDLER) {

            DebugPrint(( TEXT("FaxDevReportStatus() failed: 0x%08x"), GetExceptionCode() ));

        }

        if (!Result) {

            DebugPrint(( TEXT("FAX receive failed: 0x%08x"), FaxStatus->StatusId ));
            ReceiveFailed = TRUE;

            if (FaxStatus->StatusId == FS_NOT_FAX_CALL) {
                if (HandoffCallToRas( LineInfo, FaxReceiveItem->hCall )) {
                    FaxReceiveItem->hCall = 0;
                    LineInfo->State = FPS_NOT_FAX_CALL;
                    DeviceCanSend = FALSE;
                }
                RemoveJobQueueEntry( JobQueue );
                JobQueue = NULL;
                DeleteFile( FaxReceive->FileName );
            }

            if ( (FaxStatus->StatusId == FS_FATAL_ERROR) && (! fReceiveNoFile) ) {
                //
                // try to recover one or more pages of the received fax
                //
                if (!TiffRecoverGoodPages(FaxReceive->FileName,&RecoveredPages,&TotalPages) ) {
                    //
                    // couldn't recover any pages, just log an error and delete the received fax.
                    //
rxerr:
                    FaxLog(
                           FAXLOG_CATEGORY_INBOUND,
                           FAXLOG_LEVEL_MIN,
                           0,
                           MSG_FAX_RECEIVE_FAILED
                           );
                    //DeleteFile( FaxReceive->FileName );
                } else {
                    //
                    // recovered some pages, log a message and add to job queue
                    //
                    TCHAR RecoverCountStrBuf[64];
                    TCHAR TotalCountStrBuf[64];
                    TCHAR TimeStr[128];
                    LPTSTR ToStr;
                    TCHAR RecoverFileName[MAX_PATH];
                    
                    GenerateUniqueFileName( FaxReceiveDir, TEXT("tif"), RecoverFileName, MAX_PATH );
                    if (!CopyFile(FaxReceive->FileName,RecoverFileName,FALSE)) {
                        goto rxerr;
                    }
                     
                    FormatElapsedTimeStr(
                        (FILETIME*)&JobEntry->ElapsedTime,
                        TimeStr,
                        sizeof(TimeStr)
                        );

                    _ltot((LONG) RecoveredPages, RecoverCountStrBuf, 10);
                    _ltot((LONG) TotalPages, TotalCountStrBuf, 10);

                    if (FaxStatus->RoutingInfo == NULL || FaxStatus->RoutingInfo[0] == 0) {
                        ToStr = FaxReceive->ReceiverNumber;
                    } else {
                        ToStr = FaxStatus->RoutingInfo;

                    }
                    FaxLog(
                           FAXLOG_CATEGORY_INBOUND,
                           FAXLOG_LEVEL_MIN,
                           8,
                           MSG_FAX_RECEIVE_FAIL_RECOVER,
                           RecoverFileName,
                           FaxStatus->CSI,
                           FaxStatus->CallerId,
                           ToStr,
                           RecoverCountStrBuf,
                           TotalCountStrBuf,
                           TimeStr,
                           JobEntry->LineInfo->DeviceName
                          );
                    AddJobQueueEntry( JT_FAIL_RECEIVE,
                                      RecoverFileName,
                                      NULL,
                                      NULL,
                                      FALSE,
                                      NULL );
                }

                RemoveJobQueueEntry( JobQueue );
                JobQueue = NULL;

            }

            if (FaxStatus->StatusId == FS_USER_ABORT) {
                FaxLog(
                    FAXLOG_CATEGORY_INBOUND,
                    FAXLOG_LEVEL_MED,
                    0,
                    MSG_FAX_RECEIVE_USER_ABORT
                    );
                RemoveJobQueueEntry( JobQueue );
                JobQueue = NULL;
                //DeleteFile( FaxReceive->FileName);
            }
        } else {

            __try {

                GetSystemTimeAsFileTime( (FILETIME*) &JobEntry->EndTime );
                ReceiveTime = JobEntry->StartTime;
                JobEntry->ElapsedTime = JobEntry->EndTime - JobEntry->StartTime;

                if (!TiffPostProcessFast( FaxReceive->FileName, NULL )) {

                    DebugPrint(( TEXT("failed to post process the TIFF file") ));
                    DebugPrint(( TEXT("FAX receive %d failed"), JobId ));
                    ReceiveFailed = TRUE;

                    FaxLog(
                           FAXLOG_CATEGORY_INBOUND,
                           FAXLOG_LEVEL_MIN,
                           0,
                           MSG_FAX_RECEIVE_FAILED
                           );

                    RemoveJobQueueEntry( JobQueue );
                    JobQueue = NULL;
                } else {

                    TCHAR PageCountStrBuf[64];
                    TCHAR TimeStr[128];
                    LPTSTR ToStr;

                    DebugPrint(( TEXT("FAX receive %d succeeded"), JobId ));

                    FormatElapsedTimeStr(
                        (FILETIME*)&JobEntry->ElapsedTime,
                        TimeStr,
                        sizeof(TimeStr)
                        );

                    _ltot((LONG) FaxStatus->PageCount, PageCountStrBuf, 10);

                    if (FaxStatus->RoutingInfo == NULL || FaxStatus->RoutingInfo[0] == 0) {
                        ToStr = FaxReceive->ReceiverNumber;
                    } else {
                        ToStr = FaxStatus->RoutingInfo;

                    }
                    FaxLog(
                        FAXLOG_CATEGORY_INBOUND,
                        FAXLOG_LEVEL_MED,
                        7,
                        MSG_FAX_RECEIVE_SUCCESS,
                        FaxReceive->FileName,
                        FaxStatus->CSI,
                        FaxStatus->CallerId,
                        ToStr,
                        PageCountStrBuf,
                        TimeStr,
                        JobEntry->LineInfo->DeviceName
                        );

                    ElapsedTime = JobEntry->ElapsedTime;
                    DoFaxRoute = TRUE;
                }

            } __except (EXCEPTION_EXECUTE_HANDLER) {

                DebugPrint(( TEXT("failed to post process the TIFF file, ec=%x"), GetExceptionCode() ));
                ReceiveFailed = TRUE;
                RemoveJobQueueEntry( JobQueue );
                JobQueue = NULL;
            }

        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        DebugPrint(( TEXT("FAX receive failed due to exception in device provider, ec=0x%08x"), GetExceptionCode() ));

        ReceiveFailed = TRUE;
        RemoveJobQueueEntry( JobQueue );
        JobQueue = NULL;
    }

    if (PerfCounters && ReceiveFailed && LineInfo->State != FPS_NOT_FAX_CALL) {
        InterlockedIncrement( (PLONG)&PerfCounters->InboundFailedReceive );
    }

    //
    // end the job
    //

    JobEntry->RefCount -= 1;
    if (JobEntry->RefCount == 0 && JobEntry->hEventEnd) {
        SetEvent( JobEntry->hEventEnd );
    }

    ReleaseJob( JobEntry );

    //
    // add the microsoft fax tags to the file
    // this is necessary ONLY when we route the
    // file when doing a receive.  if we are not
    // routing the file then it is deleted, so
    // adding the tags is not necessary.
    //

    MsTagInfo.RecipName     = FaxReceive->ReceiverName;
    MsTagInfo.RecipNumber   = FaxReceive->ReceiverNumber;
    MsTagInfo.SenderName    = NULL;
    MsTagInfo.Routing       = FaxStatus->RoutingInfo;
    MsTagInfo.CallerId      = FaxStatus->CallerId;
    MsTagInfo.Csid          = FaxReceive->ReceiverNumber;
    MsTagInfo.Tsid          = FaxStatus->CSI;
    MsTagInfo.FaxTime       = ReceiveTime;

    TiffAddMsTags( FaxReceive->FileName, &MsTagInfo );

    //
    // route the newly received fax
    //

    if (DoFaxRoute) {

        if (PerfCounters){
            SYSTEMTIME SystemTime ;
            DWORD Seconds ;
            HANDLE hFileHandle;
            DWORD Bytes = 0 ;
            InterlockedIncrement( (LPLONG) &PerfCounters->InboundFaxes ) ;
            InterlockedIncrement( (LPLONG) &PerfCounters->TotalFaxes ) ;
            FileTimeToSystemTime( (FILETIME*)&ElapsedTime, &SystemTime );
            Seconds = (DWORD)( SystemTime.wSecond + 60 * ( SystemTime.wMinute + 60 * SystemTime.wHour ));
            InterlockedExchangeAdd( (PLONG)&PerfCounters->InboundPages, (LONG)FaxStatus->PageCount );
            InterlockedExchangeAdd( (PLONG)&PerfCounters->TotalPages, (LONG)FaxStatus->PageCount );
            hFileHandle = CreateFile(
                FaxReceive->FileName,
                GENERIC_READ,
                0,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL
                );
            if ( hFileHandle != INVALID_HANDLE_VALUE ){
                Bytes = GetFileSize( hFileHandle, NULL );
                CloseHandle( hFileHandle );
            }
            EnterCriticalSection( &CsPerfCounters );

            InboundSeconds += Seconds;
            TotalSeconds += Seconds;
            PerfCounters->InboundMinutes = InboundSeconds/60 ;
            PerfCounters->TotalMinutes = TotalSeconds/60;
            PerfCounters->InboundBytes += Bytes;
            PerfCounters->TotalBytes += Bytes;

            LeaveCriticalSection( &CsPerfCounters );
        }

        __try {

            BOOL RouteSucceeded;
            PROUTE_FAILURE_INFO RouteFailureInfo;
            DWORD CountFailureInfo;
            PFAX_ROUTE Route = MemAlloc( sizeof(FAX_ROUTE) );


            if (Route == NULL) {
                __leave;
            }
            //
            // now setup the fax routing data structure
            //

            Route->SizeOfStruct    = sizeof(FAX_ROUTE);
            Route->JobId           = JobId;
            Route->ElapsedTime     = ElapsedTime;
            Route->ReceiveTime     = ReceiveTime;
            Route->PageCount       = FaxStatus->PageCount;
            Route->Csid            = StringDup( FaxReceive->ReceiverNumber );
            Route->Tsid            = StringDup( FaxStatus->CSI );
            Route->CallerId        = StringDup( FaxStatus->CallerId );
            Route->ReceiverName    = StringDup( FaxReceive->ReceiverName );
            Route->ReceiverNumber  = StringDup( FaxReceive->ReceiverNumber );
            Route->DeviceName      = LineInfo->DeviceName;
            Route->DeviceId        = LineInfo->PermanentLineID;
            Route->RoutingInfo     = StringDup( FaxStatus->RoutingInfo );
            JobQueue->FaxRoute     = Route;

            JobQueue->FaxRoute =    Route;

            RouteSucceeded = FaxRoute(
                JobQueue,
                FaxReceive->FileName,
                Route,
                &RouteFailureInfo,
                &CountFailureInfo
                );


            if (!RouteSucceeded)
            {
                INT i;
                TCHAR QueueFileName[MAX_PATH];

                EnterCriticalSection( &CsQueue );
                //
                // wrap the critical section stuff in try block so we always release CsQueue
                //
                __try {

                   JobQueue->CountFailureInfo = CountFailureInfo;

                   for (i = 0; i < (INT) CountFailureInfo; i++) {
                       JobQueue->RouteFailureInfo[i] = RouteFailureInfo[i];
                   }

                   GenerateUniqueFileName( FaxQueueDir, TEXT("fqe"), QueueFileName, sizeof(QueueFileName)/sizeof(WCHAR) );

                   JobQueue->QueueFileName = StringDup( QueueFileName );
                   JobQueue->JobType = JT_ROUTING;
                   JobQueue->JobStatus = JS_RETRYING;
                   RescheduleJobQueueEntry( JobQueue );
                } __except (EXCEPTION_EXECUTE_HANDLER) {
                   DebugPrint(( TEXT("FaxRoute() crashed, ec=0x%08x"), GetExceptionCode() ));
                }

                LeaveCriticalSection( &CsQueue );

            } else {

                RemoveJobQueueEntry( JobQueue );

                JobQueue = NULL;
            }

        } __except (EXCEPTION_EXECUTE_HANDLER) {

            DebugPrint(( TEXT("FaxRoute() crashed, ec=0x%08x"), GetExceptionCode() ));

        }

    }

    EnterCriticalSection( &CsQueue );

    if (JobQueue && JobQueue->JobEntry && (JobQueue->JobType != JT_ROUTING)) {        
        JobQueue->JobStatus = JS_DELETING;
        JobQueue->JobEntry = NULL;
    }

    LeaveCriticalSection( &CsQueue );

    EndJob( JobEntry );

    //
    // clean up and exit
    //

    MemFree( FaxReceiveItem->FileName );
    MemFree( FaxReceiveItem );    
    MemFree( FaxReceive );
    MemFree( FaxStatus );

    //
    // signal our queue if we now have a send capable device available.
    // (also false if we're did a RAS handoff, since the device is still in use
    //
    if (DeviceCanSend) {
        ReleaseSemaphore( JobQueueSemaphore, 1, NULL );
    }

    SetThreadExecutionState(ES_CONTINUOUS);

    return rVal;
}


DWORD
StartFaxReceive(
    PJOB_ENTRY      JobEntry,
    HCALL           hCall,
    PLINE_INFO      LineInfo,
    LPTSTR          FileName,
    DWORD           FileNameSize
    )

/*++

Routine Description:

    This function start a FAX receive operation by creating
    a thread that calls the appropriate device provider.

Arguments:

    hCall       - Call handle
    dwMessage   - Reason for the callback
    dwInstance  - LINE_INFO pointer
    dwParam1    - Callback parameter #1
    dwParam2    - Callback parameter #2
    dwParam3    - Callback parameter #3

Return Value:

    Error code.

--*/

{
    PFAX_RECEIVE_ITEM FaxReceiveItem = NULL;
    DWORD rVal = ERROR_SUCCESS;
    HANDLE hThread;
    DWORD ThreadId;



    //
    // generate a filename for the received fax
    //

    GenerateUniqueFileName( FaxReceiveDir, TEXT("tif"), FileName, FileNameSize );

    //
    // allocate the fax receive structure
    //

    FaxReceiveItem = MemAlloc( sizeof(FAX_RECEIVE_ITEM) );
    if (!FaxReceiveItem) {
        rVal = ERROR_NOT_ENOUGH_MEMORY;
        goto exit;
    }

    //
    // setup the fax receive values
    //

    FaxReceiveItem->hCall      = hCall;
    FaxReceiveItem->LineInfo   = LineInfo;
    FaxReceiveItem->JobEntry   = JobEntry;
    FaxReceiveItem->FileName   = StringDup( FileName );

    JobEntry->JobType          = JT_RECEIVE;
    JobEntry->CallHandle       = hCall;
    JobEntry->RefCount        += 1;

    LineInfo->State            = FPS_INITIALIZING;

    //
    // start the receive operation
    //

    hThread = CreateThread(
        NULL,
        1024*100,
        (LPTHREAD_START_ROUTINE) FaxReceiveThread,
        (LPVOID) FaxReceiveItem,
        0,
        &ThreadId
        );

    if (!hThread) {
        MemFree( FaxReceiveItem );
        rVal = GetLastError();
    } else {
        CloseHandle( hThread );
    }

exit:
    return rVal;
}