(*
    Tux Commander - UChecksumDruid - Checksum calculation druid
    Copyright (C) 2004 Tomas Bzatek <tbzatek@users.sourceforge.net>
    Check for updates on tuxcmd.sourceforge.net

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*)
unit UChecksumDruid;

interface

uses
  glib2, gdk2, gtk2, SysUtils, Types, Classes, Variants, GTKControls, GTKForms, GTKStdCtrls, GTKExtCtrls, GTKConsts,
  GTKView, GTKText, GTKUtils, 
  UEngines;

type
  TFChecksumDruid = class(TGTKDialog)
    CancelButton, BackButton, ForwardButton: TGTKButton;
    ButtonBox: TGTKHButtonBox;
    TitleLabel, Page1Label, Page4Label, FilenameLabel, Page6Label, Page7Label, ProcessingLabel, Page6Spacer, ErrorLabel: TGTKLabel;
    TitleLabelBox, PageAreaColor, LeftSpacer, Page2Color, Page3Color, Page4Color, Page5Color, Page6Color, LeftSpacer7: TGTKEventBox;
    Page1, FileNameHBox, SeparateFileCheckHBox, Page7, ProgressHBox, ErrorTextViewHBox: TGTKHBox;
    Page2, Page3, Page4, Page5, Page6: TGTKEventBox;
    DruidArea, PageArea, Page2ClientArea, Page3ClientArea, Page5ClientArea, Page6ClientArea: TGTKVBox;
    FileNameEntry: TGTKEntry;
    SeparateFileCheckBox: TGTKCheckButton;
    SFVRadioButton, MD5RadioButton: TGTKRadioButton;
    Progress: TGTKProgressBar;
    ErrorLabelScrolledWindow: TGTKScrolledWindow;
    procedure FormCreate(Sender: TObject); override;
    procedure FormDestroy(Sender: TObject);
    procedure FormResponse(Sender: TObject; const ResponseID: integer);
    procedure CancelButtonClick(Sender: TObject);
    procedure BackButtonClick(Sender: TObject);
    procedure ForwardButtonClick(Sender: TObject);
    procedure FileNameEntryChanged(Sender: TObject);
    procedure SeparateFileCheckBoxToggled(Sender: TObject);
    procedure FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
  private
    ColorFG, ColorBG, ColorGray: PGdkColor;
    CurrentPage: integer;
    Processing, Stop, StopBreak: boolean;
    Buffer: Pointer;
    FileDes: TEngineFileDes;
    BufferPos: integer;
    procedure CreatePages;
    procedure SetCurrentPage(const Num: integer; const GoingForward: boolean);
    procedure Process;
    function ProcessFile(const FName: string; const IsItSFV: boolean; var HashString: string): boolean;
    procedure CheckStop;
    procedure WriteLine(const FName, CheckedFName, HashString: string; const IsItSFV, CreateFile, CloseFile: boolean);
    procedure WriteSFVComment(const FName: string);
  public
    Engine: TPanelEngine;
    FileNames: TStringList;
    DirName: string;
  end;

const NumPages = 7;

var
  FChecksumDruid: TFChecksumDruid;

implementation

uses ULocale, UCoreUtils, ULibc, UCore, DateUtils, UConfig, StrUtils, md5, crc;


