Browse Source

Merge branch 'develop'

Unknown 6 years ago
parent
commit
5360472270

+ 97 - 60
Quick.Azure.pas

@@ -7,7 +7,7 @@
   Author      : Kike Pérez
   Version     : 1.2
   Created     : 27/08/2015
-  Modified    : 20/10/2017
+  Modified    : 13/09/2017
 
   This file is part of QuickLib: https://github.com/exilon/QuickLib
 
@@ -36,6 +36,7 @@ uses
   System.SysUtils,
   System.Generics.Collections,
   IPPeerClient,
+  IdURI,
   Data.Cloud.CloudAPI,
   Data.Cloud.AzureAPI;
 
@@ -72,6 +73,8 @@ type
       function FileToArray(cFilename : string) : TArray<Byte>;
       function StreamToArray(cStream : TStream) : TArray<Byte>;
       function GMT2DateTime(const gmtdate : string):TDateTime;
+      function CheckContainer(const aContainer : string) : string;
+      function RemoveFirstSlash(const aValue : string) : string;
     public
       constructor Create; overload;
       constructor Create(azAccountName, azAccountKey : string); overload;
@@ -80,22 +83,22 @@ type
       property AccountKey : string read fAccountKey write SetAccountKey;
       property AzureProtocol : TAzureProtocol read fAzureProtocol write SetAzureProtocol;
       property TimeOut : Integer read fTimeOut write fTimeOut;
