(*
    Tux Commander - UVFSCore - VFS Core utilities and management
    Copyright (C) 2007 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 UVFSCore;

interface

uses GTKForms, ULibc, Classes, uVFSprototypes, UEngines, UCoreUtils;


type
  TVFSPlugin = class
  private
    FVFSNew: TVFSNew;
    FVFSFree: TVFSFree;
    FVFSVersion: TVFSVersion;
    FVFSGetInfo: TVFSGetInfo;
    FVFSOpenArchive: TVFSOpenArchive;
    FVFSOpenURI: TVFSOpenURI;
    FVFSClose: TVFSClose;
    FVFSListFirst: TVFSListFirst;
    FVFSListNext: TVFSListNext;
    FVFSListClose: TVFSListClose;
    FVFSChangeDir: TVFSChangeDir;
    FVFSGetPath: TVFSGetPath;
    FVFSGetPathURI: TVFSGetPathURI;
    FVFSGetFileSystemSize: TVFSGetFileSystemSize;
    FVFSGetFileSystemFree: TVFSGetFileSystemFree;
    FVFSFileExists: TVFSFileExists;
    FVFSFileInfo: TVFSFileInfo;
    FVFSMkDir: TVFSMkDir;
    FVFSRemove: TVFSRemove;
    FVFSRename: TVFSRename;
    FVFSMakeSymLink: TVFSMakeSymLink;
    FVFSChmod: TVFSChmod;
    FVFSChown: TVFSChown;
    FVFSChangeTimes: TVFSChangeTimes;
    FVFSGetDirSize: TVFSGetDirSize;
    FVFSBreakGetDirSize: TVFSBreakGetDirSize;
    FVFSOpenFile: TVFSOpenFile;
    FVFSReadFile: TVFSReadFile;
    FVFSWriteFile: TVFSWriteFile;
    FVFSCloseFile: TVFSCloseFile;
    FVFSFileSeek: TVFSFileSeek;
    FVFSSetBlockSize: TVFSSetBlockSize;
    FVFSCopyToLocal: TVFSCopyToLocal;
    FVFSCopyFromLocal: TVFSCopyFromLocal;
    FVFSIsOnSameFS: TVFSIsOnSameFS;
    FVFSTwoSameFiles: TVFSTwoSameFiles;
    FVFSGetArchiveExts: TVFSGetArchiveExts;
    FVFSGetNetworkServices: TVFSGetNetworkServices;
    FVFSGetPasswordRequired: TVFSGetPasswordRequired;
    FVFSSetCallbacks: TVFSSetCallbacks;
    FVFSResetPassword: TVFSResetPassword;
    function GetHandlesArchives: boolean;
    function GetHandlesNetwork: boolean;
  public
    ModuleHandle: Pointer;
    FullModulePath: string;  //  module path
    Extensions, Services: TOpenStringArray;  //  the list of the extensions plugin can handle
    constructor Create(PluginHandle: Pointer);
    destructor Destroy; override;

    function VFSVersion: integer;
    function ModuleID: string;
    function ModuleName: string;
    function ModuleAbout: string;
    function ModuleCopyright: string;
  published
    property HandlesArchives: boolean read GetHandlesArchives;
    property HandlesNetwork: boolean read GetHandlesNetwork;
  end;


  TVFSEngine = class(TPanelEngine)
  private
    FGlobs: Pointer;
    FSourcePlugin: TVFSPlugin;
    FBlockSize: Cardinal;
    BreakProcessingKind: integer;
    FArchiveMode: boolean;
    FArchivePath: string;
    function GetPluginID: string;
  public
    Password: string;
    PasswordUsed: boolean;
    RemoveFileOnClose: string;
    OpenedFromQuickConnect: boolean;
    CustomPluginIDSave: string;
    constructor Create(SourcePlugin: TVFSPlugin);
    function VFSOpenURI(URI: string; AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): boolean;
    function VFSOpenEx(OpenFile: string; AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): TVFSResult;
    function VFSClose: boolean;

    destructor Destroy; override;
    function GetListing(var List: TList; const AddDotFiles: boolean): integer; override;
    function GetListing(var List: TList; const AddDotFiles: boolean; APath: string): integer; override;
    function ChangeDir(const NewPath: string): integer; override;
    function ChangeDirEx(const NewPath: string; AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): integer;
    function ExplicitChDir(const NewPath: string): integer; override;
    function GetFileSystemSize: Int64; override;
    function GetFileSystemSize(const APath: string): Int64; override;
    function GetFileSystemFree: Int64; override;
    function GetFileSystemFree(const APath: string): Int64; override;
    function MakeDir(const NewDir: string): integer; override;
    function GetDirSize(APath: string): Int64; override;
    function Remove(APath: string): integer; override;
    procedure FillDirFiles(APath: string; List: TList; ALevel: word); override;
    function GetFileInfoSL(APath: string): PDataItemSL; override;
    function FileExists(const FileName: string; const Use_lstat: boolean = False): Boolean; override;
    function DirectoryExists(const FileName: string; const Use_lstat: boolean = False): Boolean; override;
    function MakeSymLink(const NewFileName, PointTo: string): integer; override;
    function Chmod(const FileName: string; const Mode: integer): integer; override;
    function Chown(const FileName: string; const UID, GID: integer): integer; override;
    procedure BreakProcessing(ProcessingKind: integer); override;
    function RenameFile(SourceFile, DestFile: string): integer; override;
    function ChangeTimes(APath: string; mtime, atime: Int64): integer; override;
    procedure GetFileSystemInfo(const APath: string; var FSSize, FSFree: Int64; var FSName: string); override;
    function OpenFile(const APath: string; Mode: integer; var Error: integer): TEngineFileDes;  override;
    function ReadFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; ABlockSize: integer; var Error: integer): integer; override;
    function WriteFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; BytesCount: integer; var Error: integer): integer; override;
    function CloseFile(const FileDescriptor: TEngineFileDes): integer; override;
    function FileSeek(const FileDescriptor: TEngineFileDes; const AbsoluteOffset: Int64; var Error: integer): Int64; override;
    function IsOnROMedium(const FileName: string): boolean; override;
    function FileCanRun(const FileName: string): boolean; override;
    function GetPath: string; override;
    procedure SetPath(Value: string); override;
    function GetPathURI: string;

    function GetBlockSize: Cardinal; override;
    procedure SetBlockSize(Value: Cardinal); override;
    function CopyFileIn(Sender: Pointer; SourceFile, DestFile: string; ProgressFunc: TEngineProgressFunc; ErrorFunc: TEngineErrorFunc; Append: boolean): boolean; override;
    function CopyFileOut(Sender: Pointer; SourceFile, DestFile: string; ProgressFunc: TEngineProgressFunc; ErrorFunc: TEngineErrorFunc; Append: boolean): boolean; override;
    function CopyFileInEx(Sender: Pointer; SourceFile, DestFile: string; ErrorFunc: TEngineErrorFunc; Append: boolean;
                          AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): boolean;
    function CopyFileOutEx(Sender: Pointer; SourceFile, DestFile: string; ErrorFunc: TEngineErrorFunc; Append: boolean;
                           AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): boolean;
    function IsOnSameFS(const Path1, Path2: string): boolean; override;
    function TwoSameFiles(const Path1, Path2: string): boolean; override;

    function GetPasswordRequired: boolean;
    procedure ResetPassword;
  published
    property Path: string read GetPath write SetPath;
    property BlockSize: Cardinal read GetBlockSize write SetBlockSize;
    property PluginID: string read GetPluginID;
    property ArchiveMode: boolean read FArchiveMode;
    property ArchivePath: string read FArchivePath;
  end;



