//
// Example script for 2BrightSparks SyncBackPro V8 that lets you treat
// all drives as a single drive.
//
// SBLang=Pascal
//
// http://www.2BrightSparks.com/
// February 2016
//
var gFSO, IsMainThread, DebugLog, IsDebugging;

//
// This is a location script
//
Function Description(var ScriptType);
begin
  Result:='All Drives Location';
  ScriptType:=SCRIPTTYPE_LOCATION;
End;

//
// This is called when the user adds the script to SyncBack
//
Function Install(Interactive);
begin
  Result:='';
//  If Interactive Then
//    SBSystem.Say 'Installed'
//  end;
End;

//
// This is called when the user removes the script to SyncBack
//
procedure Uninstall;
begin
//  SBSystem.Say 'Uninstalled'
End;

//
// The script is being exported
//
Function FilesToExport(Interactive, Cnt);
begin
  Result:='';
//  If Cnt = 0 Then
//    FilesToExport = 'D:\temp\junction.exe'
//  ELseIf Cnt = 1 Then
//    FilesToExport = 'C:\temp\test.txt'
//  Else
//    FilesToExport = ''
//  end;
End;

//
// Output a debug string
//
procedure DebugOut(Msg);
begin
  if IsDebugging then
    DebugLog.WriteLine(Msg);
End;

//
// Given a path like \C\Folder\Filename.txt then we return C:\Folder\Filename.txt
//
Function ExtractFilename(FullPath);
var
  DriveLetter, DrivePath;