-      function PutBlob(azContainer, cFilename, azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean; overload;
-      function PutBlob(azContainer : string; cStream : TStream; azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean; overload;
-      function GetBlob(azContainer, azBlobName, cFilenameTo : string; out azResponseInfo : TAzureResponseInfo) : Boolean; overload;
-      function GetBlob(azContainer, azBlobName : string; out azResponseInfo : TAzureResponseInfo; out Stream : TMemoryStream) : Boolean; overload;
+      function PutBlob(const azContainer, cFilename, azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean; overload;
+      function PutBlob(const azContainer : string; cStream : TStream; const azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean; overload;
+      function GetBlob(const azContainer, azBlobName, cFilenameTo : string; out azResponseInfo : TAzureResponseInfo) : Boolean; overload;
+      function GetBlob(const azContainer, azBlobName : string; out azResponseInfo : TAzureResponseInfo; out Stream : TMemoryStream) : Boolean; overload;
       function GetBlob(const azContainer, azBlobName : string; out azResponseInfo : TAzureResponseInfo) : TMemoryStream; overload;
-      function CopyBlob(azSourceContainer, azSourceBlobName : string; azTargetContainer, azTargetBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+      function CopyBlob(const azSourceContainer, azSourceBlobName : string; azTargetContainer, azTargetBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
       function RenameBlob(const azContainer, azSourceBlobName, azTargetBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
       function ExistsObject(const azContainer, azBlobName : string) : Boolean;
-      function ExistsFolder(azContainer : string; azFolderName : string) : Boolean;
-      function DeleteBlob(azContainer,azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
-      function ListBlobs(azContainer : string; azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TBlobList;
-      function ListBlobsNames(azContainer : string; azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TStrings;
-      function ExistsContainer(azContainer : string) : Boolean;
-      function ListContainers(azContainersStartWith : string; azResponseInfo : TAzureResponseInfo) : TStrings;
-      function CreateContainer(azContainer : string; azPublicAccess : TBlobPublicAccess; out azResponseInfo : TAzureResponseInfo) : Boolean;
-      function DeleteContainer(azContainer : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+      function ExistsFolder(const azContainer, azFolderName : string) : Boolean;
+      function DeleteBlob(const azContainer,azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+      function ListBlobs(const azContainer, azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TBlobList;
+      function ListBlobsNames(const azContainer, azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TStrings;
+      function ExistsContainer(const azContainer : string) : Boolean;
+      function ListContainers(const azContainersStartWith : string; azResponseInfo : TAzureResponseInfo) : TStrings;
+      function CreateContainer(const azContainer : string; azPublicAccess : TBlobPublicAccess; out azResponseInfo : TAzureResponseInfo) : Boolean;
+      function DeleteContainer(const azContainer : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
   end;
 
 implementation
@@ -262,23 +265,25 @@ begin
   Result.StatusMsg := ResponseInfo.StatusMessage;
 end;
 
-function TQuickAzure.PutBlob(azContainer, cFilename, azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+function TQuickAzure.PutBlob(const azContainer, cFilename, azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
   BlobService : TAzureBlobService;
   Content : TArray<Byte>;
   CloudResponseInfo : TCloudResponseInfo;
+  container : string;
+  blobname : string;
 begin
   Result := False;
   BlobService := TAzureBlobService.Create(fconAzure);
   try
-    if azContainer = '' then azContainer := '$root';
+    container := CheckContainer(azContainer);
     CloudResponseInfo := TCloudResponseInfo.Create;
     try
       BlobService.Timeout := fTimeout;
       Content := FileToArray(cFilename);
-      if azBlobName = '' then azBlobName := cFilename;
-      if azBlobName.StartsWith('/') then azBlobName := Copy(azBlobName,2,Length(azBlobName));
-      Result := BlobService.PutBlockBlob(azContainer,azBlobName,Content,EmptyStr,nil,nil,CloudResponseInfo);
+      if azBlobName = '' then blobname := cFilename;
+      if blobname.StartsWith('/') then blobname := Copy(blobname,2,Length(blobname));
+      Result := BlobService.PutBlockBlob(container,blobname,Content,EmptyStr,nil,nil,CloudResponseInfo);
       azResponseInfo := GetResponseInfo(CloudResponseInfo);
     finally
       CloudResponseInfo.Free;
@@ -288,11 +293,13 @@ begin
   end;
 end;
 
-function TQuickAzure.PutBlob(azContainer : string; cStream : TStream; azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+function TQuickAzure.PutBlob(const azContainer : string; cStream : TStream; const azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
   BlobService : TAzureBlobService;
   Content : TArray<Byte>;
   CloudResponseInfo : TCloudResponseInfo;
+  container : string;
+  blobname : string;
 begin
   azResponseInfo.StatusCode := 500;
   if cStream.Size = 0 then
@@ -301,8 +308,8 @@ begin
     Exit;
   end;
 
-  if azContainer = '' then azContainer := '$root';
-  if azBlobName.StartsWith('/') then azBlobName := Copy(azBlobName,2,Length(azBlobName));
+  container := CheckContainer(azContainer);
+  blobname := RemoveFirstSlash(azBlobName);
   try
     BlobService := TAzureBlobService.Create(fconAzure);
     try
@@ -310,7 +317,7 @@ begin
       CloudResponseInfo := TCloudResponseInfo.Create;
       try
         Content := StreamToArray(cStream);
-        Result := BlobService.PutBlockBlob(azContainer,azBlobName,Content,EmptyStr,nil,nil,CloudResponseInfo);
+        Result := BlobService.PutBlockBlob(container,blobname,Content,EmptyStr,nil,nil,CloudResponseInfo);
         azResponseInfo := GetResponseInfo(CloudResponseInfo);
       finally
         CloudResponseInfo.Free;
@@ -328,15 +335,17 @@ begin
   end;
 end;
 
-function TQuickAzure.GetBlob(azContainer, azBlobName, cFilenameTo : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+function TQuickAzure.GetBlob(const azContainer, azBlobName, cFilenameTo : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
   BlobService : TAzureBlobService;
   fs : TFileStream;
   CloudResponseInfo : TCloudResponseInfo;
+  container : string;
+  blobname : string;
 begin
   Result := False;
-  if azContainer = '' then azContainer := '$root';
-  if azBlobName.StartsWith('/') then azBlobName := Copy(azBlobName,2,Length(azBlobName));
+  container := CheckContainer(azContainer);
+  blobname := RemoveFirstSlash(azBlobName);
   BlobService := TAzureBlobService.Create(fconAzure);
   try
     BlobService.Timeout := fTimeout;
@@ -345,7 +354,7 @@ begin
       try
         CloudResponseInfo := TCloudResponseInfo.Create;
         try
-          Result := BlobService.GetBlob(azContainer,azBlobName,fs,EmptyStr,CloudResponseInfo);
+          Result := BlobService.GetBlob(container,blobname,fs,EmptyStr,CloudResponseInfo);
           azResponseInfo := GetResponseInfo(CloudResponseInfo);
         finally
           CloudResponseInfo.Free;
@@ -361,22 +370,24 @@ begin
   end;
 end;
 
-function TQuickAzure.GetBlob(azContainer, azBlobName : string; out azResponseInfo : TAzureResponseInfo; out Stream : TMemoryStream) : Boolean;
+function TQuickAzure.GetBlob(const azContainer, azBlobName : string; out azResponseInfo : TAzureResponseInfo; out Stream : TMemoryStream) : Boolean;
 var
   BlobService : TAzureBlobService;
   CloudResponseInfo : TCloudResponseInfo;
+  container : string;
+  blobname : string;
 begin
   Result := False;
   Stream := TMemoryStream.Create;
-  if azContainer = '' then azContainer := '$root';
-  if azBlobName.StartsWith('/') then azBlobName := Copy(azBlobName,2,Length(azBlobName));
+  container := CheckContainer(azContainer);
+  blobname := RemoveFirstSlash(azBlobName);
   BlobService := TAzureBlobService.Create(fconAzure);
   try
     BlobService.Timeout := fTimeout;
     try
       CloudResponseInfo := TCloudResponseInfo.Create;
       try
-        Result := BlobService.GetBlob(azContainer,azBlobName,Stream,EmptyStr,CloudResponseInfo);
+        Result := BlobService.GetBlob(container,blobname,Stream,EmptyStr,CloudResponseInfo);
         azResponseInfo := GetResponseInfo(CloudResponseInfo);
       finally
         CloudResponseInfo.Free;
@@ -394,23 +405,33 @@ begin
   GetBlob(azContainer,azBlobName,azResponseInfo,Result);
 end;
 
-function TQuickAzure.CopyBlob(azSourceContainer, azSourceBlobName : string; azTargetContainer, azTargetBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+function TQuickAzure.CheckContainer(const aContainer: string): string;
+begin
+  if aContainer = '' then Result := '$root'
+    else Result := aContainer;
+end;
+
+function TQuickAzure.CopyBlob(const azSourceContainer, azSourceBlobName : string; azTargetContainer, azTargetBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
   BlobService : TAzureBlobService;
   CloudResponseInfo : TCloudResponseInfo;
+  sourcecontainer : string;
+  targetcontainer : string;
+  sourceblobname : string;
+  targetblobname : string;
 begin
   Result := False;
-  if azSourceContainer = '' then azSourceContainer := '$root';
-  if azTargetContainer = '' then azTargetContainer := '$root';
-  if azSourceBlobName.StartsWith('/') then azSourceBlobName := Copy(azSourceBlobName,2,Length(azSourceBlobName));
-  if azTargetBlobName.StartsWith('/') then azTargetBlobName := Copy(azTargetBlobName,2,Length(azTargetBlobName));
+  sourcecontainer := CheckContainer(azSourceContainer);
+  targetcontainer := CheckContainer(azTargetContainer);
+  sourceblobname := RemoveFirstSlash(azSourceBlobName);
+  targetblobname := RemoveFirstSlash(azTargetBlobName);
   BlobService := TAzureBlobService.Create(fconAzure);
   try
     BlobService.Timeout := fTimeout;
     try
       CloudResponseInfo := TCloudResponseInfo.Create;
       try
-        Result := BlobService.CopyBlob(azTargetContainer,azTargetBlobName,azSourceContainer,azSourceBlobName,'',nil,CloudResponseInfo);
+        Result := BlobService.CopyBlob(targetcontainer,targetblobname,sourcecontainer,sourceblobname,'',nil,CloudResponseInfo);
         azResponseInfo := GetResponseInfo(CloudResponseInfo);
       finally
         CloudResponseInfo.Free;
@@ -428,14 +449,23 @@ begin
   end;
 end;
 
+function TQuickAzure.RemoveFirstSlash(const aValue: string): string;
+begin
+  if aValue.StartsWith('/') then Result := Copy(aValue,2,Length(aValue))
+    else Result := aValue;
+end;
+
 function TQuickAzure.RenameBlob(const azContainer, azSourceBlobName, azTargetBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
-  ResponseInfo : TAzureResponseInfo;
+  sourceblobname : string;
 begin
   Result := False;
-  if CopyBlob(azContainer,azSourceBlobName,azContainer,azTargetBlobName,ResponseInfo) then
+  if sourceblobname.Contains('%') then sourceblobname := azSourceBlobName
+    else sourceblobname := TIdURI.PathEncode(azSourceBlobName);
+
+  if CopyBlob(azContainer,sourceblobname,azContainer,azTargetBlobName,azResponseInfo) then
   begin
-    Result := DeleteBlob(azContainer,azSourceBlobName,ResponseInfo);
+    Result := DeleteBlob(azContainer,azSourceBlobName,azResponseInfo);
   end;
 end;
 
@@ -446,7 +476,7 @@ var
   ResponseInfo : TAzureResponseInfo;
 begin
   Result := False;
-  azBlobs := ListBlobsNames(azContainer,azBlobName,True,ResponseInfo);
+  azBlobs := ListBlobsNames(azContainer,azBlobName,False,ResponseInfo);
   try
     if (ResponseInfo.StatusCode = 200) and (Assigned(azBlobs)) then
     begin
@@ -464,7 +494,7 @@ begin
   end;
 end;
 
-function TQuickAzure.ExistsFolder(azContainer : string; azFolderName : string) : Boolean;
+function TQuickAzure.ExistsFolder(const azContainer, azFolderName : string) : Boolean;
 var
   BlobService : TAzureBlobService;
   azBlob : TAzureBlob;
@@ -472,22 +502,25 @@ var
   CloudResponseInfo : TCloudResponseInfo;
   AzParams : TStrings;
   cNextMarker : string;
+  container : string;
+  foldername : string;
 begin
   Result := False;
-  if azContainer = '' then azContainer := '$root';
+  container := CheckContainer(azContainer);
   BlobService := TAzureBlobService.Create(fconAzure);
   try
     BlobService.Timeout := fTimeout;
     AzParams := TStringList.Create;
     try
-      if not azFolderName.EndsWith('/') then azFolderName := azFolderName + '/';      
-      AzParams.Values['prefix'] := azFolderName;
+      if not azFolderName.EndsWith('/') then foldername := azFolderName + '/'
+        else foldername := azFolderName;
+      AzParams.Values['prefix'] := foldername;
       AzParams.Values['delimiter'] := '/';
       AzParams.Values['maxresults'] := '1';
       cNextMarker := '';
       CloudResponseInfo := TCloudResponseInfo.Create;
       try
-        azBlobList := BlobService.ListBlobs(azContainer,cNextMarker,AzParams,CloudResponseInfo);
+        azBlobList := BlobService.ListBlobs(container,cNextMarker,AzParams,CloudResponseInfo);
         try
           if (Assigned(azBlobList)) and (azBlobList.Count > 0) and (CloudResponseInfo.StatusCode = 200) then Result := True;
         finally
@@ -506,20 +539,22 @@ begin
   end;
 end;
 
-function TQuickAzure.DeleteBlob(azContainer,azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+function TQuickAzure.DeleteBlob(const azContainer,azBlobName : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
   BlobService : TAzureBlobService;
   CloudResponseInfo : TCloudResponseInfo;
+  container : string;
+  blobname : string;
 begin
   Result := False;
-  if azContainer = '' then azContainer := '$root';
-  if azBlobName.StartsWith('/') then azBlobName := Copy(azBlobName,2,Length(azBlobName));
+  container := CheckContainer(azContainer);
+  blobname := RemoveFirstSlash(azBlobName);
   BlobService := TAzureBlobService.Create(fconAzure);
   try
     BlobService.Timeout := fTimeout;
     CloudResponseInfo := TCloudResponseInfo.Create;
     try
-      Result := BlobService.DeleteBlob(azContainer,azBlobName,False,EmptyStr,CloudResponseInfo);
+      Result := BlobService.DeleteBlob(container,blobname,False,EmptyStr,CloudResponseInfo);
       azResponseInfo := GetResponseInfo(CloudResponseInfo);
     finally
       CloudResponseInfo.Free;
@@ -529,7 +564,7 @@ begin
   end;
 end;
 
-function TQuickAzure.ListBlobs(azContainer : string; azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TBlobList;
+function TQuickAzure.ListBlobs(const azContainer, azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TBlobList;
 var
   BlobService : TAzureBlobService;
   azBlob : TAzureBlob;
@@ -538,10 +573,11 @@ var
   CloudResponseInfo : TCloudResponseInfo;
   cNextMarker : string;
   AzParams : TStrings;
+  container : string;
 begin
   Result := TBlobList.Create(True);
   cNextMarker := '';
-  if azContainer = '' then azContainer := '$root';
+  container := CheckContainer(azContainer);
   BlobService := TAzureBlobService.Create(fconAzure);
   try
     BlobService.Timeout := fTimeout;
@@ -553,7 +589,7 @@ begin
         if cNextMarker <> '' then AzParams.Values['marker'] := cNextMarker;
         CloudResponseInfo := TCloudResponseInfo.Create;
         try
-          azBlobList := BlobService.ListBlobs(azContainer,cNextMarker,AzParams,CloudResponseInfo);
+          azBlobList := BlobService.ListBlobs(container,cNextMarker,AzParams,CloudResponseInfo);
           azResponseInfo := GetResponseInfo(CloudResponseInfo);
           if Assigned(azBlobList) then
           begin
@@ -584,7 +620,7 @@ begin
   end;
 end;
 
-function TQuickAzure.ListBlobsNames(azContainer : string; azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TStrings;
+function TQuickAzure.ListBlobsNames(const azContainer, azBlobsStartWith : string; Recursive : Boolean; out azResponseInfo : TAzureResponseInfo) : TStrings;
 var
   BlobService : TAzureBlobService;
   azBlob : TAzureBlob;
@@ -592,10 +628,11 @@ var
   CloudResponseInfo : TCloudResponseInfo;
   cNextMarker : string;
   AzParams : TStrings;
+  container : string;
 begin
   Result := TStringList.Create;
   cNextMarker := '';
-  if azContainer = '' then azContainer := '$root';
+  container := CheckContainer(azContainer);
   BlobService := TAzureBlobService.Create(fconAzure);
   CloudResponseInfo := TCloudResponseInfo.Create;
   try
@@ -607,7 +644,7 @@ begin
         if not Recursive then AzParams.Values['delimiter'] := '/';
         if cNextMarker <> '' then AzParams.Values['marker'] := cNextMarker;
 
-        azBlobList := BlobService.ListBlobs(azContainer,cNextMarker,AzParams,CloudResponseInfo);
+        azBlobList := BlobService.ListBlobs(container,cNextMarker,AzParams,CloudResponseInfo);
         azResponseInfo := GetResponseInfo(CloudResponseInfo);
         if Assigned(azBlobList) then
         begin
@@ -631,7 +668,7 @@ begin
   end;
 end;
 
-function TQuickAzure.ExistsContainer(azContainer : string) : Boolean;
+function TQuickAzure.ExistsContainer(const azContainer : string) : Boolean;
 var
   Container : string;
   Containers : TStrings;
@@ -656,7 +693,7 @@ begin
   end;
 end;
 
-function TQuickAzure.ListContainers(azContainersStartWith : string; azResponseInfo : TAzureResponseInfo) : TStrings;
+function TQuickAzure.ListContainers(const azContainersStartWith : string; azResponseInfo : TAzureResponseInfo) : TStrings;
 var
   BlobService : TAzureBlobService;
   CloudResponseInfo : TCloudResponseInfo;
@@ -704,7 +741,7 @@ begin
   end;
 end;
 
-function TQuickAzure.CreateContainer(azContainer : string; azPublicAccess : TBlobPublicAccess; out azResponseInfo : TAzureResponseInfo) : Boolean;
+function TQuickAzure.CreateContainer(const azContainer : string; azPublicAccess : TBlobPublicAccess; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
   BlobService : TAzureBlobService;
   CloudResponseInfo : TCloudResponseInfo;
@@ -724,7 +761,7 @@ begin
   end;
 end;
 
-function TQuickAzure.DeleteContainer(azContainer : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
+function TQuickAzure.DeleteContainer(const azContainer : string; out azResponseInfo : TAzureResponseInfo) : Boolean;
 var
   BlobService : TAzureBlobService;
   CloudResponseInfo : TCloudResponseInfo;

+ 7 - 1
Quick.Commons.pas

@@ -7,7 +7,7 @@
   Author      : Kike Pérez
   Version     : 1.5
   Created     : 14/07/2017
-  Modified    : 19/09/2018
+  Modified    : 14/10/2018
 
   This file is part of QuickLib: https://github.com/exilon/QuickLib
 
@@ -261,6 +261,7 @@ type
   function GetLastInputInfo(var plii: TLastInputInfo): BOOL;stdcall; external 'user32' name 'GetLastInputInfo';
   {$ENDIF}
   function RemoveLastChar(const aText : string) : string;
+  function DateTimeToSQL(aDateTime : TDateTime) : string;
 
 {$IFDEF MSWINDOWS}
 var
@@ -1135,6 +1136,11 @@ begin
   Result := aText.Remove(aText.Length - 1);
 end;
 
+function DateTimeToSQL(aDateTime : TDateTime) : string;
+begin
+  Result := FormatDateTime('YYYYMMDD hh:mm:ss',aDateTime);
+end;
+
 {$IFDEF MSWINDOWS}
 initialization
   try

+ 7 - 3
Quick.Compression.pas

@@ -37,12 +37,16 @@ uses
   System.SysUtils,
   System.ZLib;
 
-  function CompressString(const aStr : string) : string;
+type
+
+  TCompressionLevel = TZCompressionLevel;
+
+  function CompressString(const aStr : string; aLevel : TCompressionLevel = zcDefault) : string;
   function DecompressString(const aStr: string) : string;
 
 implementation
 
-function CompressString(const aStr : string) : string;
+function CompressString(const aStr : string; aLevel : TCompressionLevel = zcDefault) : string;
 var
   strstream : TStringStream;
   zipstream : TStringStream;
@@ -51,7 +55,7 @@ begin
   try
     zipstream := TStringStream.Create('',TEncoding.ANSI);
     try
-      ZCompressStream(strstream, zipstream);
+      ZCompressStream(strstream, zipstream, aLevel);
       zipstream.Position := 0;
       Result := zipstream.DataString;
     finally

+ 18 - 12
Quick.Console.pas

@@ -57,6 +57,9 @@ uses
     crt,
     {$ENDIF}
   {$ENDIF}
+  {$IFDEF DELPHILINUX}
+  Quick.SyncObjs.Linux.Compatibility,
+  {$ENDIF}
   SysUtils,
   Quick.Commons,
   Quick.Log;
@@ -93,19 +96,22 @@ type
   {$ELSE}
   TOutputProc<T> = procedure(const aLine : T) of object;
   TExecuteProc = procedure of object;
-    {$IFDEF LINUX}
-    TCoord = record
-      X : tcrtcoord;
-      Y : tcrtcoord;
-    end;
-
-    TSmallRect = record
-      Left : Byte;
-      Top : Byte;
-      Right : Byte;
-      Bottom : Byte;
-    end;
+  {$ENDIF}
+  {$IF DEFINED(FPCLINUX) OR DEFINED(DELPHILINUX)}
+    {$IFDEF DELPHILINUX}
+    tcrtcoord = Byte;
     {$ENDIF}
+  TCoord = record
+    X : tcrtcoord;
+    Y : tcrtcoord;
+  end;
+
+  TSmallRect = record
+    Left : Byte;
+    Top : Byte;
+    Right : Byte;
+    Bottom : Byte;
+  end;
   {$ENDIF}
 
   {$IFDEF MSWINDOWS}

+ 50 - 5
Quick.JSONRecord.pas

@@ -5,9 +5,9 @@
   Unit        : Quick.JSONRecord
   Description : Serializable class
   Author      : Kike Pérez
-  Version     : 1.1
+  Version     : 1.2
   Created     : 05/05/2018
-  Modified    : 28/08/2018
+  Modified    : 06/11/2018
 
   This file is part of QuickLib: https://github.com/exilon/QuickLib
 
@@ -33,6 +33,7 @@ unit Quick.JSONRecord;
 interface
 
 uses
+  Classes,
   Quick.Json.Serializer,
   Quick.AutoMapper;
 
@@ -41,7 +42,9 @@ type
   IJsonable = interface
   ['{AF71F59C-89A5-4BFB-8227-0CC3068B7671}']
     procedure FromJson(const aJson : string);
+    procedure LoadFromFile(const aJsonFilename : string);
     function ToJson(aIdent : Boolean = False) : string;
+    procedure SaveToFile(const aJsonFilename : string; aIndent : Boolean = True);
     procedure MapTo(aTgtObj : TObject);
     procedure MapFrom(aSrcObj : TObject);
   end;
@@ -49,8 +52,11 @@ type
   TJsonRecord = class(TInterfacedObject,IJsonable)
   public
     constructor CreateFromJson(const aJson : string);
+    constructor CreateFromFile(const aJsonFilename : string);
+    procedure LoadFromFile(const aJsonFilename : string);
     procedure FromJson(const aJson : string);
-    function ToJson(aIdent : Boolean = False) : string;
+    function ToJson(aIndent : Boolean = False) : string;
+    procedure SaveToFile(const aJsonFilename : string; aIndent : Boolean = True);
     function Map<T : class, constructor> : T;
     procedure MapTo(aTgtObj : TObject);
     procedure MapFrom(aSrcObj : TObject);
@@ -86,6 +92,45 @@ begin
   end;
 end;
 
+procedure TJsonRecord.LoadFromFile(const aJsonFilename: string);
+var
+  json : TStringList;
+begin
+  json := TStringList.Create;
+  try
+    json.LoadFromFile(aJsonFilename);
+    Self.FromJson(json.Text);
+  finally
+    json.Free;
+  end;
+end;
+
+constructor TJsonRecord.CreateFromFile(const aJsonFilename : string);
+var
+  json : TStringList;
+begin
+  json := TStringList.Create;
+  try
+    json.LoadFromFile(aJsonFilename);
+    CreateFromJson(json.Text);
+  finally
+    json.Free;
+  end;
+end;
+
+procedure TJsonRecord.SaveToFile(const aJsonFilename : string; aIndent : Boolean = True);
+var
+  json : TStringList;
+begin
+  json := TStringList.Create;
+  try
+    json.Text := Self.ToJson(aIndent);
+    json.SaveToFile(aJsonFilename);
+  finally
+    json.Free;
+  end;
+end;
+
 function TJsonRecord.Map<T> : T;
 begin
   Result := TMapper<T>.Map(Self);
@@ -101,13 +146,13 @@ begin
   TObjMapper.Map(Self,aTgtObj);
 end;
 
-function TJsonRecord.ToJson(aIdent : Boolean = False) : string;
+function TJsonRecord.ToJson(aIndent : Boolean = False) : string;
 var
   serializer : TJsonSerializer;
 begin
   serializer := TJsonSerializer.Create(TSerializeLevel.slPublishedProperty);
   try
-    Result := serializer.ObjectToJson(Self,aIdent);
+    Result := serializer.ObjectToJson(Self,aIndent);
   finally
     serializer.Free;
   end;

+ 5 - 3
Quick.Json.Serializer.pas

@@ -7,7 +7,7 @@
   Author      : Kike Pérez
   Version     : 1.4
   Created     : 21/05/2018
-  Modified    : 21/09/2018
+  Modified    : 16/10/2018
 
   This file is part of QuickLib: https://github.com/exilon/QuickLib
 
@@ -172,6 +172,8 @@ var
   propObj : TObject;
 begin
   if GetTypeData(aTypeInfo).DynArrElType = nil then Exit;
+  if not assigned(aJsonArray) then Exit;
+
   len := aJsonArray.Count;
   rType := GetTypeData(aTypeInfo).DynArrElType^;
   pArr := nil;
@@ -989,10 +991,10 @@ begin
                 //jValue := TJsonValue(jPair.JsonValue.Clone);
                 jValue := jPair.JsonValue;
                 jArray.AddElement(jValue);
-                jPair.JsonValue.Owned := False;
+                if jPair.JsonValue <> nil then jPair.JsonValue.Owned := False;
               finally
                 jPair.Free;
-                jValue.Owned := True;
+                if jValue <> nil then jValue.Owned := True;
               end;
             end;
             Result.JsonValue := jArray;

+ 304 - 0
Quick.Lists.pas

@@ -0,0 +1,304 @@
+{ ***************************************************************************
+
+  Copyright (c) 2016-2018 Kike Pérez
+
+  Unit        : Quick.Lists
+  Description : Generic Lists functions
+  Author      : Kike Pérez
+  Version     : 1.0
+  Created     : 04/11/2018
+  Modified    : 07/11/2018
+
+  This file is part of QuickLib: https://github.com/exilon/QuickLib
+
+ ***************************************************************************
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ *************************************************************************** }
+
+unit Quick.Lists;
+
+{$i QuickLib.inc}
+
+interface
+
+uses
+  Classes,
+  SysUtils,
+  RTTI,
+  TypInfo,
+  Quick.RTTI.Utils,
+  System.Generics.Collections,
+  System.Generics.Defaults;
+
+type
+
+  TClassField = (cfField, cfProperty);
+
+  TSearchDictionary<TKey,TValue> = class(TObjectDictionary<TKey,TValue>)
+  private
+    fIndexName : string;
+    fFieldName : string;
+    fClassField : TClassField;
+  public
+    property IndexName : string read fIndexName write fIndexName;
+    property FieldName : string read fFieldName write fFieldName;
+    property ClassField : TClassField read fClassField write fClassField;
+  end;
+
+  TIndexList<T> = class
+  private
+    fList : TList<TSearchDictionary<Variant,T>>;
+    fDictionaryIndex : TObjectDictionary<string,TSearchDictionary<Variant,T>>;
+  public
+    constructor Create;
+    destructor Destroy; override;
+    property List : TList<TSearchDictionary<Variant,T>> read fList;
+    function Get(const aIndexName : string) : TSearchDictionary<Variant,T>;
+    procedure Add(const aIndexName, aFieldName : string; aClassField : TClassField = cfProperty);
+    procedure Remove(const aIndexName : string);
+  end;
+
+  TIndexedObjectList<T: class> = class(TList<T>)
+  private
+    fOwnsObjects: Boolean;
+    fIndexes : TIndexList<T>;
+  protected
+    procedure Notify(const Value: T; Action: TCollectionNotification); override;
+  public
+    constructor Create(aOwnsObjects: Boolean = True); overload;
+    constructor Create(const aComparer: IComparer<T>; aOwnsObjects: Boolean = True); overload;
+    constructor Create(const aCollection: TEnumerable<T>; aOwnsObjects: Boolean = True); overload;
+    destructor Destroy; override;
+    property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects;
+    property Indexes : TIndexList<T> read fIndexes;
+    function Get(const aIndexName : string; aValue : Variant) : T;
+  end;
+
+  TSearchObjectList<T: class> = class(TObjectList<T>)
+  public
+    function Get(const aFieldName: string; const aValue: string; aClassField : TClassField = cfProperty) : T; overload;
+    function Get(const aFieldName : string; aValue : Int64; aClassField : TClassField = cfProperty) : T; overload;
+    function Get(const aFieldName : string; aValue : Double; aClassField : TClassField = cfProperty) : T; overload;
+    function Get(const aFieldName : string; aValue : TDateTime; aClassField : TClassField = cfProperty) : T; overload;
+  end;
+
+implementation
+
+
+
+{ TIndexedObjectList<T> }
+
+constructor TIndexedObjectList<T>.Create(aOwnsObjects: Boolean);
+begin
+  inherited Create;
+  FOwnsObjects := aOwnsObjects;
+  fIndexes := TIndexList<T>.Create;
+end;
+
+constructor TIndexedObjectList<T>.Create(const aComparer: IComparer<T>; aOwnsObjects: Boolean);
+begin
+  inherited Create(aComparer);
+  FOwnsObjects := aOwnsObjects;
+  fIndexes := TIndexList<T>.Create;
+end;
+
+constructor TIndexedObjectList<T>.Create(const aCollection: TEnumerable<T>; aOwnsObjects: Boolean);
+begin
+  inherited Create(aCollection);
+  FOwnsObjects := aOwnsObjects;
+  fIndexes := TIndexList<T>.Create;
+end;
+
+procedure TIndexedObjectList<T>.Notify(const Value: T; Action: TCollectionNotification);
+var
+  sindex : TSearchDictionary<Variant,T>;
+  propvalue : TValue;
+begin
+  inherited;
+  if Action = cnAdded then
+  begin
+    for sindex in fIndexes.List do
+    begin
+      try
+        if sindex.ClassField = TClassField.cfField then propvalue := TRTTI.GetFieldValue(TObject(Value),sindex.FieldName)
+          else propvalue := TRTTI.GetPropertyValue(TObject(Value),sindex.FieldName);
+      except
+        raise Exception.CreateFmt('Cannot add value to "%s" search dictionary!',[sindex.IndexName]);
+      end;
+      sindex.Add(propvalue.AsVariant,Value);
+    end;
+  end;
+  //remove object if owned
+  if OwnsObjects and ((Action = cnRemoved) or (Action = cnExtracted)) then
+  begin
+    for sindex in fIndexes.List do
+    begin
+      try
+        if sindex.ClassField = TClassField.cfField then propvalue := TRTTI.GetFieldValue(TObject(Value),sindex.FieldName)
+          else propvalue := TRTTI.GetPropertyValue(TObject(Value),sindex.FieldName);
+      except
+        raise Exception.CreateFmt('Cannot remove value to "%s" search dictionary!',[sindex.IndexName]);
+      end;
+      sindex.Remove(propvalue.AsVariant);
+    end;
+    Value.DisposeOf;
+  end;
+end;
+
+destructor TIndexedObjectList<T>.Destroy;
+begin
+  inherited;
+  fIndexes.Free;
+end;
+
+function TIndexedObjectList<T>.Get(const aIndexName: string; aValue : Variant): T;
+var
+  sindex : TSearchDictionary<Variant,T>;
+begin
+  Result := nil;
+  sindex := fIndexes.Get(aIndexName.ToLower);
+  if sindex <> nil then sindex.TryGetValue(aValue,Result)
+    else raise Exception.CreateFmt('Index "%s" not found!',[aIndexName]);
+end;
+
+{ TIndexList<T> }
+
+procedure TIndexList<T>.Add(const aIndexName, aFieldName : string; aClassField : TClassField = cfProperty);
+var
+  sdict : TSearchDictionary<Variant,T>;
+begin
+  if aClassField = TClassField.cfField then
+  begin
+    if not TRTTI.FieldExists(TypeInfo(T),aFieldName) then raise Exception.CreateFmt('Not found field "%s" to create a search dictionary!',[aFieldName]);
+  end
+  else
+  begin
+    if not TRTTI.PropertyExists(TypeInfo(T),aFieldName) then raise Exception.CreateFmt('Not found property "%s" to create a search dictionary!',[aFieldName]);
+  end;
+  sdict := TSearchDictionary<Variant,T>.Create;
+  sdict.IndexName := aIndexName;
+  sdict.FieldName := aFieldName;
+  sdict.ClassField := aClassField;
+  fList.Add(sdict);
+  fDictionaryIndex.Add(aIndexName.ToLower,sdict);
+end;
+
+procedure TIndexList<T>.Remove(const aIndexName: string);
+var
+  sdict : TSearchDictionary<Variant,T>;
+begin
+  if not fDictionaryIndex.ContainsKey(aIndexName) then raise Exception.CreateFmt('Cannot remove an inexistent "%s" search dictionary!',[aIndexName]);
+  fList.Remove(sdict);
+  fDictionaryIndex.Remove(aIndexName.ToLower);
+  sdict.Free;
+end;
+
+constructor TIndexList<T>.Create;
+begin
+  fList := TList<TSearchDictionary<Variant,T>>.Create;
+  fDictionaryIndex := TObjectDictionary<string,TSearchDictionary<Variant,T>>.Create;
+end;
+
+destructor TIndexList<T>.Destroy;
+var
+  sindex : TSearchDictionary<Variant,T>;
+begin
+  for sindex in fList do sindex.Free;
+  fList.Free;
+  fDictionaryIndex.Free;
+  inherited;
+end;
+
+function TIndexList<T>.Get(const aIndexName: string): TSearchDictionary<Variant, T>;
+begin
+  Result := nil;
+  fDictionaryIndex.TryGetValue(aIndexName,Result);
+end;
+
+{ TSearchObjectList<T> }
+
+function TSearchObjectList<T>.Get(const aFieldName: string; const aValue: string; aClassField : TClassField = cfProperty): T;
+var
+  val : T;
+begin
+  Result := nil;
+  for val in List do
+  begin
+    if aClassField = TClassField.cfField then
+    begin
+      if TRTTI.GetFieldValue(TObject(val),aFieldName).AsString = aValue then Exit(val);
+    end
+    else
+    begin
+      if GetStrProp(TObject(val),aFieldName) = aValue then Exit(val);
+    end;
+  end;
+end;
+
+function TSearchObjectList<T>.Get(const aFieldName: string; aValue: Int64; aClassField : TClassField = cfProperty): T;
+var
+  val : T;
+begin
+  Result := nil;
+  for val in List do
+  begin
+    if aClassField = TClassField.cfField then
+    begin
+      if TRTTI.GetFieldValue(TObject(val),aFieldName).AsInt64 = aValue then Exit(val);
+    end
+    else
+    begin
+      if GetInt64Prop(TObject(val),aFieldName) = aValue then Exit(val);
+    end;
+  end;
+end;
+
+function TSearchObjectList<T>.Get(const aFieldName: string; aValue: Double; aClassField : TClassField = cfProperty): T;
+var
+  val : T;
+begin
+  Result := nil;
+  for val in List do
+  begin
+    if aClassField = TClassField.cfField then
+    begin
+      if TRTTI.GetFieldValue(TObject(val),aFieldName).AsExtended = aValue then Exit(val);
+    end
+    else
+    begin
+      if GetFloatProp(TObject(val),aFieldName) = aValue then Exit(val);
+    end;
+  end;
+end;
+
+function TSearchObjectList<T>.Get(const aFieldName: string; aValue: TDateTime; aClassField : TClassField = cfProperty): T;
+var
+  val : T;
+begin
+  Result := nil;
+  for val in List do
+  begin
+    if aClassField = TClassField.cfField then
+    begin
+      if TRTTI.GetFieldValue(TObject(val),aFieldName).AsExtended = aValue then Exit(val);
+    end
+    else
+    begin
+      if GetFloatProp(TObject(val),aFieldName) = aValue then Exit(val);
+    end;
+  end;
+end;
+
+end.

+ 6 - 3
Quick.Log.pas

@@ -41,11 +41,14 @@ uses
   Quick.Commons,
   {$IFNDEF FPC}
   System.IOUtils,
+    {$IFDEF DELPHILINUX}
+    Quick.SyncObjs.Linux.Compatibility,
+    {$ENDIF}
   {$ELSE}
   Quick.Files,
-    {$IFDEF LINUX}
-    syncObjs,
-    {$ENDIF}
+  {$ENDIF}
+  {$IFDEF LINUX}
+  syncObjs,
   {$ENDIF}
   SysUtils;
 

+ 118 - 0
Quick.RTTI.Utils.pas

@@ -0,0 +1,118 @@
+unit Quick.RTTI.Utils;
+
+interface
+
+uses
+  SysUtils,
+  Rtti;
+
+type
+
+  TRTTI = class
+  private class var
+    fCtx : TRttiContext;
+  public
+    //class function GetProperties();
+    class function GetField(aInstance : TObject; const aFieldName : string) : TRttiField; overload;
+    class function GetField(aTypeInfo : Pointer; const aFieldName : string) : TRttiField; overload;
+    class function FieldExists(aTypeInfo : Pointer; const aFieldName : string) : Boolean;
+    class function GetFieldValue(aInstance : TObject; const aFieldName : string) : TValue; overload;
+    class function GetFieldValue(aTypeInfo : Pointer; const aFieldName: string) : TValue; overload;
+    class function GetProperty(aInstance : TObject; const aPropertyName : string) : TRttiProperty; overload;
+    class function GetProperty(aTypeInfo : Pointer; const aPropertyName : string) : TRttiProperty; overload;
+    class function PropertyExists(aTypeInfo : Pointer; const aPropertyName : string) : Boolean;
+    class function GetPropertyValue(aInstance : TObject; const aPropertyName : string) : TValue; overload;
+    class function GetPropertyValue(aTypeInfo : Pointer; const aPropertyName : string) : TValue; overload;
+  end;
+
+  ERTTIError = class(Exception);
+
+implementation
+
+{ TRTTIUtils }
+
+class function TRTTI.FieldExists(aTypeInfo: Pointer; const aFieldName: string): Boolean;
+var
+  rtype : TRttiType;
+begin
+  rtype := fCtx.GetType(aTypeInfo);
+  Result := rtype.GetField(aFieldName) <> nil;
+end;
+
+class function TRTTI.GetField(aInstance: TObject; const aFieldName: string): TRttiField;
+var
+  rtype : TRttiType;
+begin
+  rtype := fCtx.GetType(aInstance.ClassInfo);
+  if rtype <> nil then
+  begin
+    Result := rtype.GetField(aFieldName);
+  end;
+end;
+
+class function TRTTI.GetField(aTypeInfo: Pointer; const aFieldName: string): TRttiField;
+var
+  rtype : TRttiType;
+begin
+  rtype := fCtx.GetType(aTypeInfo);
+  if rtype <> nil then
+  begin
+    Result := rtype.GetField(aFieldName);
+  end;
+end;
+
+class function TRTTI.GetFieldValue(aInstance : TObject; const aFieldName: string): TValue;
+var
+  rfield: TRttiField;
+begin
+  rfield := GetField(aInstance,aFieldName);
+  if rfield <> nil then Result := rfield.GetValue(aInstance);
+end;
+
+class function TRTTI.GetFieldValue(aTypeInfo : Pointer; const aFieldName: string): TValue;
+var
+  rfield: TRttiField;
+begin
+  rfield := GetField(aTypeInfo,aFieldName);
+  if rfield <> nil then rfield.GetValue(aTypeInfo);
+end;
+
+class function TRTTI.GetProperty(aInstance: TObject; const aPropertyName: string): TRttiProperty;
+var
+  rtype : TRttiType;
+begin
+  rtype := fCtx.GetType(aInstance.ClassInfo);
+  if rtype <> nil then Result := rtype.GetProperty(aPropertyName);
+end;
+
+class function TRTTI.GetProperty(aTypeInfo: Pointer; const aPropertyName: string): TRttiProperty;
+var
+  rtype : TRttiType;
+begin
+  rtype := fCtx.GetType(aTypeInfo);
+  if rtype <> nil then  Result := rtype.GetProperty(aPropertyName);
+end;
+
+class function TRTTI.GetPropertyValue(aInstance: TObject; const aPropertyName: string): TValue;
+var
+  rprop : TRttiProperty;
+begin
+  rprop := GetProperty(aInstance,aPropertyName);
+  if rprop <> nil then Result := rprop.GetValue(aInstance);
+end;
+
+class function TRTTI.GetPropertyValue(aTypeInfo: Pointer; const aPropertyName: string): TValue;
+var
+  rprop : TRttiProperty;
+begin
+  rprop := GetProperty(aTypeInfo,aPropertyName);
+  if rprop <> nil then Result := rprop.GetValue(aTypeInfo);
+end;
+
+class function TRTTI.PropertyExists(aTypeInfo: Pointer; const aPropertyName: string): Boolean;
+begin
+  Result := fCtx.GetType(aTypeInfo).GetProperty(aPropertyName) <> nil;
+end;
+
+
+end.

+ 26 - 1
README.md

@@ -5,6 +5,8 @@
 --------
 
 Small delphi/fpc library containing interesting and quick to implement functions, created to simplify application development and crossplatform support.
+* NEW: TIndexedObjectList & TSearchObjectList.
+* NEW: RTTIUtils.
 * NEW: Improved firemonkey android compatibility.
 * NEW: JsonRecord
 * NEW: AutoMapper
@@ -319,8 +321,31 @@ var
    user, user2 : TUser;
 begin
    user := TUser.Create;
+   //show as json string
    Writeln(user.ToJson);
+   //mapping to other class
    user.Mapto(User2);
    Writeln(user2.ToJson);
+   //load from file
+   user.LoadFromFile('.\user.json');
+   //save to file
+   user2.SaveToFile('.\user2.json');
 end;
-```
+```
+
+**Quick.Lists:** Improved lists with indexing or search features.
+- TIndexedObjectList: Allows fast hashed searches by object properties or fields.
+- TSearchObjectList: Allows iteration search by object properties or fields.
+```delphi
+var
+   users : TIndexedObjectList<TUser>;
+begin
+   users := TIndexedObjectList<TUser>.Create(True);
+   //create index by property "Name"
+   users.Indexes.Add('Name','Name',TClassField.cfProperty);
+   //create index by private field "Id"
+   users.Indexes.Add('Id','fId',TClassField.cfField);
+   //get user by "Name" index
+   writeln(users.Get('Name','Peter').SurName);
+end;
+```

+ 114 - 0
samples/delphi/QuickLists/IndexedList.dpr

@@ -0,0 +1,114 @@
+program IndexedList;
+
+{$APPTYPE CONSOLE}
+
+{$R *.res}
+
+uses
+  System.SysUtils,
+  Quick.Commons,
+  Quick.Console,
+  Quick.Chrono,
+  Quick.Lists;
+
+type
+  TUser = class
+  private
+    fId : Int64;
+    fName : string;
+    fSurName : string;
+    fAge : Integer;
+  published
+    property Id : Int64 read fId write fId;
+    property Name : string read fName write fName;
+    property SurName : string read fSurName write fSurName;
+    property Age : Integer read fAge write fAge;
+  end;
+
+
+const
+  numusers = 100000;
+  UserNames : array of string = ['Cliff','Alan','Anna','Phil','John','Michel','Jennifer','Peter','Brandon','Joe','Steve','Lorraine','Bill','Tom'];
+  UserSurnames : array of string = ['Gordon','Summer','Huan','Paterson','Johnson','Michelson','Smith','Peterson','Miller','McCarney','Roller','Gonzalez','Thomson','Muller'];
+
+
+var
+  users : TIndexedObjectList<TUser>;
+  users2 : TSearchObjectList<TUser>;
+  user : TUser;
+  i : Integer;
+  crono : TChronometer;
+
+begin
+  try
+    ReportMemoryLeaksOnShutdown := True;
+    users := TIndexedObjectList<TUser>.Create(True);
+    users.Indexes.Add('Name','Name');
+    users.Indexes.Add('Surname','fSurname',TClassField.cfField);
+    users.Indexes.Add('id','Id');
+
+    users2 := TSearchObjectList<TUser>.Create(False);
+
+    cout('Generating list...',etInfo);
+    //generate first dummy entries
+    for i := 1 to numusers - high(UserNames) do
+    begin
+      user := TUser.Create;
+      user.Id := Random(999999999999999);
+      user.Name := 'Name' + i.ToString;
+      user.SurName := 'SurName' + i.ToString;
+      user.Age := 18 + Random(20);
+      users.Add(user);
+      users2.Add(user);
+    end;
+
+    //generate real entries to search
+    for i := 0 to high(UserNames) do
+    begin
+      user := TUser.Create;
+      user.Id := Random(999999999999999);
+      user.Name := UserNames[i];
+      user.SurName := UserSurnames[i];
+      user.Age := 18 + Random(20);
+      users.Add(user);
+      users2.Add(user);
+    end;
+
+    crono := TChronometer.Create;
+
+    //test search by index
+    crono.Start;
+    user := users.Get('Name','Peter');
+    crono.Stop;
+    if user <> nil then cout('Found by Index: %s %s in %s',[user.Name,user.SurName,crono.ElapsedTime],etSuccess)
+      else cout('Not found!',etError);
+
+    //test search by normal iteration
+    crono.Start;
+    for i := 0 to users.Count - 1 do
+    begin
+      if users[i].Name = 'Peter' then
+      begin
+        crono.Stop;
+        cout('Found by Iteration: %s %s at %d position in %s',[user.Name,user.SurName,i,crono.ElapsedTime],etSuccess);
+        Break;
+      end;
+    end;
+
+    //test search by embeded iteration
+    crono.Start;
+    user := users2.Get('Name','Peter');
+    crono.Stop;
+    if user <> nil then cout('Found by Search: %s %s in %s',[user.Name,user.SurName,crono.ElapsedTime],etSuccess)
+      else cout('Not found!',etError);
+
+    cout('Press a key to Exit',etInfo);
+    Readln;
+    users.Free;
+    users2.Free;
+    crono.Free;
+  except
+    on E: Exception do
+      cout('%s : %s',[E.ClassName,E.Message],etError);
+  end;
+end.

+ 625 - 0
samples/delphi/QuickLists/IndexedList.dproj

@@ -0,0 +1,625 @@
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+    <PropertyGroup>
+        <ProjectGuid>{634B996C-7A5E-47E4-87E8-A2798A11331B}</ProjectGuid>
+        <ProjectVersion>18.4</ProjectVersion>
+        <FrameworkType>None</FrameworkType>
+        <MainSource>IndexedList.dpr</MainSource>
+        <Base>True</Base>
+        <Config Condition="'$(Config)'==''">Debug</Config>
+        <Platform Condition="'$(Platform)'==''">Win32</Platform>
+        <TargetedPlatforms>3</TargetedPlatforms>
+        <AppType>Console</AppType>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='Android' and '$(Base)'=='true') or '$(Base_Android)'!=''">
+        <Base_Android>true</Base_Android>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='iOSDevice32' and '$(Base)'=='true') or '$(Base_iOSDevice32)'!=''">
+        <Base_iOSDevice32>true</Base_iOSDevice32>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='iOSDevice64' and '$(Base)'=='true') or '$(Base_iOSDevice64)'!=''">
+        <Base_iOSDevice64>true</Base_iOSDevice64>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='iOSSimulator' and '$(Base)'=='true') or '$(Base_iOSSimulator)'!=''">
+        <Base_iOSSimulator>true</Base_iOSSimulator>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='OSX32' and '$(Base)'=='true') or '$(Base_OSX32)'!=''">
+        <Base_OSX32>true</Base_OSX32>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
+        <Base_Win32>true</Base_Win32>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
+        <Base_Win64>true</Base_Win64>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
+        <Cfg_1>true</Cfg_1>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
+        <Cfg_1_Win32>true</Cfg_1_Win32>
+        <CfgParent>Cfg_1</CfgParent>
+        <Cfg_1>true</Cfg_1>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
+        <Cfg_2>true</Cfg_2>
+        <CfgParent>Base</CfgParent>
+        <Base>true</Base>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base)'!=''">
+        <DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
+        <DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
+        <DCC_E>false</DCC_E>
+        <DCC_N>false</DCC_N>
+        <DCC_S>false</DCC_S>
+        <DCC_F>false</DCC_F>
+        <DCC_K>false</DCC_K>
+        <DCC_UsePackage>RESTComponents;FireDACIBDriver;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;$(DCC_UsePackage)</DCC_UsePackage>
+        <DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)</DCC_Namespace>
+        <SanitizedProjectName>IndexedList</SanitizedProjectName>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_Android)'!=''">
+        <DCC_UsePackage>DBXSqliteDriver;DBXInterBaseDriver;FMXComponents;tethering;bindcompfmx;FmxTeeUI;fmx;dbexpress;IndyCore;dsnap;bindengine;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;ibxbindings;rtl;DbxClientDriver;CustomIPTransport;bindcomp;CoolTrayIcon_D210_XE7;IndyIPClient;dbxcds;dsnapxml;dbrtl;IndyProtocols;$(DCC_UsePackage)</DCC_UsePackage>
+        <Android_LauncherIcon36>$(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png</Android_LauncherIcon36>
+        <Android_LauncherIcon48>$(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png</Android_LauncherIcon48>
+        <Android_LauncherIcon72>$(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png</Android_LauncherIcon72>
+        <Android_LauncherIcon96>$(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png</Android_LauncherIcon96>
+        <Android_LauncherIcon144>$(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png</Android_LauncherIcon144>
+        <Android_SplashImage426>$(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png</Android_SplashImage426>
+        <Android_SplashImage470>$(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png</Android_SplashImage470>
+        <Android_SplashImage640>$(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png</Android_SplashImage640>
+        <Android_SplashImage960>$(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png</Android_SplashImage960>
+        <EnabledSysJars>android-support-v4.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services-ads-7.0.0.dex.jar;google-play-services-analytics-7.0.0.dex.jar;google-play-services-base-7.0.0.dex.jar;google-play-services-gcm-7.0.0.dex.jar;google-play-services-identity-7.0.0.dex.jar;google-play-services-maps-7.0.0.dex.jar;google-play-services-panorama-7.0.0.dex.jar;google-play-services-plus-7.0.0.dex.jar;google-play-services-wallet-7.0.0.dex.jar</EnabledSysJars>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_iOSDevice32)'!=''">
+        <DCC_UsePackage>DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;dbexpress;IndyCore;dsnap;bindengine;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;ibxbindings;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_iOSDevice64)'!=''">
+        <DCC_UsePackage>DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;dbexpress;IndyCore;dsnap;bindengine;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;FrameViewer;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;ibxbindings;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;TMSFMXPackPkgDXE11;dbxcds;dsnapxml;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_iOSSimulator)'!=''">
+        <DCC_UsePackage>DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;FmxTeeUI;fmx;dbexpress;IndyCore;dsnap;bindengine;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;ibxbindings;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;dbrtl;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_OSX32)'!=''">
+        <DCC_UsePackage>DBXSqliteDriver;DBXInterBaseDriver;tethering;bindcompfmx;inetdb;FmxTeeUI;fmx;fmxdae;dbexpress;IndyCore;dsnap;bindengine;DBXMySQLDriver;FireDACMySQLDriver;FireDACCommonODBC;bindcompdbx;IndyIPCommon;IndyIPServer;IndySystem;fmxFireDAC;FireDACPgDriver;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;xmlrtl;ibxbindings;fmxobj;rtl;DbxClientDriver;CustomIPTransport;bindcomp;IndyIPClient;dbxcds;dsnapxml;dbrtl;inetdbxpress;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
+        <DCC_ConsoleTarget>true</DCC_ConsoleTarget>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_Win32)'!=''">
+        <DCC_UsePackage>DBXSqliteDriver;UbuntuProgressPackage;DBXInterBaseDriver;vclactnband;vclFireDAC;FMXComponents;tethering;svnui;JvGlobus;FireDACADSDriver;JvPluginSystem;JvMM;tmsxlsdXE11;vcltouch;JvBands;vcldb;bindcompfmx;svn;Intraweb;JvJans;JvNet;inetdb;JvAppFrm;EssentialsDR;vcwdedXE11;vcwdXE11;FmxTeeUI;JvDotNetCtrls;AbbreviaVCLD;fmx;fmxdae;tmsdXE11;vclib;JvWizards;tmsexdXE11;dbexpress;IndyCore;vclx;JvPageComps;dsnap;JvDB;VCLRESTComponents;JclDeveloperTools;vclie;bindengine;DBXMySQLDriver;JvCmp;FireDACMySQLDriver;JvHMI;FireDACCommonODBC;FMXComponentEd;LockBoxDR;bindcompdbx;IndyIPCommon;JvCustom;advchartdedxe11;vcl;IndyIPServer;GR32_D;JvXPCtrls;PngComponents;IndySystem;advchartdxe11;dsnapcon;FireDACMSAccDriver;fmxFireDAC;vclimg;madBasic_;TeeDB;Jcl;FrameViewer;JvCore;JvCrypt;FireDACPgDriver;ibmonitor;FMXTee;SevenZippro;DbxCommonDriver;JvDlgs;JvRuntimeDesign;ibxpress;Tee;JvManagedThreads;xmlrtl;ibxbindings;fmxobj;vclwinx;JvTimeFramework;rtl;GR32_R;DbxClientDriver;CustomIPTransport;vcldsnap;JvSystem;JvStdCtrls;bindcomp;appanalytics;CoolTrayIcon_D210_XE7;tmswizdXE11;nTrayIcon;IndyIPClient;bindcompvcl;TeeUI;TMSFMXPackPkgDXE11;JvDocking;dbxcds;VclSmp;JvPascalInterpreter;adortl;KernowSoftwareFMX;JclVcl;dsnapxml;dbrtl;inetdbxpress;IndyProtocols;JvControls;JvPrintPreview;Analog_XE7;JclContainers;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
+        <DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
+        <BT_BuildType>Debug</BT_BuildType>
+        <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
+        <VerInfo_Locale>1033</VerInfo_Locale>
+        <DCC_ConsoleTarget>true</DCC_ConsoleTarget>
+        <UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44>
+        <UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Base_Win64)'!=''">
+        <DCC_UsePackage>DBXSqliteDriver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;vcltouch;vcldb;bindcompfmx;Intraweb;inetdb;EssentialsDR;vcwdXE11;FmxTeeUI;AbbreviaVCLD;fmx;fmxdae;tmsdXE11;vclib;tmsexdXE11;dbexpress;IndyCore;vclx;dsnap;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACMySQLDriver;FireDACCommonODBC;bindcompdbx;IndyIPCommon;vcl;IndyIPServer;IndySystem;advchartdxe11;dsnapcon;FireDACMSAccDriver;fmxFireDAC;vclimg;TeeDB;FireDACPgDriver;ibmonitor;FMXTee;DbxCommonDriver;ibxpress;Tee;xmlrtl;ibxbindings;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;dsnapxml;dbrtl;inetdbxpress;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
+        <DCC_ConsoleTarget>true</DCC_ConsoleTarget>
+        <UWP_DelphiLogo44>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png</UWP_DelphiLogo44>
+        <UWP_DelphiLogo150>$(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png</UWP_DelphiLogo150>
+        <DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace>
+        <BT_BuildType>Debug</BT_BuildType>
+        <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
+        <VerInfo_Locale>1033</VerInfo_Locale>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_1)'!=''">
+        <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
+        <DCC_DebugDCUs>true</DCC_DebugDCUs>
+        <DCC_Optimize>false</DCC_Optimize>
+        <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
+        <DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
+        <DCC_RemoteDebug>true</DCC_RemoteDebug>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
+        <DCC_RemoteDebug>false</DCC_RemoteDebug>
+    </PropertyGroup>
+    <PropertyGroup Condition="'$(Cfg_2)'!=''">
+        <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
+        <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
+        <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
+        <DCC_DebugInformation>0</DCC_DebugInformation>
+    </PropertyGroup>
+    <ItemGroup>
+        <DelphiCompile Include="$(MainSource)">
+            <MainSource>MainSource</MainSource>
+        </DelphiCompile>
+        <BuildConfiguration Include="Release">
+            <Key>Cfg_2</Key>
+            <CfgParent>Base</CfgParent>
+        </BuildConfiguration>
+        <BuildConfiguration Include="Base">
+            <Key>Base</Key>
+        </BuildConfiguration>
+        <BuildConfiguration Include="Debug">
+            <Key>Cfg_1</Key>
+            <CfgParent>Base</CfgParent>
+        </BuildConfiguration>
+    </ItemGroup>
+    <ProjectExtensions>
+        <Borland.Personality>Delphi.Personality.12</Borland.Personality>
+        <Borland.ProjectType>Application</Borland.ProjectType>
+        <BorlandProject>
+            <Delphi.Personality>
+                <Source>
+                    <Source Name="MainSource">IndexedList.dpr</Source>
+                </Source>
+            </Delphi.Personality>
+            <Deployment Version="3">
+                <DeployFile LocalName="Win32\Debug\IndexedList.exe" Configuration="Debug" Class="ProjectOutput">
+                    <Platform Name="Win32">
+                        <RemoteName>IndexedList.exe</RemoteName>
+                        <Overwrite>true</Overwrite>
+                    </Platform>
+                </DeployFile>
+                <DeployFile LocalName="$(BDS)\Redist\osx32\libcgunwind.1.0.dylib" Class="DependencyModule">
+                    <Platform Name="OSX32">
+                        <Overwrite>true</Overwrite>
+                    </Platform>
+                </DeployFile>
+                <DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule">
+                    <Platform Name="iOSSimulator">
+                        <Overwrite>true</Overwrite>
+                    </Platform>
+                </DeployFile>
+                <DeployFile LocalName="$(BDS)\Redist\iossimulator\libPCRE.dylib" Class="DependencyModule">
+                    <Platform Name="iOSSimulator">
+                        <Overwrite>true</Overwrite>
+                    </Platform>
+                </DeployFile>
+                <DeployFile LocalName="$(BDS)\Redist\osx32\libcgsqlite3.dylib" Class="DependencyModule">
+                    <Platform Name="OSX32">
+                        <Overwrite>true</Overwrite>
+                    </Platform>
+                </DeployFile>
+                <DeployClass Name="AdditionalDebugSymbols">
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\MacOS</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win32">
+                        <RemoteDir>Contents\MacOS</RemoteDir>
+                        <Operation>0</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="AndroidClassesDexFile">
+                    <Platform Name="Android">
+                        <RemoteDir>classes</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="AndroidGDBServer">
+                    <Platform Name="Android">
+                        <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="AndroidLibnativeArmeabiFile">
+                    <Platform Name="Android">
+                        <RemoteDir>library\lib\armeabi</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="AndroidLibnativeMipsFile">
+                    <Platform Name="Android">
+                        <RemoteDir>library\lib\mips</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="AndroidServiceOutput">
+                    <Platform Name="Android">
+                        <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="AndroidSplashImageDef">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="AndroidSplashStyles">
+                    <Platform Name="Android">
+                        <RemoteDir>res\values</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_DefaultAppIcon">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_LauncherIcon144">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-xxhdpi</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_LauncherIcon36">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-ldpi</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_LauncherIcon48">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-mdpi</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_LauncherIcon72">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-hdpi</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_LauncherIcon96">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-xhdpi</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_SplashImage426">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-small</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_SplashImage470">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-normal</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_SplashImage640">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-large</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="Android_SplashImage960">
+                    <Platform Name="Android">
+                        <RemoteDir>res\drawable-xlarge</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="DebugSymbols">
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\MacOS</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win32">
+                        <Operation>0</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="DependencyFramework">
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\MacOS</RemoteDir>
+                        <Operation>1</Operation>
+                        <Extensions>.framework</Extensions>
+                    </Platform>
+                    <Platform Name="Win32">
+                        <Operation>0</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="DependencyModule">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\MacOS</RemoteDir>
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="Win32">
+                        <Operation>0</Operation>
+                        <Extensions>.dll;.bpl</Extensions>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Required="true" Name="DependencyPackage">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\MacOS</RemoteDir>
+                        <Operation>1</Operation>
+                        <Extensions>.dylib</Extensions>
+                    </Platform>
+                    <Platform Name="Win32">
+                        <Operation>0</Operation>
+                        <Extensions>.bpl</Extensions>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="File">
+                    <Platform Name="Android">
+                        <Operation>0</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice32">
+                        <Operation>0</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>0</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>0</Operation>
+                    </Platform>
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\Resources\StartUp\</RemoteDir>
+                        <Operation>0</Operation>
+                    </Platform>
+                    <Platform Name="Win32">
+                        <Operation>0</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="iPad_Launch1024">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="iPad_Launch1536">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="iPad_Launch2048">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="iPad_Launch768">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="iPhone_Launch320">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="iPhone_Launch640">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="iPhone_Launch640x1136">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectAndroidManifest">
+                    <Platform Name="Android">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectiOSDeviceDebug">
+                    <Platform Name="iOSDevice32">
+                        <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectiOSDeviceResourceRules">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectiOSEntitlements">
+                    <Platform Name="iOSDevice32">
+                        <RemoteDir>..\</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <RemoteDir>..\</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectiOSInfoPList">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectiOSResource">
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectOSXEntitlements">
+                    <Platform Name="OSX32">
+                        <RemoteDir>..\</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectOSXInfoPList">
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectOSXResource">
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\Resources</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Required="true" Name="ProjectOutput">
+                    <Platform Name="Android">
+                        <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSDevice64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="iOSSimulator">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Linux64">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="OSX32">
+                        <RemoteDir>Contents\MacOS</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win32">
+                        <Operation>0</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="ProjectUWPManifest">
+                    <Platform Name="Win32">
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="UWP_DelphiLogo150">
+                    <Platform Name="Win32">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <DeployClass Name="UWP_DelphiLogo44">
+                    <Platform Name="Win32">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                    <Platform Name="Win64">
+                        <RemoteDir>Assets</RemoteDir>
+                        <Operation>1</Operation>
+                    </Platform>
+                </DeployClass>
+                <ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
+                <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
+                <ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
+                <ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
+                <ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
+                <ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
+                <ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
+                <ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
+            </Deployment>
+            <Platforms>
+                <Platform value="Android">False</Platform>
+                <Platform value="iOSDevice32">False</Platform>
+                <Platform value="iOSDevice64">False</Platform>
+                <Platform value="iOSSimulator">False</Platform>
+                <Platform value="OSX32">False</Platform>
+                <Platform value="Win32">True</Platform>
+                <Platform value="Win64">True</Platform>
+            </Platforms>
+        </BorlandProject>
+        <ProjectFileVersion>12</ProjectFileVersion>
+    </ProjectExtensions>
+    <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
+    <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
+    <Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
+</Project>

BIN
samples/delphi/QuickLists/IndexedList.res