procedure DoInitPlugins;

var PluginList: TList;

implementation

uses SysUtils, UConfig, ULocale;


const ConstGlobalModulePath1 = '/usr/lib/tuxcmd';
      ConstGlobalModulePath2 = '/usr/local/lib/tuxcmd';
      ConstLocalModulePath1 = '~/.tuxcmd/plugins';
      ConstLocalModulePath2 = './plugins';
      ConstLocalModulePath3 = '../lib/tuxcmd';
      ConstGlobalModulePath3 = '/usr/lib64/tuxcmd';
      ConstGlobalModulePath4 = '/usr/local/lib64/tuxcmd';
      ConstLocalModulePath4 = '../lib64/tuxcmd';

      ConstVFSVersionRequired = cVFSVersion;

var BinaryPath: string;


procedure VFSLogFunc(const s: PChar); cdecl;
begin
  DebugMsg([' ### VFS ###: ', s]);
end;

(********************************************************************************************************************************)
constructor TVFSPlugin.Create(PluginHandle: Pointer);
var s: PChar;
begin
  inherited Create;
  ModuleHandle := PluginHandle;

  //  Find the symbols
  @FVFSNew := dlsym(ModuleHandle, 'VFSNew');
  @FVFSFree := dlsym(ModuleHandle, 'VFSFree');
  @FVFSVersion := dlsym(ModuleHandle, 'VFSVersion');
  @FVFSGetInfo := dlsym(ModuleHandle, 'VFSGetInfo');
  @FVFSOpenArchive := dlsym(ModuleHandle, 'VFSOpenArchive');
  @FVFSOpenURI := dlsym(ModuleHandle, 'VFSOpenURI');
  @FVFSClose := dlsym(ModuleHandle, 'VFSClose');
  @FVFSListFirst := dlsym(ModuleHandle, 'VFSListFirst');
  @FVFSListNext := dlsym(ModuleHandle, 'VFSListNext');
  @FVFSListClose := dlsym(ModuleHandle, 'VFSListClose');
  @FVFSGetPath := dlsym(ModuleHandle, 'VFSGetPath');
  @FVFSGetPathURI := dlsym(ModuleHandle, 'VFSGetPathURI');
  @FVFSChangeDir := dlsym(ModuleHandle, 'VFSChangeDir');
  @FVFSGetFileSystemSize := dlsym(ModuleHandle, 'VFSGetFileSystemSize');
  @FVFSGetFileSystemFree := dlsym(ModuleHandle, 'VFSGetFileSystemFree');
  @FVFSFileExists := dlsym(ModuleHandle, 'VFSFileExists');
  @FVFSFileInfo := dlsym(ModuleHandle, 'VFSFileInfo');
  @FVFSMkDir := dlsym(ModuleHandle, 'VFSMkDir');
  @FVFSRemove := dlsym(ModuleHandle, 'VFSRemove');
  @FVFSRename := dlsym(ModuleHandle, 'VFSRename');
  @FVFSMakeSymLink := dlsym(ModuleHandle, 'VFSMakeSymLink');
  @FVFSChmod := dlsym(ModuleHandle, 'VFSChmod');
  @FVFSChown := dlsym(ModuleHandle, 'VFSChown');
  @FVFSChangeTimes := dlsym(ModuleHandle, 'VFSChangeTimes');
  @FVFSGetDirSize := dlsym(ModuleHandle, 'VFSGetDirSize');
  @FVFSBreakGetDirSize := dlsym(ModuleHandle, 'VFSBreakGetDirSize');
  @FVFSCopyToLocal := dlsym(ModuleHandle, 'VFSCopyToLocal');
  @FVFSCopyFromLocal := dlsym(ModuleHandle, 'VFSCopyFromLocal');
  @FVFSOpenFile := dlsym(ModuleHandle, 'VFSOpenFile');
  @FVFSReadFile := dlsym(ModuleHandle, 'VFSReadFile');
  @FVFSWriteFile := dlsym(ModuleHandle, 'VFSWriteFile');
  @FVFSCloseFile := dlsym(ModuleHandle, 'VFSCloseFile');
  @FVFSFileSeek := dlsym(ModuleHandle, 'VFSFileSeek');
  @FVFSSetBlockSize := dlsym(ModuleHandle, 'VFSSetBlockSize');
  @FVFSIsOnSameFS := dlsym(ModuleHandle, 'VFSIsOnSameFS');
  @FVFSTwoSameFiles := dlsym(ModuleHandle, 'VFSTwoSameFiles');
  @FVFSGetArchiveExts := dlsym(ModuleHandle, 'VFSGetArchiveExts');
  @FVFSGetNetworkServices := dlsym(ModuleHandle, 'VFSGetNetworkServices');
  @FVFSGetPasswordRequired := dlsym(ModuleHandle, 'VFSGetPasswordRequired');
  @FVFSSetCallbacks := dlsym(ModuleHandle, 'VFSSetCallbacks');
  @FVFSResetPassword := dlsym(ModuleHandle, 'VFSResetPassword');

  //  Initialize the extensions list
  SetLength(Extensions, 0);
  SetLength(Services, 0);
  if (@FVFSGetArchiveExts <> nil) and (@FVFSOpenArchive <> nil) then begin
    s := FVFSGetArchiveExts;
    if s <> nil then begin
      ParseString(String(s), ';', Extensions);
      real_libc_free(s);
    end;
  end;
  if (@FVFSGetNetworkServices <> nil) and (@FVFSOpenURI <> nil) then begin
    s := FVFSGetNetworkServices;
    if s <> nil then begin
      ParseString(String(s), ';', Services);
      real_libc_free(s);
    end;
  end;
end;

