diff --git a/res/S3.rc b/res/S3.rc
new file mode 100644
index 0000000..f840aa1
--- /dev/null
+++ b/res/S3.rc
@@ -0,0 +1,4 @@
+MAINICON ICON mainicon.ico
+BUCKET ICON bucket.ico
+FOLDER ICON folder.ico
+CONFIG ICON config.ico
diff --git a/res/bucket.ico b/res/bucket.ico
new file mode 100644
index 0000000..06354fb
Binary files /dev/null and b/res/bucket.ico differ
diff --git a/res/config.ico b/res/config.ico
new file mode 100644
index 0000000..f755719
Binary files /dev/null and b/res/config.ico differ
diff --git a/res/folder.ico b/res/folder.ico
new file mode 100644
index 0000000..96ad171
Binary files /dev/null and b/res/folder.ico differ
diff --git a/res/mainicon.ico b/res/mainicon.ico
new file mode 100644
index 0000000..9b176b1
Binary files /dev/null and b/res/mainicon.ico differ
diff --git a/res/pluginst.inf b/res/pluginst.inf
new file mode 100644
index 0000000..305560b
--- /dev/null
+++ b/res/pluginst.inf
@@ -0,0 +1,6 @@
+[plugininstall]
+description=S3 plugin 0.9 (32bit+64bit)
+type=wfx
+file=WvN-S3.wfx
+defaultdir=WvN-S3
+version=0.9
diff --git a/source/S3.dpr b/source/S3.dpr
new file mode 100644
index 0000000..7f40ff0
--- /dev/null
+++ b/source/S3.dpr
@@ -0,0 +1,57 @@
+library S3;
+
+{$DEFINE NOFORMS}
+{$R 'S3.res' '..\res\S3.rc'}
+
+uses
+ FastMM4,
+ System.SysUtils,
+ Vcl.Dialogs,
+ System.Classes,
+ WinApi.Windows,
+ WinApi.ShellApi,
+ System.AnsiStrings,
+ Wfx.Plugin.S3 in 'Wfx.Plugin.S3.pas',
+ Wfx.Plugin.S3.Path in 'Wfx.Plugin.S3.Path.pas',
+ Wfx.Plugin.Base in 'Wfx.Plugin.Base.pas',
+ Wfx.Plugin.intf in 'Wfx.Plugin.intf.pas',
+ Wfx.Plugin.Consts in 'Wfx.Plugin.Consts.pas',
+ Wfx.Plugin.ExportProcs in 'Wfx.Plugin.ExportProcs.pas';
+
+{$E wfx}
+{$IFDEF WIN64}
+{$E w64}
+{$IFNDEF VER230}
+{$E wfx64}
+{$ENDIF}
+{$ENDIF}
+
+
+exports
+ FsInitW,
+ FsFindFirstW,
+ FsFindNextW,
+ FsFindClose,
+ FsGetDefRootName,
+ FsExecuteFileW,
+ FsGetFileW,
+ FsStatusInfoW,
+ FsExtractCustomIconW,
+ FsDeleteFileW,
+ FsSetCryptCallbackW,
+ FsMkDirW,
+ FsRenMovFileW,
+ FsPutFileW,
+ FsRemoveDirW,
+ FsDisconnectW,
+ FsSetAttrW,
+ FsSetTimeW,
+ FsSetDefaultParams,
+ FsGetPreviewBitmapW,
+ FsLinksToLocalFiles,
+ FsGetLocalNameW;
+
+begin
+ DllProc := @DLLEntryPoint;
+ DLLEntryPoint(DLL_PROCESS_ATTACH);
+end.
diff --git a/source/S3.dproj b/source/S3.dproj
new file mode 100644
index 0000000..ec16c9d
--- /dev/null
+++ b/source/S3.dproj
@@ -0,0 +1,1090 @@
+
+
+ {266D31E2-C5A3-4D94-B869-D7C79A053614}
+ S3.dpr
+ True
+ Debug
+ 3
+ Library
+ None
+ 19.3
+ Win64
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ ..\bin
+ ..\dcu\$(platform)\$(config)\
+ false
+ 00400000
+ false
+ false
+ 1043
+ System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace)
+ false
+ S3
+ false
+ true
+ CompanyName=NiftySoft;FileDescription=AWS S3 Plugin for Total Commander;FileVersion=1.0.0.0;InternalName=S3;LegalCopyright=Wouter van Nifterick, 2023;LegalTrademarks=;OriginalFilename=;ProductName=S3.wfx;ProductVersion=1.0.0.0;Comments=;CFBundleName=
+ true
+ true
+
+
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_1024x1024.png
+ CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers
+ iPhoneAndiPad
+ true
+ Debug
+ $(MSBuildProjectName)
+
+
+ CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers
+ Debug
+
+
+ CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers
+ Debug
+
+
+ true
+ 1033
+ CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
+ System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
+ (None)
+ .wfx
+ Z:\prog64\TotalCommander\TOTALCMD.EXE
+
+
+ true
+ 1033
+ CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
+ System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)
+ (None)
+ .wfx64
+ PerMonitorV2
+ Z:\prog64\TotalCommander\TOTALCMD64.EXE
+
+
+ RELEASE;$(DCC_Define)
+ false
+ 0
+ 0
+
+
+ true
+ 1033
+ 9
+ CompanyName=;FileVersion=1.0.0.9;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
+
+
+ true
+ 1033
+ 22
+ CompanyName=;FileVersion=1.0.0.22;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
+
+
+ DEBUG;$(DCC_Define)
+ true
+ true
+ false
+ true
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+ 1033
+ CompanyName=;FileVersion=1.0.0.190;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
+ 190
+
+
+ 1033
+ CompanyName=;FileVersion=1.0.0.448;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
+ 448
+ z:\prog64\TotalCommander\plugins\wfx\S3\
+ Z:\prog64\TotalCommander\TOTALCMD64.EXE
+ true
+ true
+ true
+
+
+
+ MainSource
+
+
+
+
+
+
+
+
+
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+ Cfg_2
+ Base
+
+
+
+ Delphi.Personality.12
+
+
+
+
+
+ Microsoft Office 2000 Sample Automation Server Wrapper Components
+ Microsoft Office XP Sample Automation Server Wrapper Components
+
+
+
+ False
+ False
+ False
+ True
+ True
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ S3.dll
+ true
+
+
+
+
+ S3.dll
+ true
+
+
+
+
+ true
+
+
+
+
+ 1
+
+
+ 0
+
+
+
+
+ classes
+ 64
+
+
+ classes
+ 64
+
+
+
+
+ classes
+ 1
+
+
+
+
+ res\xml
+ 1
+
+
+ res\xml
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\armeabi
+ 1
+
+
+ library\lib\armeabi
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\mips
+ 1
+
+
+ library\lib\mips
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\values-v21
+ 1
+
+
+ res\values-v21
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+
+
+ res\drawable-ldpi
+ 1
+
+
+ res\drawable-ldpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+
+
+ res\drawable-small
+ 1
+
+
+ res\drawable-small
+ 1
+
+
+
+
+ res\drawable-normal
+ 1
+
+
+ res\drawable-normal
+ 1
+
+
+
+
+ res\drawable-large
+ 1
+
+
+ res\drawable-large
+ 1
+
+
+
+
+ res\drawable-xlarge
+ 1
+
+
+ res\drawable-xlarge
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 0
+
+
+
+
+ 1
+ .framework
+
+
+ 1
+ .framework
+
+
+ 1
+ .framework
+
+
+ 0
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 0
+ .dll;.bpl
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 0
+ .bpl
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+
+ Contents\Resources
+ 1
+
+
+ Contents\Resources
+ 1
+
+
+ Contents\Resources
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 0
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 12
+
+
+
+
+
diff --git a/source/S3.res b/source/S3.res
new file mode 100644
index 0000000..fb6a353
Binary files /dev/null and b/source/S3.res differ
diff --git a/source/WFX_S3_Group.groupproj b/source/WFX_S3_Group.groupproj
new file mode 100644
index 0000000..ccb3c9e
--- /dev/null
+++ b/source/WFX_S3_Group.groupproj
@@ -0,0 +1,48 @@
+
+
+ {0BA6C6CB-5C4C-4E9D-A4EA-50362C904A6D}
+
+
+
+
+
+
+
+
+
+
+ Default.Personality.12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/Wfx.Plugin.Base.pas b/source/Wfx.Plugin.Base.pas
new file mode 100644
index 0000000..026c118
--- /dev/null
+++ b/source/Wfx.Plugin.Base.pas
@@ -0,0 +1,341 @@
+unit Wfx.Plugin.Base;
+
+interface
+
+uses
+ System.SysUtils,
+ System.Classes,
+ System.Generics.Collections,
+ Wfx.Plugin.Intf,
+ Wfx.Plugin.Consts,
+ WinApi.Windows;
+
+type
+
+ TWFXPlugin = class abstract(TInterfacedObject, IWfxPlugin)
+ protected
+ FEnterText : TRequestProcW;
+ FPluginNumber : Integer;
+ FProgressBarProc: TProgressProcW;
+ FLogProc : TLogProcW;
+ PathExe : string;
+ FFileListIndex : Integer;
+ FFileList : TList;
+ AbortCopy : Boolean;
+ FCurrentFile : TFileInfo;
+ DefaultParams : TFsDefaultParamStruct;
+ constructor Create; virtual;
+ destructor Destroy; override;
+
+ procedure LogDebug(const msg:string);
+ public
+ procedure RefreshTc(aMainWin:THandle);
+ function GetPluginName: string; virtual; abstract;
+ function ExecuteFile(aMainWin: THandle; aRemoteName, Verb: string): Integer; virtual; abstract;
+ function FindFirstFile(var aFindData: TWin32FindData; Path: string): THandle; virtual; abstract;
+ function FindNextFile(aHandle: THandle; var FindDataW: TWin32FindData): bool; virtual;
+ function GetFile(aRemoteName: string; aLocalName: string): Integer; virtual; abstract;
+ function Delete(const aRemoteName: string): Boolean; virtual; abstract;
+ procedure Init; virtual;
+
+ function GetDLLPathName: string;
+ procedure TCShowMessage(const aTitle, aText: string);
+ function Input(const aTitle, Question: string; var aText: string; aInputType: Integer = RT_Other): Boolean;
+ procedure BuildFindData(var aFindData: TWin32FindDataW; const aFileInfo: TFileInfo); virtual;
+
+ public
+ private procedure SetFileListIndex(const aValue: Integer);
+ private function GetFileListIndex: Integer;
+ public property FileListIndex: Integer read GetFileListIndex write SetFileListIndex;
+
+ private function GetPluginNumber: Integer;
+ private procedure SetPluginNumber(aValue: Integer);
+ public property PluginNumber: Integer read GetPluginNumber write SetPluginNumber;
+
+ private procedure SetEnterText(const aValue: TRequestProcW);
+ private function GetEnterText: TRequestProcW;
+ public property EnterText: TRequestProcW read GetEnterText write SetEnterText;
+
+ private procedure SetProgressBarProc(const aValue: TProgressProcW);
+ private function GetProgressBarProc: TProgressProcW;
+ public property ProgressBarProc: TProgressProcW read GetProgressBarProc write SetProgressBarProc;
+
+ private function GetLogProc: TLogProcW;
+ private procedure SetLogProc(const aValue: TLogProcW);
+ public property LogProc: TLogProcW read GetLogProc write SetLogProc;
+
+
+ private function GetFileCount: Integer;
+ public property FileCount: Integer read GetFileCount;
+
+ private function GetCurrentFile: TFileInfo;
+ private procedure SetCurrentFile(const aValue: TFileInfo);
+
+ property CurrentFile: TFileInfo read GetCurrentFile write SetCurrentFile;
+
+ public function GetIcon(RemoteName: string; ExtractFlags: Integer; var TheIcon: HICON): Integer;virtual;
+
+ procedure Connect(aRemoteName: string); virtual;
+ function Disconnect(aDisconnectRoot:String):Boolean; virtual;
+
+ procedure SetCryptCallback(CryptProcW:TCryptProcW;CryptoNr,Flags:integer); virtual;
+ function MkDir(RemoteDir:String):Boolean; virtual;
+ function RenMovFile(OldName,NewName:String;Move,OverWrite:Boolean; RemoteInfo:pRemoteInfo):integer; virtual;
+ function PutFile(LocalName,RemoteName:String;CopyFlags:integer):integer; virtual;
+ function RemoveDir(RemoteName:String):Boolean; virtual;
+ function SetAttr(RemoteName:String;NewAttr:integer):Boolean; virtual;
+ function SetTime(RemoteName:String;CreationTime,LastAccessTime, LastWriteTime:PFileTime):Boolean; virtual;
+ procedure SetDefaultParams(dps:pFsDefaultParamStruct); virtual;
+ function GetPreviewBitmap(RemoteName:String;width,height:integer; var ReturnedBitmap:hbitmap):integer; virtual;
+ function LinksToLocalFiles:Boolean; virtual;
+ function GetLocalName(var RemoteName:String;maxlen:integer):Boolean; virtual;
+ end;
+
+implementation
+
+{ TWFXPlugin }
+
+uses WinApi.Messages;
+
+function DateTimeToFileTime(MyDateTime: TDateTime): TFileTime;
+var
+ MyFileAge : Integer;
+ MyLocalFileTime: _FILETIME;
+begin
+ MyFileAge := DateTimeToFileDate(MyDateTime);
+ DosDateTimeToFileTime(LongRec(MyFileAge).Hi, LongRec(MyFileAge).Lo, MyLocalFileTime);
+ LocalFileTimeToFileTime(MyLocalFileTime, Result);
+end;
+
+
+
+constructor TWFXPlugin.Create;
+begin
+ PathExe := ExtractFilePath(GetDLLPathName);
+ FFileList := TList.Create;
+end;
+
+procedure TWFXPlugin.Connect(aRemoteName: string);
+begin
+ LogDebug('Connect ' + aRemoteName);
+end;
+
+destructor TWFXPlugin.Destroy;
+begin
+ Self.FFileList.Free;
+ inherited;
+end;
+
+function TWFXPlugin.Disconnect(aDisconnectRoot: String): Boolean;
+begin
+ LogDebug('Disconnect ' + aDisconnectRoot);
+ Result := True;
+end;
+
+function TWFXPlugin.FindNextFile(aHandle: THandle; var FindDataW: TWin32FindData): bool;
+begin
+ if aHandle = 0 then
+ Exit(false);
+
+ FileListIndex := FileListIndex + 1;
+ Result := FileCount > FileListIndex;
+ if Result then
+ BuildFindData(FindDataW, CurrentFile);
+end;
+
+function TWFXPlugin.GetCurrentFile: TFileInfo;
+begin
+ if FileListIndex < 0 then Exit(TFileInfo.Create);
+ if FileListIndex >= FFileList.Count then Exit(TFileInfo.Create);
+
+ Result := FFileList[FileListIndex];
+end;
+
+function TWFXPlugin.GetDLLPathName: string;
+begin
+ Result := GetModuleName(HInstance);
+end;
+
+function TWFXPlugin.GetEnterText: TRequestProcW;
+begin
+ Result := FEnterText
+end;
+
+function TWFXPlugin.GetFileCount: Integer;
+begin
+ if FFileList = nil then
+ Exit(0);
+
+ Result := FFileList.Count;
+end;
+
+function TWFXPlugin.GetFileListIndex: Integer;
+begin
+ Result := FFileListIndex;
+end;
+
+function TWFXPlugin.GetIcon(RemoteName: string; ExtractFlags: Integer; var TheIcon: HICON): Integer;
+begin
+ Result := 0;
+end;
+
+function TWFXPlugin.GetLocalName(var RemoteName: String; maxlen: integer): Boolean;
+begin
+ Result := True;
+end;
+
+function TWFXPlugin.GetLogProc: TLogProcW;
+begin
+ Result := FLogProc;
+end;
+
+function TWFXPlugin.GetPluginNumber: Integer;
+begin
+ Result := FPluginNumber;
+end;
+
+function TWFXPlugin.GetPreviewBitmap(RemoteName: String; width, height: integer; var ReturnedBitmap: hbitmap): integer;
+begin
+ Result := 0;
+end;
+
+function TWFXPlugin.GetProgressBarProc: TProgressProcW;
+begin
+ Result := FProgressBarProc;
+end;
+
+procedure TWFXPlugin.TCShowMessage(const aTitle, aText: string);
+const max_msg_size = 256;
+var a: array [0 .. max_msg_size] of char;
+begin
+ EnterText(FPluginNumber, RT_MsgOK, PChar(aTitle), PChar(aText), @a, max_msg_size);
+end;
+
+
+
+procedure TWFXPlugin.Init;
+begin
+
+end;
+
+procedure TWFXPlugin.BuildFindData(var aFindData: TWin32FindDataW; const aFileInfo: TFileInfo);
+begin
+ aFindData := Default (TWin32FindDataW);
+ StrCopy(aFindData.cFileName, PChar(aFileInfo.FileName));
+ aFindData.nFileSizeLow := aFileInfo.Size;
+ aFindData.ftCreationTime := DateTimeToFileTime(aFileInfo.Date);
+ aFindData.ftLastWriteTime := DateTimeToFileTime(aFileInfo.Date);
+ aFindData.ftLastAccessTime := DateTimeToFileTime(aFileInfo.Date);
+
+ if aFileInfo.ReadOnly then
+ aFindData.dwFileAttributes := aFindData.dwFileAttributes + FILE_ATTRIBUTE_READONLY;
+
+ if aFileInfo.IsVirtual then
+ aFindData.dwFileAttributes := aFindData.dwFileAttributes + FILE_ATTRIBUTE_VIRTUAL ;
+
+ if aFileInfo.FileType = TFileType.ftDir then
+ aFindData.dwFileAttributes := aFindData.dwFileAttributes + FILE_ATTRIBUTE_DIRECTORY;
+
+end;
+
+function TWFXPlugin.Input(const aTitle, Question: string; var aText: string; aInputType: Integer = RT_Other): Boolean;
+var
+ a: array [0 .. 255] of char;
+begin
+ StrCopy(@a, PChar(aText));
+ Result := EnterText(FPluginNumber, aInputType, PChar(aTitle), PChar(Question), @a, 256);
+ aText := a;
+end;
+
+function TWFXPlugin.LinksToLocalFiles: Boolean;
+begin
+ Result := False;
+end;
+
+procedure TWFXPlugin.LogDebug(const msg: string);
+begin
+ if not IsDebuggerPresent then
+ Exit;
+
+ OutputDebugString( PwideChar(PWideString(WideString(msg))));
+end;
+
+function TWFXPlugin.MkDir(RemoteDir: String): Boolean;
+begin
+ Result := False;
+end;
+
+function TWFXPlugin.PutFile(LocalName, RemoteName: String; CopyFlags: integer): integer;
+begin
+ Result := FS_FILE_OK;
+end;
+
+procedure TWFXPlugin.RefreshTc(aMainWin:THandle);
+begin
+ const cm_RereadSource = 540;
+ PostMessage(aMainWin, WM_USER+51, cm_RereadSource, 0);
+
+end;
+
+function TWFXPlugin.RemoveDir(RemoteName: String): Boolean;
+begin
+ Result := False;
+end;
+
+function TWFXPlugin.RenMovFile(OldName, NewName: String; Move, OverWrite: Boolean; RemoteInfo: pRemoteInfo): integer;
+begin
+ Result := FS_FILE_OK;
+end;
+
+function TWFXPlugin.SetAttr(RemoteName: String; NewAttr: integer): Boolean;
+begin
+ Result := False;
+end;
+
+procedure TWFXPlugin.SetCryptCallback(CryptProcW: TCryptProcW; CryptoNr, Flags: integer);
+begin
+
+end;
+
+procedure TWFXPlugin.SetCurrentFile(const aValue: TFileInfo);
+begin
+ FCurrentFile := aValue;
+end;
+
+procedure TWFXPlugin.SetDefaultParams(dps: pFsDefaultParamStruct);
+begin
+ DefaultParams := dps^;
+end;
+
+procedure TWFXPlugin.SetEnterText(const aValue: TRequestProcW);
+begin
+ FEnterText := aValue;
+end;
+
+procedure TWFXPlugin.SetFileListIndex(const aValue: Integer);
+begin
+ FFileListIndex := aValue;
+end;
+
+procedure TWFXPlugin.SetLogProc(const aValue: TLogProcW);
+begin
+ FLogProc := aValue
+end;
+
+procedure TWFXPlugin.SetPluginNumber(aValue: Integer);
+begin
+ FPluginNumber := aValue;
+end;
+
+procedure TWFXPlugin.SetProgressBarProc(const aValue: TProgressProcW);
+begin
+ FProgressBarProc := aValue;
+end;
+
+
+function TWFXPlugin.SetTime(RemoteName: String; CreationTime, LastAccessTime, LastWriteTime: PFileTime): Boolean;
+begin
+ Result := False;
+end;
+
+end.
diff --git a/source/Wfx.Plugin.Consts.pas b/source/Wfx.Plugin.Consts.pas
new file mode 100644
index 0000000..8de4dcb
--- /dev/null
+++ b/source/Wfx.Plugin.Consts.pas
@@ -0,0 +1,133 @@
+unit Wfx.Plugin.Consts;
+
+interface
+
+uses
+ Winapi.Windows;
+
+const
+ INIT_OK = 0;
+
+{ ids for FsGetFile }
+const
+ FS_FILE_OK = 0;
+ FS_FILE_EXISTS = 1;
+ FS_FILE_NOTFOUND = 2;
+ FS_FILE_READERROR = 3;
+ FS_FILE_WRITEERROR = 4;
+ FS_FILE_USERABORT = 5;
+ FS_FILE_NOTSUPPORTED = 6;
+ FS_FILE_EXISTSRESUMEALLOWED = 7;
+
+const
+ FS_EXEC_OK = 0;
+ FS_EXEC_ERROR = 1;
+ FS_EXEC_YOURSELF = -1;
+ FS_EXEC_SYMLINK = -2;
+
+const
+ FS_COPYFLAGS_OVERWRITE = 1;
+ FS_COPYFLAGS_RESUME = 2;
+ FS_COPYFLAGS_MOVE = 4;
+ FS_COPYFLAGS_EXISTS_SAMECASE = 8;
+ FS_COPYFLAGS_EXISTS_DIFFERENTCASE = 16;
+
+ { flags for tRequestProc }
+const
+ RT_Other = 0;
+ RT_UserName = 1;
+ RT_Password = 2;
+ RT_Account = 3;
+ RT_UserNameFirewall = 4;
+ RT_PasswordFirewall = 5;
+ RT_TargetDir = 6;
+ RT_URL = 7;
+
+ RT_MsgOK = 8;
+ RT_MsgYesNo = 9;
+ RT_MsgOKCancel = 10;
+
+ { flags for tLogProc }
+type
+ TMsgType =
+ record
+ const
+ connect = 1;
+ disconnect = 2;
+ details = 3;
+ transfercomplete = 4;
+ connectcomplete = 5;
+ importanterror = 6;
+ operationcomplete = 7;
+ end;
+
+ { flags for FsStatusInfo }
+const
+ FS_STATUS_START = 0;
+ FS_STATUS_END = 1;
+
+const
+ FS_STATUS_OP_LIST = 1;
+ FS_STATUS_OP_GET_SINGLE = 2;
+ FS_STATUS_OP_GET_MULTI = 3;
+ FS_STATUS_OP_PUT_SINGLE = 4;
+ FS_STATUS_OP_PUT_MULTI = 5;
+ FS_STATUS_OP_RENMOV_SINGLE = 6;
+ FS_STATUS_OP_RENMOV_MULTI = 7;
+ FS_STATUS_OP_DELETE = 8;
+ FS_STATUS_OP_ATTRIB = 9;
+ FS_STATUS_OP_MKDIR = 10;
+ FS_STATUS_OP_EXEC = 11;
+ FS_STATUS_OP_CALCSIZE = 12;
+ FS_STATUS_OP_SEARCH = 13;
+ FS_STATUS_OP_SEARCH_TEXT = 14;
+
+ { Flags for FsExtractCustomIcon }
+const
+ FS_ICONFLAG_SMALL = 1;
+ FS_ICONFLAG_BACKGROUND = 2;
+
+ FS_ICON_USEDEFAULT = 0;
+ FS_ICON_EXTRACTED = 1;
+ FS_ICON_EXTRACTED_DESTROY = 2;
+ FS_ICON_DELAYED = 3;
+
+type
+ TRemoteInfo =
+ record
+ SizeLow : Integer;
+ SizeHigh : Integer;
+ LastWriteTime : TFileTime;
+ Attr : Integer;
+ end;
+
+ pRemoteInfo = ^TRemoteInfo;
+
+type
+ TFsDefaultParamStruct =
+ record
+ Size : Integer;
+ PluginInterfaceVersionLow : Integer;
+ PluginInterfaceVersionHi : Integer;
+ DefaultIniName : array [0 .. MAX_PATH - 1] of AnsiChar;
+ end;
+
+ pFsDefaultParamStruct = ^TFsDefaultParamStruct;
+
+ { callback functions }
+type
+ TProgressProc = function (PluginNr: Integer; SourceName, TargetName: PAnsiChar; PercentDone: Integer) : Integer; stdcall;
+ TProgressProcW = function (PluginNr: Integer; SourceName, TargetName: PWideChar; PercentDone: Integer): Integer; stdcall;
+ TLogProc = procedure(PluginNr: Integer; MsgType : Integer; LogString : PAnsiChar); stdcall;
+ TLogProcW = procedure(PluginNr: Integer; MsgType : Integer; LogString : PWideChar); stdcall;
+ TRequestProc = function (PluginNr: Integer; RequestType: Integer; CustomTitle, CustomText, ReturnedText: PAnsiChar; maxlen: Integer): bool; stdcall;
+ TRequestProcW = function (PluginNr: Integer; RequestType: Integer; CustomTitle, CustomText, ReturnedText: PWideChar; maxlen: Integer): bool; stdcall;
+
+ PCryptProc = ^TCryptProc;
+ TCryptProc = function (PluginNr, CryptoNumber:integer; mode:integer; ConnectionName, Password:PAnsiChar; maxlen:integer):integer; stdcall;
+ PCryptProcW = ^TCryptProcW;
+ TCryptProcW = function (PluginNr, CryptoNumber:integer; mode:integer; ConnectionName, Password:PWideChar; maxlen:integer):integer; stdcall;
+
+implementation
+
+end.
diff --git a/source/Wfx.Plugin.ExportProcs.pas b/source/Wfx.Plugin.ExportProcs.pas
new file mode 100644
index 0000000..594d799
--- /dev/null
+++ b/source/Wfx.Plugin.ExportProcs.pas
@@ -0,0 +1,198 @@
+unit Wfx.Plugin.ExportProcs;
+
+interface
+
+uses
+ System.SysUtils,
+ Vcl.Dialogs,
+ System.Classes,
+ WinApi.Windows,
+ WinApi.ShellApi,
+ System.AnsiStrings,
+ Wfx.Plugin.Consts;
+
+procedure DLLEntryPoint(dwReason: DWORD);
+function FsInitW(aPluginNr: Integer; aProgressProcW: TProgressProcW; aLogProcW: TLogProcW; aRequestProcW: TRequestProcW): Integer; stdcall;
+
+function FsExecuteFileW(MainWin: THandle; RemoteName, Verb: PWideChar): Integer; stdcall;
+function FsFindFirstW(aPath: PWideChar; aFindData: PWin32FindDataW): Integer; stdcall;
+function FsFindNextW(aHandle: THandle; var FindDataW: TWin32FindDataW): bool; stdcall;
+function FsFindClose(Hdl: THandle): Integer; stdcall;
+function FsDeleteFileW(aRemoteName: PWideChar): bool; stdcall;
+function FsGetFileW(RemoteName, LocalName: PWideChar; CopyFlags: Integer; RemoteInfo: pRemoteInfo): Integer; stdcall;
+procedure FsGetDefRootName(DefRootName: PAnsiChar; MaxLen: Integer); stdcall;
+procedure FsStatusInfoW(RemoteDir: PWideChar; InfoStartEnd, InfoOperation: Integer); stdcall;
+function FsExtractCustomIconW(RemoteName: PWideChar; ExtractFlags: Integer; var TheIcon: HIcon): Integer; stdcall;
+
+procedure FsSetCryptCallbackW(CryptProcW:TCryptProcW;CryptoNr,Flags:integer); stdcall;
+function FsMkDirW(RemoteDir:pwidechar):bool; stdcall;
+function FsRenMovFileW(OldName,NewName:pwidechar;Move,OverWrite:bool; RemoteInfo:pRemoteInfo):integer; stdcall;
+function FsPutFileW(LocalName,RemoteName:pwidechar;CopyFlags:integer):integer; stdcall;
+function FsRemoveDirW(RemoteName:pwidechar):bool; stdcall;
+procedure FsConnectW(RemoteName:pwidechar); stdcall;
+function FsDisconnectW(DisconnectRoot:pwidechar):bool; stdcall;
+function FsSetAttrW(RemoteName:pwidechar;NewAttr:integer):bool; stdcall;
+function FsSetTimeW(RemoteName:pwidechar;CreationTime,LastAccessTime, LastWriteTime:PFileTime):bool; stdcall;
+procedure FsSetDefaultParams(dps:pFsDefaultParamStruct); stdcall;
+function FsGetPreviewBitmapW(RemoteName:pwidechar;width,height:integer; var ReturnedBitmap:hbitmap):integer; stdcall;
+function FsLinksToLocalFiles:bool; stdcall;
+function FsGetLocalNameW(RemoteName:pwidechar;maxlen:integer):bool; stdcall;
+
+implementation
+
+uses
+ Wfx.Plugin.Intf;
+
+var
+ Plugin: IWfxPlugin;
+
+procedure DLLEntryPoint(dwReason: DWORD);
+begin
+ case dwReason of
+ DLL_PROCESS_ATTACH:
+ Plugin := globalPluginFactory;
+ DLL_PROCESS_DETACH:
+ Plugin := nil;
+ end;
+end;
+
+function FsInitW(aPluginNr: Integer; aProgressProcW: TProgressProcW; aLogProcW: TLogProcW; aRequestProcW: TRequestProcW): Integer; stdcall;
+begin
+ Plugin.PluginNumber := aPluginNr;
+ Plugin.EnterText := aRequestProcW;
+ Plugin.ProgressBarProc := aProgressProcW;
+ Plugin.LogProc := aLogProcW;
+ Plugin.Init;
+ Result := INIT_OK
+end;
+
+function FsExecuteFileW(MainWin: THandle; RemoteName, Verb: PWideChar): Integer; stdcall;
+begin
+ Result := Plugin.ExecuteFile(MainWin, RemoteName, Verb);
+end;
+
+function FsFindFirstW(aPath: PWideChar; aFindData: PWin32FindDataW): Integer; stdcall;
+begin
+ Result := Plugin.FindFirstFile(aFindData^, aPath);
+ if Plugin.FileCount = 0 then
+ begin
+ SetLastError(ERROR_NO_MORE_FILES);
+ aFindData.dwFileAttributes := 0;
+ Result := -1;
+ end;
+end;
+
+function FsFindNextW(aHandle: THandle; var FindDataW: TWin32FindDataW): bool; stdcall;
+begin
+ if Plugin.FileCount = 0 then
+ Exit(False);
+
+ Result := Plugin.FindNextFile(aHandle, FindDataW);
+end;
+
+function FsFindClose(Hdl: THandle): Integer; stdcall;
+begin
+ Result := 0;
+end;
+
+function FsDeleteFileW(aRemoteName: PWideChar): bool; stdcall;
+begin
+ Result := Plugin.Delete(aRemoteName);
+end;
+
+function FsGetFileW(RemoteName, LocalName: PWideChar; CopyFlags: Integer; RemoteInfo: pRemoteInfo): Integer; stdcall;
+begin
+ Result := Plugin.GetFile(RemoteName, LocalName)
+end;
+
+procedure FsGetDefRootName(DefRootName: PAnsiChar; MaxLen: Integer); stdcall;
+begin
+ System.AnsiStrings.StrLCopy(DefRootName, PAnsiChar(AnsiString(Plugin.GetPluginName)), MaxLen - 1);
+end;
+
+procedure FsStatusInfoW(RemoteDir: PWideChar; InfoStartEnd, InfoOperation: Integer); stdcall;
+begin
+ if (InfoStartEnd <> FS_STATUS_END) and
+ (InfoOperation = FS_STATUS_OP_GET_MULTI) then
+ begin
+
+ end;
+end;
+
+function FsExtractCustomIconW(RemoteName: PWideChar; ExtractFlags: Integer; var TheIcon: HIcon): Integer; stdcall;
+begin
+ Result := Plugin.GetIcon(RemoteName, ExtractFlags, TheIcon);
+end;
+
+
+procedure FsSetCryptCallbackW(CryptProcW:TCryptProcW;CryptoNr,Flags:integer); stdcall;
+begin
+ Plugin.SetCryptCallback(CryptProcW, CryptoNr, Flags)
+end;
+
+function FsMkDirW(RemoteDir:pwidechar):bool; stdcall;
+begin
+ Result := Plugin.MkDir(RemoteDir);
+end;
+
+function FsRenMovFileW(OldName,NewName:pwidechar;Move,OverWrite:bool; RemoteInfo:pRemoteInfo):integer; stdcall;
+begin
+ Result := Plugin.RenMovFile(OldName, newName, Move, Overwrite, remoteInfo);
+end;
+
+function FsPutFileW(LocalName,RemoteName:pwidechar;CopyFlags:integer):integer; stdcall;
+begin
+ Result := Plugin.PutFile(LocalName, RemoteName, CopyFlags);
+end;
+
+function FsRemoveDirW(RemoteName:pwidechar):bool; stdcall;
+begin
+ Result := Plugin.RemoveDir(RemoteName)
+end;
+
+procedure FsConnectW(RemoteName:pwidechar); stdcall;
+begin
+ Plugin.Connect(RemoteName);
+end;
+
+function FsDisconnectW(DisconnectRoot:pwidechar):bool; stdcall;
+begin
+ Result := Plugin.Disconnect(DisconnectRoot)
+end;
+
+function FsSetAttrW(RemoteName:pwidechar;NewAttr:integer):bool; stdcall;
+begin
+ Result := Plugin.SetAttr(RemoteName, NewAttr)
+end;
+
+function FsSetTimeW(RemoteName:pwidechar;CreationTime,LastAccessTime, LastWriteTime:PFileTime):bool; stdcall;
+begin
+ Result := Plugin.SetTime(RemoteName, CreationTime, LastAccessTime, LastWriteTime)
+end;
+
+procedure FsSetDefaultParams(dps:pFsDefaultParamStruct); stdcall;
+begin
+ Plugin.SetDefaultParams(dps)
+end;
+
+function FsGetPreviewBitmapW(RemoteName:pwidechar;width,height:integer; var ReturnedBitmap:hbitmap):integer; stdcall;
+begin
+ Result := Plugin.GetPreviewBitmap(RemoteName, width, height, ReturnedBitmap)
+end;
+
+function FsLinksToLocalFiles:bool; stdcall;
+begin
+ Result := Plugin.LinksToLocalFiles
+end;
+
+function FsGetLocalNameW(RemoteName:pwidechar;maxlen:integer):bool; stdcall;
+var
+ rem, loc:string;
+begin
+ loc := RemoteName;
+ Result := Plugin.GetLocalName(loc, maxlen)
+end;
+
+
+
+end.
diff --git a/source/Wfx.Plugin.S3.Path.pas b/source/Wfx.Plugin.S3.Path.pas
new file mode 100644
index 0000000..e19d885
--- /dev/null
+++ b/source/Wfx.Plugin.S3.Path.pas
@@ -0,0 +1,105 @@
+unit Wfx.Plugin.S3.Path;
+
+interface
+
+type
+ TS3TcPath = record
+ BucketName: string;
+ TcPathWithoutBucket :string;
+ S3Path :string;
+ function IsRoot(const aRemoteName:string): Boolean;
+ function IsBucket(const aRemoteName:string): Boolean;
+ function IsS3Object(const aRemoteName:string): Boolean;
+ function GetBucketName(const aRemoteName:string):string;
+ function StripAnyBucket(const aRemoteName:string):string;
+ function StripKnownBucket(const aRemoteName:string):string;
+ function RemoteNameToS3(const aRemoteName:string):string;
+ function GetS3FileName(const aRemoteName:string):string;
+ function GetPrefix(const aRemoteName:string):string;
+ constructor Create(aBucketName:string);
+ end;
+
+implementation
+
+uses
+ System.SysUtils,
+ System.Classes,
+ System.AnsiStrings,
+ System.IOUtils
+;
+
+
+{ TS3TcPath }
+
+function TS3TcPath.RemoteNameToS3(const aRemoteName: string): string;
+begin
+ Result := aRemoteName.Replace('\' + BucketName + '\', '');
+end;
+
+function TS3TcPath.StripAnyBucket(const aRemoteName: string): string;
+begin
+ const LBucketName = GetBucketName(aRemoteName);
+
+ Result :=
+ aRemoteName
+ .TrimLeft(['\'])
+ .Replace(LBucketName,'')
+ .TrimLeft(['\'])
+ .Replace('\','/');
+
+end;
+
+function TS3TcPath.StripKnownBucket(const aRemoteName: string): string;
+begin
+ Result :=
+ aRemoteName
+ .TrimLeft(['\'])
+ .Replace(BucketName,'')
+ .TrimLeft(['\'])
+ .Replace('\','/');
+
+end;
+
+function TS3TcPath.IsBucket(const aRemoteName: string): Boolean;
+begin
+ Result := aRemoteName.StartsWith('\') and aRemoteName.EndsWith('\') and
+ (aRemoteName.TrimLeft(['\']).TrimRight(['\']) = aRemoteName.Replace('\',''));
+end;
+
+function TS3TcPath.IsRoot(const aRemoteName: string): Boolean;
+begin
+ Result := aRemoteName = '\';
+end;
+
+function TS3TcPath.IsS3Object(const aRemoteName: string): Boolean;
+begin
+ Result := not IsBucket(aRemoteName)
+end;
+
+function TS3TcPath.GetS3FileName(const aRemoteName: string): string;
+begin
+ Result := TPath.GetFileName(StripKnownBucket(aRemoteName))
+end;
+
+constructor TS3TcPath.Create(aBucketName: string);
+begin
+ BucketName := aBucketName;
+end;
+
+function TS3TcPath.GetBucketName(const aRemoteName: string): string;
+begin
+ var Slugs := aRemoteName.TrimLeft(['\']).Split(['\']);
+ if length(Slugs) > 0 then
+ Result :=Slugs[0]
+ else
+ Result := '';
+end;
+
+function TS3TcPath.GetPrefix(const aRemoteName: string): string;
+begin
+ Result := StripAnyBucket(aRemoteName);
+ if Result <> '' then
+ Result := Result + '/'
+end;
+
+end.
diff --git a/source/Wfx.Plugin.S3.pas b/source/Wfx.Plugin.S3.pas
new file mode 100644
index 0000000..e909a9e
--- /dev/null
+++ b/source/Wfx.Plugin.S3.pas
@@ -0,0 +1,554 @@
+unit Wfx.Plugin.S3;
+
+interface
+
+uses
+ System.SysUtils,
+ System.Classes,
+ System.AnsiStrings,
+ System.IOUtils,
+ System.Generics.Collections,
+ System.IniFiles,
+ System.TypInfo,
+ WinApi.ShFolder,
+
+ WinApi.Windows,
+ WinApi.Messages,
+
+ Data.Cloud.CloudAPI,
+ Data.Cloud.AmazonAPI,
+
+ Wfx.Plugin.intf,
+ Wfx.Plugin.Base,
+ Wfx.Plugin.Consts,
+ Wfx.Plugin.S3.Path, Vcl.Dialogs;
+
+type
+ TPluginMode = ( pmInit, pmPickProfile, pmPickBucket, pmShowFolderContents );
+ TS3Plugin = class(TWFXPlugin, IWfxPlugin)
+ const
+ PLUGIN_NAME = 'S3';
+ private
+ procedure SetBucketName(const Value: string);
+ procedure ConnectToS3;
+ protected
+ FConnectionInfo: TAmazonConnectionInfo;
+ S3 : TAmazonStorageService;
+ FBuckets : TStrings;
+ FRegion : string;
+ FBucketName : string;
+ FCurrentPath : string;
+ Path : TS3TcPath;
+ FProfile : string;
+ FPickProfile : TFileInfo;
+ PluginMode : TPluginMode;
+ FProfiles : TStringList;
+ public
+ constructor Create; override;
+ destructor Destroy; override;
+ procedure Init; override;
+ function GetPluginName: string;override;
+ function GetUserPath:string;
+ function FindFileInfo(aRemoteName: string; var fi:TFileInfo):boolean;
+ function ExecuteFile(aMainWin: THandle; aRemoteName, aVerb: string): Integer; override;
+ function FindFirstFile(var aFindData: TWin32FindData; aPath: string): THandle; override;
+ function GetFile(aRemoteName: string; aLocalName: string): Integer;override;
+ function GetIcon(aRemoteName: string; ExtractFlags: Integer; var TheIcon: HICON): Integer;override;
+ function Delete(const aRemoteName: string): Boolean; override;
+ function MkDir(aRemoteDir:String):Boolean;override;
+ function RenMovFile(aOldName,aNewName:String;aMove,aOverWrite:Boolean; aRemoteInfo:pRemoteInfo):integer;override;
+ function PutFile(aLocalName,aRemoteName:String;aCopyFlags:integer):integer;override;
+ function RemoveDir(aRemoteName:String):Boolean;override;
+ function Disconnect(aDisconnectRoot:String):Boolean;override;
+ function GetLocalName(var aRemoteName:String;maxlen:integer):Boolean;override;
+
+ property BucketName:string read FBucketName write SetBucketName;
+
+
+ end;
+
+implementation
+
+function ISO8601ToDateTime(Value: String):TDateTime;
+var
+ fs: TFormatSettings;
+begin
+ fs := TFormatSettings.Create(GetThreadLocale);
+ fs.DateSeparator := '-';
+ fs.ShortDateFormat := 'yyyy-MM-dd';
+ Value := Value.Replace('T', ' ').Replace('Z','').Substring(0,19);
+ Result := StrToDateTime(Value, fs);
+end;
+
+procedure TS3Plugin.ConnectToS3;
+begin
+ const awsPath = GetUserPath;
+ const credentials = TPath.Combine(TPath.Combine(awsPath,'.aws'),'credentials');
+
+ FProfiles.Free;
+ FProfiles := TStringList.Create;
+ var AccountName := '';
+ var AccountKey := '';
+
+ if TFile.Exists(credentials) then
+ begin
+ const ini = TIniFile.Create(credentials);
+ try
+ AccountName := ini.ReadString(FProfile,'aws_access_key_id',AccountName);
+ AccountKey := ini.ReadString(FProfile,'aws_secret_access_key',AccountKey);
+ FRegion := ini.ReadString(FProfile,'region',FRegion);
+ ini.ReadSections(FProfiles);
+ finally
+ ini.Free;
+ end;
+ end;
+
+ FConnectionInfo.Free;
+ FConnectionInfo := TAmazonConnectionInfo.Create(nil);
+ FConnectionInfo.AccountName := AccountName;
+ FConnectionInfo.AccountKey := AccountKey;
+ FConnectionInfo.Region := FREgion;
+ S3.Free;
+ S3 := TAmazonStorageService.Create(FConnectionInfo);
+
+ if (AccountName = '') or
+ (AccountKey = '') or
+ (FRegion = '') then
+ PluginMode := TPluginMode.pmPickProfile
+ else
+ PluginMode := TPluginMode.pmPickBucket;
+
+
+end;
+
+constructor TS3Plugin.Create;
+begin
+ inherited;
+ PluginMode := TPluginMode.pmInit;
+ Path := TS3TcPath.Create('');
+ FProfiles := TStringList.Create;
+ FBuckets := TStringList.Create;
+
+end;
+
+function TS3Plugin.Delete(const aRemoteName: string): Boolean;
+ var res:TCloudResponseInfo;
+ var s3ObjectName: string;
+
+begin
+ res := TCloudResponseInfo.Create;
+ try
+ if Path.IsBucket(aRemoteName) then
+ begin
+ Result := S3.DeleteBucket(Path.GetBucketName(aRemoteName), res, FRegion );
+ end
+ else
+ begin
+ s3ObjectName := Path.StripKnownBucket(aRemoteName);
+ Result := S3.DeleteObject( BucketName, s3ObjectName, res, FRegion );
+ end;
+ finally
+ LogDebug(res.StatusMessage);
+ res.Free;
+ end;
+end;
+
+destructor TS3Plugin.Destroy;
+begin
+ FBuckets.Free;
+ S3.Free;
+ FConnectionInfo.Free;
+ inherited;
+end;
+
+function TS3Plugin.Disconnect(aDisconnectRoot: String): Boolean;
+begin
+ inherited;
+ Result := false;
+end;
+
+function TS3Plugin.ExecuteFile(aMainWin: THandle; aRemoteName, aVerb: string): Integer;
+begin
+ try
+ var f:TFileinfo;
+ if not self.FindFileInfo(aRemoteName, f) then
+ Exit(FS_EXEC_ERROR);
+
+ if f.FileType = TFileType.ftAction then
+ begin
+ if Assigned(f.OnExecute) then
+ f.OnExecute(aMainWin, aRemoteName, aVerb, @f);
+ end;
+ Result := FS_EXEC_OK;
+ except
+ on E: Exception do
+ begin
+ Result := FS_EXEC_ERROR;
+ TCShowMessage('', E.Message);
+ end;
+ end;
+end;
+
+function TS3Plugin.FindFileInfo(aRemoteName: string; var fi:TFileInfo):boolean;
+begin
+ const a = aRemoteName.Split(['\']);
+
+ var l := '';
+ if length(a)>0 then
+ l := a[high(a)]
+ else
+ l := aRemoteName;
+
+ for var f in FFileList do
+ begin
+ if f.FileName = l then
+ begin
+ fi := f;
+ Exit(True);
+ end;
+ end;
+ Result := False;
+end;
+
+function TS3Plugin.FindFirstFile(var aFindData: TWin32FindData; aPath: string): THandle;
+begin
+ FCurrentPath := aPath;
+
+ FFileList.Clear;
+
+ if pluginMode=TPluginMode.pmShowFolderContents then
+ if Path.IsRoot(FCurrentPath) then
+ PluginMode := TPluginMode.pmPickBucket;
+
+ case pluginMode of
+ pmInit:
+ FFileList.Add(FPickProfile);
+
+ pmPickProfile:
+ begin
+ for var p in FProfiles do
+ begin
+ var f := TFileInfo.Create;
+ f.FileName := p;
+ f.IsVirtual := True;
+ f.OnExecute :=
+ procedure(aMainWin: THandle; aRemoteName, aVerb: string; sender:PFileInfo)
+ begin
+ const a = aRemoteName.Split(['\']);
+
+ var l := '';
+ if length(a)>0 then
+ l := a[high(a)]
+ else
+ l := aRemoteName;
+
+ PluginMode := TPluginMode.pmPickBucket;
+
+ FProfile := l;
+ ConnectToS3;
+ RefreshTc(aMainWin);
+ end;
+ f.FileType := TFileType.ftAction;
+
+ FFileList.Add(f);
+ end;
+ end;
+ pmPickBucket:
+ begin
+ FFileList.Add(FPickProfile);
+ FBuckets := S3.ListBuckets;
+ LogDebug('Retrieved bucket list');
+ for var bucketName in FBuckets do
+ begin
+ var FileInfo:= TFileInfo.Create;
+ var item := bucketName.Split(['=']);
+ if length(item) = 2 then
+ begin
+ FileInfo.FileName := item[0];
+ FileInfo.Date := ISO8601ToDateTime(item[1]);
+ end
+ else
+ begin
+ FileInfo.FileName := bucketName;
+ FileInfo.Date := 0;
+ end;
+
+ FileInfo.Directory := '';
+ FileInfo.ReadOnly := True;
+ FileInfo.Size := 0;
+ FileInfo.FileType := TFileType.ftDir;
+ FileInfo.OnExecute :=
+ procedure(aMainWin: THandle; aRemoteName, aVerb: string; sender:PFileInfo)
+ begin
+ PluginMode := TPluginMode.pmShowFolderContents;
+ end;
+ FFileList.Add(FileInfo);
+ end;
+ FFileListIndex := 0;
+ PluginMode := TPluginMode.pmShowFolderContents;
+ end;
+ pmShowFolderContents:
+ begin
+ const LBucketName = Path.GetBucketName(FCurrentPath);
+ const LDirectory = Path.GetPrefix(FCurrentPath);
+ var res := TCloudResponseInfo.Create;
+ var params := TStringList.Create;
+ params.AddPair('prefix', LDirectory );
+
+ var LBucketResult := S3.GetBucket(LBucketName, params, res, FRegion);
+
+ if LBucketResult <> nil then
+ begin
+ BucketName := LBucketResult.Name;
+ for var o in LBucketResult.Objects do
+ begin
+ if o.Name = LDirectory then
+ Continue;
+
+ var FileInfo:= TFileInfo.Create;
+ var Name := o.Name.TrimRight(['/']).Replace('/','\');
+
+ FileInfo.Directory := LBucketResult.RequestPrefix;
+ FileInfo.Date := ISO8601ToDateTime(o.LastModified);
+ FileInfo.Size := o.Size;
+ if o.Name.EndsWith('/') then
+ FileInfo.FileType := TFileType.ftDir
+ else
+ FileInfo.FileType := TFileType.ftFile;
+
+ FileInfo.ReadOnly := false;
+ const prefix = ExtractFilePath(Name).Replace('\','/');
+ if prefix <> LBucketResult.RequestPrefix then
+ Continue;
+
+ FileInfo.FileName := Name.Substring(length(Prefix),MaxInt);
+
+ FFileList.Add(fileInfo);
+ end;
+ end
+ else
+ TCShowMessage('Error', res.StatusMessage );
+
+ res.Free;
+ end;
+
+
+ end;
+
+
+ if FFileList.Count > 0 then
+ begin
+ FFileListIndex := 0;
+ BuildFindData(aFindData, FFileList[0]);
+ end
+ else
+ begin
+ FFileListIndex := -1;
+ end;
+ Result := 1;
+
+end;
+
+procedure TS3Plugin.Init;
+begin
+ inherited;
+
+ FProfile := 'default';
+ FRegion := 'eu-west-1';
+ FPickProfile.FileName := '[PICK AWS PROFILE]';
+
+ FPickProfile.FileType := TFileType.ftAction;
+ FPickProfile.ReadOnly := True;
+ FPickProfile.Size := 0;
+ FPickProfile.OnExecute :=
+ procedure(aMainWin: THandle; aRemoteName, aVerb: string; sender:PFileInfo)
+ begin
+ PluginMode := TPluginMode.pmPickProfile;
+ RefreshTc(aMainWin);
+ end;
+
+ PluginMode := TPluginMode.pmPickProfile;
+
+ ConnectToS3;
+
+ FBuckets.Free;
+ FBuckets := TStringList.Create;
+end;
+
+function TS3Plugin.MkDir(aRemoteDir: String): Boolean;
+var res:TCloudResponseInfo;
+begin
+ inherited;
+ aRemoteDir := aRemoteDir.TrimLeft(['\']);
+ var Slugs := aRemoteDir.Split(['\']);
+ if length(Slugs) = 1 then
+ begin
+ // we're at the root, so let's create a bucket
+ res := TCloudResponseInfo.Create;
+ Result := S3.CreateBucket(aRemoteDir, TAmazonACLType.amzbaPrivate, FRegion, res);
+ end
+ else
+ begin
+ // we're in a bucket, so let's create a folder
+ res := TCloudResponseInfo.Create;
+ var s3Name := Path.StripKnownBucket(aRemoteDir) + '/';
+ S3.UploadObject(BucketName, s3Name, [], false, nil, nil, TAmazonACLType.amzbaNotSpecified, res, FRegion );
+
+ end;
+ Exit(False);
+end;
+
+function TS3Plugin.PutFile(aLocalName, aRemoteName: String; aCopyFlags: integer): integer;
+var res:TCloudResponseInfo;
+begin
+ if BucketName = '' then
+ begin
+ TCShowMessage('Cannot upload file', 'Select a bucket first');
+ Exit(FS_FILE_OK);
+ end;
+
+ try
+ res := TCloudResponseInfo.Create;
+ var s3Name := Path.StripKnownBucket(aRemoteName);
+ S3.UploadObject(BucketName, s3Name, TFile.ReadAllBytes(aLocalName), false, nil, nil, TAmazonACLType.amzbaNotSpecified, res, FRegion );
+ Result := FS_FILE_OK;
+ except
+ Result := FS_FILE_WRITEERROR;
+ end;
+end;
+
+
+
+function TS3Plugin.GetPluginName: string;
+begin
+ Result := PLUGIN_NAME;
+end;
+
+function TS3Plugin.RemoveDir(aRemoteName: String): Boolean;
+begin
+ Result := Delete(aRemoteName);
+end;
+
+function TS3Plugin.RenMovFile(aOldName, aNewName: String; aMove, aOverWrite: Boolean; aRemoteInfo: pRemoteInfo): integer;
+begin
+ try
+ // Shitty, S3 does not support renaming of objects directly.
+ // Amazon suggests creating a new object, and deleting the old
+ var params := TAmazonGetObjectOptionals.Create;
+
+ var oldName := Path.StripAnyBucket(aOldName);
+ var oldBucket := Path.GetBucketName(aOldName);
+ var newName := Path.StripAnyBucket(aNewName);
+ var newBucket := Path.GetBucketName(aNewName);
+
+ S3.CopyObject(newBucket, newName, oldBucket, oldName, nil, nil, FRegion);
+ if oldBucket = newBucket then
+ S3.DeleteObject(oldBucket, oldName, nil, FRegion) ;
+ Result := FS_FILE_OK;
+ except
+ Result := 1;
+ end;
+end;
+
+procedure TS3Plugin.SetBucketName(const Value: string);
+begin
+ FBucketName := Value;
+ Path.BucketName := Value;
+end;
+
+function TS3Plugin.GetUserPath: string;
+var
+ LStr: array[0 .. MAX_PATH] of Char;
+begin
+ const CSIDL_PROFILE = $28;
+ SetLastError(ERROR_SUCCESS);
+ if SHGetFolderPath(0, CSIDL_PROFILE, 0, 0, @LStr) = S_OK then
+ Result := LStr;
+end;
+
+function TS3Plugin.GetFile(aRemoteName, aLocalName: string): Integer;
+begin
+ try
+ if (FCurrentPath = '\') then
+ begin
+ Result := FS_FILE_NOTSUPPORTED;
+ exit;
+ end
+ else
+ begin
+ if not AbortCopy then
+ begin
+ var s := TFileStream.Create(aLocalName, fmCreate);
+ var s3Item := Path.StripKnownBucket(aRemoteName);
+ try
+ var params := TAmazonGetObjectOptionals.Create;
+
+ if S3.GetObject( BucketName, s3Item, params, s, nil, FRegion ) then
+ begin
+ Result := FS_FILE_OK
+ end
+ else
+ Result := FS_FILE_READERROR;
+ finally
+ s.Free;
+ end;
+ end
+ end;
+ Result := FS_FILE_OK;
+ except
+ on E: Exception do
+ begin
+ Result := FS_FILE_READERROR;
+ TCShowMessage('', E.Message);
+ end;
+ end;
+end;
+
+function TS3Plugin.GetIcon(aRemoteName: string; ExtractFlags: Integer; var TheIcon: HICON): Integer;
+begin
+ Result := FS_ICON_USEDEFAULT;
+ if aRemoteName.EndsWith('\..\') then
+ Exit;
+
+ if Path.IsRoot(aRemoteName) then
+ begin
+ TheIcon := LoadIcon(HInstance, 'BUCKET');
+ Result := FS_ICON_EXTRACTED;
+ Exit;
+ end;
+
+ var fi:= TFileInfo.Create;
+ if FindFileInfo(aRemoteName, fi) then
+ if fi.FileType = TFileType.ftAction then
+
+ begin
+ TheIcon := LoadIcon(HInstance, 'CONFIG');
+ Result := FS_ICON_EXTRACTED;
+ Exit;
+ end;
+
+ if Path.IsBucket(aRemoteName) then
+ begin
+ TheIcon := LoadIcon(HInstance, 'BUCKET');
+ Result := FS_ICON_EXTRACTED;
+ Exit;
+ end;
+
+end;
+
+function TS3Plugin.GetLocalName(var aRemoteName: String; maxlen: integer): Boolean;
+begin
+ Result := True;
+end;
+
+
+
+
+initialization
+ globalPluginFactory :=
+ function:IWfxPlugin
+ begin
+ Result := TS3Plugin.Create;
+ end;
+
+end.
diff --git a/source/Wfx.Plugin.intf.pas b/source/Wfx.Plugin.intf.pas
new file mode 100644
index 0000000..23b9b73
--- /dev/null
+++ b/source/Wfx.Plugin.intf.pas
@@ -0,0 +1,111 @@
+unit Wfx.Plugin.intf;
+
+interface
+
+uses
+ Wfx.Plugin.Consts,
+ WinApi.Windows;
+
+type
+ PFileInfo = ^TFileInfo;
+ TFileType = (ftFile,ftDir,ftAction);
+ TFileInfoProc = reference to procedure(aMainWin: THandle; aRemoteName, aVerb: string; sender:PFileInfo);
+ TFileInfo = record
+ FileName: string;
+ Directory: string;
+ Date: TDateTime;
+ Size: Int64;
+ ReadOnly: Boolean;
+ IsVirtual: Boolean;
+ OnExecute: TFileInfoProc;
+ FileType:TFileType;
+ class function Create:TFileInfo;static;
+ end;
+
+ IWfxPlugin = interface
+ ['{659F13FB-D7DF-4416-A1A4-526A78208865}']
+ function GetDLLPathName: string;
+ function GetPluginName: string;
+ function ExecuteFile(MainWin: THandle; RemoteName, Verb: string): Integer;
+ function FindFirstFile(var FindData: TWin32FindDataW; Path: string): THandle;
+ function FindNextFile(aHandle: THandle; var FindDataW: TWin32FindDataW): bool;
+ function Delete(const RemoteName: string): Boolean;
+ procedure TCShowMessage(const Title, Text: string);
+ function Input(const Title, Question: string; var Text: string; InputType: Integer = RT_Other): Boolean;
+
+ function GetFileListIndex: Integer;
+ procedure SetFileListIndex(const Value: Integer);
+ property FileListIndex: Integer read GetFileListIndex write SetFileListIndex;
+
+ procedure SetPluginNumber(AValue: Integer);
+ function GetPluginNumber: Integer;
+ property PluginNumber: Integer read GetPluginNumber write SetPluginNumber;
+
+ function GetProgressBarProc: TProgressProcW;
+ procedure SetProgressBarProc(const Value: TProgressProcW);
+ property ProgressBarProc : TProgressProcW read GetProgressBarProc write SetProgressBarProc;
+
+ function GetLogProc: TLogProcW;
+ procedure SetLogProc(const Value: TLogProcW);
+ property LogProc : TLogProcW read GetLogProc write SetLogProc ;
+
+ function GetEnterText: TRequestProcW;
+ procedure SetEnterText(const Value: TRequestProcW);
+ property EnterText: TRequestProcW read GetEnterText write SetEnterText;
+
+ function GetFileCount: integer;
+ property FileCount:integer read GetFileCount;
+
+ function GetCurrentFile:TFileInfo;
+ property CurrentFile:TFileInfo read GetCurrentFile;
+
+ procedure Init;
+
+ function GetFile(aRemoteName,aLocalName:string):integer;
+
+ function GetIcon(RemoteName: string; ExtractFlags: Integer; var TheIcon: HIcon):Integer;
+
+
+ procedure SetCryptCallback(CryptProcW:TCryptProcW;CryptoNr,Flags:integer);
+ function MkDir(RemoteDir:String):Boolean;
+ function RenMovFile(OldName,NewName:String;Move,OverWrite:Boolean; RemoteInfo:pRemoteInfo):integer;
+ function PutFile(LocalName,RemoteName:String;CopyFlags:integer):integer;
+ function RemoveDir(RemoteName:String):Boolean;
+ procedure Connect(aRemoteName: string);
+ function Disconnect(DisconnectRoot:String):Boolean;
+ function SetAttr(RemoteName:String;NewAttr:integer):Boolean;
+ function SetTime(RemoteName:String;CreationTime,LastAccessTime, LastWriteTime:PFileTime):Boolean;
+ procedure SetDefaultParams(dps:pFsDefaultParamStruct);
+ function GetPreviewBitmap(RemoteName:String;width,height:integer; var ReturnedBitmap:hbitmap):integer;
+ function LinksToLocalFiles:Boolean;
+ function GetLocalName(var RemoteName:String;maxlen:integer):Boolean;
+ end;
+
+type
+ TPluginFactory = reference to function:IWfxPlugin;
+
+var globalPluginFactory :TPluginFactory ;
+
+implementation
+
+uses System.SysUtils;
+
+{ TFileInfo }
+
+class function TFileInfo.Create: TFileInfo;
+begin
+ Result.FileName := '';
+ Result.Date := now;
+ Result.Size := 0;
+ Result.FileType := TFileType.ftFile;
+ Result.ReadOnly := False;
+ Result.IsVirtual := True;
+ Result.OnExecute :=
+ procedure(aMainWin: THandle; aRemoteName, aVerb: string; sender:PFileInfo)
+ begin
+
+ end;
+
+end;
+
+end.
diff --git a/test/S3.Tests.dpr b/test/S3.Tests.dpr
new file mode 100644
index 0000000..1a6be6a
--- /dev/null
+++ b/test/S3.Tests.dpr
@@ -0,0 +1,71 @@
+program S3.Tests;
+
+{$IFNDEF TESTINSIGHT}
+{$APPTYPE CONSOLE}
+{$ENDIF}
+{$STRONGLINKTYPES ON}
+uses
+ FastMM4,
+ DUnitX.MemoryLeakMonitor.FastMM4,
+ System.SysUtils,
+ {$IFDEF TESTINSIGHT}
+ TestInsight.DUnitX,
+ {$ELSE}
+ DUnitX.Loggers.Console,
+ DUnitX.Loggers.Xml.NUnit,
+ {$ENDIF }
+ DUnitX.TestFramework,
+ Wfx.Plugin.S3.tests in 'Wfx.Plugin.S3.tests.pas',
+ Wfx.Plugin.S3.Path in '..\source\Wfx.Plugin.S3.Path.pas',
+ Wfx.Plugin.ExportProcs in '..\source\Wfx.Plugin.ExportProcs.pas',
+ Wfx.Plugin.Consts in '..\source\Wfx.Plugin.Consts.pas',
+ Wfx.Plugin.intf in '..\source\Wfx.Plugin.intf.pas',
+ Wfx.Plugin.Base in '..\source\Wfx.Plugin.Base.pas',
+ Wfx.Plugin.S3 in '..\source\Wfx.Plugin.S3.pas';
+
+var
+ LRunner : ITestRunner;
+ LResults : IRunResults;
+ LLogger : ITestLogger;
+ LNUnitLogger : ITestLogger;
+begin
+{$IFDEF TESTINSIGHT}
+ TestInsight.DUnitX.RunRegisteredTests;
+ Exit;
+{$ENDIF}
+ try
+ //Check command line optTDUnitX.RegisterTestFixture(TestFixtureClassName);ions, will exit if invalid
+ TDUnitX.CheckCommandLine;
+ //Create the test runner
+ LRunner := TDUnitX.CreateRunner;
+ //Tell the runner to use RTTI to find Fixtures
+ LRunner.UseRTTI := True;
+ //tell the runner how we will log things
+ //Log to the console window
+ LLogger := TDUnitXConsoleLogger.Create(true);
+ LRunner.AddLogger(LLogger);
+ //Generate an NUnit compatible XML File
+ TDUnitX.Options.ExitBehavior := TDUnitXExitBehavior.Pause;
+ LNUnitLogger := TDUnitXXMLNUnitFileLogger.Create(TDUnitX.Options.XMLOutputFile);
+ LRunner.AddLogger(LNUnitLogger);
+ LRunner.FailsOnNoAsserts := False; //When true, Assertions must be made during tests;
+
+ //Run tests
+ LResults := LRunner.Execute;
+ if not LResults.AllPassed then
+ System.ExitCode := EXIT_ERRORS;
+
+ {$IFNDEF CI}
+ //We don't want this happening when running under CI.
+ if TDUnitX.Options.ExitBehavior = TDUnitXExitBehavior.Pause then
+ begin
+ System.Write('Done.. press key to quit.');
+ System.Readln;
+ end;
+ {$ENDIF}
+ except
+ on E: Exception do
+ System.Writeln(E.ClassName, ': ', E.Message);
+ end;
+end.
+
diff --git a/test/S3.Tests.dproj b/test/S3.Tests.dproj
new file mode 100644
index 0000000..44193d2
--- /dev/null
+++ b/test/S3.Tests.dproj
@@ -0,0 +1,958 @@
+
+
+ {7CFA85E4-E75B-4B2C-B5ED-2CE6031B6CE6}
+ 19.3
+ VCL
+ True
+ Debug
+ Win32
+ 1
+ Console
+ S3.Tests.dpr
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ .\$(Platform)\$(Config)
+ .\$(Platform)\$(Config)
+ false
+ false
+ false
+ false
+ false
+ System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)
+ true
+ $(BDS)\bin\delphi_PROJECTICON.ico
+ $(BDS)\bin\delphi_PROJECTICNS.icns
+ $(DUnitX);$(DCC_UnitSearchPath)
+ S3_Tests
+ SKIA;$(DCC_Define)
+
+
+ soapserver;IndySystem;fmx;DbxCommonDriver;bindengine;IndyIPCommon;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;bindcomp;FmxTeeUI;FireDACCommon;Skia.Package.RTL;IndyCore;RESTBackendComponents;bindcompfmx;bindcompdbx;rtl;FireDACSqliteDriver;DbxClientDriver;RESTComponents;DBXSqliteDriver;IndyIPServer;fmxFireDAC;dbexpress;dsnapxml;soapmidas;inet;FireDAC;fmxase;xmlrtl;tethering;dbrtl;dsnap;Skia.Package.FMX;CloudService;CustomIPTransport;FMXTee;soaprtl;DBXInterBaseDriver;FireDACIBDriver;$(DCC_UsePackage)
+
+
+ soapserver;IndySystem;fmx;DbxCommonDriver;bindengine;IndyIPCommon;FireDACCommonODBC;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;bindcomp;FmxTeeUI;FireDACCommon;Skia.Package.RTL;IndyCore;RESTBackendComponents;bindcompfmx;bindcompdbx;inetdb;rtl;FireDACMySQLDriver;FireDACSqliteDriver;DbxClientDriver;RESTComponents;DBXSqliteDriver;IndyIPServer;fmxFireDAC;dbexpress;dsnapxml;soapmidas;DBXMySQLDriver;inet;FireDACPgDriver;FireDAC;fmxase;inetdbxpress;xmlrtl;tethering;dbrtl;dsnap;fmxdae;Skia.Package.FMX;CloudService;CustomIPTransport;fmxobj;FMXTee;soaprtl;DBXInterBaseDriver;FireDACIBDriver;$(DCC_UsePackage)
+
+
+ soapserver;IndySystem;fmx;DbxCommonDriver;bindengine;IndyIPCommon;FireDACCommonODBC;FireDACCommonDriver;IndyProtocols;IndyIPClient;dbxcds;bindcomp;FmxTeeUI;FireDACCommon;IndyCore;RESTBackendComponents;bindcompfmx;bindcompdbx;inetdb;rtl;FireDACMySQLDriver;FireDACSqliteDriver;DbxClientDriver;RESTComponents;DBXSqliteDriver;IndyIPServer;fmxFireDAC;dbexpress;dsnapxml;soapmidas;DBXMySQLDriver;inet;FireDACPgDriver;FireDAC;fmxase;inetdbxpress;xmlrtl;tethering;dbrtl;dsnap;fmxdae;CloudService;CustomIPTransport;fmxobj;FMXTee;soaprtl;DBXInterBaseDriver;FireDACIBDriver;$(DCC_UsePackage)
+
+
+ soapserver;DAV_ASIOHost_D19;IndySystem;DAV_VSTHost_D19;vclwinx;DAV_DSP_D19;fmx;Skia.Package.VCL;vclie;DbxCommonDriver;bindengine;vcldb;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;DAV_GUI_D19;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IconFontsImageList;SynEditDR;IndyIPClient;dbxcds;vcledge;vclFireDAC;LSPComponents;bindcompvclwinx;bindcomp;FmxTeeUI;FireDACCommon;Skia.Package.RTL;MidiControls;MidiComponents2010;IndyCore;RESTBackendComponents;bindcompfmx;bindcompdbx;inetdb;rtl;FireDACMySQLDriver;DAV_VSTPlugin_D19;FireDACSqliteDriver;DbxClientDriver;FireDACADSDriver;GR32_R;Tee;RESTComponents;DBXSqliteDriver;vcl;vclactnband;TeeUI;IndyIPServer;fmxFireDAC;dbexpress;dsnapxml;dsnapcon;soapmidas;adortl;SVGIconImageListFMX;DBXMySQLDriver;VirtualTreesDR;DAV_Common_D19;VclSmp;inet;DAV_SEHost_D19;vclimg;vcltouch;FireDACPgDriver;FireDAC;fmxase;inetdbxpress;xmlrtl;tethering;GR32_D;dbrtl;ARH;bindcompvcl;dsnap;fmxdae;TeeDB;Skia.Package.FMX;CloudService;FireDACMSAccDriver;CustomIPTransport;SVGIconPackage;IconFontsImageListFMX;fmxobj;bindcompvclsmp;FMXTee;SVGIconImageList;soaprtl;vcldsnap;DBXInterBaseDriver;FireDACIBDriver;$(DCC_UsePackage)
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
+ Debug
+ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=
+ 1033
+
+
+ soapserver;IndySystem;vclwinx;fmx;Skia.Package.VCL;vclie;DbxCommonDriver;bindengine;vcldb;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;SynEditDR;IndyIPClient;dbxcds;vcledge;vclFireDAC;LSPComponents;bindcompvclwinx;bindcomp;FmxTeeUI;FireDACCommon;Skia.Package.RTL;IndyCore;RESTBackendComponents;bindcompfmx;bindcompdbx;inetdb;rtl;FireDACMySQLDriver;FireDACSqliteDriver;DbxClientDriver;FireDACADSDriver;Tee;RESTComponents;DBXSqliteDriver;vcl;vclactnband;TeeUI;IndyIPServer;fmxFireDAC;dbexpress;dsnapxml;dsnapcon;soapmidas;adortl;SVGIconImageListFMX;DBXMySQLDriver;VirtualTreesDR;VclSmp;inet;vclimg;vcltouch;FireDACPgDriver;FireDAC;fmxase;inetdbxpress;xmlrtl;tethering;dbrtl;bindcompvcl;dsnap;fmxdae;TeeDB;Skia.Package.FMX;CloudService;FireDACMSAccDriver;CustomIPTransport;SVGIconPackage;fmxobj;bindcompvclsmp;FMXTee;SVGIconImageList;soaprtl;vcldsnap;DBXInterBaseDriver;FireDACIBDriver;$(DCC_UsePackage)
+
+
+ DEBUG;$(DCC_Define)
+ true
+ false
+ true
+ true
+ true
+ true
+ true
+
+
+ false
+
+
+ false
+ RELEASE;$(DCC_Define)
+ 0
+ 0
+
+
+
+ MainSource
+
+
+
+
+
+
+
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+ Cfg_2
+ Base
+
+
+
+ Delphi.Personality.12
+ Console
+
+
+
+ S3.Tests.dpr
+
+
+
+
+
+ .\
+ 0
+ sk4d.dll
+ true
+
+
+
+
+ S3_Tests.exe
+ true
+
+
+
+
+ true
+
+
+
+
+ .\
+ 0
+ sk4d.dll
+ true
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ Contents\MacOS\
+ 1
+ sk4d.dylib
+ true
+
+
+
+
+ Contents\MacOS\
+ 1
+ sk4d.dylib
+ true
+
+
+
+
+ .\
+ 0
+ sk4d.dll
+ true
+
+
+
+
+ .\
+ 0
+ sk4d.dll
+ true
+
+
+
+
+ Contents\MacOS\
+ 1
+ sk4d.dylib
+ true
+
+
+
+
+ Contents\MacOS\
+ 1
+ sk4d.dylib
+ true
+
+
+
+
+ 1
+
+
+ 0
+
+
+
+
+ classes
+ 64
+
+
+ classes
+ 64
+
+
+
+
+ res\xml
+ 1
+
+
+ res\xml
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\armeabi
+ 1
+
+
+ library\lib\armeabi
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\mips
+ 1
+
+
+ library\lib\mips
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\values-v21
+ 1
+
+
+ res\values-v21
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+
+
+ res\drawable-ldpi
+ 1
+
+
+ res\drawable-ldpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+
+
+ res\drawable-small
+ 1
+
+
+ res\drawable-small
+ 1
+
+
+
+
+ res\drawable-normal
+ 1
+
+
+ res\drawable-normal
+ 1
+
+
+
+
+ res\drawable-large
+ 1
+
+
+ res\drawable-large
+ 1
+
+
+
+
+ res\drawable-xlarge
+ 1
+
+
+ res\drawable-xlarge
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 0
+
+
+
+
+ 1
+ .framework
+
+
+ 1
+ .framework
+
+
+ 1
+ .framework
+
+
+ 0
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 0
+ .dll;.bpl
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 0
+ .bpl
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+ ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+
+ Contents\Resources
+ 1
+
+
+ Contents\Resources
+ 1
+
+
+ Contents\Resources
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 0
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+ False
+ True
+ False
+
+
+ 12
+
+
+
+
+
diff --git a/test/S3.Tests.res b/test/S3.Tests.res
new file mode 100644
index 0000000..73d4f5a
Binary files /dev/null and b/test/S3.Tests.res differ
diff --git a/test/Wfx.Plugin.S3.tests.pas b/test/Wfx.Plugin.S3.tests.pas
new file mode 100644
index 0000000..351bb0c
--- /dev/null
+++ b/test/Wfx.Plugin.S3.tests.pas
@@ -0,0 +1,53 @@
+unit Wfx.Plugin.S3.tests;
+
+interface
+
+uses
+
+ Wfx.Plugin.Intf,
+ Wfx.Plugin.S3,
+ DUnitX.TestFramework;
+
+type
+ [TestFixture]
+ S3PluginFixture = class
+ SUT : TS3Plugin;
+ public
+ [Setup]
+ procedure Setup;
+ [TearDown]
+ procedure TearDown;
+
+ [Test]
+ procedure PluginNameNotEmpty;
+
+ [Test]
+ procedure InitDoesNotRaise;
+ end;
+
+implementation
+
+procedure S3PluginFixture.Setup;
+begin
+ SUT := TS3Plugin.Create;
+end;
+
+procedure S3PluginFixture.TearDown;
+begin
+ SUT.Free;
+end;
+
+procedure S3PluginFixture.PluginNameNotEmpty;
+begin
+ Assert.IsNotEmpty(SUT.GetPluginName);
+end;
+
+procedure S3PluginFixture.InitDoesNotRaise();
+begin
+ Assert.WillNotRaiseAny( SUT.Init );
+end;
+
+initialization
+ TDUnitX.RegisterTestFixture(S3PluginFixture);
+
+end.