procedure TFChecksumDruid.FormCreate(Sender: TObject);
begin
  CurrentPage := 0;
  Processing := False;
  Stop := False;
  StopBreak := False;
  Buffer := nil;
  BufferPos := 0;
  SetDefaultSize(400, 210);
  Caption := LANGCreateChecksumsCaption;
  OnDestroy := FormDestroy;
  OnResponse := FormResponse;
  FileNames := TStringList.Create;
  ShowSeparator := False;
  Buttons := [];
  ActionArea.SetSizeRequest(0, 0);
  DruidArea := TGTKVBox.Create(Self);
  DruidArea.BorderWidth := 2;
  ClientArea.AddControlEx(DruidArea, True, True, 0);
  CancelButton := TGTKButton.CreateFromStock(Self, 'gtk-cancel');
  CancelButton.OnClick := CancelButtonClick;
  BackButton := TGTKButton.CreateFromStock(Self, 'gtk-go-back');
  BackButton.OnClick := BackButtonClick;
  ForwardButton := TGTKButton.CreateFromStock(Self, 'gtk-go-forward');
  ForwardButton.OnClick := ForwardButtonClick;
  ButtonBox := TGTKHButtonBox.Create(Self);
  ButtonBox.Layout := blEnd;
  ButtonBox.Spacing := 10;
  ButtonBox.BorderWidth := 5;
  ButtonBox.AddControlEx(CancelButton, False, False, 0);
  ButtonBox.AddControlEx(BackButton, False, False, 0);
  ButtonBox.AddControlEx(ForwardButton, False, False, 0);
  ClientArea.AddControlEndEx(ButtonBox, False, False, 0);
  ColorFG := GetDefaultForegroundColor(3);
  ColorBG := GetDefaultBackgroundColor(3);
  ColorGray := GetDefaultBackgroundColor(2);
  TitleLabel := TGTKLabel.Create(Self);
  TitleLabelBox := TGTKEventBox.Create(Self);
  TitleLabelBox.AddControl(TitleLabel);
  TitleLabelBox.BorderWidth := 0;
  TitleLabel.SetAlignment(0, 0.5);
  TitleLabel.SetPadding(5, 5);
  TitleLabel.SetForegroundColor(ColorFG);
  TitleLabelBox.SetBackgroundColor(ColorBG);
  DruidArea.AddControlEx(TitleLabelBox, False, False, 0);
  PageAreaColor := TGTKEventBox.Create(Self);
  PageAreaColor.SetBackgroundColor(ColorGray);
  PageArea := TGTKVBox.Create(Self);
  PageAreaColor.AddControl(PageArea);
  DruidArea.AddControlEx(PageAreaColor, True, True, 0);

  CreatePages;

  SetCurrentPage(1, True);
  OnKeyDown := FormKeyDown;
end;

procedure TFChecksumDruid.FormDestroy(Sender: TObject);
begin
  FileNames.Free;
end;

procedure TFChecksumDruid.FormResponse(Sender: TObject; const ResponseID: integer);
begin
  Stop := True;
end;

procedure TFChecksumDruid.FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean);
begin
//  if Key = GDK_ESCAPE then ModalResult := mbCancel;
  if (Key = GDK_RETURN) or (Key = GDK_KP_ENTER) then ForwardButtonClick(Sender);
end;