destructor TVFSPlugin.Destroy;
begin
  SetLength(Extensions, 0);
  SetLength(Services, 0);
  if ModuleHandle <> nil then dlclose(ModuleHandle);
  inherited Destroy;
end;

(********************************************************************************************************************************)
function TVFSPlugin.VFSVersion: integer;
begin
  if @FVFSVersion <> nil then Result := FVFSVersion
                         else Result := -1;
end;

procedure _free_PVFSInfo(Info: PVFSInfo);
begin
  if Info <> nil then begin
    if Info^.Name <> nil then real_libc_free(Info^.Name);
    if Info^.ID <> nil then real_libc_free(Info^.ID);
    if Info^.About <> nil then real_libc_free(Info^.About);
    if Info^.Copyright <> nil then real_libc_free(Info^.Copyright);
  end;
end;

function TVFSPlugin.ModuleID: string;
var Info: PVFSInfo;
begin
  if @FVFSGetInfo <> nil then begin
    Info := FVFSGetInfo;
    Result := String(Info.ID);
    _free_PVFSInfo(Info);
  end else Result := '';
end;

function TVFSPlugin.ModuleName: string;
var Info: PVFSInfo;
begin
  if @FVFSGetInfo <> nil then begin
    Info := FVFSGetInfo;
    Result := String(Info.Name);
    _free_PVFSInfo(Info);
  end else Result := '';
end;

function TVFSPlugin.ModuleAbout: string;
var Info: PVFSInfo;
begin
  if @FVFSGetInfo <> nil then begin
    Info := FVFSGetInfo;
    Result := String(Info.About);
    _free_PVFSInfo(Info);
  end else Result := '';
end;

function TVFSPlugin.ModuleCopyright: string;
var Info: PVFSInfo;
begin
  if @FVFSGetInfo <> nil then begin
    Info := FVFSGetInfo;
    Result := String(Info.Copyright);
    _free_PVFSInfo(Info);
  end else Result := '';
end;

function TVFSPlugin.GetHandlesArchives: boolean;
begin
  Result := (Length(Extensions) > 0) and (@FVFSOpenArchive <> nil);
end;

function TVFSPlugin.GetHandlesNetwork: boolean;
begin
  Result := (Length(Services) > 0) and (@FVFSOpenURI <> nil);
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
constructor TVFSEngine.Create(SourcePlugin: TVFSPlugin);
begin
  inherited Create;
  FSourcePlugin := SourcePlugin;
  FBlockSize := 65536;
  FArchiveMode := False;
  FArchivePath := '';
  BreakProcessingKind := 0;
  FGlobs := nil;
  Password := '';
  PasswordUsed := False;
  RemoveFileOnClose := '';
  OpenedFromQuickConnect := False;
  CustomPluginIDSave := '';

  if @FSourcePlugin.FVFSNew <> nil then FGlobs := FSourcePlugin.FVFSNew(@VFSLogFunc);
end;

