unit Logging;

{
  Inno Setup
  Copyright (C) 1997-2007 Jordan Russell
  Portions by Martijn Laan
  For conditions of distribution and use, see LICENSE.TXT.

  Logging functions

  $jrsoftware: issrc/Projects/Logging.pas,v 1.12 2009/03/23 23:27:14 mlaan Exp $
}

interface

procedure Log(const S: String);
procedure LogFmt(const S: String; const Args: array of const);
procedure StartLogging(const Prefix: String);
procedure StartLoggingWithFixedFilename(const Filename: String);
function GetLogFileName: String;

const
  SYesNo: array[Boolean] of String = ('No', 'Yes');

implementation

uses
  Windows, SysUtils, Int64Em, CmnFunc2, FileClass, DebugClient;

var
  LogFile: TTextFileWriter;
  LogFileName: String;
  LocalTimeBias: Integer64;

procedure InitLocalTimeBias;
var
  UTCTime, LocalTime: Integer64;
begin
  GetSystemTimeAsFileTime(TFileTime(UTCTime));
  if FileTimeToLocalFileTime(TFileTime(UTCTime), TFileTime(LocalTime)) then begin
    Dec6464(LocalTime, UTCTime);
    LocalTimeBias := LocalTime;
  end;
end;

procedure GetFixedLocalTime(var ST: TSystemTime);
{ Like GetLocalTime, but uses our LocalTimeBias as the offset, which cannot
  change while the program is running } 
var
  FT: Integer64;
begin
  GetSystemTimeAsFileTime(TFileTime(FT));
  Inc6464(FT, LocalTimeBias);
  FileTimeToSystemTime(TFileTime(FT), ST);
end;

procedure LogLogOpened;
var
  Offset: Integer64;
  PlusOrMinus: Char;
begin
  Offset := LocalTimeBias;
  if Longint(Offset.Hi) >= 0 then
    PlusOrMinus := '+'
  else begin
    PlusOrMinus := '-';
    { Negate it }
    Offset.Lo := not Offset.Lo;
    Offset.Hi := not Offset.Hi;
    Inc64(Offset, 1);
  end;
  Div64(Offset, 60 * 10000000);
  LogFmt('Log opened. (Time zone: UTC%s%.2u:%.2u)', [PlusOrMinus,
    Offset.Lo div 60, Offset.Lo mod 60]);
end;

procedure StartLogging(const Prefix: String);
var
  Dir, DateStr, Filename: String;
  I: Cardinal;
  ST: TSystemTime;
  F: TTextFileWriter;
begin
  if Assigned(LogFile) then
    Exit;  { logging was already started }
  Dir := GetTempDir;
  GetFixedLocalTime(ST);
  DateStr := Format('%.4u-%.2u-%.2u', [ST.wYear, ST.wMonth, ST.wDay]);
  I := 1;
  while True do begin
    Filename := Dir + Format('%s Log %s #%.3u.txt', [Prefix, DateStr, I]);
    if not FileOrDirExists(Filename) then begin
      F := nil;
      try
        F := TTextFileWriter.Create(Filename, fdCreateNew, faWrite, fsRead);
      except
        on E: EFileError do begin
          { Don't propagate ERROR_FILE_EXISTS errors; just try again.
            (Yes, we already checked if the file existed first, but this helps
            to make it race-proof.) }
          if E.ErrorCode <> ERROR_FILE_EXISTS then
            raise;
        end;
      end;
      if Assigned(F) then begin
        LogFile := F;
        LogFileName := FileName;
        Break;
      end;
    end;
    Inc(I);
  end;
  LogLogOpened;
end;

procedure StartLoggingWithFixedFilename(const Filename: String);
begin
  if Assigned(LogFile) then
    Exit;  { logging was already started }
  LogFile := TTextFileWriter.Create(Filename, fdCreateAlways, faWrite, fsRead);
  LogFileName := FileName;
  LogLogOpened;
end;

function GetLogFileName: String;
begin
  Result := LogFileName;
end;

procedure Log(const S: String);

  procedure WriteStr(const S: String);
  begin
    LogFile.Write(S);
  end;

var
  ST: TSystemTime;
  LineStart, I: Integer;
begin
  if Assigned(LogFile) then begin
    GetFixedLocalTime(ST);
    try
      WriteStr(Format('%.4u-%.2u-%.2u %.2u:%.2u:%.2u.%.3u   ',
        [ST.wYear, ST.wMonth, ST.wDay, ST.wHour, ST.wMinute, ST.wSecond,
         ST.wMilliseconds]));
      LineStart := 1;
      { Lines except for last line }
      for I := 1 to Length(S) do begin
        if S[I] = #10 then begin
          WriteStr(Copy(S, LineStart, I - LineStart + 1));
          LineStart := I + 1;
          { Indent }
          WriteStr('                          ');
        end;
      end;
      { Last line }
      if LineStart <= Length(S) then
        WriteStr(Copy(S, LineStart, Length(S) - LineStart + 1));
      WriteStr(#13#10);
    except
      { Failed to write? Close the file and don't log anything further. }
      try
        FreeAndNil(LogFile);
      except
      end;
    end;
  end;
  if Debugging then
    DebugNotifyLogMessage(S);
end;

procedure LogFmt(const S: String; const Args: array of const);
begin
  if Assigned(LogFile) or Debugging then
    Log(Format(S, Args));
end;

initialization
  InitLocalTimeBias;
finalization
  if Assigned(LogFile) then begin
    Log('Log closed.');
    FreeAndNil(LogFile);
  end;
end.