procedure TFChecksumDruid.CreatePages;
begin
  Page1 := TGTKHBox.Create(Self);
  Page1.Homogeneous := False;
  LeftSpacer := TGTKEventBox.Create(Self);
  LeftSpacer.SetBackgroundColor(ColorBG);
  LeftSpacer.SetSizeRequest(100, -1);
  Page1Label := TGTKLabel.Create(Self);
  Page1Label.LineWrap := True;
  Page1Label.SetAlignment(0.5, 0.5);
  Page1Label.Caption := LANGCCHKSUMPage1Text;
  Page1.AddControlEx(LeftSpacer, False, False, 0);
  Page1.AddControlEx(Page1Label, True, True, 0);

  Page2 := TGTKEventBox.Create(Self);
  Page2.SetBackgroundColor(0, 0, 0);
  Page2Color := TGTKEventBox.Create(Self);
  Page2Color.BorderWidth := 1;
  Page2Color.SetBackgroundColor(ColorGray);
  Page2.AddControl(Page2Color);
  Page2ClientArea := TGTKVBox.Create(Self);
  Page2Color.AddControl(Page2ClientArea);
  SFVRadioButton := TGTKRadioButton.Create(Self);
  SFVRadioButton.Caption := LANGCCHKSUMSFVFile;
  MD5RadioButton := TGTKRadioButton.Create(Self);
  MD5RadioButton.Caption := LANGCCHKSUMMD5sumFile;
  MD5RadioButton.SetRadioGroup(SFVRadioButton);
  SFVRadioButton.Checked := True;
  Page2ClientArea.AddControlEx(SFVRadioButton, False, False, 3);
  Page2ClientArea.AddControlEx(MD5RadioButton, False, False, 3);
  Page2ClientArea.BorderWidth := 30;

  Page3 := TGTKEventBox.Create(Self);
  Page3.SetBackgroundColor(0, 0, 0);
  Page3Color := TGTKEventBox.Create(Self);
  Page3Color.BorderWidth := 1;
  Page3Color.SetBackgroundColor(ColorGray);
  Page3.AddControl(Page3Color);
  Page3ClientArea := TGTKVBox.Create(Self);
  Page3Color.AddControl(Page3ClientArea);
  FilenameLabel := TGTKLabel.Create(Self);
  FilenameLabel.Caption := LANGCCHKSUMFileName;
  FilenameLabel.UseUnderline := True;
  FileNameEntry := TGTKEntry.Create(Self);
  FileNameEntry.Text := 'checksums.sfv';
  FileNameEntry.SetSizeRequest(215, -1);
  FileNameEntry.OnChanged := FileNameEntryChanged;
  FilenameLabel.FocusControl := FileNameEntry;
  SeparateFileCheckBox := TGTKCheckButton.CreateWithLabel(Self, LANGCCHKSUMCreateSeparateChecksumFiles);
  SeparateFileCheckBox.OnToggled := SeparateFileCheckBoxToggled;
  Page3ClientArea.BorderWidth := 30;
  FileNameHBox := TGTKHBox.Create(Self);
  FileNameHBox.Homogeneous := False;
  FileNameHBox.AddControlEx(FilenameLabel, False, False, 5);
  FileNameHBox.AddControlEx(FileNameEntry, False, False, 5);
  Page3ClientArea.AddControlEx(FileNameHBox, False, False, 0);
  SeparateFileCheckHBox := TGTKHBox.Create(Self);
  SeparateFileCheckHBox.Homogeneous := False;
  SeparateFileCheckHBox.AddControlEx(SeparateFileCheckBox, False, False, 20);
  SeparateFileCheckHBox.AddControlEx(TGTKLabel.Create(Self), True, True, 0);
  Page3ClientArea.AddControlEx(SeparateFileCheckHBox, False, False, 5);

  Page4 := TGTKEventBox.Create(Self);
  Page4.SetBackgroundColor(0, 0, 0);
  Page4Color := TGTKEventBox.Create(Self);
  Page4Color.BorderWidth := 1;
  Page4Color.SetBackgroundColor(ColorGray);
  Page4.AddControl(Page4Color);
  Page4Label := TGTKLabel.Create(Self);
  Page4Label.LineWrap := True;
  Page4Label.SetAlignment(0.5, 0.5);
  Page4Label.Caption := LANGCCHKSUMPage4Text; 
  Page4Color.AddControl(Page4Label);

  Page5 := TGTKEventBox.Create(Self);
  Page5.SetBackgroundColor(0, 0, 0);
  Page5Color := TGTKEventBox.Create(Self);
  Page5Color.BorderWidth := 1;
  Page5Color.SetBackgroundColor(ColorGray);
  Page5.AddControl(Page5Color);
  Page5ClientArea := TGTKVBox.Create(Self);
  Page5Color.AddControl(Page5ClientArea);
  ProcessingLabel := TGTKLabel.Create(Self);
  ProcessingLabel.Caption := Format(LANGCCHKSUMNowProcessingFileS, ['---']);
  ProcessingLabel.XPadding := 20;
  ProcessingLabel.XAlign := 0;
  ProcessingLabel.YPadding := 10;
  ProcessingLabel.SetSizeRequest(10, -1); 
  Page5ClientArea.AddControlEx(ProcessingLabel, False, False, 10);
  ProgressHBox := TGTKHBox.Create(Self);
  Progress := TGTKProgressBar.Create(Self);
  ProgressHBox.AddControlEx(Progress, True, True, 20);
  Page5ClientArea.AddControlEx(ProgressHBox, True, True, 10);
  Page5ClientArea.AddControlEx(TGTKLabel.Create(Self), True, True, 0);

  Page6 := TGTKEventBox.Create(Self);
  Page6.SetBackgroundColor(0, 0, 0);
  Page6Color := TGTKEventBox.Create(Self);
  Page6Color.BorderWidth := 1;
  Page6Color.SetBackgroundColor(ColorGray);
  Page6.AddControl(Page6Color);
  Page6Label := TGTKLabel.Create(Self);
  Page6Label.LineWrap := True;
  Page6Label.SetAlignment(0.5, 0.4);
  Page6Label.Caption := LANGCCHKSUMPage6Text;
  Page6Label.UseMarkup := True;
  Page6Label.XAlign := 0.05;
  Page6Label.YAlign := 0;
  Page6Spacer := TGTKLabel.Create(Self);
  Page6Spacer.SetSizeRequest(-1, 6);
  Page6ClientArea := TGTKVBox.Create(Self);
  Page6ClientArea.AddControlEx(Page6Spacer, False, False, 0);
  Page6ClientArea.AddControlEx(Page6Label, False, False, 0);
  Page6Color.AddControl(Page6ClientArea);
  ErrorLabel := TGTKLabel.Create(Self);
  ErrorLabel.Selectable := True;
  ErrorLabel.SetAlignment(0, 0);
  ErrorLabel.SetPadding(2, 2);
  ErrorLabel.Alignment := taLeftJustify;
  ErrorLabelScrolledWindow := TGTKScrolledWindow.Create(Self);
  ErrorLabelScrolledWindow.HorizScrollBarPolicy := sbAutomatic;
  ErrorLabelScrolledWindow.VertScrollBarPolicy := sbAutomatic;
  ErrorLabelScrolledWindow.AddWithViewPort(ErrorLabel);
  ErrorTextViewHBox := TGTKHBox.Create(Self);
  ErrorTextViewHBox.AddControlEx(ErrorLabelScrolledWindow, True, True, 25);
  Page6ClientArea.AddControlEx(ErrorTextViewHBox, True, True, 10);

  Page7 := TGTKHBox.Create(Self);
  Page7.Homogeneous := False;
  LeftSpacer7 := TGTKEventBox.Create(Self);
  LeftSpacer7.SetBackgroundColor(ColorBG);
  LeftSpacer7.SetSizeRequest(100, -1);
  Page7Label := TGTKLabel.Create(Self);
  Page7Label.LineWrap := True;
  Page1Label.SetAlignment(0.5, 0.5);
  Page7Label.Caption := LANGCCHKSUMPage7Text;
  Page7.AddControlEx(LeftSpacer7, False, False, 0);
  Page7.AddControlEx(Page7Label, True, True, 0);

  PageArea.AddControlEx(Page1, True, True, 0);
  PageArea.AddControlEx(Page2, True, True, 0);
  PageArea.AddControlEx(Page3, True, True, 0);
  PageArea.AddControlEx(Page4, True, True, 0);
  PageArea.AddControlEx(Page5, True, True, 0);
  PageArea.AddControlEx(Page6, True, True, 0);
  PageArea.AddControlEx(Page7, True, True, 0);
