unit ORSystem;

{$O-}

interface

uses SysUtils, Windows, Classes, Forms, Registry, ORFn;

const
  CPRS_ROOT_KEY =  HKEY_LOCAL_MACHINE;
  CPRS_USER_KEY =  HKEY_CURRENT_USER;
  CPRS_SOFTWARE = 'Software\Vista\CPRS';
  CPRS_REG_AUTO = 'AutoUpdate';
  CPRS_REG_GOLD = 'GoldCopyPath';
  CPRS_REG_ONLY = 'LimitUpdate';
  CPRS_REG_ASK  = 'AskFirst';
  CPRS_REG_LAST = 'LastUpdate-';
  CPRS_USER_LAST = 'Software\Vista\CPRS\LastUpdate';
  CPRS_LAST_DATE = 'Software\Vista\CPRS\DateUpdated';

  { values that can be passed to FileVersionValue }
  FILE_VER_COMPANYNAME      = '\StringFileInfo\040904E4\CompanyName';
  FILE_VER_FILEDESCRIPTION  = '\StringFileInfo\040904E4\FileDescription';
  FILE_VER_FILEVERSION      = '\StringFileInfo\040904E4\FileVersion';
  FILE_VER_INTERNALNAME     = '\StringFileInfo\040904E4\InternalName';
  FILE_VER_LEGALCOPYRIGHT   = '\StringFileInfo\040904E4\LegalCopyright';
  FILE_VER_ORIGINALFILENAME = '\StringFileInfo\040904E4\OriginalFilename';
  FILE_VER_PRODUCTNAME      = '\StringFileInfo\040904E4\ProductName';
  FILE_VER_PRODUCTVERSION   = '\StringFileInfo\040904E4\ProductVersion';
  FILE_VER_COMMENTS         = '\StringFileInfo\040904E4\Comments';


function AppOutOfDate(AppName: string): Boolean;
function ClientVersion(const AFileName: string): string;
function CompareVersion(const A, B: string): Integer;
procedure CopyFileDate(const Source, Dest: string);
procedure CopyLastWriteTime(const Source, Dest: string);
//procedure CopyFileWithDate(const FromFileName, ToFileName: string);
procedure Delay(i: Integer);
//procedure FileCopy(const FromFileName, ToFileName: string);
//procedure FileCopyWithDate(const FromFileName, ToFileName: string);
function FileVersionValue(const AFileName, AValueName: string): string;
function FullToFilePart(const AFileName: string): string;
function FullToPathPart(const AFileName: string): string;
function IsWin95Style: Boolean;
function ParamIndex(const AName: string): Integer;
function ParamSearch(const AName: string): string;
function QuotedExeName: string;
function RegKeyExists(ARoot: HKEY; const AKey: string): Boolean;
function RegReadInt(const AName: string): Integer;
function RegReadStr(const AName: string): string;
function RegReadBool(const AName: string): Boolean;
procedure RegWriteInt(const AName: string; AValue: Integer);
procedure RegWriteStr(const AName, AValue: string);
procedure RegWriteBool(const AName: string; AValue: Boolean);
function UserRegReadDateTime(const AKey, AName: string): TDateTime;
procedure UserRegWriteDateTime(const AKey, AName: string; AValue: TDateTime);
function UserRegReadInt(const AKey, AName: string): Integer;
procedure UserRegWriteInt(const AKey, AName: string; AValue: Integer);
procedure RunProgram(const AppName: string);
function UpdateSelf: Boolean;

implementation

const
  CREATE_KEY = True;  // cause key to be created if it's not in the registry

function FileLastWrite(const FileName: string): LARGE_INTEGER;
var
  AHandle: THandle;
  FindData: TWin32FindData;
begin
  Result.QuadPart := 0;
  AHandle := FindFirstFile(PChar(FileName), FindData);
  if AHandle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(AHandle);
    Result.LowPart  := FindData.ftLastWriteTime.dwLowDateTime;
    Result.HighPart := FindData.ftLastWriteTime.dwHighDateTime;
  end;
end;

function AppOutOfDate(AppName: string): Boolean;
const
  FIVE_SECONDS = 0.000055;
  FIVE_SECONDS_NT = 50000000;
var
  GoldName, DriveRoot, x: string;
  DriveType: Integer;
  LastWriteApp, LastWriteGold: LARGE_INTEGER;
