{
   Double Commander
   -------------------------------------------------------------------------
   Virtual File System - class for manage WFX plugins (Version 1.3)
 
   Copyright (C) 2007-2018 Alexander Koblov (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, see <http://www.gnu.org/licenses/>.
}

unit uWFXmodule;

{$mode objfpc}{$H+}

interface

uses
  SysUtils, Classes, Graphics, WfxPlugin, uWFXprototypes,
  dynlibs, DCClassesUtf8, Extension, DCBasicTypes, DCXmlConfig,
  uWdxPrototypes, uWdxModule, uFileSource;

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(TPluginWDX)
  private
    FBackgroundFlags: Integer;
    FIconMutex: TRTLCriticalSection;
  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;
    //---------------------
    FsSetAttr:TFsSetAttr;
    FsSetTime:TFsSetTime;
    FsExtractCustomIcon:TFsExtractCustomIcon;
    FsRenMovFile:TFsRenMovFile;
    FsDisconnect:TFsDisconnect;
    FsGetPreviewBitmap:TFsGetPreviewBitmap;
    FsLinksToLocalFiles:TFsLinksToLocalFiles;
    FsGetLocalName:TFsGetLocalName;
    //---------------------
    FsGetBackgroundFlags: TFsGetBackgroundFlags;
    //---------------------
    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;
    //-----------------------
    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;
    function WfxContentGetDefaultView(out DefaultView: TFileSourceFields): Boolean;
    function WfxExtractCustomIcon(var RemoteName: String; AIconSize: Integer; out TheIcon: TWfxIcon): Integer;
  private
    function LoadModule(const sName: String):Boolean; overload; {Load plugin}
    procedure UnloadModule; override;
  public
    constructor Create; override;
    destructor Destroy; override;

    procedure VFSInit;

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

    function ContentPlugin: Boolean;

    property BackgroundFlags: Integer read FBackgroundFlags write FBackgroundFlags;
  end;

  { TWFXModuleList }

  TWFXModuleList = class(TStringList)
  private
    FModuleList: TStringListEx;
  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
    constructor Create; reintroduce;
    destructor Destroy; override;
  public
    procedure Load(AConfig: TXmlConfig; ANode: TXmlNode); overload;
    procedure Save(AConfig: TXmlConfig; ANode: TXmlNode); overload;
    function ComputeSignature(seed: dword): dword;
    function Add(Ext: String; FileName: String): Integer; reintroduce;
    function FindFirstEnabledByName(Name: String): Integer;
    function LoadModule(const FileName: String): TWfxModule;

    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
  //Lazarus, Free-Pascal, etc.
  LazUTF8, FileUtil,

  //DC
  uDCUtils, uLng, uGlobsPaths, uOSUtils, uWfxPluginUtil, DCOSUtils,
  DCStrUtils, DCConvertEncoding, uComponentsSignature, uOSForms, uExtension;

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;

function ConvertString(const S: String): TStringArray;
var
  Item: String = '';
  Index: Integer = 1;
begin
  Result:= Default(TStringArray);
  while Index < High(S) do
  begin
    if S[Index] = '\' then
    begin
      case S[Index + 1] of
        '\':
        begin
          Item += '\';
          Inc(Index, 2);
          Continue;
        end;
        'n':
        begin
          AddString(Result, Item);
          Item:= EmptyStr;
          Inc(Index, 2);
          Continue;
        end;
      end;
    end;
    Item += S[Index];
    Inc(Index);
  end;
  if Length(Item) > 0 then
  begin
    AddString(Result, Item + S[High(S)]);
  end;
end;

function ConvertFunction(const S: String): String;
var
  AValues: TStringArray;
  Plugin, Field, Arg: String;
begin
  Result:= EmptyStr;
  if Length(S) < 3 then Exit;
  if not StrBegins(S, '[=') then Exit(S);
  if (S[Low(S)] = '[') and (S[High(S)] = ']') then
  begin
    AValues:= (Copy(S, 2, Length(S) - 2)).Split(['.']);
    if (Length(AValues) > 1) then
    begin
      Plugin:= LowerCase(AValues[0]);
      if (Plugin = '=<fs>') then
        Result:= 'Plugin(FS)'
      else if (Plugin = '=tc') then
      begin
        Result:= 'DC()';
        Field:= LowerCase(AValues[1]);
        if (Field = 'writedate') then
          AValues[1]:= 'GETFILETIME'
        else if (Field = 'attributestr') then
          AValues[1]:= 'GETFILEATTR'
        else if (Field = 'writetime') then
        begin
          AValues[1]:= 'GETFILETIME';
          if (Length(AValues) = 2) then
          begin
            AddString(AValues, DefaultFormatSettings.LongTimeFormat);
          end;
        end
        else if (Field = 'size') then
        begin
          AValues[1]:= 'GETFILESIZE';
          if (Length(AValues) = 3) then
          begin
            Arg:= LowerCase(AValues[2]);
            if (Arg = 'bytes') then
              AValues[2]:= 'BYTE'
            else if (Arg = 'kbytes') then
              AValues[2]:= 'KILO'
            else if (Arg = 'mbytes') then
              AValues[2]:= 'MEGA'
            else if (Arg = 'gbytes') then
              AValues[2]:= 'GIGA'
            else
              AValues[2]:= 'FLOAT';
          end;
        end;
      end;
      if (Length(AValues) = 2) then
        Result+= '.' + AValues[1] + '{}'
      else begin
        Result+= '.' + AValues[1] + '{' + AValues[2] + '}';
      end;
      Result:= '[' + Result + ']';
    end;
  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(CeUtf8ToUtf16(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;
    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(CeUtf8ToUtf16(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;
  MainWin:= GetWindowHandle(MainWin);
  if Assigned(FsExecuteFileW) then
    begin
      pwcRemoteName:= GetMem(MAX_PATH * SizeOf(WideChar));
      StrPCopyW(pwcRemoteName, CeUtf8ToUtf16(RemoteName));
      Result:= FsExecuteFileW(MainWin, pwcRemoteName, PWideChar(CeUtf8ToUtf16(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(CeUtf8ToUtf16(OldName)), PWideChar(CeUtf8ToUtf16(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(CeUtf8ToUtf16(RemoteName)), PWideChar(CeUtf8ToUtf16(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(CeUtf8ToUtf16(LocalName)), PWideChar(CeUtf8ToUtf16(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(CeUtf8ToUtf16(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(CeUtf8ToUtf16(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(CeUtf8ToUtf16(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(CeUtf8ToUtf16(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(CeUtf8ToUtf16(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, CeUtf8ToUtf16(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(CeUtf8ToUtf16(DisconnectRoot)))
  else if Assigned(FsDisconnect) then
    Result:= FsDisconnect(PAnsiChar(CeUtf8ToSys(DisconnectRoot)))
  else
    Result:= False;
end;

function TWFXModule.WfxContentGetDefaultView(out DefaultView: TFileSourceFields): Boolean;
const
  MAX_LEN = 4096;
var
  Index: Integer;
  ViewContents, ViewHeaders,
  ViewWidths, ViewOptions: TStringArray;
  usContents, usHeaders, usWidths, usOptions: String;
  asContents, asHeaders, asWidths, asOptions: array[0..MAX_LEN] of AnsiChar;
  wsContents, wsHeaders, wsWidths, wsOptions: array[0..MAX_LEN] of WideChar;
begin
  Result:= False;
  DefaultView:= Default(TFileSourceFields);

  if Assigned(FsContentGetDefaultViewW) then
  begin
    Result:= FsContentGetDefaultViewW(wsContents, wsHeaders, wsWidths, wsOptions, MAX_LEN);
    if Result then
    begin
      usContents:= CeUtf16ToUtf8(wsContents);
      usHeaders:= CeUtf16ToUtf8(wsHeaders);
      usWidths:= CeUtf16ToUtf8(wsWidths);
      usOptions:= CeUtf16ToUtf8(wsOptions);
    end;
  end
  else if Assigned(FsContentGetDefaultView) then
  begin
    Result:= FsContentGetDefaultView(asContents, asHeaders, asWidths, asOptions, MAX_LEN);
    if Result then
    begin
      usContents:= CeSysToUtf8(asContents);
      usHeaders:= CeSysToUtf8(asHeaders);
      usWidths:= CeSysToUtf8(asWidths);
      usOptions:= CeSysToUtf8(asOptions);
    end;
  end;
  if Result then
  begin
    ViewHeaders:= ConvertString(usHeaders);
    ViewWidths:= SplitString(usWidths, ',');
    ViewOptions:= SplitString(usOptions,'|');
    ViewContents:= ConvertString(usContents);

    SetLength(DefaultView, Length(ViewWidths));

    for Index:= Low(DefaultView) to High(DefaultView) do
    begin
      if (Index = 0) then
      begin
        DefaultView[Index].Header:= rsColName;
        DefaultView[Index].Content:= '[DC().GETFILENAMENOEXT{}]';
      end
      else if (Index = 1) then
      begin
        DefaultView[Index].Header:= rsColExt;
        DefaultView[Index].Content:= '[DC().GETFILEEXT{}]';
      end
      else begin
        DefaultView[Index].Header:= ViewHeaders[Index - 2];
        DefaultView[Index].Content:= ConvertFunction(ViewContents[Index - 2]);
      end;
      DefaultView[Index].Width:= StrToInt(ViewWidths[Index]);
      if (DefaultView[Index].Width < 0) then
      begin
        DefaultView[Index].Align:= taRightJustify;
        DefaultView[Index].Width:= Abs(DefaultView[Index].Width);
      end
      else begin
        DefaultView[Index].Align:= taLeftJustify;
      end;
    end;
  end;
end;

function TWFXModule.WfxExtractCustomIcon(var RemoteName: String; AIconSize: Integer; out TheIcon: TWfxIcon): Integer;
var
  Flags: Integer = FS_ICONFLAG_BACKGROUND;
  asFileName: array[0..MAX_PATH] of AnsiChar;
  wsFileName: array[0..MAX_PATH] of WideChar;
begin
  TheIcon:= Default(TWfxIcon);

  if (AIconSize = 16) then begin
    Flags:= Flags or FS_ICONFLAG_SMALL;
  end;
  TheIcon.Size:= AIconSize;

  EnterCriticalSection(FIconMutex);
  try
    if Assigned(FsExtractCustomIconW) then
    begin
      StrPLCopy(wsFileName, CeUtf8ToUtf16(RemoteName), MAX_PATH);
      Result:= FsExtractCustomIconW(wsFileName, Flags, @TheIcon);
      if Result in [FS_ICON_EXTRACTED, FS_ICON_EXTRACTED_DESTROY] then
        RemoteName:= CeUtf16ToUtf8(UnicodeString(wsFileName));
    end
    else if Assigned(FsExtractCustomIcon) then
    begin
      StrPLCopy(asFileName, CeUtf8ToSys(RemoteName), MAX_PATH);
      Result:= FsExtractCustomIcon(asFileName, Flags, @TheIcon);
      if Result in [FS_ICON_EXTRACTED, FS_ICON_EXTRACTED_DESTROY] then
        RemoteName:= CeSysToUtf8(StrPas(asFileName));
    end
    else begin
      Result:= FS_ICON_USEDEFAULT;
    end;
  finally
    LeaveCriticalSection(FIconMutex);
  end;
end;

constructor TWFXModule.Create;
begin
  inherited;
  FName:= 'FS';
  InitCriticalSection(FIconMutex);
end;

destructor TWFXModule.Destroy;
begin
  if IsLoaded then
  begin
    if Assigned(ContentPluginUnloading) then
      ContentPluginUnloading;
    if Assigned(ExtensionFinalize) then
      ExtensionFinalize(nil);
  end;
  inherited Destroy;
  DoneCriticalSection(FIconMutex);
end;

function TWFXModule.LoadModule(const sName: String): Boolean;
var
  AHandle: TLibHandle;
begin
  EnterCriticalSection(FMutex);
  try
    if FModuleHandle <> NilHandle then Exit(True);
    FModulePath:= mbExpandFileName(sName);
    AHandle := mbLoadLibrary(FModulePath);
    Result := AHandle <> NilHandle;
    if not Result then Exit;

  { Mandatory }
    FsInit := TFsInit(GetProcAddress(AHandle,'FsInit'));
    FsFindFirst := TFsFindFirst(GetProcAddress(AHandle,'FsFindFirst'));
    FsFindNext := TFsFindNext(GetProcAddress(AHandle,'FsFindNext'));
    FsFindClose := TFsFindClose(GetProcAddress(AHandle,'FsFindClose'));
  { Unicode }
    FsInitW := TFsInitW(GetProcAddress(AHandle,'FsInitW'));
    FsFindFirstW := TFsFindFirstW(GetProcAddress(AHandle,'FsFindFirstW'));
    FsFindNextW := TFsFindNextW(GetProcAddress(AHandle,'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;
      FreeLibrary(AHandle);
      Exit(False);
    end;

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

    VFSInit;

    FModuleHandle := AHandle;
  finally
    LeaveCriticalSection(FMutex);
  end;
end;

procedure TWFXModule.UnloadModule;
var
  AHandle: TLibHandle;
begin
  EnterCriticalSection(FMutex);
  try
    if FModuleHandle <> NilHandle then
    begin
      AHandle:= FModuleHandle;
      FModuleHandle := NilHandle;
      FreeLibrary(AHandle);
    end;

  { 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;
    //---------------------
    FsContentGetDefaultView := nil;
    ContentGetDetectString := nil;
    ContentGetSupportedField := nil;
    ContentGetValue := nil;
    ContentSetDefaultParams := nil;
    ContentStopGetValue := nil;
    ContentGetDefaultSortOrder := nil;
    ContentGetSupportedFieldFlags := nil;
    ContentSetValue := nil;
    ContentPluginUnloading := 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;
    //---------------------
    FsContentGetDefaultViewW := nil;
    ContentGetValueW := nil;
    ContentStopGetValueW := nil;
    ContentSetValueW := nil;
    // Extension API
    ExtensionInitialize:= nil;
    ExtensionFinalize:= nil;
  finally
    LeaveCriticalSection(FMutex);
  end;
end;

procedure TWFXModule.VFSInit;
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
    InitializeExtension(@StartupInfo);

    ExtensionInitialize(@StartupInfo);
  end;

  CallContentSetDefaultParams;
  CallContentGetSupportedField;
  if Length(Self.DetectStr) = 0 then
    Self.DetectStr := CallContentGetDetectString;
end;

function TWFXModule.VFSConfigure(Parent: HWND): 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;
    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 := RepairPluginName(StrPas(pcRootName));
    finally
      FreeMem(pcRootName);
    end;
  end;
end;

function TWFXModule.ContentPlugin: Boolean;
begin
  Result:= Assigned(ContentGetValue) or Assigned(ContentGetValueW);
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;

constructor TWFXModuleList.Create;
begin
  FModuleList:= TStringListEx.Create;
  FModuleList.Sorted:= True;
end;

destructor TWFXModuleList.Destroy;
var
  I: Integer;
begin
  for I:= 0 to FModuleList.Count - 1 do
  begin
    TWfxModule(FModuleList.Objects[I]).Free;
  end;
  FreeAndNil(FModuleList);
  inherited Destroy;
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(RepairPluginName(AName), APath);
          Enabled[I] := AConfig.GetAttr(ANode, 'Enabled', True);
        end;
      end;
      ANode := ANode.NextSibling;
    end;
  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;

{ TWFXModuleList.ComputeSignature }
function TWFXModuleList.ComputeSignature(seed: dword): dword;
var
  iIndex: integer;
begin
  result := seed;
  for iIndex := 0 to pred(Count) do
  begin
    result := ComputeSignatureBoolean(result, Enabled[iIndex]);
    result := ComputeSignatureString(result, Name[iIndex]);
    result := ComputeSignatureString(result, FileName[iIndex]);
  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;

function TWFXModuleList.LoadModule(const FileName: String): TWfxModule;
var
  Index: Integer;
begin
  if FModuleList.Find(FileName, Index) then
    Result := TWfxModule(FModuleList.Objects[Index])
  else begin
    Result := TWfxModule.Create;
    if not Result.LoadModule(FileName) then
      FreeAndNil(Result)
    else begin
      FModuleList.AddObject(FileName, Result);
    end;
  end;
end;

end.
