/* -*- Mode: C++; tab-width: 4; tabs-indent-mode: nil -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */
/* su_win.c
 * Win specific softupdate routines
 */

#ifdef XP_OS2
  #include os2updt.h"
#endif  

#include <stdafx.h>
#include "xp_mcom.h"
#include "su_folderspec.h"
#include "su_instl.h"
#include "xp_str.h"
#include "npapi.h"
#include "libevent.h"
#include "fe_proto.h"
#include "xpgetstr.h"
#include <windows.h>
#include "NSReg.h"

#ifdef WIN32
#include <winver.h>
#elif !defined (XP_OS2)
#include <ver.h>
#endif

extern NPError wfe_GetPluginsDirectory(CString& csDirname);
extern "C" char * FE_GetProgramDirectory(char *buffer, int length);

extern "C" int SU_NEED_TO_REBOOT;

XP_Bool fe_FileNeedsUpdate(char *sourceFile, char *targetFile);

#if defined(WIN32) || defined(XP_OS2)
#define su_FileRemove(file,type) XP_FileRemove((file),(type))
#else
int su_FileRemove(char *fname, XP_FileType type);
#endif



char * FE_GetDirectoryPath( su_DirSpecID folderID)
{
	char * directory = NULL;
    char path[_MAX_PATH];
    DWORD dwVersion;
    UINT len;

	switch( folderID)
	{
	case ePluginFolder:
		{
			NPError err;
			CString c;
			err = wfe_GetPluginsDirectory(c);
			if (err == 0) {
    			c += (TCHAR)'\\';
				directory = XP_STRDUP( c );
            }
		}
		break;

	case eProgramFolder:
        FE_GetProgramDirectory( path, _MAX_PATH );
        directory = XP_STRDUP( path );
        break;

	case ePackageFolder:
        break;

    case eTemporaryFolder:
    {
        char* tmpdir = XP_TempDirName();
        int slen;

        XP_STRNCPY_SAFE( path, tmpdir, _MAX_PATH );
        XP_FREEIF(tmpdir);

        slen = XP_STRLEN(path);
        if ( slen < _MAX_PATH - 1)
        {
            path[slen]   = '\\';
            path[slen+1] = '\0';
        }
        else
            break;
 		
        directory = XP_STRDUP( path );
    }
        break;

	case eCommunicatorFolder:
	{
		char* p;

        	FE_GetProgramDirectory( path, _MAX_PATH );
		
		if ( (p = XP_STRRCHR( path, '\\' )) ) *p =  '\0';
		if ( (p = XP_STRRCHR( path, '\\' )) ) p[1] =  '\0';

        	directory = XP_STRDUP( path );
	}
        break;


    case eWin_System16Folder:
        len = GetSystemDirectory( path, _MAX_PATH );
		
#ifndef XP_OS2
        // If Windows NT
    	dwVersion = GetVersion();
        if ( dwVersion < 0x80000000 ) {
            // and the last two chars of the system dir are "32"
            if ( XP_STRCMP( path+len-2, "32" ) == 0 ) {
                // 16-bit system dir is just "System"
                len = len - 2;
            }
        }
#endif

        // Need enough space to add the trailing backslash
        if(len > _MAX_PATH-2)
            break;
        path[len]   = '\\';
        path[len+1] = '\0';

        directory = XP_STRDUP( path );
        break;

	case eWin_SystemFolder:
        len = GetSystemDirectory( path, _MAX_PATH );
        // Need enough space to add the trailing backslash
        if(len > _MAX_PATH-2)
            break;
        path[len]   = '\\';
        path[len+1] = '\0';

        directory = XP_STRDUP( path );
        break;

	case eWin_WindowsFolder:
        len = GetWindowsDirectory( path, _MAX_PATH );
        // Need enough space to add the trailing backslash
        if(len > _MAX_PATH-2)
            break;
        path[len]   = '\\';
        path[len+1] = '\0';

        directory = XP_STRDUP( path );
        break;

	case eJavaBinFolder:
        FE_GetProgramDirectory( path, _MAX_PATH );
        directory = PR_smprintf("%sjava\\bin\\", path);
        break;

	case eJavaClassesFolder:
        FE_GetProgramDirectory( path, _MAX_PATH );
        directory = PR_smprintf("%sjava\\classes\\", path);
        break;

	case eJavaDownloadFolder:
        FE_GetProgramDirectory( path, _MAX_PATH );
        directory = PR_smprintf("%sjava\\download\\", path);
        break;

    case eNetHelpFolder:
        {
            char* tmpdir = FE_GetNetHelpDir();
            if ('/' != *tmpdir ) {
                /* we got a valid file url from FE_GetNetHelpDir() */
                directory = WH_FileName(tmpdir+7, xpURL);
            }
            else {
                /* windows FE couldn't find registry setting */
                FE_GetProgramDirectory( path, _MAX_PATH );
                if(strlen(path)+strlen("NetHelp\\")+1 > _MAX_PATH)
                    break;
                XP_STRCAT( path, "NetHelp\\" );
                directory = XP_STRDUP( path );
            }
            XP_FREEIF(tmpdir);
        }
        break;
       
    case eOSDriveFolder:
        len = GetWindowsDirectory( path, _MAX_PATH );
        if (len)
        {
            if ( path[1] == ':' && path[2] == '\\' )
            {
                path[3] = 0;
                directory = XP_STRDUP( path );
            }
        }
        break;
      


    // inapplicable platform specific directories
    case eMac_SystemFolder:
	case eMac_DesktopFolder:
	case eMac_TrashFolder:
    case eMac_StartupFolder:
	case eMac_ShutdownFolder:
	case eMac_AppleMenuFolder:
	case eMac_ControlPanelFolder:
	case eMac_ExtensionFolder:
	case eMac_FontsFolder:
	case eMac_PreferencesFolder:
	case eUnix_LocalFolder:
	case eUnix_LibFolder:
        break;

    case eFileURLFolder: // should never be handled in this routine
    default:
        // someone added a new one and didn't take care of it
        XP_ASSERT(0);
		break;
	}
	return directory;
}