destructor TVFSEngine.Destroy;
begin
  try
    if @FSourcePlugin.FVFSFree <> nil then FSourcePlugin.FVFSFree(FGlobs);
    if Length(Trim(RemoveFileOnClose)) > 0 then
      if unlink(PChar(RemoveFileOnClose)) <> 0 then
        DebugMsg(['(EE) TVFSEngine.Destroy: error while removing ''', RemoveFileOnClose, ''': ', strerror(errno)]);
  except
    on E: Exception do DebugMsg(['*** TVFSEngine.Destroy() -Exception: ', E.Message]);
  end;
end;

function TVFSEngine.VFSOpenURI(URI: string; AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): boolean;
begin
  Result := False;
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSOpenURI <> nil) then begin
    if @FSourcePlugin.FVFSSetCallbacks <> nil then
      FSourcePlugin.FVFSSetCallbacks(FGlobs, AskQuestionCallback, AskPasswordCallback, ProgressCallback, CallbackData);
    Result := FSourcePlugin.FVFSOpenURI(FGlobs, PChar(URI)) = cVFS_OK;
    FArchiveMode := False;
    FArchivePath := '';
    if @FSourcePlugin.FVFSSetCallbacks <> nil then
      FSourcePlugin.FVFSSetCallbacks(FGlobs, nil, nil, nil, nil);
  end;
end;

function TVFSEngine.VFSOpenEx(OpenFile: string; AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): TVFSResult;
begin
  Result := cVFS_OK;
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSOpenArchive <> nil) then begin
    if @FSourcePlugin.FVFSSetCallbacks <> nil then
      FSourcePlugin.FVFSSetCallbacks(FGlobs, AskQuestionCallback, AskPasswordCallback, ProgressCallback, CallbackData);
    Result := FSourcePlugin.FVFSOpenArchive(FGlobs, PChar(OpenFile));
    FArchiveMode := True;
    if Result = cVFS_OK then FArchivePath := OpenFile
                        else FArchivePath := '';
    if @FSourcePlugin.FVFSSetCallbacks <> nil then
      FSourcePlugin.FVFSSetCallbacks(FGlobs, nil, nil, nil, nil);
  end;
end;

function TVFSEngine.VFSClose: boolean;
begin
  Result := False;
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSClose <> nil)
    then Result := FSourcePlugin.FVFSClose(FGlobs) = cVFS_OK;
end;

function TVFSEngine.GetListing(var List: TList; const AddDotFiles: boolean; APath: string): integer;
var P: PVFSItem;
    Item: PDataItem;
    i, Res: integer;
begin
  DebugMsg(['^^VFS (II): GetListing begin']);
  Result := 0;
  try
    if @FSourcePlugin.FVFSListFirst = nil then Exit;
    P := real_libc_malloc(SizeOf(TVFSItem));
    memset(P, 0, SizeOf(TVFSItem));
//    DebugMsg(['Item = ', Int64(P)]);
//        DebugMsg(['FVFSListFirst']);
    Res := FSourcePlugin.FVFSListFirst(FGlobs, PChar(APath), P);
    if Res <> cVFS_OK then begin
      FSourcePlugin.FVFSListClose(FGlobs);
      if Res = cVFS_Not_More_Files then Result := 0
                                   else Result := Res;
      real_libc_free(P);
      Exit;
    end;

    repeat
//          DebugMsg(['begin--']);
      if AddDotFiles or (not ((Length(P^.FName) > 1) and (P^.FName[0] = '.') and (P^.FName[1] <> '.'))) then begin
//            DebugMsg(['Checkpoint 1']);
        Item := malloc(SizeOf(TDataItem));
        memset(Item, 0, SizeOf(TDataItem));
//            DebugMsg(['Checkpoint 2']);
        for i := 0 to Length(Item^.ColumnData) - 1 do Item^.ColumnData[i] := nil;
//            DebugMsg(['Checkpoint 3']);
        with Item^ do
          try
            FName := strdup(P^.FName);
            FDisplayName := strdup(P^.FDisplayName);
            if P^.sLinkTo <> nil
              then begin
                     LnkPointTo := strdup(P^.sLinkTo);
                     DebugMsg(['LnkPointTo = ', P^.sLinkTo]);
              end else LnkPointTo := nil;
            Mode := P^.iMode;
//              DebugMsg(['Checkpoint 4']);
            IsDotFile := (Length(FName) > 1) and (FName[0] = '.') and (FName[1] <> '.');
            IsDir := TVFSItemType(P^.ItemType) = vDirectory;
            IsLnk := TVFSItemType(P^.ItemType) = vSymlink;
            IsBlk := TVFSItemType(P^.ItemType) = vBlockdev;
            IsChr := TVFSItemType(P^.ItemType) = vChardev;
            IsFIFO := TVFSItemType(P^.ItemType) = vFifo;
            IsSock := TVFSItemType(P^.ItemType) = vSock;
//              DebugMsg(['Checkpoint 5']);
            ModifyTime := P^.m_time;
//              DebugMsg(['Returned datetime: ', Longword(P^.m_time)]);
//              DebugMsg(['Checkpoint 6']);
            UID := P^.iUID;
//              DebugMsg(['Checkpoint 7']);
            GID := P^.iGID;
//              DebugMsg(['Checkpoint 8']);
            UpDir := False;
//              DebugMsg(['Checkpoint 9']);
            Selected := False;
//              DebugMsg(['Checkpoint 10']);
            Size := P^.iSize;
//              DebugMsg(['Checkpoint 11']);
            List.Add(Item);
//              DebugMsg(['Checkpoint 12']);
          except
            on E: Exception do
              DebugMsg(['^^VFS (EE): GetListing: Item-Exception: ', E.Message]);
          end;
      end; // of if AddDotFiles
      if P^.FName <> nil then real_libc_free(P^.FName);
      if P^.FDisplayName <> nil then real_libc_free(P^.FDisplayName);
      if P^.sLinkTo <> nil then real_libc_free(P^.sLinkTo);
      real_libc_free(P);    //  Not needed - just zero-erase the memory
//          DebugMsg(['Checkpoint 13']);
      P := real_libc_malloc(SizeOf(TVFSItem));
      memset(P, 0, SizeOf(TVFSItem));
//      DebugMsg(['Item = ', Int64(P)]);
//          DebugMsg(['Checkpoint 14']);
//          DebugMsg(['FVFSListNext --begin']);
      Res := FSourcePlugin.FVFSListNext(FGlobs, PChar(APath), P);
//          DebugMsg(['FVFSListNext --end']);
//          Sleep(500);
    until (Res <> cVFS_OK) or (BreakProcessingKind = 2);
    if BreakProcessingKind <> 0 then DebugMsg(['^^VFS (WW): GetListing: stopped by BreakProcessing']);

    real_libc_free(P);
    FSourcePlugin.FVFSListClose(FGlobs);
    if Res <> cVFS_Not_More_Files then Result := Res;
  except
    on E: Exception do
      DebugMsg(['^^VFS (EE): GetListing: Exception: ', E.Message]);
  end;
  BreakProcessingKind := 0;
  DebugMsg(['^^VFS (II): GetListing end.']);
end;

function TVFSEngine.GetListing(var List: TList; const AddDotFiles: boolean): integer;
begin
  Result := GetListing(List, AddDotFiles, GetPath);
end;

function TVFSEngine.ExplicitChDir(const NewPath: string): integer;
begin
  Result := libc_chdir(PChar(NewPath));
  if Result <> 0 then Result := errno;
end;

function TVFSEngine.GetFileSystemSize: Int64;
begin
  Result := GetFileSystemSize(GetPath);
end;

function TVFSEngine.GetFileSystemSize(const APath: string): Int64;
begin
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSGetFileSystemSize <> nil)
    then Result := FSourcePlugin.FVFSGetFileSystemSize(FGlobs, PChar(APath))
    else Result := 0;
end;

function TVFSEngine.GetFileSystemFree: Int64;
begin
  Result := GetFileSystemFree(GetPath);
end;

function TVFSEngine.GetFileSystemFree(const APath: string): Int64;
begin
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSGetFileSystemFree <> nil)
    then Result := FSourcePlugin.FVFSGetFileSystemFree(FGlobs, PChar(APath))
    else Result := 0;
end;

procedure TVFSEngine.GetFileSystemInfo(const APath: string; var FSSize, FSFree: Int64; var FSName: string);
begin
  FSSize := GetFileSystemSize(APath);
  FSFree := GetFileSystemFree(APath);
  FSName := 'plugin';
end;

function TVFSEngine.IsOnROMedium(const FileName: string): boolean;
begin
  Result := True;
end;

function TVFSEngine.FileCanRun(const FileName: string): boolean;
var Item: PDataItemSL;
begin
  Item := GetFileInfoSL(FileName);
  Result := Assigned(Item) and Item^.IsExecutable;
  FreeDataItem(Item);
end;

function TVFSEngine.GetPath: string;
var s: PChar;
begin
  Result := '/';
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSGetPath <> nil) then begin
    s := FSourcePlugin.FVFSGetPath(FGlobs);
    if s <> nil then begin
      Result := String(s);
      real_libc_free(s);
    end;
  end;
end;

function TVFSEngine.GetPathURI: string;
var s: PChar;
begin
  Result := '';
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSGetPathURI <> nil) then begin
    s := FSourcePlugin.FVFSGetPathURI(FGlobs);
    if s <> nil then begin
      Result := String(s);
      real_libc_free(s);
    end;
  end;
end;

function TVFSEngine.ChangeDir(const NewPath: string): integer;
begin
  DebugMsg(['^^VFS (II): ChangeDir begin']);
  Result := 0;
  try
    Result := FSourcePlugin.FVFSChangeDir(FGlobs, PChar(NewPath));
//        Sleep(3000);
  except
    on E: Exception do DebugMsg(['^^VFS (EE): ChangeDir: Exception: ', E.Message]);
  end;
  DebugMsg(['^^VFS (II): ChangeDir end.']);
end;

function TVFSEngine.ChangeDirEx(const NewPath: string; AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): integer;
begin
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSChangeDir <> nil) then begin
    if @FSourcePlugin.FVFSSetCallbacks <> nil then
      FSourcePlugin.FVFSSetCallbacks(FGlobs, AskQuestionCallback, AskPasswordCallback, ProgressCallback, CallbackData);
    Result := ChangeDir(NewPath);
    if @FSourcePlugin.FVFSSetCallbacks <> nil then
      FSourcePlugin.FVFSSetCallbacks(FGlobs, nil, nil, nil, nil);
  end;
end;

procedure TVFSEngine.SetPath(Value: string);
begin
  ChangeDir(Value);
end;

function TVFSEngine.FileExists(const FileName: string; const Use_lstat: boolean = False): Boolean;
begin
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSFileExists <> nil)
    then Result := FSourcePlugin.FVFSFileExists(FGlobs, PChar(FileName), Use_lstat)
    else Result := False;
end;

function TVFSEngine.DirectoryExists(const FileName: string; const Use_lstat: boolean = False): Boolean;
var P: PVFSItem;
    Res: integer;
begin
  if @FSourcePlugin.FVFSFileExists <> nil then begin
    Result := FSourcePlugin.FVFSFileExists(FGlobs, PChar(FileName), Use_lstat);
    if Result and (@FSourcePlugin.FVFSFileInfo <> nil) then begin
      P := real_libc_malloc(SizeOf(TVFSItem));
      memset(P, 0, SizeOf(TVFSItem));
      Res := FSourcePlugin.FVFSFileInfo(FGlobs, PChar(FileName), P);
      if (Res <> cVFS_OK) or (P = nil) or (TVFSItemType(P^.ItemType) <> vDirectory) then Result := False;
      real_libc_free(P);
    end;
  end else Result := False;
end;

function TVFSEngine.GetFileInfoSL(APath: string): PDataItemSL;
var P: PVFSItem;
    Item: PDataItemSL;
    Res: integer;
begin
  Result := nil;
  if @FSourcePlugin.FVFSFileInfo = nil then Exit;
  P := real_libc_malloc(SizeOf(TVFSItem));
  memset(P, 0, SizeOf(TVFSItem));

  Res := FSourcePlugin.FVFSFileInfo(FGlobs, PChar(APath), P);
  if Res <> cVFS_OK then begin
    DebugMsg(['*** VFSFileInfo(', APath, ') failed. Code = ', Res]);
    Exit;
  end;

  try
    Item := malloc(SizeOf(TDataItemSL));
    memset(Item, 0, SizeOf(TDataItemSL));
    with Item^ do begin
{      FName := strdup(P^.FName);
      FDisplayName := StrToUTF8(P^.FName); }
      FName := strdup(PChar(APath));

      //* TODO
      FDisplayName := StrToUTF8(PChar(APath));
      if P^.sLinkTo <> nil then LnkPointTo := strdup(P^.sLinkTo)
                           else LnkPointTo := nil;
      ADestination := nil;
      Stage1 := True;
      Level := 0;
      IsDir := TVFSItemType(P^.ItemType) = vDirectory;
      IsLnk := TVFSItemType(P^.ItemType) = vSymlink;
      ForceMove := False;
{***} IsOnRO := True;
      IsExecutable := P^.iMode and S_IXUSR = S_IXUSR;
      Mode := P^.iMode;
      ModifyTime := P^.m_time;
      mtime := P^.m_time;
      atime := P^.a_time;
      UID := P^.iUID;
      GID := P^.iGID;
      Size := P^.iSize;
      PackedSize := P^.iPackedSize;
      if P^.FName <> nil then real_libc_free(P^.FName);
      if P^.FDisplayName <> nil then real_libc_free(P^.FDisplayName);
      if P^.sLinkTo <> nil then real_libc_free(P^.sLinkTo);
      real_libc_free(P);
   end;
   Result := Item;
  except
    on E: Exception do DebugMsg(['*** TVFSEngine.GetFileInfoSL(APath=', APath, ') -Exception: ', E.Message]);
  end;
end;

function TVFSEngine.MakeDir(const NewDir: string): integer;
begin
  if @FSourcePlugin.FVFSMkDir <> nil then Result := FSourcePlugin.FVFSMkDir(FGlobs, PChar(NewDir))
                                     else Result := cVFS_Failed;
end;

function TVFSEngine.Remove(APath: string): integer;
begin
  if @FSourcePlugin.FVFSRemove <> nil then Result := FSourcePlugin.FVFSRemove(FGlobs, PChar(APath))
                                      else Result := cVFS_Failed;
end;

function TVFSEngine.RenameFile(SourceFile, DestFile: string): integer;
begin
  if @FSourcePlugin.FVFSRename <> nil then Result := FSourcePlugin.FVFSRename(FGlobs, PChar(SourceFile), PChar(DestFile))
                                      else Result := cVFS_Failed;
end;

procedure TVFSEngine.FillDirFiles(APath: string; List: TList; ALevel: word);
var Item: PDataItemSL;
    i, Res: integer;
    FilesList: TList;
    LocalList: TStringList;
    P: PVFSItem;


  procedure AddEntry(FPath: string; AddCurrDirStage, AStage1: boolean);
  begin
    Item := malloc(SizeOf(TDataItemSL));
    memset(Item, 0, SizeOf(TDataItemSL));
    with Item^ do begin
//      AName := malloc(Length(FPath) + 1);
//      memset(AName, 0, Length(FPath) + 1);
      FName := strdup(PChar(FPath));

      //* TODO
      FDisplayName := StrToUTF8(PChar(FPath));
      if P^.sLinkTo <> nil then LnkPointTo := strdup(P^.sLinkTo)
                           else LnkPointTo := nil;
      ADestination := nil;
      Stage1 := AStage1;
      IsDir := TVFSItemType(P^.ItemType) = vDirectory;
      IsLnk := TVFSItemType(P^.ItemType) = vSymlink;
      if IsLnk and AddCurrDirStage then DebugMsg(['*** Assertion failed AddEntry: Item^.IsLnk = True']);
      ForceMove := False;
{***} IsOnRO := True;
      IsExecutable := AddCurrDirStage or (P^.iMode and S_IXUSR = S_IXUSR);
      Mode := P^.iMode;
      ModifyTime := P^.m_time;
      mtime := P^.m_time;
      atime := P^.a_time;
      UID := P^.iUID;
      GID := P^.iGID;
      Size := P^.iSize;
      PackedSize := P^.iPackedSize;
      Level := ALevel + Ord(not AddCurrDirStage);
    end;
    if AddCurrDirStage then List.Add(Item)
                       else FilesList.Add(Item);       
  end;

begin
  if not Assigned(List) then Exit;
  FilesList := TList.Create;
  LocalList := TStringList.Create;
  try
  try
    P := real_libc_malloc(SizeOf(TVFSItem));
    memset(P, 0, SizeOf(TVFSItem));
    Res := FSourcePlugin.FVFSFileInfo(FGlobs, PChar(APath), P);
    if Res <> cVFS_OK then DebugMsg(['*** FillDirFiles - VFSFileInfo(', APath, ') failed. Code = ', Res]);
    AddEntry(APath, True, True);
    if P^.FName <> nil then real_libc_free(P^.FName);
    if P^.FDisplayName <> nil then real_libc_free(P^.FDisplayName);
    if P^.sLinkTo <> nil then real_libc_free(P^.sLinkTo);
    real_libc_free(P);

    APath := IncludeTrailingPathDelimiter(APath);
    if @FSourcePlugin.FVFSChangeDir <> nil then Res := FSourcePlugin.FVFSChangeDir(FGlobs, PChar(APath))
                                           else Exit;
    if Res <> 0 then Exit;

    if @FSourcePlugin.FVFSListFirst = nil then Exit;
    P := real_libc_malloc(SizeOf(TVFSItem));
    memset(P, 0, SizeOf(TVFSItem));
    Res := FSourcePlugin.FVFSListFirst(FGlobs, PChar(APath), P);
    if Res <> cVFS_OK then begin
      FSourcePlugin.FVFSListClose(FGlobs);
      if P^.FName <> nil then real_libc_free(P^.FName);
      if P^.FDisplayName <> nil then real_libc_free(P^.FDisplayName);
      if P^.sLinkTo <> nil then real_libc_free(P^.sLinkTo);
      real_libc_free(P);
      Exit;
    end;

    repeat
      if TVFSItemType(P^.ItemType) = vDirectory
        then LocalList.Add(APath + String(P^.FName))
        else AddEntry(APath + String(P^.FName), False, True);
      if P^.FName <> nil then real_libc_free(P^.FName);
      if P^.FDisplayName <> nil then real_libc_free(P^.FDisplayName);
      if P^.sLinkTo <> nil then real_libc_free(P^.sLinkTo);
      real_libc_free(P);
      P := real_libc_malloc(SizeOf(TVFSItem));
      memset(P, 0, SizeOf(TVFSItem));
      Res := FSourcePlugin.FVFSListNext(FGlobs, PChar(GetPath), P);
    until (Res <> cVFS_OK);

    if P^.FName <> nil then real_libc_free(P^.FName);
    if P^.FDisplayName <> nil then real_libc_free(P^.FDisplayName);
    if P^.sLinkTo <> nil then real_libc_free(P^.sLinkTo);
    real_libc_free(P);
    FSourcePlugin.FVFSListClose(FGlobs);

    if LocalList.Count > 0 then
      for i := 0 to LocalList.Count - 1 do
        FillDirFiles(LocalList[i], List, ALevel + 1);

    if FilesList.Count > 0 then
      for i := 0 to FilesList.Count - 1 do
        List.Add(FilesList[i]);
  except
    on E: Exception do DebugMsg(['*** TVFSEngine.FillDirFiles(APath=', APath, ', Level=', ALevel, ') -Exception: ', E.Message]);
  end;
  finally
    P := real_libc_malloc(SizeOf(TVFSItem));
    memset(P, 0, SizeOf(TVFSItem));
    Res := FSourcePlugin.FVFSFileInfo(FGlobs, PChar(APath), P);
    if Res <> cVFS_OK then DebugMsg(['*** FillDirFiles - VFSFileInfo(', APath, ') failed. Code = ', Res]);
    AddEntry(APath, True, False);
    if P^.FName <> nil then real_libc_free(P^.FName);
    if P^.FDisplayName <> nil then real_libc_free(P^.FDisplayName);
    if P^.sLinkTo <> nil then real_libc_free(P^.sLinkTo);
    real_libc_free(P);

    LocalList.Free;
    FilesList.Free;
  end;
end;

function TVFSEngine.MakeSymLink(const NewFileName, PointTo: string): integer;
begin
  if @FSourcePlugin.FVFSMakeSymLink <> nil then Result := FSourcePlugin.FVFSMakeSymLink(FGlobs, PChar(NewFileName), PChar(PointTo))
                                           else Result := cVFS_Failed;
end;

function TVFSEngine.Chmod(const FileName: string; const Mode: integer): integer;
begin
  if @FSourcePlugin.FVFSChmod <> nil then Result := FSourcePlugin.FVFSChmod(FGlobs, PChar(FileName), Mode)
                                     else Result := cVFS_Failed;
end;

function TVFSEngine.Chown(const FileName: string; const UID, GID: integer): integer;
begin
  if @FSourcePlugin.FVFSChown <> nil then Result := FSourcePlugin.FVFSChown(FGlobs, PChar(FileName), UID, GID)
                                     else Result := cVFS_Failed;
end;

function TVFSEngine.ChangeTimes(APath: string; mtime, atime: Int64): integer;
begin
  if @FSourcePlugin.FVFSChangeTimes <> nil then Result := FSourcePlugin.FVFSChangeTimes(FGlobs, PChar(APath), mtime, atime)
                                           else Result := cVFS_Failed;
end;

function TVFSEngine.GetDirSize(APath: string): Int64;
begin
  if @FSourcePlugin.FVFSGetDirSize <> nil then Result := FSourcePlugin.FVFSGetDirSize(FGlobs, PChar(APath))
                                          else Result := 0;
end;

procedure TVFSEngine.BreakProcessing(ProcessingKind: integer);
begin
  case ProcessingKind of
    1: if @FSourcePlugin.FVFSBreakGetDirSize <> nil then FSourcePlugin.FVFSBreakGetDirSize(FGlobs);
  end;
end;

function TVFSEngine.GetBlockSize: Cardinal;
begin
  Result := FBlockSize;
end;

procedure TVFSEngine.SetBlockSize(Value: Cardinal);
begin
  if (FGlobs <> nil) and (@FSourcePlugin.FVFSSetBlockSize <> nil)
    then FSourcePlugin.FVFSSetBlockSize(FGlobs, Value);
end;



(********************************************************************************************************************************)
function TVFSEngine.IsOnSameFS(const Path1, Path2: string): boolean;
begin
  if @FSourcePlugin.FVFSIsOnSameFS <> nil then Result := FSourcePlugin.FVFSIsOnSameFS(FGlobs, PChar(Path1), PChar(Path2))
                                          else Result := True;
end;

function TVFSEngine.OpenFile(const APath: string; Mode: integer; var Error: integer): TEngineFileDes;
var i: integer;
begin
  if @FSourcePlugin.FVFSOpenFile <> nil then begin
    Result := FSourcePlugin.FVFSOpenFile(FGlobs, PChar(APath), Mode, @i);
    Error := i;
  end else Result := nil;
end;

function TVFSEngine.ReadFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; ABlockSize: integer; var Error: integer): integer;
var i: integer;
begin
  if @FSourcePlugin.FVFSReadFile <> nil then begin
    Result := FSourcePlugin.FVFSReadFile(FGlobs, FileDescriptor, Buffer, ABlockSize, @i);
    Error := i;
  end else Result := -1;
end;

function TVFSEngine.WriteFile(const FileDescriptor: TEngineFileDes; Buffer: Pointer; BytesCount: integer; var Error: integer): integer;
var i: integer;
begin
  if @FSourcePlugin.FVFSWriteFile <> nil then begin
    Result := FSourcePlugin.FVFSWriteFile(FGlobs, FileDescriptor, Buffer, BytesCount, @i);
    Error := i;
  end else Result := -1;
end;

function TVFSEngine.CloseFile(const FileDescriptor: TEngineFileDes): integer;
begin
  if @FSourcePlugin.FVFSCloseFile <> nil then Result := FSourcePlugin.FVFSCloseFile(FGlobs, FileDescriptor)
                                         else Result := 0;
end;

function TVFSEngine.FileSeek(const FileDescriptor: TEngineFileDes; const AbsoluteOffset: Int64; var Error: integer): Int64;
var i: integer;
begin
  if @FSourcePlugin.FVFSFileSeek <> nil then begin
    Result := FSourcePlugin.FVFSFileSeek(FGlobs, FileDescriptor, AbsoluteOffset, @i);
    Error := i;
  end else Result := -1;
end;

function TVFSEngine.TwoSameFiles(const Path1, Path2: string): boolean;
begin
  if @FSourcePlugin.FVFSTwoSameFiles <> nil then Result := FSourcePlugin.FVFSTwoSameFiles(FGlobs, PChar(Path1), PChar(Path2))
                                            else Result := False;
end;



(********************************************************************************************************************************)
function TVFSEngine.CopyFileIn(Sender: Pointer; SourceFile, DestFile: string; ProgressFunc: TEngineProgressFunc; ErrorFunc: TEngineErrorFunc; Append: boolean): boolean;
begin
  Result := CopyFileInEx(Sender, SourceFile, DestFile, ErrorFunc, Append, nil, nil, nil, nil);
end;

function TVFSEngine.CopyFileOut(Sender: Pointer; SourceFile, DestFile: string; ProgressFunc: TEngineProgressFunc; ErrorFunc: TEngineErrorFunc; Append: boolean): boolean;
begin
  Result := CopyFileInEx(Sender, SourceFile, DestFile, ErrorFunc, Append, nil, nil, nil, nil);
end;

function TVFSEngine.CopyFileOutEx(Sender: Pointer; SourceFile, DestFile: string; ErrorFunc: TEngineErrorFunc; Append: boolean;
                                  AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): boolean;
var Res: TVFSResult;
begin
  Result := False;
  try
    if @FSourcePlugin.FVFSCopyToLocal <> nil then begin
//      DebugMsg(['0 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$']);
//      DebugMsg([' Pointer(FGlobs) = 0x', IntToHex(Int64(FGlobs), 16), ', Pointer(@NewVFSCopyCallBackFunc) = 0x', IntToHex(Int64(@NewVFSCopyCallBackFunc), 16), ', Pointer(Self) = 0x', IntToHex(Int64(Self), 16)]);
      try
        if @FSourcePlugin.FVFSSetCallbacks <> nil then
          FSourcePlugin.FVFSSetCallbacks(FGlobs, AskQuestionCallback, AskPasswordCallback, ProgressCallback, CallbackData);
        Res := FSourcePlugin.FVFSCopyToLocal(FGlobs, PChar(SourceFile), PChar(DestFile), Append);
        if @FSourcePlugin.FVFSSetCallbacks <> nil then
          FSourcePlugin.FVFSSetCallbacks(FGlobs, nil, nil, nil, nil);
      except
        on E: Exception do begin
          DebugMsg(['*** Exception raised in TVFSEngine.CopyFileOut(Sender=', QWord(Sender), ', SourceFile=', SourceFile, ', DestFile=', DestFile, '):  (', E.ClassName, '): ', E.Message]);
          Res := cVFS_WriteErr;
        end;
      end;
//      DebugMsg(['1 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$']);
      Result := Res = cVFS_OK;
      if (Res <> cVFS_OK) and Assigned(ErrorFunc) then
        case Res of
          cVFS_ReadErr: Result := ErrorFunc(Sender, 6, 0, SourceFile);
          cVFS_WriteErr: Result := ErrorFunc(Sender, 7, 0, DestFile);
          cVFS_mallocFailed: ErrorFunc(Sender, 1, 0, SourceFile);
          cVFS_Cancelled: ErrorFunc(Sender, 0, 0, SourceFile);
         end;
    end else Result := False;
  except
    on E: Exception do DebugMsg(['*** Exception raised in TVFSEngine.CopyFileOut(Sender=', QWord(Sender), ', SourceFile=', SourceFile, ', DestFile=', DestFile, '):  (', E.ClassName, '): ', E.Message]);
  end;
end;

function TVFSEngine.CopyFileInEx(Sender: Pointer; SourceFile, DestFile: string; ErrorFunc: TEngineErrorFunc;
                                 Append: boolean; AskQuestionCallback: PVFSAskQuestionCallback; AskPasswordCallback: PVFSAskPasswordCallback; ProgressCallback: PVFSProgressCallback; CallbackData: Pointer): boolean;
var Res: TVFSResult;
begin
  Result := False;
  try
    if @FSourcePlugin.FVFSCopyFromLocal <> nil then begin
      try
        if @FSourcePlugin.FVFSSetCallbacks <> nil then
          FSourcePlugin.FVFSSetCallbacks(FGlobs, AskQuestionCallback, AskPasswordCallback, ProgressCallback, CallbackData);
        Res := FSourcePlugin.FVFSCopyFromLocal(FGlobs, PChar(SourceFile), PChar(DestFile), Append);
        if @FSourcePlugin.FVFSSetCallbacks <> nil then
          FSourcePlugin.FVFSSetCallbacks(FGlobs, nil, nil, nil, nil);
      except
        on E: Exception do begin
          DebugMsg(['*** Exception raised in TVFSEngine.CopyFileIn(Sender=', QWord(Sender), ', SourceFile=', SourceFile, ', DestFile=', DestFile, '):  (', E.ClassName, '): ', E.Message]);
          Res := cVFS_WriteErr;
        end;
      end;
      Result := Res = cVFS_OK;
      if (Res <> cVFS_OK) and Assigned(ErrorFunc) then
        case Res of
          cVFS_ReadErr: Result := ErrorFunc(Sender, 6, 0, SourceFile);
          cVFS_WriteErr: Result := ErrorFunc(Sender, 7, 0, DestFile);
          cVFS_mallocFailed: ErrorFunc(Sender, 1, 0, SourceFile);
          cVFS_Cancelled: ErrorFunc(Sender, 0, 0, SourceFile);
        end;
    end else Result := False;
  except
    on E: Exception do DebugMsg(['*** Exception raised in TVFSEngine.CopyFileIn(Sender=', QWord(Sender), ', SourceFile=', SourceFile, ', DestFile=', DestFile, '):  (', E.ClassName, '): ', E.Message]);
  end;
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
function TVFSEngine.GetPasswordRequired: boolean;
begin
  if @FSourcePlugin.FVFSGetPasswordRequired <> nil then Result := FSourcePlugin.FVFSGetPasswordRequired(FGlobs)
                                                   else Result := False;
end;

procedure TVFSEngine.ResetPassword;
begin
  if @FSourcePlugin.FVFSResetPassword <> nil then
    FSourcePlugin.FVFSResetPassword(FGlobs);
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
function TVFSEngine.GetPluginID: string;
begin
  if FSourcePlugin <> nil then Result := FSourcePlugin.ModuleID
                          else Result := '';
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
function GetBinaryPath: string;
var i: integer;
    Buf: array[0..1024] of char;
begin
  Result := ParamStr(0);   //  Fallback
  try
    DebugMsg(['Trying to determine binary path...']);
    DebugMsg(['Fallback path = ', Result]);
    Buf[0] := #0;
    FillChar(Buf, 1025, 0);
    i := readlink(PChar(Format('/proc/%d/exe', [getpid])), @Buf, 1024);
    if i < 1 then begin
      DebugMsg(['Something went wrong during readlink: ', string(strerror(errno))]);
      Exit;
    end;
    Buf[i] := #0;
    Result := string(PChar(@Buf));
    DebugMsg(['Path to executable = ', Result]);
  except
    on E: Exception do DebugMsg(['*** Exception raised in GetBinaryPath: (', E.ClassName, '): ', E.Message]);
  end;
end;


procedure QueryModules(APath: string);
var Handle: PDIR;
    DirEnt: PDirent64;
    s: string;
    PluginItem: TVFSPlugin;
    ModuleHandler: Pointer;
    VFSVersionFunc: TVFSVersion;
    i: integer;
    Buf: PChar;
    b: boolean;
begin
  if Length(APath) < 1 then Exit;
  try
  try
    APath := ReplaceStr(APath, '~', GetHomePath);
    DebugMsg(['------------------ Querying modules in ', APath, ' ---']);

    //  First change to the program starting directory
    if APath[1] <> '/' then begin
      s := ExtractFilePath(BinaryPath);
      DebugMsg(['----> Changing dir to ', s]);
      i := libc_chdir(PChar(s));
      if i <> 0 then DebugMsg(['Something went wrong during chdir: ', string(strerror(errno))]);
      s := get_current_dir_name;
      DebugMsg(['   [II] Changed dir to ', s]);
    end;

    //  Change to the required directory
    s := APath;
    DebugMsg(['----> Changing dir to ', s]);
    i := libc_chdir(PChar(s));
    if i <> 0 then begin
      DebugMsg(['Something went wrong during chdir: ', string(strerror(errno))]);
      Exit;
    end;
    s := get_current_dir_name;
    DebugMsg(['   [II] Changed dir to ', s, ', trying to list the files...']);

    //  Going to read the directory and search for the files...
    Handle := opendir(PChar(s));
    if not Assigned(Handle) then begin
      DebugMsg(['Something went wrong during opendir: ', string(strerror(errno))]);
      Exit;
    end;
    repeat
      DirEnt := readdir64(Handle);
      if Assigned(DirEnt) then begin
        Buf := PChar(@DirEnt^.d_name[0]);
        if Assigned(Buf) and (strcmp(Buf, '.') <> 0) and (strcmp(Buf, '..') <> 0) then
        begin
          DebugMsg(['$$$ Found ', Buf, ', trying to load...']);

          //  Try to find the plugin in the plugin list to prevent multiple plugin loading
          b := False;
          if PluginList.Count > 0 then
            for i := 0 to PluginList.Count - 1 do
              if CompareStr(TVFSPlugin(PluginList[i]).FullModulePath, IncludeTrailingPathDelimiter(s) + Buf) = 0 then begin
                b :=  True;
                Break;
              end;

          if b then DebugMsg(['Module ', s, ' is already loaded --> skipping...']) else begin
            ModuleHandler := dlopen(PChar(IncludeTrailingPathDelimiter(s) + Buf), RTLD_NOW);
            if ModuleHandler = nil then DebugMsg([' XXX Error loading module: ', dlerror])
            else try
                 @VFSVersionFunc := dlsym(ModuleHandler, 'VFSVersion');
                 if (@VFSVersionFunc <> nil) and (VFSVersionFunc = ConstVFSVersionRequired) then begin
                   PluginItem := TVFSPlugin.Create(ModuleHandler);
                   PluginItem.FullModulePath := IncludeTrailingPathDelimiter(s) + Buf;
                   PluginList.Add(PluginItem);
                 end else DebugMsg(['  $XXX: Error getting version info or version mismatch']);
               except end;
          end;
        end;
      end;  
    until DirEnt = nil;
    closedir(Handle);
  except
    on E: Exception do DebugMsg(['*** Exception raised in QueryModules(APath = ', APath, '): (', E.ClassName, '): ', E.Message]);
  end;
  finally
    DebugMsg(['------------------ Done querying modules ---------------']);
  end;
end;


procedure UnloadModules;
var i: integer;
begin
  if Assigned(PluginList) and (PluginList.Count > 0) then
    for i := PluginList.Count - 1 downto 0 do
      try
        dlclose(TVFSPlugin(PluginList[i]).ModuleHandle);
        TVFSPlugin(PluginList[i]).Free;
        PluginList.Delete(i);
      except
        on E: Exception do DebugMsg(['*** Exception raised in UnloadModules(i = ', i, '): (', E.ClassName, '): ', E.Message]);	
      end;
end;


procedure DoInitPlugins;
begin
  PluginList := TList.Create;
  if not ParamDisablePlugins then begin
    BinaryPath := GetBinaryPath;
    QueryModules(ConstGlobalModulePath1);
    QueryModules(ConstGlobalModulePath2);
    QueryModules(ConstLocalModulePath1);
    QueryModules(ConstLocalModulePath2);
    QueryModules(ConstLocalModulePath3);
{$IFDEF CPU64}
    QueryModules(ConstGlobalModulePath3);
    QueryModules(ConstGlobalModulePath4);
    QueryModules(ConstLocalModulePath4);
{$ENDIF}
  end;
end;

initialization
finalization
  UnloadModules;
  PluginList.Free;
end.