begin
  Result:='';

  if (FullPath = '\') or (FullPath = '') then begin
    //
    // The root cannot be used
    //
    Exit;
  end else begin
    //
    // Return the filename
    //
    DriveLetter:=Copy(FullPath, 2, 1);
    if (DriveLetter = '') then
      Exit;

    DrivePath:=Copy(FullPath, 3, Length(FullPath) - 2);
    If (DrivePath = '') then
      DrivePath:='\';

    Result:=DriveLetter + ':' + DrivePath;
  end;
end;

//
// Return an error message if we cannot connect
//
function LocConnect(MainThread);
var
  Folder;
begin
  Result:='';

  IsMainThread:=MainThread;

  gFSO:=CreateOleObject('Scripting.FileSystemObject');

  IsDebugging:=FALSE;
  Exit;

  //----UNCOMMENT BELOW TO ENABLE DEBUGGING
//  IsDebugging:=TRUE;
//  If not gFSO.FolderExists('C:\temp\') Then
//    gFSO.CreateFolder('C:\temp\');
//
//  Folder:=gFSO.GetFolder('C:\temp\');
//  If Not IsMainThread Then
//    DebugLog:=Folder.CreateTextFile('debug.txt', True, True)
//  else
//    DebugLog:=Folder.CreateTextFile('debug_main.txt', True, True);
//
//  DebugOut('---Connected: ' + DateTimeToStr(Now));
end;

//
// Return an error message if we cannot disconnect
//
Function LocDisconnect;
begin
  Result:='';

  gFSO:=Unassigned;

  if IsDebugging then begin
    DebugOut('---Disconnected: ' + DateTimeToStr(Now));
    DebugLog.WriteBlankLines(1);
    DebugLog.Close;
    DebugLog:=Unassigned;
    IsDebugging:=FALSE;
  end;
end;

//
// Return an error message if we cannot prepare it
//
Function LocScanPrepare;
begin
  DebugOut('---LocScanPrepare');

  Result:='';
end;

//
// Return an error message if we are not connected
//
Function LocConnected;
begin
  DebugOut('---LocConnected');

  Result:='';
end;

//
// Return an error message if we cannot reconnect
//
Function LocReconnect;
begin
  DebugOut('---LocReconnect');

  Result:='';
end;

//
// Return an error message if the directory does not exist. The full directory
// is passed (which always includes a trailing backslash).
//
Function LocDirExists(FullPath);
begin
  DebugOut('---LocDirExists:' + FullPath);

  FullPath:=ExtractFilename(FullPath);
  If (FullPath = '') Then
    Result:=''
  else If gFSO.FolderExists(FullPath) Then
    Result:=''
  else
    Result:='Folder does not exist';
end;

//
// Return an error message if the file does not exist. The full filename
// is passed.
//
Function LocFileExists(FullPath);
begin
  DebugOut('---LocFileExists:' + FullPath);

  FullPath:=ExtractFilename(FullPath);
  If (FullPath = '') then
    Result:='File does not exist'
  Else If gFSO.FileExists(FullPath) Then
    Result:=''
  Else
    Result:='File does not exist';
end;

//
// Create a directory. A full path is passed, including trailing backslash.
// Returns an error message on failure (if the directory already exists then
// no error should be returned).
//
function LocMakeDir(FullPath);
begin
  DebugOut('---LocMakeDir:' + FullPath);

  Result:='';

  FullPath:=ExtractFilename(FullPath);
  If (FullPath = '') Then
    Exit
  Else If Not gFSO.FolderExists(FullPath) Then
    gFSO.CreateFolder(FullPath);
end;

//
// Delete a directory. A full path is passed, including trailing backslash.
// Returns an error message on failure. No error message should be returned if
// the directory does not exist. A directory should not be deleted by the script
// unless it is empty, i.e. has no files or directories in it.
//
function LocRemoveDir(FullPath);
var
  Folder;
begin
  DebugOut('---LocRemoveDir:' + FullPath);

  Result:='';

  FullPath:=ExtractFilename(FullPath);

  If FullPath = '' Then
    Exit;
  Else If gFSO.FolderExists(FullPath) Then begin
    Folder:=gFSO.GetFolder(FullPath);

    If (Folder.Files.Count > 0) Then begin
      Result:='Directory contains files'
      Exit;
    end;

    If (Folder.SubFolders.Count > 0) Then begin
      Result:='Directory contains directories'
      Exit;
    end;

      Folder.Delete(TRUE);

    Folder:=Unassigned;
  end;
end;

//
// Rename a folder. Return an error message on failure.
//
function LocRenameDir(FromPath, ToPath);
var
  FullFromPath, FullToPath, TempFldr;
begin
  DebugOut('---LocRenameDir:' + FromPath + '*' + ToPath);

  FullFromPath:=ExtractFilename(FromPath);
  FullToPath:=ExtractFilename(ToPath);

  If (FullFromPath = '') Then begin
    Result:='Root directory cannot be moved'
    Exit;
  end Else If (FullToPath = '') Then begin
    Result:='Cannot move to root directory'
    Exit;
  end;

  If Not gFSO.FolderExists(FullFromPath) Then begin
    Result:='Source directory does not exist'
    Exit;
  end;

  If gFSO.FolderExists(FullToPath) Then begin
    If SBSystem.SameFilenames(FullFromPath, FullToPath, TRUE) Then begin
      Result:='To destination directory already exists'
      Exit;
    end;
  end;

  // Do not include trailing backslash
  If SBSystem.IsFolder(FullFromPath) Then
    FullFromPath:=SBSystem.ExcludeTrailingBackslash(FullFromPath);
  If SBSystem.IsFolder(FullToPath) Then
    FullToPath:=SBSystem.ExcludeTrailingBackslash(FullToPath);

  // Cannot rename just the case, e.g. \abc to \ABC
  // Must instead do a two-step process
  If SBSystem.SameFilenames(FullFromPath, FullToPath, FALSE) Then begin
    TempFldr:=FullToPath + '.' + SBSystem.UniqueID;
    gFSO.MoveFolder(FullFromPath, TempFldr);
    gFSO.MoveFolder(TempFldr, FullToPath);
  end Else
    gFSO.MoveFolder(FullFromPath, FullToPath);

  Result:='';
end;

//
// Rename a file. Return an error message on failure.
//
function LocRenameFile(FromName, ToName);
var
  FullFromName, FullToName, FileObj, Attrs;
begin
  DebugOut('---LocRenameFile:' + FromName + '*' + ToName);

  FullFromName:=ExtractFilename(FromName);
  FullToName:=ExtractFilename(ToName);

  If Not gFSO.FileExists(FullFromName) Then begin
    Result:='Source file does not exist';
    Exit;
  end;

  // Get file attributes. Rename will give archive attribute.
  FileObj:=gFSO.GetFile(FullFromName);
  Attrs:=FileObj.Attributes;

  If gFSO.FileExists(FullToName) Then begin
    If SBSystem.SameFilenames(FullFromName, FullToName, TRUE) Then begin
      Result:='To destination file already exists';
      Exit;
    end;
  end;

  gFSO.MoveFile(FullFromName, FullToName);
  Result:='';

  // Set attributes
  FileObj:=gFSO.GetFile(FullToName);
  FileObj.Attributes:=Attrs;
end;

//
// Delete a file. Return an error message on failure. Note that if the file does
// not exist then no error should be returned and DoesNotExist should be set
// to TRUE.
//
function LocDeleteFile(FullPath; var DoesNotExist);
begin
  DebugOut('---LocDeleteFile:' + FullPath);

  Result:='';

  FullPath:=ExtractFilename(FullPath)

  DoesNotExist:=not gFSO.FileExists(FullPath);

  If Not DoesNotExist Then
    gFSO.DeleteFile(FullPath, True);
end;

//
// Calculate CRC32 hash value of a file
//
function LocCRC32(FullPath; var CRC32);
begin
  DebugOut('---LocCRC32:' + FullPath);

  FullPath:=ExtractFilename(FullPath);
  CRC32:=SBSystem.CRC32(FullPath);
  Result:='';
end;

//
// Set the last modification date & time of a file. ModDateTime is the date &
// time in the local timezone. On failure an error message should be returned.
//
function LocSetModDateTime(FullPath, ModDateTime);
begin
  DebugOut('---LocSetModDateTime:' + FullPath);

  FullPath:=ExtractFilename(FullPath);
  Result:=SBSystem.SetLastModDateTime(FullPath, ModDateTime);
end;

//
// Set the creation date & time of a file or folder. CreateDateTime is the date &
// time in the local timezone. On failure an error message should be returned.
//
Function LocSetCreateDateTime(FullPath, CreateDateTime);
begin
  DebugOut('---LocSetCreateDateTime:' + FullPath);

  FullPath:=ExtractFilename(FullPath);
  Result:=SBSystem.SetCreateDateTime(FullPath, CreateDateTime);
end;

//
// Get a file or folders attributes. A folder will have a trailing backslash.
// On failure return an error message.
//
function LocGetAttributes(Filename; var Attrs);
var
  FolderObj, FileObj;
begin
  DebugOut('---LocGetAttributes:' + Filename + ',' + Attrs);

  Filename:=ExtractFilename(Filename);
  If (Filename = '') Then begin
    // Its a special folder
    Result:='';
    Attrs:=1 + 2 + 4 + 16;
    Exit;
  end;

  If SBSystem.IsFolder(Filename) Then begin
    //
    // A folder
    //
    If not gFSO.FolderExists(Filename) Then begin
      Result:='Folder does not exist';
      Exit;
    end;

    FolderObj:=gFSO.GetFolder(Filename);
    Attrs:=FolderObj.Attributes;
    Result:='';
  end Else begin
    //
    // A file
    //
    If not gFSO.FileExists(Filename) Then begin
      Result:='File does not exist';
      Exit;
    end;

    FileObj:=gFSO.GetFile(Filename);
    Attrs:=FileObj.Attributes;
    Result:=''
  end;
end;

//
// Set a file or folders attributes. A folder will have a trailing backslash.
// On failure return an error message.
//
function LocSetAttributes(Filename, Attrs);
var
  FolderObj, FileObj;
begin
  DebugOut('---LocSetAttributes:' + Filename + ',' + Attrs);

  Filename:=ExtractFilename(Filename);
  If (Filename = '') Then begin
    // Its a special folder
    Result:='Cannot set roots attributes';
    Exit;
  end;

  If SBSystem.IsFolder(Filename) Then begin
    //
    // A folder
    //
    If not gFSO.FolderExists(Filename) Then begin
      Result:='Folder does not exist';
      Exit;
    end;

    FolderObj:=gFSO.GetFolder(Filename);
    FolderObj.Attributes:=Attrs;
    Result:='';
  end Else begin
    //
    // A file
    //
    If not gFSO.FileExists(Filename) Then begin
      Result:='File does not exist';
      Exit;
    end;

    FileObj:=gFSO.GetFile(Filename);
    FileObj.Attributes:=Attrs;
    Result:=''
  end;
end;

//
// Return the number of free bytes available for file storage. If it's not
// relevant or practical then return -1.
//
//
// We cannot know because it could be any drive
//
Function LocFreeSpace;
begin
  DebugOut('---LocFreeSpace');

  Result:= -1;
end;

//
// Return the abilities, i.e. what it can do and support. The values are
// a bitmask, e.g. CAN_USEATTRIBUTES + CAN_NTFSATTRIBUTES
//
// The following are valid for scripts:
//
//  CAN_COPYDIRATTRS =        2       Can the directory attributes and date & times be copied to a new directory?
//  CAN_USEATTRIBUTES =       32      Do files/folders have Windows attributes?
//  CAN_EXACTDATETIME =       64      Are file dates & times stored exactly (including milli-seconds)?
//  CAN_CHANGEATTRIBUTES =    128     Can file/folder attributes be changed?
//  CAN_CHANGEDATETIME =      256     Can file/folder date & times be changed?
//  CAN_HAVEEMPTYPATH =       1024    Can the base path be empty?
//  CAN_VERSION =             4096    Does the location support versioning?
//  CAN_NTFSATTRIBUTES =      16384   Files & folders have NTFS extended attributes?
//  CAN_WRITEONCE =           65536   Write-once, meaning cannot read from it only write to it, e.g. burn location and spanned/split zip
//  CAN_MOVE_FILES =          131072  Can files be moved?
//  CAN_USECRC32 =            262144  Can use CRC32 hashing?
//  CAN_MOVE_FOLDERS =        524288  Can folders be moved?
//
function LocAbilities;
begin
  // Cannot use CAN_EXACTDATETIME because some drives may be FAT32
  // Cannot use CAN_NTFSATTRIBUTES because some drives may be FAT32
  Result:= CAN_COPYDIRATTRS + CAN_USEATTRIBUTES + CAN_CHANGEATTRIBUTES +
    CAN_CHANGEDATETIME + CAN_HAVEEMPTYPATH + CAN_VERSION + CAN_MOVE_FILES +
    CAN_USECRC32 + CAN_MOVE_FOLDERS;
end;

//
// Take a file from the file-system and store it. fromFName is the full filename
// of the file to copy. toFName is where the file should ultimately be copied
// to (take note of DoSafeCopy parameter). fromAttrs are the files attributes
// (ignore them if the value is < 0). from DateTime is the last modification
// date & time of the file (in the current timezone, also ignore it if its
// value is <= 1.0). fromFileSize is the size of the file in bytes (it may be
// less than zero if its unknown). Note that its a 64-bit value. If DoSafeCopy
// is TRUE then the file should be copied to a temporary file first and not to
// toFName. The full path of the safe filename used should be returned in
// SafeFName.
//
// On failure an error message should be returned.
//
function LocPutEx(fromFName, toFName, fromAttrs, fromDateTime, fromCreateDateTime, fromFileSize, fromNTFSSec, DoSafeCopy; var SafeFName);
var
  FileObj, Attrs, FulltoFName, r;
begin
  DebugOut('---LocPutEx:' + fromFName + '*' + toFName);

  If Not gFSO.FileExists(fromFName) Then
    Result:='File does not exist'
  Else begin
    // Safe copy?
    If DoSafeCopy Then begin
      // Note that we must return a SafeFName that we will understand when
      // it is passed back to us (we will be asked to move the file)
      FulltoFName:=ExtractFilename(toFName) + '.$$$';
      SafeFName:=toFName + '.$$$';
    end Else begin
      FulltoFName:=ExtractFilename(toFName);
      SafeFName:='';
    end;

    // Destination file read-only?
    If gFSO.FileExists(FulltoFName) Then begin
      FileObj:=gFSO.GetFile(FulltoFName);
      Attrs:=FileObj.Attributes;
      If (Attrs and 1 <> 0) Then
        FileObj.Attributes:=Attrs - 1;

      FileObj:=Unassigned;
    end;

    // SBSystem.UpdateFileStatus('Copying ' + fromFName + '...')
    r:=CopyFile(fromFName, FulltoFName);
    if (r <> 0) then begin
      Result:=SysErrorMessage(r);
      Exit;
    end;

    If (fromAttrs >= 0) Then begin
      FileObj:=gFSO.GetFile(FulltoFName);
      FileObj.Attributes:=fromAttrs;
      FileObj:=Unassigned;
    end;

    If (fromDateTime > 1.0) Then
      SBSystem.SetLastModDateTime(FulltoFName, fromDateTime);

    If (fromCreateDateTime > 1.0) Then
      SBSystem.SetCreateDateTime(FulltoFName, fromCreateDateTime);

    Result:='';
  end;
end;

//
// Put a file onto the file-system. fromFName is the full filename of the file
// the script should copy, and toFName is the full filename of where to copy
// the file to (on file-system).
//
// On failure an error message should be returned.
//
Function LocGet(fromFName, toFName);
var
  FullFromName, FileObj, Attrs, r;
begin
  DebugOut('---LocGet:' + fromFName + '*' + toFName);

  FullFromName:=ExtractFilename(fromFName);

  If Not gFSO.FileExists(FullFromName) Then
    Result:='File does not exist'
  Else begin
    // Read-only?
    If gFSO.FileExists(toFName) Then begin
      FileObj:=gFSO.GetFile(toFName);
      Attrs:=FileObj.Attributes;
      If (Attrs And 1 <> 0) Then
        FileObj.Attributes:=Attrs - 1;
      FileObj:=Unassigned;
    end;

    // gFSO.CopyFile(FullFromName, toFName, True);
    r:=CopyFile(FullFromName, toFName);
    if (r = 0) then
      Result:=''
    else
      Result:=SysErrorMessage(r);
  end;
end;

//
// Scan a path and return all the files and folders in that path.
//
function LocScanList(FullPath);
var
  Folder, DiskDrive, DriveLetter, DrivePath,
  SubFol, FileItem, DrivesEnum, FoldersEnum, FilesEnum;
begin
  Result:='';

  if (FullPath = '\') or (FullPath = '') then begin
    //
    // Return the list of drives
    //
    DrivesEnum:=TEnumVariant.Create(gFSO.Drives);
    try
      while DrivesEnum.ForEach(DiskDrive) do begin
        If (DiskDrive.IsReady = TRUE) then
          If not SBLocation.AddDir(DiskDrive.DriveLetter) then
            Exit;
      end;
    finally
      DrivesEnum.Free;
    end;
  end else begin
    //
    // Return the list of folders and files
    //
    DriveLetter:=Copy(FullPath, 2, 1);
    if (DriveLetter = '') then
      Exit;

    DrivePath:=Copy(FullPath, 3, Length(FullPath) - 2);
    If (DrivePath = '') then
      DrivePath:= '\';

    // Return a list of sub-folders
    Folder:=gFSO.GetFolder(DriveLetter + ':' + DrivePath);
    FoldersEnum:=TEnumVariant.Create(Folder.SubFolders);
    try
      while FoldersEnum.ForEach(SubFol) do begin
        If not SBLocation.AddDirEx2(SubFol.Name, SubFol.Attributes, SubFol.DateLastModified, SubFol.DateCreated, '') then
          Exit;
      end;
    finally
      FoldersEnum.Free;
    end;

    // Return a list of files
    FilesEnum:=TEnumVariant.Create(Folder.Files);
    try
      while FilesEnum.ForEach(FileItem) do begin
        If not SBLocation.AddFileEx(FileItem.Name, '', VarToInt64(FileItem.Size), FileItem.Attributes, FileItem.DateLastModified, FileItem.DateCreated, '') then
          Exit;
      end;
    finally
      FilesEnum.Free;
    end;
  end;
end;