int FE_ExecuteFile( const char * filename, const char * cmdline )
{
    if ( cmdline == NULL || filename == NULL )
        return -1;

    DWORD hInst = WinExec( cmdline, SW_NORMAL );
    su_DeleteOldFileLater( (char*)filename );

    if ( hInst < 32 )
        return -1;
    else
        return 0;
}



/* Template for a delayed "rename" of a file on windows platform, need to:
     - Convert filenames to native form
     - Add error checking
 */
#ifndef NO_ERROR
#define NO_ERROR 0
#endif

int    FE_ReplaceExistingFile(char *CurrentFname, XP_FileType ctype,
		char *FinalFname, XP_FileType ftype, XP_Bool force)
{
    int     err;
    char *  currentName;
    char *  finalName;

    /* Convert file names to their native format */
    currentName = WH_FileName (CurrentFname, ctype);
    finalName   = WH_FileName (FinalFname, ftype);

    if ( currentName == NULL || finalName == NULL ) {
        err = -1;
        goto cleanup;
    }

    // replace file if forced or if windows version info is newer
    if (force || fe_FileNeedsUpdate( currentName, finalName ) )
    {
        /* try to delete the existing file */
        err = su_FileRemove( FinalFname, ftype );
        if ( NO_ERROR == err )
        {
            err = XP_FileRename(CurrentFname, ctype, FinalFname, ftype);
        }
        else
        {
            /* couldn't delete, probably in use. Schedule for later */
    	    DWORD	dwVersion, dwWindowsMajorVersion;

            char* final = finalName;
            char* current = currentName;

#if !defined(XP_OS2)
#if !defined(XP_WIN16)
    	    /* Get OS version info */
    	    dwVersion = GetVersion();
    	    dwWindowsMajorVersion =  (DWORD)(LOBYTE(LOWORD(dwVersion)));

	        /* Get build numbers for Windows NT or Win32s */

        	if (dwVersion < 0x80000000)                // Windows NT
    	    {
    		    /* On Windows NT */

                if ( WFE_IsMoveFileExBroken() )
                {
                    /* the MOVEFILE_DELAY_UNTIL_REBOOT option doesn't work on
                     * NT 3.51 SP4 or on NT 4.0 until SP2
                     */
                    struct stat statbuf;
                    BOOL nameFound = FALSE;
                    char tmpname[_MAX_PATH];

                    XP_STRNCPY_SAFE( tmpname, finalName, _MAX_PATH );
                    int len = strlen(tmpname);
                    while (!nameFound && len < _MAX_PATH ) {
                        tmpname[len-1] = '~';
                        tmpname[len] = '\0';
                        if ( stat(tmpname, &statbuf) != 0 )
                            nameFound = TRUE;
                        else
                            len++;
                    }
                    if ( nameFound ) {
                        if ( MoveFile( finalName, tmpname ) ) {
                            if ( MoveFile( currentName, finalName ) ) {
                                err = su_DeleteOldFileLater( tmpname );
                            }
                            else {
                                /* 2nd move failed, put old file back */
                                MoveFile( tmpname, finalName );
                            }
                        }
                        else {
                            /* non-executable in use; schedule for later */
                            err = su_ReplaceOldFileLater( CurrentFname, FinalFname );
                        }
                    }
                }
                else if ( MoveFileEx(currentName, finalName, MOVEFILE_DELAY_UNTIL_REBOOT) )
                {
		            err = NO_ERROR;
                }
	        }
	        else // Windows 95 or Win16
	        {
        		/*
 		         * Place an entry in the WININIT.INI file in the Windows directory
		         * to delete finalName and rename currentName to be finalName at reboot
 		         */

                int strlen;
 		        char	Src[_MAX_PATH];   // 8.3 name
 		        char	Dest[_MAX_PATH];  // 8.3 name

                strlen = GetShortPathName( (LPCTSTR)currentName, (LPTSTR)Src, (DWORD)sizeof(Src) );
                if ( strlen > 0 ) {
                    current = Src;
                }

		        strlen = GetShortPathName( (LPCTSTR) finalName, (LPTSTR) Dest, (DWORD) sizeof(Dest));
                if ( strlen > 0 ) {
                    final = Dest;
                }

#endif /* !XP_WIN16 */

                /* NOTE: use OEM filenames! Even though it looks like a Windows
                 *       .INI file, WININIT.INI is processed under DOS 
                 */
                AnsiToOem( final, final );
                AnsiToOem( current, current );

                if ( WritePrivateProfileString( "Rename", final, current, "WININIT.INI" ) )
		            err = NO_ERROR;

#if !defined(XP_WIN16)
            }
#endif /* XP_WIN16 */

#else /* XP_OS2 */
            ULONG usRetCode;
            usRetCode = WriteLockFileRenameEntry(final,current,"SOFTUPDT.LST");
            if (usRetCode == NO_ERROR) {
                err = NO_ERROR;
            }
#endif /* XP_OS2 */

            if ( NO_ERROR == err )
                err = REBOOT_NEEDED;
	    }
    } // (force || fe_FileNeedsUpdate( currentName, finalName ))
    else {
        // file to be installed was older than existing file--clean up
        err = XP_FileRemove( CurrentFname, ctype );
    }

cleanup:
    XP_FREEIF(finalName);
    XP_FREEIF(currentName);

	return err;
}


