{ DISQLite3 example project performing an online backup of an existing database
  file.

  Visit the DISQLite3 Internet site for latest information and updates:

    http://www.yunqa.de

  Copyright (c) 2009-2021 Ralf Junker, Yunqa <delphi@yunqa.de>

------------------------------------------------------------------------------ }

program DISQLite3_Backup;

{$APPTYPE CONSOLE}
{$I DICompilers.inc}
{$I DISQLite3.inc}

uses
  {$IFDEF FastMM}{$I FastMM_uses.inc}{$ENDIF}
  DISystemCompat, Windows, SysUtils, DISQLite3Api;

{$R ..\Yunqa.res}

const
  { Default number of pages to transfer in a single backup step.
    Increase for speed, decrease for concurrency. }
  BACKUP_STEP_PAGES = 50;
  { Default number of milli-seconds to wait between two backup steps.
    Increase for concurrency, decrease for speed. }
  BACKUP_SLEEP_MS = 50;

type
  { Type definition of callback function triggered periodically by backupDb
    (below) to indicate the backup progress. }
  TBackupProgressFunc = procedure(const ARemaining, APageCount: Int64);

  { Perform an online backup of database ADb to the database file named
    by AFilename. This function copies ABackupStepPages database pages from ADb
    to AFilename, then unlocks ADb and sleeps for ABackupSleepPages ms, then
    repeats the process until the entire database is backed up.

    The third argument passed to this function must be a pointer to a
    TBackupProgressFunc function. After each set of ABackupStepPages pages is
    backed up, the progress function is invoked with two integer parameters: the
    number of pages left to copy, and the total number of pages in the source
    file. This information may be used, for example, to update a GUI progress bar.

    While this function is running, another thread may use the database ADb, or
    another process may access the underlying database file via a separate
    connection.

    If the backup process is successfully completed, SQLITE_OK is returned.
    Otherwise, if an error occurs, an SQLite error code is returned. }
function backupDb(
  const ADB: sqlite3_ptr; { Database to back up. }
  const AFileName: Utf8String; { Name of file to back up to. }
  const AProgress: TBackupProgressFunc = nil; { Progress function to invoke. }
  const ABackupStepPages: Integer = BACKUP_STEP_PAGES;
  const ABackupSleepPages: Integer = BACKUP_SLEEP_MS
  ): Integer;
var
  DbFile: sqlite3_ptr; { Database connection opened on AFilename. }
  pBackup: sqlite3_backup_ptr; { Backup handle used to copy data. }
begin
  { Open the database file identified by Filename. }
  Result := sqlite3_open(PUtf8Char(AFileName), @DbFile);
  if Result = SQLITE_OK then
    try
      { Open the sqlite3_backup object used to accomplish the transfer }
      pBackup := sqlite3_backup_init(DbFile, 'main', ADB, 'main');
      if Assigned(pBackup) then
        try
          { Each iteration of this loop copies ABackupStepPages database pages
            from database ADb to the backup database. If the return value of
            backup_step() indicates that there are still further pages to copy,
            sleep for SLEEP_MS ms before repeating. }
          repeat
            Result := sqlite3_backup_step(pBackup, ABackupStepPages);
            if Assigned(AProgress) then
              AProgress(
                sqlite3_backup_remaining(pBackup),
                sqlite3_backup_pagecount(pBackup));
            if Result in [SQLITE_OK, SQLITE_BUSY, SQLITE_LOCKED] then
              sqlite3_sleep(ABackupSleepPages);
          until not (Result in [SQLITE_OK, SQLITE_BUSY, SQLITE_LOCKED]);
        finally
          { Release resources allocated by backup_init(). }
          sqlite3_backup_finish(pBackup);
          Result := sqlite3_errcode(DbFile);
        end;
    finally
      { Close the database connection opened on database file FileName. }
      sqlite3_close(DbFile);
    end;
end;

//------------------------------------------------------------------------------

procedure BackupProgress(const ARemaining, APageCount: Int64);
var
  Percent: Int64;
begin
  { APageCount may be 0 if the backup could not start yet because the
    destination database is locked exclusively. If this happens, the backup will
    start automatically when the lock is released and APageCount will increment
    accordingly. }
  if APageCount > 0 then
    Percent := 100 * (APageCount - ARemaining) div APageCount
  else
    Percent := 0;
  WriteLn(Percent, '% done');
end;

//------------------------------------------------------------------------------

var
  DbSource: sqlite3_ptr;
  BackupFile: Utf8String = '';
  SourceFile: Utf8String = '';
begin
  {$IFDEF FastMM}{$I FastMM_init.inc}{$ENDIF}

  { Disable all FPU and SSE exceptions. }
  Set8087CW($133F); {$IFDEF CPUX64}SetMXCSR($1F80); {$ENDIF CPUX64}

  SourceFile := sqlite3_encode_utf8(ParamStr(1));
  BackupFile := sqlite3_encode_utf8(ParamStr(2));

  if (SourceFile = '') or (BackupFile = '') then
    begin
      WriteLn('Usage: ', ParamStr(0), ' <SourceFile> <BackupFile>');
      Exit;
    end;

  try
    { Initialize the DISQLite3 library prior to using any other DISQLite3
      functionality. See also sqlite3_shutdown() below.}
    sqlite3_check(sqlite3_initialize);
    try
      WriteLn('SQLite Version ', sqlite3_libversion);

      { Open the source database file. }
      sqlite3_check(sqlite3_open_v2(PUtf8Char(SourceFile), @DbSource, SQLITE_OPEN_READONLY, nil));
      try
        { Run the backup. }
        sqlite3_check(backupDb(DbSource, BackupFile, BackupProgress), DbSource);
      finally
        { Close the source database file. }
        sqlite3_check(sqlite3_close(DbSource), DbSource);
      end;
    finally
      { Deallocate any resources that were allocated by sqlite3_initialize()
        above. }
      sqlite3_check(sqlite3_shutdown);
    end;

  except
    on e: Exception do
      begin
        WriteLn(e.Message);
        ExitCode := 1;
      end;
  end;

  WriteLn;
  WriteLn('Done - Press ENTER to Exit');
  ReadLn;
end.