end;


(********************************************************************************************************************************)
procedure TFChecksumDruid.SetCurrentPage(const Num: integer; const GoingForward: boolean);
const Ext: array[boolean] of string = ('.md5', '.sfv');
var s: string;
    i: TEngineFileDes;
    Error: integer;
begin
  //  A simple test before we can continue
  if (CurrentPage = 3) and (not SeparateFileCheckBox.Checked) and GoingForward then begin
    s := UTF8ToStr(FileNameEntry.Text);
    i := Engine.OpenFile(s, omWrite, Error);
    if Error <> 0 then begin
      Application.MessageBox(PGtkWindow(FWidget), Format(LANGCCHKSUMAnErrorOccuredWhileOpeningFileSS, [StrToUTF8(ExtractFileName(s)), GetErrorString(Error)]),
                             [mbOK], mbError, mbNone, mbOK);
      Exit;
    end;
    Engine.CloseFile(i);
    Engine.Remove(s);
  end;

  DebugMsg(['TFChecksumDruid.SetCurrentPage: CurrentPage = ', CurrentPage, ', new = ', Num]);

  //  Changing page
  CurrentPage := Num;
  if CurrentPage < 1 then CurrentPage := 1;
  if CurrentPage > NumPages then CurrentPage := NumPages;
  case CurrentPage of
    1: TitleLabel.SetMarkup(LANGCCHKSUMPage1Title);
    2: TitleLabel.SetMarkup(LANGCCHKSUMPage2Title);
    3: TitleLabel.SetMarkup(LANGCCHKSUMPage3Title);
    4: TitleLabel.SetMarkup(LANGCCHKSUMPage4Title);
    5: TitleLabel.SetMarkup(LANGCCHKSUMPage5Title);
    6: TitleLabel.SetMarkup(LANGCCHKSUMPage6Title);
    7: TitleLabel.SetMarkup(LANGCCHKSUMPage7Title);
  end;
  Page1.Visible := CurrentPage = 1;
  Page2.Visible := CurrentPage = 2;
  Page3.Visible := CurrentPage = 3;
  Page4.Visible := CurrentPage = 4;
  Page5.Visible := CurrentPage = 5;
  Page6.Visible := CurrentPage = 6;
  Page7.Visible := CurrentPage = 7;
  BackButton.Enabled := CurrentPage > 1;
  ForwardButton.Enabled := CurrentPage < NumPages;
  if CurrentPage < NumPages then begin
    if not ForwardButton.UseStock then ForwardButton.Caption := 'gtk-go-forward';
    ForwardButton.UseStock := True;
  end else begin
    ForwardButton.UseStock := False;
    ForwardButton.Caption := LANGCCHKSUMFinishCaption;
  end;
  //  after page changed
  case CurrentPage of
    1: ForwardButton.SetFocus;
    2: if SFVRadioButton.Checked then SFVRadioButton.SetFocus
                                 else MD5RadioButton.SetFocus;
    3: begin
         SeparateFileCheckBox.Visible := MD5RadioButton.Checked and (FileNames.Count > 1);
         FileNameEntry.Enabled := (not SeparateFileCheckBox.Checked) or (not SeparateFileCheckBox.Visible);
         if FileNames.Count = 1
           then FileNameEntry.Text := StrToUTF8(ExtractFileName(FileNames[0]) + Ext[SFVRadioButton.Checked])
           else FileNameEntry.Text := StrToUTF8(DirName + Ext[SFVRadioButton.Checked]);
         FileNameEntry.SetFocus;
         FileNameEntryChanged(Self);
       end;
    4: ForwardButton.SetFocus;
    5: Process;
    6: begin
         BackButton.SetFocus;
         ForwardButton.Enabled := False;
         s := ErrorLabel.Caption;
         if (Length(s) > 1) and (s[Length(s)] = #10) then Delete(s, Length(s), 1);
         ErrorLabel.Caption := s;
       end;
    7: begin
         BackButton.Enabled := False;
         CancelButton.Enabled := False;
         ForwardButton.Enabled := True;
         ForwardButton.SetFocus;
       end;
  end;
end;

procedure TFChecksumDruid.CancelButtonClick(Sender: TObject);
begin
  ModalResult := mbCancel;
end;

procedure TFChecksumDruid.BackButtonClick(Sender: TObject);
begin
  SetCurrentPage(CurrentPage - 1 - Ord(CurrentPage = 6), False);
end;

procedure TFChecksumDruid.ForwardButtonClick(Sender: TObject);
begin
  if CurrentPage = 7 then ModalResult := mbOK
                     else SetCurrentPage(CurrentPage + 1, True);
end;

procedure TFChecksumDruid.FileNameEntryChanged(Sender: TObject);
begin
  ForwardButton.Enabled := Length(Trim(FileNameEntry.Text)) > 0;
end;

procedure TFChecksumDruid.SeparateFileCheckBoxToggled(Sender: TObject);
begin
  FileNameEntry.Enabled := not SeparateFileCheckBox.Checked;
end;

(********************************************************************************************************************************)
procedure TFChecksumDruid.CheckStop;
begin
  if Stop and (not StopBreak) then
    if Application.MessageBox(PGtkWindow(FWidget), LANGCCHKSUMAreYouSureYouWantToAbortTheProcessing, [mbYes, mbNo], mbQuestion, mbNone, mbNo) = mbYes
      then StopBreak := True
      else begin  Stop := False;  StopBreak := False;  end;
end;

procedure TFChecksumDruid.Process;
const Ext: array[boolean] of string = ('.md5', '.sfv');
var i: integer;
    MaxSize, LastValue: Int64;
    Stat: PDataItemSL;
    s, s2: string;
begin
  if FileNames.Count = 0 then begin
    SetCurrentPage(7, True);
    Exit;
  end;
  FileNames.Sorted := True;
  CancelButton.SetFocus;
  ForwardButton.Enabled := False;
  BackButton.Enabled := False;
  Processing := True;
  Stop := False;
  StopBreak := False;
  ErrorLabel.Caption := '';
  Application.ProcessMessages;

  //  Compute maximal size of selected files
  MaxSize := 0;
  for i := 0 to FileNames.Count - 1 do begin
    Stat := Engine.GetFileInfoSL(FileNames[i]);
    if Assigned(Stat) then begin
      Inc(MaxSize, Stat.Size);
      FreeDataItem(Stat);
    end;
  end;
  Progress.Max := MaxSize;
  Progress.Value := 0;
  Progress.Text := '0 %';

  if SFVRadioButton.Checked then WriteSFVComment(ProcessPattern(Engine, UTF8ToStr(FileNameEntry.Text), IncludeTrailingPathDelimiter(ExtractFilePath(FileNames[0])), '', False));
  //  Process each file
  for i := 0 to FileNames.Count - 1 do begin
    ProcessingLabel.Caption := Format(LANGCCHKSUMNowProcessingFileS, [StrToUTF8(ExtractFileName(FileNames[i]))]);
    LastValue := Progress.Value;
    Stat := Engine.GetFileInfoSL(FileNames[i]);
    Application.ProcessMessages;
    try
      if ProcessFile(FileNames[i], SFVRadioButton.Checked, s) then begin
        if not SeparateFileCheckBox.Checked
          then s2 := ProcessPattern(Engine, StrToUTF8(FileNameEntry.Text), IncludeTrailingPathDelimiter(ExtractFilePath(FileNames[0])), '', False)
          else s2 := IncludeTrailingPathDelimiter(ExtractFilePath(FileNames[i])) + ExtractFileName(FileNames[i]) + Ext[SFVRadioButton.Checked];
        WriteLine(s2, FileNames[i], s, SFVRadioButton.Checked, ((i = 0) and MD5RadioButton.Checked) or SeparateFileCheckBox.Checked,
                 (i = FileNames.Count - 1) or SeparateFileCheckBox.Checked);
      end;
    except end;
    if Assigned(Stat) then begin
      Progress.Value := LastValue + Stat.Size;
      FreeDataItem(Stat);
    end;
    Progress.Text := Format('%d %%', [Trunc(Progress.Fraction * 100)]);
    Application.ProcessMessages;
    CheckStop;
    if StopBreak then Break;
  end;

  Application.ProcessMessages;
  Processing := False;
  Stop := False;
  StopBreak := False;
  Application.ProcessMessages;
  SetCurrentPage(7 - Ord(ErrorLabel.Caption <> ''), ErrorLabel.Caption = '');
end;

function TFChecksumDruid.ProcessFile(const FName: string; const IsItSFV: boolean; var HashString: string): boolean;
const ChksumBlockSize = 65536*4;
var FD: TEngineFileDes;
    Error, Count: integer;
    Buffer: Pointer;
    CRC: LongWord;
    MDContext: TMDContext;
    MDDigest: TMDDigest;
begin
  HashString := '';
  Result := False;
  try
    Buffer := malloc(ChksumBlockSize);
    memset(Buffer, 0, ChksumBlockSize);
  except
    ErrorLabel.Caption := ErrorLabel.Caption + LANGAnErrorOccuredWhileInitializingMemoryBlock + #10;
    Exit;
  end;
  CRC := 0;
  if not IsItSFV then MDInit(MDContext, MD_VERSION_5);

  FD := Engine.OpenFile(FName, omRead, Error);
  if Error <> 0 then begin
    ErrorLabel.Caption := ErrorLabel.Caption + Format(LANGCCHKSUMAnErrorOccuredWhileOpeningFileSS, [StrToUTF8(ExtractFileName(FName)), GetErrorString(Error)]);
    Exit;
  end;
  repeat
    Count := Engine.ReadFile(FD, Buffer, ChksumBlockSize, Error);
    if Error <> 0 then begin
      ErrorLabel.Caption := ErrorLabel.Caption + Format(LANGCCHKSUMAnErrorOccuredWhileReadingFileSS, [StrToUTF8(ExtractFileName(FName)), GetErrorString(Error)]);
      Engine.CloseFile(FD);
      Exit;
    end;
    if IsItSFV then CRC := CRC32(CRC, Buffer, Count)
               else MDUpdate(MDContext, Buffer^, Count);
    Progress.Value := Progress.Value + Count;
    Progress.Text := Format('%d %%', [Trunc(Progress.Fraction * 100)]);
    Application.ProcessMessages;
    CheckStop;
  until (Count < ChksumBlockSize) or Stop;
  Engine.CloseFile(FD);
  libc_free(Buffer);

  if IsItSFV then HashString := IntToHex(CRC, 8) else
  begin
    MDFinal(MDContext, MDDigest);
    HashString := MDPrint(MDDigest);
  end;
  Result := True;
end;

const ChksumBlockSize = 32768;  //  Maximum of PByteArray

procedure TFChecksumDruid.WriteLine(const FName, CheckedFName, HashString: string; const IsItSFV, CreateFile, CloseFile: boolean);
var i, Error, Count: integer;
    s: string;
begin
  if CreateFile then begin
    FileDes := Engine.OpenFile(FName, omWrite, Error);
    if Error <> 0 then begin
      ErrorLabel.Caption := ErrorLabel.Caption + Format(LANGCCHKSUMAnErrorOccuredWhileOpeningFileSS, [StrToUTF8(ExtractFileName(FName)), GetErrorString(Error)]);
      Exit;
    end;
    try
      Buffer := malloc(ChksumBlockSize);
      memset(Buffer, 0, ChksumBlockSize);
    except
      ErrorLabel.Caption := ErrorLabel.Caption + LANGAnErrorOccuredWhileInitializingMemoryBlock + #10;
      Exit;
    end;
    BufferPos := -1;
  end;
  if Buffer = nil then Exit;

  if IsItSFV then s := Format('%s %s'#13#10, [ExtractFileName(CheckedFName), Trim(WideUpperCase(HashString))])
             else s := Format('%s  %s'#10, [Trim(AnsiLowerCase(HashString)), ExtractFileName(CheckedFName)]);
  for i := 1 to Length(s) do begin
    if BufferPos + 1 >= ChksumBlockSize then begin
      Count := Engine.WriteFile(FileDes, Buffer, ChksumBlockSize, Error);
      BufferPos := -1;
      if (Error <> 0) or (Count <> ChksumBlockSize) then begin
        ErrorLabel.Caption := ErrorLabel.Caption + Format(LANGCCHKSUMAnErrorOccuredWhileWritingFileSS, [StrToUTF8(ExtractFileName(FName)), GetErrorString(Error)]);
        Exit;
      end;
    end;
    Inc(BufferPos);
    PByteArray(Buffer)^[BufferPos] := Byte(s[i]);
  end;

  if CloseFile then begin
    Inc(BufferPos);  //  Counting with zero-starting element
    Count := Engine.WriteFile(FileDes, Buffer, BufferPos, Error);
    if (Error <> 0) or (Count <> BufferPos) then begin
      ErrorLabel.Caption := ErrorLabel.Caption + Format(LANGCCHKSUMAnErrorOccuredWhileWritingFileSS, [StrToUTF8(ExtractFileName(FName)), GetErrorString(Error)]);
      Exit;
    end;
    BufferPos := -1;
    Engine.CloseFile(FileDes);
    libc_free(Buffer);
  end;
end;

procedure TFChecksumDruid.WriteSFVComment(const FName: string);
var i, Error, Count: integer;
    Stat: PDataItemSL;
    s: string;
begin
  FileDes := Engine.OpenFile(FName, omWrite, Error);
  if Error <> 0 then begin
    ErrorLabel.Caption := ErrorLabel.Caption + Format(LANGCCHKSUMAnErrorOccuredWhileOpeningFileSS, [StrToUTF8(ExtractFileName(FName)), GetErrorString(Error)]);
    Exit;
  end;
  try
    Buffer := malloc(ChksumBlockSize);
    memset(Buffer, 0, ChksumBlockSize);
  except
    ErrorLabel.Caption := ErrorLabel.Caption + LANGAnErrorOccuredWhileInitializingMemoryBlock + #10;
    Exit;
  end;
  BufferPos := -1;
  if Buffer = nil then Exit;

  s := Format(';  Generated by WIN-SFV32 v1 [added for sfv32 compatibility]'#13#10';  Using Tux Commander v%s [%s] on %s'#13#10 +
              ';  http://tuxcmd.sourceforge.net/'#13#10'; '#13#10'; /----'#13#10, [ConstAboutVersion, ConstAboutBuildDate,
              SysUtils.FormatDateTime('mm.dd.yyyy "at" hh:nn:ss', Now)]);
  for i := 0 to FileNames.Count - 1 do begin
    Stat := Engine.GetFileInfoSL(FileNames[i]);
    if Assigned(Stat) then begin
      s := s + Format(';  %s %s %s'#13#10, [PadRightStr(IntToStr(Stat^.Size), 11),
                      FormatDate(Stat^.ModifyTime, True, True, 999, 999, 1, '%Y-%m-%d', '%k:%M.%S'), ExtractFileName(FileNames[i])]);
      FreeDataItem(Stat);
    end;
  end;
  s := s + '; \----'#13#10'; '#13#10; 

  for i := 1 to Length(s) do begin
    if BufferPos + 1 >= ChksumBlockSize then begin
      Count := Engine.WriteFile(FileDes, Buffer, ChksumBlockSize, Error);
      BufferPos := -1;
      if (Error <> 0) or (Count <> ChksumBlockSize) then begin
        ErrorLabel.Caption := ErrorLabel.Caption + Format(LANGCCHKSUMAnErrorOccuredWhileWritingFileSS, [StrToUTF8(ExtractFileName(FName)), GetErrorString(Error)]);
        Exit;
      end;
    end;
    Inc(BufferPos);
    PByteArray(Buffer)^[BufferPos] := Byte(s[i]);
  end;
end;

end.