void PR_CALLBACK SU_AlertCallback(void * dummy)
{
	MWContext * cx;
	cx = XP_FindSomeContext();
	FE_Alert(cx, XP_GetString(SU_NEED_TO_REBOOT));
}



XP_Bool fe_FileNeedsUpdate(char *sourceFile, char *targetFile)
{
    XP_Bool needUpdate;

#if defined XP_OS2
    needUpdate = TRUE; //Do not have internal version control so
                       //assume that files should be replaced as
                       //in unix version
#else

    DWORD targetInfoSize;
    DWORD sourceInfoSize;
    DWORD bogusHandle;
    void *sourceData;
    void *targetData;
    VS_FIXEDFILEINFO *sourceInfo;
    VS_FIXEDFILEINFO *targetInfo;
    UINT  targIBSize;
    UINT  srcIBSize;

    // always replace old file if it doesn't have version info
    targetInfoSize = GetFileVersionInfoSize( targetFile, &bogusHandle );
    if (targetInfoSize == 0) 
        return TRUE;

    // if new file doesn't have a version assume it's older than one that does
    sourceInfoSize = GetFileVersionInfoSize( sourceFile, &bogusHandle );
    if (sourceInfoSize == 0) 
        return FALSE;
    
    // both files have version info blocks; we must compare them
    needUpdate = TRUE;
    sourceData = XP_ALLOC(sourceInfoSize);
    targetData = XP_ALLOC(targetInfoSize);

    if ( (sourceData != NULL) && (targetData != NULL) &&
        GetFileVersionInfo(targetFile, NULL, targetInfoSize, targetData) &&
        GetFileVersionInfo(sourceFile, NULL, sourceInfoSize, sourceData) &&
        VerQueryValue(targetData, "\\", (void**)&targetInfo, &targIBSize)  &&
        VerQueryValue(sourceData, "\\", (void**)&sourceInfo, &srcIBSize) )
    {
        DWORD targetMajorVer = targetInfo->dwFileVersionMS;
        DWORD targetMinorVer = targetInfo->dwFileVersionLS;
        DWORD sourceMajorVer = sourceInfo->dwFileVersionMS;
        DWORD sourceMinorVer = sourceInfo->dwFileVersionLS;

        // don't replace a file with a newer version
        if (targetMajorVer > sourceMajorVer) 
        {
            needUpdate = FALSE;
        }
        else if (targetMajorVer == sourceMajorVer && targetMinorVer > sourceMinorVer)
        {
            needUpdate = FALSE;
        }
        else
            needUpdate = TRUE;
    }

    XP_FREEIF(sourceData);
    XP_FREEIF(targetData);
#endif //XP_OS2
    return needUpdate;
}