begin
  Result := False;
  // check command line params for no-update parameter
  if ParamIndex('NOCOPY') > 0 then Exit;
  // check time of last update, don't retry if too recently called
  if Abs(Now - UserRegReadDateTime(CPRS_LAST_DATE, FullToFilePart(AppName))) < FIVE_SECONDS
    then Exit;
  // check auto-update registry entry
  if RegReadBool(CPRS_REG_AUTO) = False then Exit;
  // check directory - if remote then don't allow update
  if Pos('\\', AppName) = 1 then Exit;
  if Pos(':', AppName) > 0
    then DriveRoot := Piece(AppName, ':', 1) + ':\'
    else DriveRoot := '\';
  DriveType := GetDriveType(PChar(DriveRoot));
  if not ((DriveType = DRIVE_FIXED) or (DriveType = DRIVE_REMOVABLE)) then Exit;
  // check registry to see if updates limited to particular directory
  x := RegReadStr(CPRS_REG_ONLY);
  if (Length(x) > 0) and (CompareText(x, FullToPathPart(AppName)) <> 0) then Exit;
  // check for different file date in the gold directory
  GoldName := RegReadStr(CPRS_REG_GOLD);
  if Length(GoldName) = 0 then Exit;
  GoldName := GoldName + FullToFilePart(AppName);
  if FileExists(GoldName) then
  begin
    LastWriteApp  := FileLastWrite(AppName);
    LastWriteGold := FileLastWrite(GoldName);
    // check within 5 seconds to work around diffs in NTFS & FAT timestamps
    if Abs(LastWriteApp.QuadPart - LastWriteGold.QuadPart) > FIVE_SECONDS_NT then Result := True;
    //if CompareFileTime(LastWriteApp, LastWriteGold) <> 0 then Result := True;
  end;
end;

function ClientVersion(const AFileName: string): string;
var
  ASize, AHandle: DWORD;
  Buf: string;
  FileInfoPtr: Pointer; //PVSFixedFileInfo;
