{
   Double Commander
   -------------------------------------------------------------------------
   Virtual File System - class for manage WFX plugins (Version 1.3)
 
   Copyright (C) 2007-2013  Koblov Alexander (Alexx2000@mail.ru)
 
   Callback functions based on:
     Total Commander filesystem plugins debugger
     Author: Pavel Dubrovsky
     
   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 uWFXmodule;

{$mode objfpc}{$H+}

interface

uses
  SysUtils, Classes, WfxPlugin, uWFXprototypes,
  dynlibs, DCClassesUtf8, Extension, DCBasicTypes, DCXmlConfig;

const
  WFX_SUCCESS      =  0;
  WFX_NOTSUPPORTED = -10;
  WFX_ERROR        = -20;

type

  { TWfxFindData }

  TWfxFindData = record
    FileAttributes : TFileAttrs;
    CreationTime,
    LastAccessTime,
    LastWriteTime : TDateTime;
    FileSize : Int64;
    Reserved0,
    Reserved1 : LongWord;
    FileName : String;
    AlternateFileName : String;
    case Boolean of
    True:  ( FindDataA: TWin32FindData;  );
    False: ( FindDataW: TWin32FindDataW; );
  end;

  { TWFXModule }

  TWFXModule = class
  private
    FModuleHandle: TLibHandle;  // Handle to .DLL or .so
    FModuleFileName: String;
    FBackgroundFlags: Integer;
  public
  { Mandatory }
    FsInit : TFsInit;
    FsFindFirst : TFsFindFirst;
    FsFindNext : TFsFindNext;
    FsFindClose : TFsFindClose;
  { Optional }
    FsSetCryptCallback: TFsSetCryptCallback;
    FsGetDefRootName : TFsGetDefRootName;
    FsGetFile : TFsGetFile;
    FsPutFile : TFsPutFile;
    FsDeleteFile : TFsDeleteFile;
    FsRemoveDir : TFsRemoveDir;
    FsExecuteFile : TFsExecuteFile;
    FsMkDir : TFsMkDir;
    FsStatusInfo : TFsStatusInfo;
    FsSetDefaultParams : TFsSetDefaultParams;
    FsContentPluginUnloading : TFsContentPluginUnloading;
    //---------------------
    FsSetAttr:TFsSetAttr;
    FsSetTime:TFsSetTime;
    FsExtractCustomIcon:TFsExtractCustomIcon;
    FsRenMovFile:TFsRenMovFile;
    FsDisconnect:TFsDisconnect;
    FsGetPreviewBitmap:TFsGetPreviewBitmap;
    FsLinksToLocalFiles:TFsLinksToLocalFiles;
    FsGetLocalName:TFsGetLocalName;
    //---------------------
    FsGetBackgroundFlags: TFsGetBackgroundFlags;
    //---------------------
    FsContentGetDetectString:TFsContentGetDetectString;
    FsContentGetSupportedField:TFsContentGetSupportedField;
    FsContentGetValue:TFsContentGetValue;
    FsContentSetDefaultParams:TFsContentSetDefaultParams;
    FsContentStopGetValue:TFsContentStopGetValue;
    FsContentGetDefaultSortOrder:TFsContentGetDefaultSortOrder;
    FsContentGetSupportedFieldFlags:TFsContentGetSupportedFieldFlags;
    FsContentSetValue:TFsContentSetValue;
    FsContentGetDefaultView:TFsContentGetDefaultView;
    { Unicode }
    FsInitW: TFsInitW;
    FsFindFirstW: TFsFindFirstW;
    FsFindNextW: TFsFindNextW;
    //---------------------
    FsSetCryptCallbackW: TFsSetCryptCallbackW;
    FsMkDirW: TFsMkDirW;
    FsExecuteFileW: TFsExecuteFileW;
    FsRenMovFileW: TFsRenMovFileW;
    FsGetFileW: TFsGetFileW;
    FsPutFileW: TFsPutFileW;
    FsDeleteFileW: TFsDeleteFileW;
    FsRemoveDirW: TFsRemoveDirW;
    FsDisconnectW: TFsDisconnectW;
    FsSetAttrW: TFsSetAttrW;
    FsSetTimeW: TFsSetTimeW;
    FsStatusInfoW: TFsStatusInfoW;
    FsExtractCustomIconW: TFsExtractCustomIconW;
    FsGetPreviewBitmapW: TFsGetPreviewBitmapW;
    FsGetLocalNameW: TFsGetLocalNameW;
    //-----------------------
    FsContentGetValueW: TFsContentGetValueW;
    FsContentStopGetValueW: TFsContentStopGetValueW;
    FsContentSetValueW: TFsContentSetValueW;
    FsContentGetDefaultViewW: TFsContentGetDefaultViewW;
    { Extension API }
    ExtensionInitialize: TExtensionInitializeProc;
    ExtensionFinalize:   TExtensionFinalizeProc;
  public
    function WfxFindFirst(Path: String; var FindData: TWfxFindData): THandle;
    function WfxFindNext(Hdl: THandle; var FindData: TWfxFindData): Boolean;
    procedure WfxStatusInfo(RemoteDir: String; InfoStartEnd, InfoOperation: Integer);
    function WfxExecuteFile(MainWin: HWND; var RemoteName: String; Verb: String): Integer;
    function WfxRenMovFile(OldName, NewName: String; Move, OverWrite: Boolean; RemoteInfo: PRemoteInfo): Integer;
    function WfxGetFile(RemoteName, LocalName: String; CopyFlags: Integer; RemoteInfo: PRemoteInfo): Integer;
    function WfxPutFile(LocalName, RemoteName: String; CopyFlags: Integer): Integer;
    function WfxSetAttr(RemoteName: String; NewAttr: LongInt): Boolean;
    {en
       Each of CreationTime, LastAccessTime, LastWriteTime may be @nil to leave the value unchanged.
    }
    function WfxSetTime(RemoteName: String; pCreationTime, pLastAccessTime, pLastWriteTime: PWfxFileTime): Boolean;
    function WfxMkDir(const sBasePath, sDirName: String): LongInt;
    function WfxRemoveDir(const sDirName: String): Boolean;
    function WfxDeleteFile(const sFileName: String): Boolean;
    function WfxGetLocalName(var sFileName: String): Boolean;
    function WfxDisconnect(const DisconnectRoot: String): Boolean;
  public
    constructor Create;
    destructor Destroy; override;
    function LoadModule(const sName: String):Boolean; {Load plugin}
    procedure UnloadModule;
    procedure VFSInit(Data: PtrInt);

    function VFSConfigure(Parent: THandle):Boolean;
    function VFSRootName: String;

    function IsLoaded: Boolean;

    property BackgroundFlags: Integer read FBackgroundFlags write FBackgroundFlags;
  end;

  { TWFXModuleList }

  TWFXModuleList = class(TStringList)
  private
    function GetAEnabled(Index: Integer): Boolean;
    function GetAFileName(Index: Integer): String;
    function GetAName(Index: Integer): String;
    procedure SetAEnabled(Index: Integer; const AValue: Boolean);
    procedure SetAFileName(Index: Integer; const AValue: String);
    procedure SetAName(Index: Integer; const AValue: String);
  public
    procedure Load(Ini: TIniFileEx); overload;
    procedure Load(AConfig: TXmlConfig; ANode: TXmlNode); overload;
    procedure Save(Ini: TIniFileEx); overload;
    procedure Save(AConfig: TXmlConfig; ANode: TXmlNode); overload;
    function Add(Ext: String; FileName: String): Integer; reintroduce;
    function FindFirstEnabledByName(Name: String): Integer;

    property Name[Index: Integer]: String read GetAName write SetAName;
    property FileName[Index: Integer]: String read GetAFileName write SetAFileName;
    property Enabled[Index: Integer]: Boolean read GetAEnabled write SetAEnabled;
  end;

  function GetErrorMsg(iErrorMsg: LongInt): String;

implementation

uses
  LazUTF8, uLng, FileUtil, uGlobsPaths, uOSUtils, uWfxPluginUtil,
  fDialogBox, uDebug, DCOSUtils, DCStrUtils, DCConvertEncoding;

const
  WfxIniFileName = 'wfx.ini';

function GetErrorMsg(iErrorMsg: LongInt): String;
begin
  case iErrorMsg of
  WFX_ERROR:
    Result:= 'Unknown error!';
  WFX_NOTSUPPORTED,
  FS_FILE_NOTSUPPORTED:
    Result:= rsMsgErrNotSupported;
  FS_FILE_NOTFOUND:
    Result:= 'File not found!';
  FS_FILE_READERROR:
    Result:= rsMsgErrERead;
  FS_FILE_WRITEERROR:
    Result:= rsMsgErrEWrite;
  FS_FILE_USERABORT:
    Result:= rsMsgErrEAborted;
  end;
end;

{ TWfxFindData }

procedure ConvertFindData(var FindData: TWfxFindData; AnsiData: Boolean);
begin
  with FindData do
  begin
    // Convert file attributes
    FileAttributes:= FindDataW.dwFileAttributes;
    CreationTime:= WfxFileTimeToDateTime(FindDataW.ftCreationTime);
    LastAccessTime:= WfxFileTimeToDateTime(FindDataW.ftLastAccessTime);
    LastWriteTime:= WfxFileTimeToDateTime(FindDataW.ftLastWriteTime);
    Int64Rec(FileSize).Lo:= FindDataW.nFileSizeLow;
    Int64Rec(FileSize).Hi:= FindDataW.nFileSizeHigh;
    Reserved0:= FindDataW.dwReserved0;
    Reserved1:= FindDataW.dwReserved1;
    // Convert file name
    if AnsiData then
      begin
        FileName:= CeSysToUtf8(FindDataA.cFileName);
        AlternateFileName:= CeSysToUtf8(FindDataA.cAlternateFileName);
      end
    else
      begin
        FileName:= UTF16ToUTF8(UnicodeString(FindDataW.cFileName));
        AlternateFileName:= UTF16ToUTF8(UnicodeString(FindDataW.cAlternateFileName));
      end;
  end;
end;

{ TWFXModule }

function TWFXModule.WfxFindFirst(Path: String; var FindData: TWfxFindData): THandle;
begin
  try
    if Assigned(FsFindFirstW) then
      begin
        Result:= FsFindFirstW(PWideChar(UTF8Decode(Path)), FindData.FindDataW);
        if Result <> wfxInvalidHandle then ConvertFindData(FindData, False);
      end
    else if Assigned(FsFindFirst) then
      begin
        Result:= FsFindFirst(PAnsiChar(CeUtf8ToSys(Path)), FindData.FindDataA);
        if Result <> wfxInvalidHandle then ConvertFindData(FindData, True);
      end;
  except
    on E: Exception do
    begin
      Result:= wfxInvalidHandle;
      DCDebug(ClassName + '.WfxFindFirst(). Error: ' + E.Message);
    end;
  end;
end;

function TWFXModule.WfxFindNext(Hdl: THandle; var FindData: TWfxFindData): Boolean;
begin
  if Assigned(FsFindFirstW) then
    begin
      Result:= FsFindNextW(Hdl, FindData.FindDataW);
      if Result then ConvertFindData(FindData, False);
    end
  else if Assigned(FsFindFirst) then
    begin
      Result:= FsFindNext(Hdl, FindData.FindDataA);
      if Result then ConvertFindData(FindData, True);
    end
  else Result:= False;
end;

procedure TWFXModule.WfxStatusInfo(RemoteDir: String; InfoStartEnd,
  InfoOperation: Integer);
begin
  if Assigned(FsStatusInfoW) then
    FsStatusInfoW(PWideChar(UTF8Decode(RemoteDir)), InfoStartEnd, InfoOperation)
  else if Assigned(FsStatusInfo) then
    FsStatusInfo(PAnsiChar(CeUtf8ToSys(RemoteDir)), InfoStartEnd, InfoOperation);
end;

function TWFXModule.WfxExecuteFile(MainWin: HWND; var RemoteName: String; Verb: String): Integer;
var
  pacRemoteName: PAnsiChar;
  pwcRemoteName: PWideChar;
begin
  Result:= WFX_NOTSUPPORTED;
  if Assigned(FsExecuteFileW) then
    begin
      pwcRemoteName:= GetMem(MAX_PATH * SizeOf(WideChar));
      StrPCopyW(pwcRemoteName, UTF8Decode(RemoteName));
      Result:= FsExecuteFileW(MainWin, pwcRemoteName, PWideChar(UTF8Decode(Verb)));
      if Result = FS_EXEC_SYMLINK then
          RemoteName:= UTF16ToUTF8(UnicodeString(pwcRemoteName));
      FreeMem(pwcRemoteName);
    end
  else if Assigned(FsExecuteFile) then
    begin
      pacRemoteName:= GetMem(MAX_PATH);
      StrPCopy(pacRemoteName, CeUtf8ToSys(RemoteName));
      Result:= FsExecuteFile(MainWin, pacRemoteName, PAnsiChar(CeUtf8ToSys(Verb)));
      if Result = FS_EXEC_SYMLINK then
          RemoteName:= CeSysToUtf8(StrPas(pacRemoteName));
      FreeMem(pacRemoteName);
    end;
end;

function TWFXModule.WfxRenMovFile(OldName, NewName: String; Move,
                                  OverWrite: Boolean; RemoteInfo: PRemoteInfo): Integer;
begin
  Result:= FS_FILE_NOTSUPPORTED;
  if Assigned(FsRenMovFileW) then
    Result:= FsRenMovFileW(PWideChar(UTF8Decode(OldName)), PWideChar(UTF8Decode(NewName)), Move, OverWrite, RemoteInfo)
  else if Assigned(FsRenMovFile) then
    Result:= FsRenMovFile(PAnsiChar(CeUtf8ToSys(OldName)), PAnsiChar(CeUtf8ToSys(NewName)), Move, OverWrite, RemoteInfo);
end;

function TWFXModule.WfxGetFile(RemoteName, LocalName: String;
                               CopyFlags: Integer; RemoteInfo: PRemoteInfo): Integer;
begin
  Result:= FS_FILE_NOTSUPPORTED;
  if Assigned(FsGetFileW) then
    Result:= FsGetFileW(PWideChar(UTF8Decode(RemoteName)), PWideChar(UTF8Decode(LocalName)), CopyFlags, RemoteInfo)
  else if Assigned(FsGetFile) then
    Result:= FsGetFile(PAnsiChar(CeUtf8ToSys(RemoteName)), PAnsiChar(CeUtf8ToSys(LocalName)), CopyFlags, RemoteInfo);
end;

function TWFXModule.WfxPutFile(LocalName, RemoteName: String; CopyFlags: Integer): Integer;
begin
  Result:= FS_FILE_NOTSUPPORTED;
  if Assigned(FsPutFileW) then
    Result:= FsPutFileW(PWideChar(UTF8Decode(LocalName)), PWideChar(UTF8Decode(RemoteName)), CopyFlags)
  else if Assigned(FsPutFile) then
    Result:= FsPutFile(PAnsiChar(CeUtf8ToSys(LocalName)), PAnsiChar(CeUtf8ToSys(RemoteName)), CopyFlags);
end;

function TWFXModule.WfxSetAttr(RemoteName: String; NewAttr: LongInt): Boolean;
begin
  Result:= False;
  if Assigned(FsSetAttrW) then
    Result:= FsSetAttrW(PWideChar(UTF8Decode(RemoteName)), NewAttr)
  else if Assigned(FsSetAttr) then
    Result:= FsSetAttr(PAnsiChar(CeUtf8ToSys(RemoteName)), NewAttr);
end;

function TWFXModule.WfxSetTime(RemoteName: String; pCreationTime,
                               pLastAccessTime, pLastWriteTime: PWfxFileTime): Boolean;
begin
  Result:= False;
  if Assigned(FsSetTimeW) then
    Result:= FsSetTimeW(PWideChar(UTF8Decode(RemoteName)), pCreationTime, pLastAccessTime, pLastWriteTime)
  else if Assigned(FsSetTime) then
    Result:= FsSetTime(PAnsiChar(CeUtf8ToSys(RemoteName)), pCreationTime, pLastAccessTime, pLastWriteTime);
end;

function TWFXModule.WfxMkDir(const sBasePath, sDirName: String): LongInt;
begin
  Result:= WFX_NOTSUPPORTED;
  if Assigned(FsMkDirW) then
    begin
      WfxStatusInfo(sBasePath, FS_STATUS_START, FS_STATUS_OP_MKDIR);
      if FsMkDirW(PWideChar(UTF8Decode(sDirName))) then
        Result:= WFX_SUCCESS
      else
        Result:= WFX_ERROR;
      WfxStatusInfo(sBasePath, FS_STATUS_END, FS_STATUS_OP_MKDIR);
    end
  else if Assigned(FsMkDir) then
    begin
      WfxStatusInfo(sBasePath, FS_STATUS_START, FS_STATUS_OP_MKDIR);
      if FsMkDir(PAnsiChar(CeUtf8ToSys(sDirName))) then
        Result:= WFX_SUCCESS
      else
        Result:= WFX_ERROR;
      WfxStatusInfo(sBasePath, FS_STATUS_END, FS_STATUS_OP_MKDIR);
    end;
end;

function TWFXModule.WfxRemoveDir(const sDirName: String): Boolean;
begin
  Result:= False;
  if Assigned(FsRemoveDirW) then
    Result:= FsRemoveDirW(PWideChar(UTF8Decode(sDirName)))
  else if Assigned(FsRemoveDir) then
    Result:= FsRemoveDir(PAnsiChar(CeUtf8ToSys(sDirName)));
end;

function TWFXModule.WfxDeleteFile(const sFileName: String): Boolean;
begin
  Result:= False;
  if Assigned(FsDeleteFileW) then
    Result:= FsDeleteFileW(PWideChar(UTF8Decode(sFileName)))
  else if Assigned(FsDeleteFile) then
    Result:= FsDeleteFile(PAnsiChar(CeUtf8ToSys(sFileName)));
end;

function TWFXModule.WfxGetLocalName(var sFileName: String): Boolean;
var
  pacRemoteName: PAnsiChar;
  pwcRemoteName: PWideChar;
begin
  Result:= False;
  if Assigned(FsGetLocalNameW) then
    begin
      pwcRemoteName:= GetMem(MAX_PATH * SizeOf(WideChar));
      StrPCopyW(pwcRemoteName, UTF8Decode(sFileName));
      Result:= FsGetLocalNameW(pwcRemoteName, MAX_PATH);
      if Result = True then
        sFileName:= UTF16ToUTF8(UnicodeString(pwcRemoteName));
      FreeMem(pwcRemoteName);
    end
  else if Assigned(FsGetLocalName) then
    begin
      pacRemoteName:= GetMem(MAX_PATH);
      StrPCopy(pacRemoteName, CeUtf8ToSys(sFileName));
      Result:= FsGetLocalName(pacRemoteName, MAX_PATH);
      if Result = True then
        sFileName:= CeSysToUtf8(StrPas(pacRemoteName));
      FreeMem(pacRemoteName);
    end;
end;

function TWFXModule.WfxDisconnect(const DisconnectRoot: String): Boolean;
begin
  if Assigned(FsDisconnectW) then
    Result:= FsDisconnectW(PWideChar(UTF8Decode(DisconnectRoot)))
  else if Assigned(FsDisconnect) then
    Result:= FsDisconnect(PAnsiChar(CeUtf8ToSys(DisconnectRoot)))
  else
    Result:= False;
end;

constructor TWFXModule.Create;
begin
  inherited;
end;

destructor TWFXModule.Destroy;
begin
  if IsLoaded then
  begin
    if Assigned(FsContentPluginUnloading) then
      FsContentPluginUnloading;
    if Assigned(ExtensionFinalize) then
      ExtensionFinalize(nil);
    //------------------------------------------------------
    UnloadModule;
  end;
  inherited;
end;

function TWFXModule.LoadModule(const sName: String): Boolean;
begin
  FModuleHandle := mbLoadLibrary(sName);
  if FModuleHandle = 0 then Exit(False);

  DCDebug('WFX module loaded ' + sName + ' at ' + hexStr(Pointer(FModuleHandle)));

  FModuleFileName:= sName;
{ Mandatory }
  FsInit := TFsInit(GetProcAddress(FModuleHandle,'FsInit'));
  FsFindFirst := TFsFindFirst(GetProcAddress(FModuleHandle,'FsFindFirst'));
  FsFindNext := TFsFindNext(GetProcAddress(FModuleHandle,'FsFindNext'));
  FsFindClose := TFsFindClose(GetProcAddress(FModuleHandle,'FsFindClose'));
{ Unicode }
  FsInitW := TFsInitW(GetProcAddress(FModuleHandle,'FsInitW'));
  FsFindFirstW := TFsFindFirstW(GetProcAddress(FModuleHandle,'FsFindFirstW'));
  FsFindNextW := TFsFindNextW(GetProcAddress(FModuleHandle,'FsFindNextW'));

  Result:= (FsInit <> nil) and (FsFindFirst <> nil) and (FsFindNext <> nil);
  if (Result = False) then
  begin
    FsInit:= nil;
    FsFindFirst:= nil;
    FsFindNext:= nil;
    Result:= (FsInitW <> nil) and (FsFindFirstW <> nil) and (FsFindNextW <> nil);
  end;

  if (Result = False) or (FsFindClose = nil) then
  begin
    FsInitW:= nil;
    FsFindFirstW:= nil;
    FsFindNextW:= nil;
    FsFindClose:= nil;
    Exit(False);
  end;

{ Optional }
  FsSetCryptCallback:= TFsSetCryptCallback(GetProcAddress(FModuleHandle,'FsSetCryptCallback'));
  FsGetDefRootName := TFsGetDefRootName(GetProcAddress(FModuleHandle,'FsGetDefRootName'));
  FsExecuteFile := TFsExecuteFile(GetProcAddress(FModuleHandle,'FsExecuteFile'));
  FsGetFile := TFsGetFile(GetProcAddress(FModuleHandle,'FsGetFile'));
  FsPutFile := TFsPutFile(GetProcAddress(FModuleHandle,'FsPutFile'));
  FsDeleteFile := TFsDeleteFile(GetProcAddress(FModuleHandle,'FsDeleteFile'));
  FsMkDir := TFsMkDir(GetProcAddress(FModuleHandle,'FsMkDir'));
  FsRemoveDir := TFsRemoveDir(GetProcAddress(FModuleHandle,'FsRemoveDir'));
  FsStatusInfo := TFsStatusInfo(GetProcAddress(FModuleHandle,'FsStatusInfo'));
  FsSetDefaultParams := TFsSetDefaultParams(GetProcAddress(FModuleHandle,'FsSetDefaultParams'));
  FsContentPluginUnloading := TFsContentPluginUnloading(GetProcAddress(FModuleHandle,'FsContentPluginUnloading'));
  //---------------------
  FsSetAttr := TFsSetAttr (GetProcAddress(FModuleHandle,'FsSetAttr'));
  FsSetTime := TFsSetTime (GetProcAddress(FModuleHandle,'FsSetTime'));
  FsExtractCustomIcon := TFsExtractCustomIcon (GetProcAddress(FModuleHandle,'FsExtractCustomIcon'));
  FsRenMovFile := TFsRenMovFile (GetProcAddress(FModuleHandle,'FsRenMovFile'));
  FsDisconnect := TFsDisconnect (GetProcAddress(FModuleHandle,'FsDisconnect'));
  FsGetPreviewBitmap := TFsGetPreviewBitmap (GetProcAddress(FModuleHandle,'FsGetPreviewBitmap'));
  FsLinksToLocalFiles := TFsLinksToLocalFiles (GetProcAddress(FModuleHandle,'FsLinksToLocalFiles'));
  FsGetLocalName := TFsGetLocalName (GetProcAddress(FModuleHandle,'FsGetLocalName'));
  //---------------------
  FsGetBackgroundFlags := TFsGetBackgroundFlags (GetProcAddress(FModuleHandle,'FsGetBackgroundFlags'));
  //---------------------
  FsContentGetDetectString := TFsContentGetDetectString (GetProcAddress(FModuleHandle,'FsContentGetDetectString'));
  FsContentGetSupportedField := TFsContentGetSupportedField (GetProcAddress(FModuleHandle,'FsContentGetSupportedField'));
  FsContentGetValue := TFsContentGetValue (GetProcAddress(FModuleHandle,'FsContentGetValue'));
  FsContentSetDefaultParams := TFsContentSetDefaultParams (GetProcAddress(FModuleHandle,'FsContentSetDefaultParams'));
  FsContentStopGetValue := TFsContentStopGetValue (GetProcAddress(FModuleHandle,'FsContentStopGetValue'));
  FsContentGetDefaultSortOrder := TFsContentGetDefaultSortOrder (GetProcAddress(FModuleHandle,'FsContentGetDefaultSortOrder'));
  FsContentGetSupportedFieldFlags := TFsContentGetSupportedFieldFlags (GetProcAddress(FModuleHandle,'FsContentGetSupportedFieldFlags'));
  FsContentSetValue := TFsContentSetValue (GetProcAddress(FModuleHandle,'FsContentSetValue'));
  FsContentGetDefaultView := TFsContentGetDefaultView (GetProcAddress(FModuleHandle,'FsContentGetDefaultView'));
{ Unicode }
  FsSetCryptCallbackW:= TFsSetCryptCallbackW(GetProcAddress(FModuleHandle,'FsSetCryptCallbackW'));
  FsMkDirW := TFsMkDirW(GetProcAddress(FModuleHandle,'FsMkDirW'));
  FsExecuteFileW := TFsExecuteFileW(GetProcAddress(FModuleHandle,'FsExecuteFileW'));
  FsRenMovFileW := TFsRenMovFileW(GetProcAddress(FModuleHandle,'FsRenMovFileW'));
  FsGetFileW := TFsGetFileW(GetProcAddress(FModuleHandle,'FsGetFileW'));
  FsPutFileW := TFsPutFileW(GetProcAddress(FModuleHandle,'FsPutFileW'));
  FsDeleteFileW := TFsDeleteFileW(GetProcAddress(FModuleHandle,'FsDeleteFileW'));
  FsRemoveDirW := TFsRemoveDirW(GetProcAddress(FModuleHandle,'FsRemoveDirW'));
  FsDisconnectW := TFsDisconnectW(GetProcAddress(FModuleHandle,'FsDisconnectW'));
  FsSetAttrW := TFsSetAttrW (GetProcAddress(FModuleHandle,'FsSetAttrW'));
  FsSetTimeW := TFsSetTimeW (GetProcAddress(FModuleHandle,'FsSetTimeW'));
  FsStatusInfoW := TFsStatusInfoW(GetProcAddress(FModuleHandle,'FsStatusInfoW'));
  FsExtractCustomIconW := TFsExtractCustomIconW(GetProcAddress(FModuleHandle,'FsExtractCustomIconW'));
  FsGetLocalNameW := TFsGetLocalNameW(GetProcAddress(FModuleHandle,'FsGetLocalNameW'));
  { Extension API }
  ExtensionInitialize:= TExtensionInitializeProc(GetProcAddress(FModuleHandle,'ExtensionInitialize'));
  ExtensionFinalize:= TExtensionFinalizeProc(GetProcAddress(FModuleHandle,'ExtensionFinalize'));
end;

procedure TWFXModule.UnloadModule;
begin
{$IF (not DEFINED(LINUX)) or ((FPC_VERSION > 2) or ((FPC_VERSION=2) and (FPC_RELEASE >= 5)))}
  if FModuleHandle <> 0 then
    FreeLibrary(FModuleHandle);
{$ENDIF}
  FModuleHandle := 0;
{ Mandatory }
  FsInit := nil;
  FsFindFirst := nil;
  FsFindNext := nil;
  FsFindClose := nil;
{ Optional }
  FsSetCryptCallback := nil;
  FsGetDefRootName := nil;
  FsGetFile := nil;
  FsPutFile := nil;
  FsDeleteFile := nil;
  FsRemoveDir := nil;
  FsExecuteFile := nil;
  FsMkDir := nil;
  FsStatusInfo := nil;
  FsSetDefaultParams:=nil;
  //---------------------
  FsSetAttr := nil;
  FsSetTime := nil;
  FsExtractCustomIcon := nil;
  FsRenMovFile := nil;
  FsDisconnect := nil;
  FsGetPreviewBitmap := nil;
  FsLinksToLocalFiles := nil;
  FsGetLocalName := nil;
  //---------------------
  FsGetBackgroundFlags := nil;
  //---------------------
  FsContentGetDetectString := nil;
  FsContentGetSupportedField := nil;
  FsContentGetValue := nil;
  FsContentSetDefaultParams := nil;
  FsContentStopGetValue := nil;
  FsContentGetDefaultSortOrder := nil;
  FsContentGetSupportedFieldFlags := nil;
  FsContentSetValue := nil;
  FsContentGetDefaultView := nil;
  FsContentPluginUnloading := nil;
{ Unicode }
  FsInitW := nil;
  FsFindFirstW := nil;
  FsFindNextW := nil;
  //---------------------
  FsSetCryptCallbackW:= nil;
  FsMkDirW := nil;
  FsExecuteFileW := nil;
  FsRenMovFileW := nil;
  FsGetFileW := nil;
  FsPutFileW := nil;
  FsDeleteFileW := nil;
  FsRemoveDirW := nil;
  FsDisconnectW := nil;
  FsSetAttrW := nil;
  FsSetTimeW := nil;
  FsStatusInfoW := nil;
  FsExtractCustomIconW := nil;
  FsGetLocalNameW := nil;
  // Extension API
  ExtensionInitialize:= nil;
  ExtensionFinalize:= nil;
end;

procedure TWFXModule.VFSInit(Data: PtrInt);
var
  dps: tFsDefaultParamStruct;
  StartupInfo: TExtensionStartupInfo;
begin
  if Assigned(FsSetDefaultParams) then
  begin
    dps.DefaultIniName := mbFileNameToSysEnc(gpCfgDir + WfxIniFileName);
    dps.PluginInterfaceVersionHi:= 2;
    dps.PluginInterfaceVersionLow:= 0;
    dps.Size:= SizeOf(dps);
    FsSetDefaultParams(@dps);
  end;

  if not Assigned(FsGetBackgroundFlags) then
    FBackgroundFlags:= 0
  else
    FBackgroundFlags:= FsGetBackgroundFlags();

  // Extension API
  if Assigned(ExtensionInitialize) then
    begin
      FillByte(StartupInfo, SizeOf(TExtensionStartupInfo), 0);

      with StartupInfo do
      begin
        StructSize:= SizeOf(TExtensionStartupInfo);
        PluginDir:= ExtractFilePath(FModuleFileName);
        PluginConfDir:= gpCfgDir;
        InputBox:= @fDialogBox.InputBox;
        MessageBox:= @fDialogBox.MessageBox;
        DialogBoxLFM:= @fDialogBox.DialogBoxLFM;
        DialogBoxLRS:= @fDialogBox.DialogBoxLRS;
        DialogBoxLFMFile:= @fDialogBox.DialogBoxLFMFile;
        SendDlgMsg:= @fDialogBox.SendDlgMsg;
      end;

      ExtensionInitialize(@StartupInfo);
    end;
end;

function TWFXModule.VFSConfigure(Parent: THandle): Boolean;
var
  RemoteName: String;
begin
  try
    RemoteName:= PathDelim;
    WFXStatusInfo(PathDelim, FS_STATUS_START, FS_STATUS_OP_EXEC);
    Result:= (WfxExecuteFile(Parent, RemoteName, 'properties') = FS_EXEC_OK);
    WFXStatusInfo(PathDelim, FS_STATUS_END, FS_STATUS_OP_EXEC);
  except
    on E: Exception do
    begin
      Result:= False;
      DCDebug(ClassName + '.VFSConfigure(). Error: ' + E.Message);
    end;
  end;
end;

function TWFXModule.VFSRootName: String;
var
  pcRootName : PAnsiChar;
begin
  Result:= EmptyStr;
  if Assigned(FsGetDefRootName) then
  begin
    pcRootName:= GetMem(MAX_PATH);
    Assert(Assigned(pcRootName));
    try
      FsGetDefRootName(pcRootName, MAX_PATH);
      Result := StrPas(pcRootName);
    finally
      FreeMem(pcRootName);
    end;
  end;
end;

function TWFXModule.IsLoaded: Boolean;
begin
Result := (FModuleHandle <> 0);
end;

{ TWFXModuleList }

function TWFXModuleList.GetAEnabled(Index: Integer): Boolean;
begin
  Result:= Boolean(PtrInt(Objects[Index]));
end;

function TWFXModuleList.GetAFileName(Index: Integer): String;
begin
  Result:= ValueFromIndex[Index];
end;

function TWFXModuleList.GetAName(Index: Integer): String;
begin
  Result:= Names[Index];
end;

procedure TWFXModuleList.SetAEnabled(Index: Integer; const AValue: Boolean);
begin
  Objects[Index]:= TObject(PtrInt(AValue));
end;

procedure TWFXModuleList.SetAFileName(Index: Integer; const AValue: String);
begin
  ValueFromIndex[Index]:= AValue;
end;

procedure TWFXModuleList.SetAName(Index: Integer; const AValue: String);
var
  sValue : String;
begin
  sValue:= ValueFromIndex[Index];
  Self[Index]:= AValue + '=' + sValue;
end;

procedure TWFXModuleList.Load(Ini: TIniFileEx);
var
  I: Integer;
  sCurrPlugin: String;
  LEnabled: Boolean;
begin
  Ini.ReadSectionRaw('FileSystemPlugins', Self);
  for I:= 0 to Count - 1 do
  begin
    sCurrPlugin := Name[I];
    if (sCurrPlugin = '') then
      Continue;

    LEnabled := (sCurrPlugin[1] <> '#');
    Enabled[I]:= LEnabled;
    if not LEnabled then
      Name[I]:= Copy(sCurrPlugin, 2, MaxInt);
  end;
end;

procedure TWFXModuleList.Load(AConfig: TXmlConfig; ANode: TXmlNode);
var
  I: Integer;
  AName, APath: String;
begin
  Clear;

  ANode := ANode.FindNode('WfxPlugins');
  if Assigned(ANode) then
  begin
    ANode := ANode.FirstChild;
    while Assigned(ANode) do
    begin
      if ANode.CompareName('WfxPlugin') = 0 then
      begin
        if AConfig.TryGetValue(ANode, 'Name', AName) and
           AConfig.TryGetValue(ANode, 'Path', APath) then
        begin
          I := Add(AName, APath);
          Enabled[I] := AConfig.GetAttr(ANode, 'Enabled', True);
        end
        else
          DCDebug('Invalid entry in configuration: ' + AConfig.GetPathFromNode(ANode) + '.');
      end;
      ANode := ANode.NextSibling;
    end;
  end;
end;

procedure TWFXModuleList.Save(Ini: TIniFileEx);
var
 I: Integer;
 LName: String;
begin
  Ini.EraseSection('FileSystemPlugins');
  for I := 0 to Count - 1 do
  begin
    if Enabled[I] then
      LName := Name[I]
    else
      LName := '#' + Name[I];

    Ini.WriteString('FileSystemPlugins', LName, FileName[I]);
  end;
end;

procedure TWFXModuleList.Save(AConfig: TXmlConfig; ANode: TXmlNode);
var
  I: Integer;
  SubNode: TXmlNode;
begin
  ANode := AConfig.FindNode(ANode, 'WfxPlugins', True);
  AConfig.ClearNode(ANode);
  for I := 0 to Count - 1 do
    begin
      SubNode := AConfig.AddNode(ANode, 'WfxPlugin');
      AConfig.SetAttr(SubNode, 'Enabled', Enabled[I]);
      AConfig.AddValue(SubNode, 'Name', Name[I]);
      AConfig.AddValue(SubNode, 'Path', FileName[I]);
    end;
end;

function TWFXModuleList.Add(Ext: String; FileName: String): Integer;
begin
  Result:= AddObject(Ext + '=' + FileName, TObject(True));
end;

function TWFXModuleList.FindFirstEnabledByName(Name: String): Integer;
begin
  for Result := 0 to Count - 1 do
    if Enabled[Result] and (DoCompareText(Names[Result], Name) = 0) then
      Exit;
  Result := -1;
end;

end.