#if !defined(WIN32) && !defined(XP_OS2)

#define BUFLEN 1024
void FE_ScheduleRenameUtility(void)
{
    int  cmdlength;
    char *folder;
    char * buf = (char*)XP_ALLOC(BUFLEN);
    if ( buf != NULL ) {
        FE_GetProgramDirectory(buf, BUFLEN);
        XP_STRCAT(buf, "NSINIT.EXE,");
        cmdlength = XP_STRLEN(buf);

        if (!GetProfileString("Windows","Run","",(buf+cmdlength),
            (BUFLEN-cmdlength))) 
        {
            // no other RUN commands so don't need trailing comma
            buf[cmdlength-1] = '\0';
        }

        WriteProfileString("Windows","Run",buf);

        XP_FREE(buf);
    }
}


// WIN16 only, Win32 is macro'd to usual XP_FileRemove()
int su_FileRemove(char *fname, XP_FileType type)
{
    HMODULE     hinst;
    char        *pFile;
    int         err = NO_ERROR;

    // convert to native filename and see if it's currently running
    pFile = WH_FileName( fname, type );
    if (pFile != NULL ) {
        // Don't try to delete if Windows says it's in use
        hinst = GetModuleHandle(pFile);
        if ( hinst != NULL ) {
            // GetModuleHandle() accepts the full path but matches only on the
            // root name.  Use GetModuleFileName() to verify a true match
            char module[_MAX_PATH];
            GetModuleFileName( hinst, module, _MAX_PATH );
            if ( stricmp( pFile, module ) == 0 ) {
                // they match
                err = -1;
            }
        }

        XP_FREE(pFile);
    }

    if ( err == NO_ERROR ) {
        // wasn't in use (or couldn't convert to native filename)
        // try to remove the old way
        err = XP_FileRemove( fname, type );
    }

    return err;
}

#endif /* !WIN32 */



#ifdef WIN32
BOOL WFE_IsMoveFileExBroken()
{
    /* the NT option MOVEFILE_DELAY_UNTIL_REBOOT is broken on 
     * Windows NT 3.51 Service Pack 4 and NT 4.0 before Service Pack 2
     */
    BOOL broken = FALSE;
    OSVERSIONINFO osinfo;

    // they *all* appear broken--better to have one way that works.
    return TRUE;

    osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    if (GetVersionEx(&osinfo) && osinfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
        if ( osinfo.dwMajorVersion == 3 && osinfo.dwMinorVersion == 51 )
        {
            if ( 0 == stricmp(osinfo.szCSDVersion,"Service Pack 4"))
            {
                broken = TRUE;
            }
        }
        else if ( osinfo.dwMajorVersion == 4 )
        {
            if (osinfo.szCSDVersion[0] == '\0' || 
                (0 == stricmp(osinfo.szCSDVersion,"Service Pack 1")))
            {
                broken = TRUE;
            }
        }
    }

    return broken;
}

#endif /* WIN32 */