begin
  Result := '';
  ASize:=GetFileVersionInfoSize(PChar(AFileName), AHandle);
  if ASize > 0 then
  begin
    SetLength(Buf, ASize);
    GetFileVersionInfo(PChar(AFileName), AHandle, ASize, Pointer(Buf));
    VerQueryValue(Pointer(Buf), '\', FileInfoPtr, ASize);
    with TVSFixedFileInfo(FileInfoPtr^) do Result := IntToStr(HIWORD(dwFileVersionMS)) + '.' +
                                                     IntToStr(LOWORD(dwFileVersionMS)) + '.' +
                                                     IntToStr(HIWORD(dwFileVersionLS)) + '.' +
                                                     IntToStr(LOWORD(dwFileVersionLS));
  end;
end;

function FileVersionValue(const AFileName, AValueName: string): string;
type
  PValBuf = ^TValBuf;
  TValBuf = array[0..255] of Char;
var
  VerSize, ValSize, AHandle: DWORD;
  VerBuf: Pointer;
  ValBuf: PValBuf;
begin
  Result := '';
  VerSize:=GetFileVersionInfoSize(PChar(AFileName), AHandle);
  if VerSize > 0 then
  begin
    GetMem(VerBuf, VerSize);
    try
      GetFileVersionInfo(PChar(AFileName), AHandle, VerSize, VerBuf);
      VerQueryValue(VerBuf, PChar(AValueName), Pointer(ValBuf), ValSize);
      SetString(Result, ValBuf^, ValSize);
    finally
      FreeMem(VerBuf);
    end;
  end;
end;

function CompareVersion(const A, B: string): Integer;
var
  NumA, NumB: Integer;
begin
  NumA := (StrToInt(Piece(A, '.', 1)) * 16777216) +
          (StrToInt(Piece(A, '.', 2)) * 65536) +
          (StrToInt(Piece(A, '.', 3)) * 256) +
           StrToInt(Piece(A, '.', 4));
  NumB := (StrToInt(Piece(B, '.', 1)) * 16777216) +
          (StrToInt(Piece(B, '.', 2)) * 65536) +
          (StrToInt(Piece(B, '.', 3)) * 256) +
           StrToInt(Piece(B, '.', 4));
  Result := NumA - NumB;
end;

procedure CopyFileDate(const Source, Dest: string);
{ from TI2972 }
var
  SourceHand, DestHand: Integer;
begin
  SourceHand := FileOpen(Source, fmOutput);       { open source file }
  DestHand := FileOpen(Dest, fmInput);            { open dest file }
  FileSetDate(DestHand, FileGetDate(SourceHand)); { get/set date }
  FileClose(SourceHand);                          { close source file }
  FileClose(DestHand);                            { close dest file }
end;

procedure CopyLastWriteTime(const Source, Dest: string);
var
  HandleSrc, HandleDest: Integer;
  LastWriteTime: TFileTime;
begin
  HandleSrc  := FileOpen(Source, fmOpenRead or fmShareDenyNone);
  HandleDest := FileOpen(Dest,   fmOpenWrite);
  if (HandleSrc > 0) and (HandleDest > 0) then
  begin
    if GetFileTime(THandle(HandleSrc), nil, nil, @LastWriteTime) = TRUE
      then SetFileTime(THandle(HandleDest), nil, nil, @LastWriteTime);
    FileClose(HandleSrc);
    FileClose(HandleDest);
  end;
end;

procedure Delay(i: Integer);
const
  AMilliSecond = 0.000000011574;
var
  Start: TDateTime;
begin
  Start := Now;
  while Now < (Start + (i * AMilliSecond)) do Application.ProcessMessages;
end;

procedure FileCopy(const FromFileName, ToFileName: string);
var
  FromFile, ToFile: file;
  NumRead, NumWritten: Integer;
  Buf: array[1..16384] of Char;
begin
  AssignFile(FromFile, FromFileName);                  // Input file
  Reset(FromFile, 1);		                               // Record size = 1
  AssignFile(ToFile, ToFileName);	                     // Output file
  Rewrite(ToFile, 1);		                               // Record size = 1
  repeat
    BlockRead(FromFile, Buf, SizeOf(Buf), NumRead);
    BlockWrite(ToFile, Buf, NumRead, NumWritten);
  until (NumRead = 0) or (NumWritten <> NumRead);
  CloseFile(FromFile);
  CloseFile(ToFile);
end;

procedure FileCopyWithDate(const FromFileName, ToFileName: string);
var
  FileHandle, ADate: Integer;
begin
  FileCopy(FromFileName, ToFileName);
  FileHandle := FileOpen(FromFileName, fmOpenRead or fmShareDenyNone);
  ADate := FileGetDate(FileHandle);
  FileClose(FileHandle);
  if ADate < 0 then Exit;
  FileHandle := FileOpen(ToFileName, fmOpenWrite or fmShareDenyNone);
  if FileHandle > 0 then FileSetDate(FileHandle, ADate);
  FileClose(FileHandle);
end;

procedure CopyFileWithDate(const FromFileName, ToFileName: string);
var
  FileHandle, ADate: Integer;
begin
  if CopyFile(PChar(FromFileName), PChar(ToFileName), False) then
  begin
    FileHandle := FileOpen(FromFileName, fmOpenRead or fmShareDenyNone);
    ADate := FileGetDate(FileHandle);
    FileClose(FileHandle);
    if ADate < 0 then Exit;
    FileHandle := FileOpen(ToFileName, fmOpenWrite or fmShareDenyNone);
    if FileHandle > 0 then FileSetDate(FileHandle, ADate);
    FileClose(FileHandle);
  end;
end;

function FullToFilePart(const AFileName: string): string;
var
  DirBuf: string;
  FilePart: PChar;
  NameLen: DWORD;
begin
  Result := '';
  SetString(DirBuf, nil, 255);
  NameLen := GetFullPathName(PChar(AFileName), 255, PChar(DirBuf), FilePart);
  if NameLen > 0 then Result := FilePart;
end;

function FullToPathPart(const AFileName: string): string;
var
  DirBuf: string;
  FilePart: PChar;
  NameLen: Cardinal;
begin
  Result := '';
  SetString(DirBuf, nil, 255);
  NameLen := GetFullPathName(PChar(AFileName), 255, PChar(DirBuf), FilePart);
  if NameLen > 0 then Result := Copy(DirBuf, 1, NameLen - StrLen(FilePart));
end;

function IsWin95Style: Boolean;
begin
  Result := Lo(GetVersion) >= 4;          // True = Win95 interface, otherwise old interface
end;

function ParamIndex(const AName: string): Integer;
var
  i: Integer;
  x: string;
begin
  Result := 0;
  for i := 1 to ParamCount do
  begin
    x := UpperCase(ParamStr(i));
    x := Piece(x, '=', 1);
    if x = Uppercase(AName) then
    begin
      Result := i;
      Break;
    end;
  end; {for i}
end;

function ParamSearch(const AName: string): string;
var
  i: Integer;
  x: string;
begin
  Result := '';
  for i := 1 to ParamCount do
  begin
    x := UpperCase(ParamStr(i));
    x := Copy(x, 1, Pos('=', x) - 1);
    if x = Uppercase(AName) then
    begin
      Result := UpperCase(Copy(ParamStr(i), Length(x) + 2, Length(ParamStr(i))));
      Break;
    end;
  end; {for i}
end;

function QuotedExeName: string;
var
  i: Integer;
begin
  Result := '"' + ParamStr(0) + '"';
  for i := 1 to ParamCount do Result := Result + ' ' + ParamStr(i);
end;

function RegReadInt(const AName: string): Integer;
var
  Registry: TRegistry;
begin
  Result := 0;
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_ROOT_KEY;
    if Registry.OpenKeyReadOnly(CPRS_SOFTWARE) and Registry.ValueExists(AName)
      then Result := Registry.ReadInteger(AName);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

function RegReadStr(const AName: string): string;
var
  Registry: TRegistry;
begin
  Result := '';
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_ROOT_KEY;
    if Registry.OpenKeyReadOnly(CPRS_SOFTWARE) and Registry.ValueExists(AName)
      then Result := Registry.ReadString(AName);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

function RegReadBool(const AName: string): Boolean;
var
  Registry: TRegistry;
begin
  Result := False;
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_ROOT_KEY;
    if Registry.OpenKeyReadOnly(CPRS_SOFTWARE) and Registry.ValueExists(AName)
      then Result := Registry.ReadBool(AName);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

procedure RegWriteInt(const AName: string; AValue: Integer);
var
  Registry: TRegistry;
begin
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_ROOT_KEY;
    if Registry.OpenKey(CPRS_SOFTWARE, CREATE_KEY) then Registry.WriteInteger(AName, AValue);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

procedure RegWriteStr(const AName, AValue: string);
var
  Registry: TRegistry;
begin
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_ROOT_KEY;
    if Registry.OpenKey(CPRS_SOFTWARE, CREATE_KEY) then Registry.WriteString(AName, AValue);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

procedure RegWriteBool(const AName: string; AValue: Boolean);
var
  Registry: TRegistry;
begin
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_ROOT_KEY;
    if Registry.OpenKey(CPRS_SOFTWARE, CREATE_KEY) then Registry.WriteBool(AName, AValue);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

function RegKeyExists(ARoot: HKEY; const AKey: string): Boolean;
var
  Registry: TRegistry;
begin
  Result := False;
  Registry := TRegistry.Create;
  try
    Registry.RootKey := ARoot;
    //Result := Registry.KeyExists(AKey); {this tries to open key with full access}
    if Registry.OpenKeyReadOnly(AKey) and (Registry.CurrentKey <> 0) then Result := True;
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

function UserRegReadDateTime(const AKey, AName: string): TDateTime;
var
  Registry: TRegistry;
begin
  Result := 0;
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_USER_KEY;
    if Registry.OpenKey(AKey, CREATE_KEY) and Registry.ValueExists(AName) then
    try
      Result := Registry.ReadDateTime(AName);
    except
      on ERegistryException do Result := 0;
    end;
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

procedure UserRegWriteDateTime(const AKey, AName: string; AValue: TDateTime);
var
  Registry: TRegistry;
begin
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_USER_KEY;
    if Registry.OpenKey(AKey, CREATE_KEY) then Registry.WriteDateTime(AName, AValue);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

function UserRegReadInt(const AKey, AName: string): Integer;
var
  Registry: TRegistry;
begin
  Result := 0;
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_USER_KEY;
    if Registry.OpenKey(AKey, CREATE_KEY) and Registry.ValueExists(AName)
      then Result := Registry.ReadInteger(AName);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

procedure UserRegWriteInt(const AKey, AName: string; AValue: Integer);
var
  Registry: TRegistry;
begin
  Registry := TRegistry.Create;
  try
    Registry.RootKey := CPRS_USER_KEY;
    if Registry.OpenKey(AKey, CREATE_KEY) then Registry.WriteInteger(AName, AValue);
    Registry.CloseKey;
  finally
    Registry.Free;
  end;
end;

procedure RunProgram(const AppName: string);
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
begin
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.CB := SizeOf(StartInfo);
  CreateProcess(nil, PChar(AppName), nil, nil, False, DETACHED_PROCESS or NORMAL_PRIORITY_CLASS,
    nil, nil, StartInfo, ProcInfo);
end;

function UpdateSelf: Boolean;
var
  CPRSUpdate: string;
begin
  // auto-update if newer version available
  Result := False;
  CPRSUpdate := RegReadStr(CPRS_REG_GOLD) + 'CPRSUpdate.exe';
  if not FileExists(CPRSUpdate) then CPRSUpdate := 'CPRSUpdate.exe';
  if AppOutOfDate(Application.ExeName) and FileExists(CPRSUpdate) then
  begin
    Result := True;
    RunProgram(CPRSUpdate + ' COPY=' + QuotedExeName);
  end;
end;

(*
procedure UpdateAppFromGold(const AppName: string);
var
  GoldName: string;
begin
  Delay(1500);
  // do a rename of AppName in case problem?
  GoldName := RegReadStr(CPRS_REG_GOLD);
  if Length(GoldName) = 0 then Exit;
  if GoldName[Length(GoldName)] <> '\' then GoldName := GoldName + '\';
  GoldName := GoldName + ReverseStr(Piece(ReverseStr(AppName), '\', 1));
  CopyFileWithDate(GoldName, AppName);
end;
*)

end.
