{*------------------------------------------------------------------------------
  Query Amazon product information through their WebServices

  @Author Patrick M. Kolla
  @Version 0.3
  @Todo Make sure GetDisplayName has a proper return value
  @Todo Make sure all INI file fields are written using proper methods
  @Todo Make sure all sorting functions do proper comparisons
-------------------------------------------------------------------------------}
// *****************************************************************************
// Copyright: 2007-2009 Safer-Networking Ltd. All rights reserved.
// File:      snlProtoAmazonOnca.pas
// License:   LGPL 2.1
// Compiler:  Delphi, FreePascal
// Purpose:   Query Amazon product information through their WebServices
// Authors:   Patrick M. Kolla
// *****************************************************************************
// Dependencies:
// snlCollection    (standard functions to allow collection sorting)
// XMLLib           (XML reading option; read the accompanied license!)
// snlWinAPIWinInet (aka pkWinINet; download files via http; http://ccrdude.net/)
// Synapse          (alternative to previous)
// crc_hash         (hmac/sha256; http://home.netsurf.de/wolfgang.ehrhardt/crchash_en.html)
// uBase64Codec     (base64 encoding; http://www.delphipraxis.net/topic1153.html)
// *****************************************************************************
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
// *****************************************************************************
// Changelog (new entries first):
// ---------------------------------------
// 2009-07-27  pk  30m  Updated to support new signing
// 2008-06-05  pk  10m  Disabled no longer available DevToken method
// 2007-07-06  pk  10m  Added QueryArtistAlbum method
// 2007-07-05  pk  30m  Changed Node readers to use return value
// 2007-07-03  pk  30m  Added AWS (contrary to DevToken) method, set QueryType
// 2007-07-03  pk  10m  Added error message property
// 2007-07-03  pk  10m  Added Indy as a download alternative
// 2007-07-03  pk  30m  Added download and parsing of information
// 2007-07-03  --  ---  Unit created by CollectionTemplater
// *****************************************************************************

unit snlProtoAmazonOnca;

// You may make your choices here:

{$DEFINE GUICollection}         // if you want to support TListView
{$DEFINE CollectionFiltering}   // filtered views of the list
{$DEFINE CollectionSorting}     // sorting of the list
{$DEFINE CollectionIniOps}      // loading and saving to an INI file
{$DEFINE CollectionXMLOps}      // loading and saving to a XML file
  // The first DEFINEd XML unit will be used for loading from XML files
  {$DEFINE XMLLib}              // See bottom, by Thomas Koos (info@muetze1.de)
  {.$DEFINE JclSimpleXml}       // MPL 1.1, by the Christophe Paris, Florent Ouchet
  {.$DEFINE SimpleXML}          // Freeware, by Michael Vlasov

  // The first DEFINEd Internet access unit will be using for downloading
  {.$DEFINE WinInet}
  {$DEFINE Synapse}
  {.$DEFINE Indy9}

// Please leave the following untouched:

{$IFNDEF FPC}
  {$DEFINE GUIAvail}
{$ELSE FPC}
  {$IFDEF LCL}
    // GUI functions only with proper LCL initialization:
    {$DEFINE GUIAvail}
  {$ENDIF LCL}
  {$IFDEF Indy9}
    // Do not use Indy9 on FreePascal
    {$UNDEF Indy9}
    {$DEFINE Synpase}
  {$ENDIF Indy9}
  {$IFDEF WinInet}
    // Do not use WinInet on FreePascal
    {$UNDEF WinInet}
    {$DEFINE Synpase}
  {$ENDIF WinInet}
{$ENDIF FPC}

{$IFNDEF GUIAvail}
  {$UNDEF GUICollection}
{$ENDIF GUIAvail}

{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF FPC}

interface

uses
   SysUtils,
   {$IFDEF GUICollection} ComCtrls, {$ENDIF GUICollection}
   {$IFDEF CollectionXMLOps}
      {$IF DEFINED(XMLLib)} XMLLib, StrAdapterUTF_8,
      {$ELSEIF DEFINED(JclSimpleXml)} JclSimpleXml,
      {$ELSEIF DEFINED(SimpleXML)} SimpleXML,
      {$IFEND}
      Variants,
   {$ENDIF CollectionXMLOps}
   Classes;

type
   TAmazonItemEnum = (aiASIN, aiProductName, aiProductWebsite, aiCatalog, aiArtists, aiReleaseDate, aiManufacturer, aiImageUrlSmall, aiImageUrlMedium, aiImageUrlLarge, aiPriceList, aiPriceAmazon, aiPriceUsed, aiAvailability);
   TAmazonItemSet = set of TAmazonItemEnum;
   TAmazonQueryType = (aqtDevToken, aqtAWS);

   TAmazonList = class;

   { TAmazonItem }

   TAmazonItem = class(TCollectionItem)
   private
      FOwner: TAmazonList; /// TAmazonList owning this TAmazonItem.
      FASIN: AnsiString;
      FProductName: AnsiString;
      FProductWebsite: AnsiString;
      FCatalog: AnsiString;
      FArtists: TStringList;
      FReleaseDate: TDateTime;
      FManufacturer: AnsiString;
      FImageUrlSmall: AnsiString;
      FImageUrlMedium: AnsiString;
      FImageUrlLarge: AnsiString;
      FPriceList: AnsiString;
      FPriceAmazon: AnsiString;
      FPriceUsed: AnsiString;
      FAvailability: AnsiString;
      function ReadNodeAttribText(const ParentNode: TXMLNode; const AttribName: AnsiString; const NodeDefault: AnsiString = ''): AnsiString;
      function ReadNodeText(const ParentNode: TXMLNode; const NodeName: AnsiString; const NodeDefault: AnsiString = ''): AnsiString;
      function ReadNodeLevel2Text(const ParentNode: TXMLNode; const NodeName, NodeName2: AnsiString; const NodeDefault: AnsiString = ''): AnsiString;
   protected
      function GetDisplayName: String; override;
      function LoadFromDevTokenXmlNode(const Node: TXMLNode): boolean;
      function LoadFromAWSXmlNode(const Node: TXMLNode): boolean;
   public
      constructor Create(ACollection: TCollection); override;
      destructor Destroy; override;
      procedure Assign(Source: TPersistent); override;
      {$IFDEF GUICollection}
      procedure ToListItem(const Item: TListItem);
      procedure ToListItemTagsRevisited(const Item: TListItem);
      {$ENDIF GUICollection}
      function LoadFromXmlNode(const Node: TXMLNode): boolean;
      function DownloadAndSaveLargeImageFile(const LocalFilename: AnsiString): boolean;
   published
      property ASIN: AnsiString read FASIN write FASIN;
      property ProductName: AnsiString read FProductName write FProductName;
      property ProductWebsite: AnsiString read FProductWebsite write FProductWebsite;
      property Catalog: AnsiString read FCatalog write FCatalog;
      property Artists: TStringList read FArtists write FArtists;
      property ReleaseDate: TDateTime read FReleaseDate write FReleaseDate;
      property Manufacturer: AnsiString read FManufacturer write FManufacturer;
      property ImageUrlSmall: AnsiString read FImageUrlSmall write FImageUrlSmall;
      property ImageUrlMedium: AnsiString read FImageUrlMedium write FImageUrlMedium;
      property ImageUrlLarge: AnsiString read FImageUrlLarge write FImageUrlLarge;
      property PriceList: AnsiString read FPriceList write FPriceList;
      property PriceAmazon: AnsiString read FPriceAmazon write FPriceAmazon;
      property PriceUsed: AnsiString read FPriceUsed write FPriceUsed;
      property Availability: AnsiString read FAvailability write FAvailability;
   end;

   { TAmazonList }

   TAmazonList = class(TCollection)
   private
      {$IFDEF CollectionFiltering}
      FFilteredItems: array of TAmazonItem; /// List of visible items after filtering.
      FVisibleColumns: TAmazonItemSet;
      FAssociateID: AnsiString;
      FDeveloperToken: AnsiString; /// Needed for XML method
      FLastErrorMessage: AnsiString; /// Needed for XML method
      FAccessKeyID: AnsiString;
      FQueryType: TAmazonQueryType;
      FSecretAccessKey: AnsiString; /// Needed for AWS method
      function GetFilteredItem(Index: Integer): TAmazonItem;
      function GetFilteredCount: integer;
      procedure AddFilteredItem(Value: TAmazonItem);
      {$ENDIF CollectionFiltering}
      function GetItem(Index: Integer): TAmazonItem;
      procedure SetItem(Index: Integer; Value: TAmazonItem);
      function GetColumnVisible(Index: TAmazonItemEnum): boolean;
      procedure SetColumnVisible(Index: TAmazonItemEnum; const Value: boolean);
      function QueryGeneralDevToken(const QueryName, QueryField: AnsiString): boolean;
      function QueryGeneralAWS(const Operation, QueryName, QueryField: AnsiString): boolean;
      function ToHTMLParameter(const Text: AnsiString): AnsiString;
   public
      constructor Create;
      destructor Destroy; override;
      function QueryAsin(const Asin: AnsiString): boolean;
      function QueryUpc(const Upc: AnsiString): boolean;
      function QueryAuthor(const Author: AnsiString): boolean;
      function QueryArtist(const ArtistName: AnsiString): boolean;
      function QueryActor(const Actor: AnsiString): boolean;
      function QueryDirector(const Director: AnsiString): boolean;
      function QueryManufacturer(const Manufacturer: AnsiString): boolean;
      function QuerySimilarities(const Similarities: AnsiString): boolean;
      function QueryArtistAlbum(const Artist, Album: AnsiString): boolean;
      function Add: TAmazonItem;
      function CountASIN(const AASIN: AnsiString): integer;
      function CountProductName(const AProductName: AnsiString): integer;
      function CountProductWebsite(const AProductWebsite: AnsiString): integer;
      function CountCatalog(const ACatalog: AnsiString): integer;
      function CountArtists(const AArtists: TStringList): integer;
      function CountReleaseDate(const AReleaseDate: TDateTime): integer;
      function CountManufacturer(const AManufacturer: AnsiString): integer;
      function CountImageUrlSmall(const AImageUrlSmall: AnsiString): integer;
      function CountImageUrlMedium(const AImageUrlMedium: AnsiString): integer;
      function CountImageUrlLarge(const AImageUrlLarge: AnsiString): integer;
      function CountPriceList(const APriceList: AnsiString): integer;
      function CountPriceAmazon(const APriceAmazon: AnsiString): integer;
      function CountPriceUsed(const APriceUsed: AnsiString): integer;
      function CountAvailability(const AAvailability: AnsiString): integer;
      function FindASIN(const AASIN: AnsiString): TAmazonItem;
      function FindProductName(const AProductName: AnsiString): TAmazonItem;
      function FindProductWebsite(const AProductWebsite: AnsiString): TAmazonItem;
      function FindCatalog(const ACatalog: AnsiString): TAmazonItem;
      function FindArtists(const AArtists: TStringList): TAmazonItem;
      function FindReleaseDate(const AReleaseDate: TDateTime): TAmazonItem;
      function FindManufacturer(const AManufacturer: AnsiString): TAmazonItem;
      function FindImageUrlSmall(const AImageUrlSmall: AnsiString): TAmazonItem;
      function FindImageUrlMedium(const AImageUrlMedium: AnsiString): TAmazonItem;
      function FindImageUrlLarge(const AImageUrlLarge: AnsiString): TAmazonItem;
      function FindPriceList(const APriceList: AnsiString): TAmazonItem;
      function FindPriceAmazon(const APriceAmazon: AnsiString): TAmazonItem;
      function FindPriceUsed(const APriceUsed: AnsiString): TAmazonItem;
      function FindAvailability(const AAvailability: AnsiString): TAmazonItem;
      {$IFDEF CollectionIniOps}
      procedure LoadFromINIFile(const AFilename: AnsiString);
      procedure SaveToINIFile(const AFilename: AnsiString);
      {$ENDIF CollectionIniOps}
      {$IFDEF CollectionXMLOps}
      procedure LoadFromXMLFile(const AFilename: AnsiString; const AsAttributes: boolean = false);
      procedure SaveToXMLFile(const AFilename: AnsiString; const AsAttributes: boolean = false);
      {$ENDIF CollectionXMLOps}
      {$IFDEF CollectionFiltering}
      procedure ClearFilterList;
      function FilterASIN(const AASIN: AnsiString): integer;
      function FilterProductName(const AProductName: AnsiString): integer;
      function FilterProductWebsite(const AProductWebsite: AnsiString): integer;
      function FilterCatalog(const ACatalog: AnsiString): integer;
      function FilterArtists(const AArtists: TStringList): integer;
      function FilterReleaseDate(const AReleaseDate: TDateTime): integer;
      function FilterManufacturer(const AManufacturer: AnsiString): integer;
      function FilterImageUrlSmall(const AImageUrlSmall: AnsiString): integer;
      function FilterImageUrlMedium(const AImageUrlMedium: AnsiString): integer;
      function FilterImageUrlLarge(const AImageUrlLarge: AnsiString): integer;
      function FilterPriceList(const APriceList: AnsiString): integer;
      function FilterPriceAmazon(const APriceAmazon: AnsiString): integer;
      function FilterPriceUsed(const APriceUsed: AnsiString): integer;
      function FilterAvailability(const AAvailability: AnsiString): integer;
      {$ENDIF CollectionFiltering}
      procedure ListASIN(const AList: TStrings);
      procedure ListProductName(const AList: TStrings);
      procedure ListProductWebsite(const AList: TStrings);
      procedure ListCatalog(const AList: TStrings);
      procedure ListManufacturer(const AList: TStrings);
      procedure ListImageUrlSmall(const AList: TStrings);
      procedure ListImageUrlMedium(const AList: TStrings);
      procedure ListImageUrlLarge(const AList: TStrings);
      procedure ListPriceList(const AList: TStrings);
      procedure ListPriceAmazon(const AList: TStrings);
      procedure ListPriceUsed(const AList: TStrings);
      procedure ListAvailability(const AList: TStrings);
      {$IFDEF CollectionSorting}
      procedure SortASIN;
      procedure SortProductName;
      procedure SortProductWebsite;
      procedure SortCatalog;
      procedure SortArtists;
      procedure SortReleaseDate;
      procedure SortManufacturer;
      procedure SortImageUrlSmall;
      procedure SortImageUrlMedium;
      procedure SortImageUrlLarge;
      procedure SortPriceList;
      procedure SortPriceAmazon;
      procedure SortPriceUsed;
      procedure SortAvailability;
      {$ENDIF CollectionSorting}
      {$IFDEF GUICollection}
      procedure ToListItem(const Item: TListItem);
      procedure ToListItemTagsRevisited(const Item: TListItem);
      procedure ToFilteredListItem(const Item: TListItem);
      {$ENDIF GUICollection}
      property Items[Index: Integer]: TAmazonItem read GetItem write SetItem; default;
      {$IFDEF CollectionFiltering}
      property FilteredCount: integer read GetFilteredCount;
      property FilteredItems[Index: Integer]: TAmazonItem read GetFilteredItem;
      {$ENDIF CollectionFiltering}
      property ColumnVisible[Index: TAmazonItemEnum]: boolean read GetColumnVisible write SetColumnVisible;
      property DeveloperToken: AnsiString read FDeveloperToken write FDeveloperToken;
      property AssociateID: AnsiString read FAssociateID write FAssociateID;
      property AccessKeyID: AnsiString read FAccessKeyID write FAccessKeyID;
      property SecretAccessKey: AnsiString read FSecretAccessKey write FSecretAccessKey;
      property QueryType: TAmazonQueryType read FQueryType write FQueryType;
      property LastErrorMessage: AnsiString read FLastErrorMessage;
   end;

   TSignedAmazonURL = class(TStringList)
   private
      FScript: AnsiString;
      FServer: AnsiString;
      FSecretAccessKey: AnsiString;
      function GetSignature: AnsiString;
      function GetURL: AnsiString;
      function GetStringToSign: AnsiString;
      // function Sign(Secret, URLMessage: AnsiString): AnsiString;
   public
      constructor Create;
      procedure AddParameter(ParameterName, Value: AnsiString);
      property Script: AnsiString read FScript write FScript;
      property Server: AnsiString read FServer write FServer;
      property Signature: AnsiString read GetSignature;
      property StringToSign: AnsiString read GetStringToSign;
      property URL: AnsiString read GetURL;
      property SecretAccessKey: AnsiString read FSecretAccessKey write FSecretAccessKey;
   end;

implementation

uses
   {$IFDEF CollectionIniOps} IniFiles, {$ENDIF CollectionIniOps}
   snlCollection, // Check GetCollectionList below if you don't want to use this
   {$IFDEF WinInet} pkWinInet, {$ENDIF WinInet}
   {$IFDEF Synapse} httpsend, {$ENDIF Synapse}
   {$IFDEF Indy9} IdHTTP, {$ENDIF Indy9}
   hash, hmac, hmacsha2,
   uBase64Codec,
   Windows;

function DownloadSomeFile(const URL: AnsiString; const Stream: TStream): boolean;
{$IFDEF WinInet} var dwError: DWord;     {$ENDIF WinInet}
{$IFDEF Indy9}   var httpIndy: TIdHTTP;  {$ENDIF Indy9}
begin
   {$IF DEFINED(WinInet)}
   Result := pkHTTPDownload(URL, Stream, dwError);
   {$ELSEIF DEFINED(Synapse)}
   Result := HttpGetBinary(URL, Stream);
   {$ELSEIF DEFINED(Indy9)}
   httpIndy := TIdHTTP.Create(nil);
   try
      try
         httpIndy.Get(URL, Stream);
         Result := true;
      finally
         FreeAndNil(httpIndy);
      end;
   except
      Result := false;
   end;
   {$ELSE}
   raise Exception.Create('You need to specify a downloader DEFINE!');
   {$IFEND}
end;

function ByteToHex(const ByteValue: byte): AnsiString;
var o1,o2: byte;
const chars : array[0..15] of AnsiChar = ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
begin
   o1 := byte(ByteValue and $0F);
   o2 := byte(ByteValue and $F0 shr 4);
   ByteToHex := chars[o2]+chars[o1];
end;

function StringToBinarySeparator(const NormalText: AnsiString; const SeparatorChar: AnsiChar): AnsiString;
var i: integer;
    b: byte;
begin
   Result := '';
   if Length(NormalText)=0
    then Exit;
   if Length(NormalText)=1
    then Result := ByteToHex(Ord(NormalText[1]))
   else for i := 1 to Length(NormalText) do begin
      b := Ord(NormalText[i]);
      if (not ((b=0) and (i=Length(NormalText)))) then begin
         Result := Result + ByteToHex(b);
         if (i<Length(NormalText)) and (SeparatorChar<>#0)
          then Result := Result + SeparatorChar;
      end;
   end;
end;

function RFC3986CharacterEncode(const Text: AnsiString): AnsiString;
begin
   Result := Text;
   // gen delims
   Result := StringReplace(Result, ':', '%3A', [rfReplaceAll]);
   Result := StringReplace(Result, '/', '%2F', [rfReplaceAll]);
   Result := StringReplace(Result, '?', '%3F', [rfReplaceAll]);
   Result := StringReplace(Result, '#', '%23', [rfReplaceAll]);
   Result := StringReplace(Result, '[', '%5B', [rfReplaceAll]);
   Result := StringReplace(Result, ']', '%5D', [rfReplaceAll]);
   Result := StringReplace(Result, '@', '%40', [rfReplaceAll]);
   // sub delims
   Result := StringReplace(Result, '!', '%21', [rfReplaceAll]);
   Result := StringReplace(Result, '$', '%24', [rfReplaceAll]);
   Result := StringReplace(Result, '&', '%26', [rfReplaceAll]);
   Result := StringReplace(Result, '''', '%27', [rfReplaceAll]);
   Result := StringReplace(Result, '(', '%28', [rfReplaceAll]);
   Result := StringReplace(Result, ')', '%29', [rfReplaceAll]);
   Result := StringReplace(Result, '*', '%2A', [rfReplaceAll]);
   Result := StringReplace(Result, '+', '%2B', [rfReplaceAll]);
   Result := StringReplace(Result, ',', '%2C', [rfReplaceAll]);
   Result := StringReplace(Result, ';', '%3B', [rfReplaceAll]);
   Result := StringReplace(Result, '=', '%3D', [rfReplaceAll]);
end;

function ByteCompare(List: TStringList; Index1, Index2: Integer): Integer;
begin
   Result := CompareStr(List[Index1], List[Index2]);
   (* From the help:
      CompareStr compares S1 to S2, with case-sensitivity. The return value is
      less than 0 if S1 is less than S2, 0 if S1 equals S2, or greater than 0
      if S1 is greater than S2. The compare operation is based on the 8-bit
      ordinal value of each character and is not affected by the current locale.
   *)
end;

{$IFDEF CollectionSorting}
{*------------------------------------------------------------------------------
  Returns the list belonging to a collection to allow sorting.

  @param ACollection  Collection to sort
  @return  TList that can be sorted.
-------------------------------------------------------------------------------}
(* This function is available in snlCollection.pas. 
   Uncomment this only if you want to remove that dependency!
function GetCollectionList(const ACollection: TCollection): TList;
var mMethod: TMethod;
    lMethod: TList absolute mMethod;
    pItemClass: Pointer;
begin
   {$IFNDEF Linux}
   pItemClass := @ACollection.ItemClass;
   inc(Integer(pItemClass), SizeOf(TCollectionItemClass));
   mMethod := TMethod(pItemClass^);
   Result := lMethod;
   {$ENDIF Linux}
end;
*)

{*------------------------------------------------------------------------------
  Makes a text a bit more xml compliant by escaping ampersands. Probably not
  complete, since other characters have to be replaced as well.

  @param AText  Input text
  @return  Text that can be used in XML.
-------------------------------------------------------------------------------}
(* This function is available in snlCollection.pas. 
   Uncomment this only if you want to remove that dependency!
function XMLify(AText: AnsiString): AnsiString;
begin
{_}AText := StringReplace(AText, '&', '&amp;', [rfReplaceAll]);
{_}Result := AText;
end;
*)

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by ASIN for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareASIN(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by ASIN for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).ASIN, TAmazonItem(item2).ASIN);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by ProductName for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareProductName(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by ProductName for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).ProductName, TAmazonItem(item2).ProductName);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by ProductWebsite for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareProductWebsite(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by ProductWebsite for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).ProductWebsite, TAmazonItem(item2).ProductWebsite);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by Catalog for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareCatalog(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by Catalog for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).Catalog, TAmazonItem(item2).Catalog);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by Artists for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareArtists(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by Artists for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).Artists.Text, TAmazonItem(item2).Artists.Text);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by ReleaseDate for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareReleaseDate(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by ReleaseDate for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   // Accuracy is one second max, adjust factor if you want.
   Result := Round((TAmazonItem(item1).ReleaseDate - TAmazonItem(item2).ReleaseDate) * 86400);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by Manufacturer for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareManufacturer(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by Manufacturer for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).Manufacturer, TAmazonItem(item2).Manufacturer);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by ImageUrlSmall for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareImageUrlSmall(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by ImageUrlSmall for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).ImageUrlSmall, TAmazonItem(item2).ImageUrlSmall);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by ImageUrlMedium for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareImageUrlMedium(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by ImageUrlMedium for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).ImageUrlMedium, TAmazonItem(item2).ImageUrlMedium);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by ImageUrlLarge for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareImageUrlLarge(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by ImageUrlLarge for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).ImageUrlLarge, TAmazonItem(item2).ImageUrlLarge);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by PriceList for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemComparePriceList(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by PriceList for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).PriceList, TAmazonItem(item2).PriceList);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by PriceAmazon for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemComparePriceAmazon(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by PriceAmazon for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).PriceAmazon, TAmazonItem(item2).PriceAmazon);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by PriceUsed for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemComparePriceUsed(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by PriceUsed for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).PriceUsed, TAmazonItem(item2).PriceUsed);
end;

{*------------------------------------------------------------------------------
  Comparison of 2 TAmazonItem items by Availability for sorting purposes.

  @param item1  Item to be compared
  @param item2  Item to compare against
  @return  Either zero or a negativ or positive value.
-------------------------------------------------------------------------------}
function TAmazonItemCompareAvailability(item1,item2: pointer): integer;
// Purpose: Comparison of 2 TAmazonItem items by Availability for sorting purposes.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := AnsiCompareText(TAmazonItem(item1).Availability, TAmazonItem(item2).Availability);
end;

{$ENDIF CollectionSorting}

{ TAmazonItem }

{*------------------------------------------------------------------------------
  Assigns properties of one TAmazonItem to another. 

  @param Source  Object which properties should be copied.
-------------------------------------------------------------------------------}
procedure TAmazonItem.Assign(Source: TPersistent);
// Purpose: Assigns properties of one TAmazonItem to another.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   if Source is TAmazonItem then with Source as TAmazonItem do begin
      Self.FOwner := FOwner;
      Self.FArtists.Assign(FArtists);
      Self.FReleaseDate := FReleaseDate;
      Self.FASIN := FASIN;
      Self.FProductName := FProductName;
      Self.FProductWebsite := FProductWebsite;
      Self.FCatalog := FCatalog;
      Self.FManufacturer := FManufacturer;
      Self.FImageUrlSmall := FImageUrlSmall;
      Self.FImageUrlMedium := FImageUrlMedium;
      Self.FImageUrlLarge := FImageUrlLarge;
      Self.FPriceList := FPriceList;
      Self.FPriceAmazon := FPriceAmazon;
      Self.FPriceUsed := FPriceUsed;
      Self.FAvailability := FAvailability;
   end else inherited Assign(Source);
end;

{*------------------------------------------------------------------------------
  Constructor for TAmazonItem 

  @param ACollection  The collection object this TAmazonItem is inserted to.
-------------------------------------------------------------------------------}
constructor TAmazonItem.Create(ACollection: TCollection);
// Purpose: Constructor for TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   inherited Create(ACollection);
   FOwner := TAmazonList(ACollection);
   FArtists := TStringList.Create;
end;

{*------------------------------------------------------------------------------
  Destructor for TAmazonItem 
-------------------------------------------------------------------------------}
destructor TAmazonItem.Destroy;
// Purpose: Destructor for TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   FreeAndNil(FArtists);
   inherited Destroy;
end;

function TAmazonItem.DownloadAndSaveLargeImageFile(
  const LocalFilename: AnsiString): boolean;
var fs: TFileStream;
begin
   fs := TFileStream.Create(LocalFilename, fmCreate or fmOpenWrite);
   try
      Result := DownloadSomeFile(FImageUrlLarge, fs);
   finally
      FreeAndNil(fs);
   end;
end;

{*------------------------------------------------------------------------------
  Returns string representing this item.

  @return  Name of item
-------------------------------------------------------------------------------}
function TAmazonItem.GetDisplayName: String;
// Purpose: Returns string representing this item.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   { DONE -oPatrick M. Kolla -cStart : Make sure to use a proper GetDisplayName }
   Result := FASIN;
   if Result = ''
    then Result := inherited GetDisplayName;
end;

function TAmazonItem.LoadFromAWSXmlNode(const Node: TXMLNode): boolean;
var nodeAttributes: TXMLNode;
    fs: TFormatSettings;
    s: AnsiString;
begin
   Result := Assigned(Node.Nodes.GetNode('ASIN'));
   FASIN := ReadNodeText(Node, 'ASIN');
   FProductWebsite := ReadNodeText(Node, 'DetailPageURL');
   FImageUrlSmall := ReadNodeLevel2Text(Node, 'SmallImage', 'URL');
   FImageUrlMedium := ReadNodeLevel2Text(Node, 'MediumImage', 'URL');
   FImageUrlLarge := ReadNodeLevel2Text(Node, 'LargeImage', 'URL');
   nodeAttributes := Node.Nodes.GetNode('ItemAttributes');
   if Assigned(nodeAttributes) then begin
      FProductName := ReadNodeText(nodeAttributes, 'Title');
      FArtists.Clear;
      s := ReadNodeText(nodeAttributes, 'Artist');
      if Length(s)=0
       then s := ReadNodeText(nodeAttributes, 'Author'); 
      FArtists.Add(s);
      FManufacturer := ReadNodeText(nodeAttributes, 'Manufacturer');
      try
         fs.ShortDateFormat := 'yyyy-mm-dd';
         fs.DateSeparator := '-';
         s := ReadNodeText(nodeAttributes, 'ReleaseDate');
         if Length(s)=0
          then s := ReadNodeText(nodeAttributes, 'PublicationDate'); 
         FReleaseDate := StrToDate(s, fs);
      except
         FReleaseDate := 0;
      end;
   end;
end;

function TAmazonItem.ReadNodeText(const ParentNode: TXMLNode; const NodeName: AnsiString; const NodeDefault: AnsiString): AnsiString;
var nodeTemp: TXMLNode;
begin
   nodeTemp := ParentNode.Nodes.GetNode(NodeName);
   if Assigned(nodeTemp)
    then Result := nodeTemp.Value.AsString
     else Result := NodeDefault;
end;
function TAmazonItem.ReadNodeAttribText(const ParentNode: TXMLNode; const AttribName: AnsiString; const NodeDefault: AnsiString): AnsiString;
begin
   Result := ParentNode.Attributes.AttributeByName[AttribName].Value.AsString;
end;

function TAmazonItem.ReadNodeLevel2Text(const ParentNode: TXMLNode;
  const NodeName, NodeName2, NodeDefault: AnsiString): AnsiString;
var nodeTemp: TXMLNode;
begin
   Result := NodeDefault;
   nodeTemp := ParentNode.Nodes.GetNode(NodeName);
   if not Assigned(nodeTemp)
    then Exit;
   nodeTemp := nodeTemp.Nodes.GetNode(NodeName2);
   if Assigned(nodeTemp)
    then Result := nodeTemp.Value.AsString
end;

function TAmazonItem.LoadFromDevTokenXmlNode(const Node: TXMLNode): boolean;
var nodeArtists: TXMLNode;
    iArtist: integer;
    fs: TFormatSettings;
    s: AnsiString;
begin
   Result := Assigned(Node.Nodes.GetNode('Asin'));
   if not Result
    then Exit;
   FArtists.Clear;
   nodeArtists := Node.Nodes.GetNode('Artists');
   if Assigned(nodeArtists)
    then for iArtist := 0 to Pred(nodeArtists.Nodes.Count) do try
      if nodeArtists.Nodes[iArtist].Name='Artist'
       then FArtists.Add(nodeArtists.Nodes[iArtist].Value.AsString);
   except
   end;
   { TODO : Convert text to release date! }
   try
      GetLocaleFormatSettings($0407, fs);
      fs.LongDateFormat := 'dd. mmmm yyyy';
      s := ReadNodeText(Node, 'ReleaseDate');
      FReleaseDate := StrToDate(s, fs);
   except
      FReleaseDate := 0;
   end;
   FASIN := ReadNodeText(Node, 'Asin');
   FProductName := ReadNodeText(Node, 'ProductName');
   FProductWebsite := ReadNodeAttribText(Node, 'url');
   FCatalog := ReadNodeText(Node, 'Catalog');
   FManufacturer := ReadNodeText(Node, 'Manufacturer');
   FImageUrlSmall := ReadNodeText(Node, 'ImageUrlSmall');
   FImageUrlMedium := ReadNodeText(Node, 'ImageUrlMedium');
   FImageUrlLarge := ReadNodeText(Node, 'ImageUrlLarge');
   FPriceList := ReadNodeText(Node, 'ListPrice', '?');
   FPriceAmazon := ReadNodeText(Node, 'OurPrice', '?');
   FPriceUsed := ReadNodeText(Node, 'UsedPrice', '?');
   FAvailability := ReadNodeText(Node, 'Availability');
end;

function TAmazonItem.LoadFromXmlNode(const Node: TXMLNode): boolean;
begin
   if Node.Name='Details'
    then Result := LoadFromDevTokenXmlNode(Node)
     else if Node.Name='Item'
      then Result := LoadFromAWSXmlNode(Node)
       else Result := false;
end;

{$IFDEF GUICollection}
{*------------------------------------------------------------------------------
  Fill a TListItem with properties of this TAmazonItem.

  @param Item  List item to fill with properties of this TAmazonItem.
-------------------------------------------------------------------------------}
procedure TAmazonItem.ToListItem(const Item: TListItem);
// Purpose: Fill a TListItem with properties of this TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   while Item.SubItems.Count<6
    do Item.SubItems.Add('');
   { DONE -oPatrick M. Kolla -cStart : Sort list in your preferred way }
   Item.Caption := FASIN;
   Item.SubItems[0] := FProductName;
   Item.SubItems[1] := FArtists.CommaText;
   Item.SubItems[2] := FormatDateTime('c',FReleaseDate);
   Item.SubItems[3] := FManufacturer;
   Item.SubItems[4] := FPriceAmazon;
   Item.SubItems[5] := FAvailability;
end;

{*------------------------------------------------------------------------------
  Fill a TListItem with properties of this TAmazonItem.

  @param Item  List item to fill with properties of this TAmazonItem.
-------------------------------------------------------------------------------}
procedure TAmazonItem.ToListItemTagsRevisited(const Item: TListItem);
// Purpose: Fill a TListItem with properties of this TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   while Item.SubItems.Count<6
    do Item.SubItems.Add('');
   { DONE -oPatrick M. Kolla -cStart : Sort list in your preferred way }
   Item.Caption := FArtists.CommaText;
   Item.SubItems[0] := FProductName;
   Item.SubItems[1] := FormatDateTime('yyyy-mm-dd',FReleaseDate);
   Item.SubItems[2] := FASIN;
   Item.SubItems[3] := FManufacturer;
   Item.SubItems[4] := FImageUrlLarge;
   Item.SubItems[5] := FProductWebsite;
end;
{$ENDIF GUICollection}

{ TAmazonList }

{*------------------------------------------------------------------------------
  Adds a new item of type TAmazonItem to list TAmazonList.

  @return  Returns the added object of type TAmazonItem.
-------------------------------------------------------------------------------}
function TAmazonList.Add: TAmazonItem;
// Purpose: Adds a new item of type TAmazonItem to list TAmazonList.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := TAmazonItem(inherited Add);
end;

{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AASIN  The ASIN that should be counted.
  @return  Count of items matching the given ASIN.
-------------------------------------------------------------------------------}
function TAmazonList.CountASIN(const AASIN: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ASIN=AASIN
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AProductName  The ProductName that should be counted.
  @return  Count of items matching the given ProductName.
-------------------------------------------------------------------------------}
function TAmazonList.CountProductName(const AProductName: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ProductName=AProductName
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AProductWebsite  The ProductWebsite that should be counted.
  @return  Count of items matching the given ProductWebsite.
-------------------------------------------------------------------------------}
function TAmazonList.CountProductWebsite(const AProductWebsite: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ProductWebsite=AProductWebsite
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param ACatalog  The Catalog that should be counted.
  @return  Count of items matching the given Catalog.
-------------------------------------------------------------------------------}
function TAmazonList.CountCatalog(const ACatalog: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Catalog=ACatalog
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AArtists  The Artists that should be counted.
  @return  Count of items matching the given Artists.
-------------------------------------------------------------------------------}
function TAmazonList.CountArtists(const AArtists: TStringList): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Artists=AArtists
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AReleaseDate  The ReleaseDate that should be counted.
  @return  Count of items matching the given ReleaseDate.
-------------------------------------------------------------------------------}
function TAmazonList.CountReleaseDate(const AReleaseDate: TDateTime): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ReleaseDate=AReleaseDate
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AManufacturer  The Manufacturer that should be counted.
  @return  Count of items matching the given Manufacturer.
-------------------------------------------------------------------------------}
function TAmazonList.CountManufacturer(const AManufacturer: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Manufacturer=AManufacturer
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AImageUrlSmall  The ImageUrlSmall that should be counted.
  @return  Count of items matching the given ImageUrlSmall.
-------------------------------------------------------------------------------}
function TAmazonList.CountImageUrlSmall(const AImageUrlSmall: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ImageUrlSmall=AImageUrlSmall
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AImageUrlMedium  The ImageUrlMedium that should be counted.
  @return  Count of items matching the given ImageUrlMedium.
-------------------------------------------------------------------------------}
function TAmazonList.CountImageUrlMedium(const AImageUrlMedium: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ImageUrlMedium=AImageUrlMedium
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AImageUrlLarge  The ImageUrlLarge that should be counted.
  @return  Count of items matching the given ImageUrlLarge.
-------------------------------------------------------------------------------}
function TAmazonList.CountImageUrlLarge(const AImageUrlLarge: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ImageUrlLarge=AImageUrlLarge
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param APriceList  The PriceList that should be counted.
  @return  Count of items matching the given PriceList.
-------------------------------------------------------------------------------}
function TAmazonList.CountPriceList(const APriceList: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).PriceList=APriceList
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param APriceAmazon  The PriceAmazon that should be counted.
  @return  Count of items matching the given PriceAmazon.
-------------------------------------------------------------------------------}
function TAmazonList.CountPriceAmazon(const APriceAmazon: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).PriceAmazon=APriceAmazon
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param APriceUsed  The PriceUsed that should be counted.
  @return  Count of items matching the given PriceUsed.
-------------------------------------------------------------------------------}
function TAmazonList.CountPriceUsed(const APriceUsed: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).PriceUsed=APriceUsed
       then Inc(Result);
   end;
end;
{*------------------------------------------------------------------------------
  Counts all matching TAmazonItem.

  @param AAvailability  The Availability that should be counted.
  @return  Count of items matching the given Availability.
-------------------------------------------------------------------------------}
function TAmazonList.CountAvailability(const AAvailability: AnsiString): integer;
// Purpose: Counts all matching TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := 0;
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Availability=AAvailability
       then Inc(Result);
   end;
end;

{*------------------------------------------------------------------------------
  Constructor for TAmazonList.
-------------------------------------------------------------------------------}
constructor TAmazonList.Create;
// Purpose: Constructor for TAmazonList.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   inherited Create(TAmazonItem);
   {$IFDEF CollectionFiltering}
   SetLength(FFilteredItems,0);
   {$ENDIF CollectionFiltering}
   FVisibleColumns := [aiASIN, aiProductName, aiProductWebsite, aiCatalog, aiArtists, aiReleaseDate, aiManufacturer, aiImageUrlSmall, aiImageUrlMedium, aiImageUrlLarge, aiPriceList, aiPriceAmazon, aiPriceUsed, aiAvailability];
   FQueryType := aqtDevToken;
end;

{*------------------------------------------------------------------------------
  Destructor for TAmazonList.
-------------------------------------------------------------------------------}
destructor TAmazonList.Destroy;
// Purpose: Destructor for TAmazonList.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   inherited;
end;

{$IFDEF CollectionIniOps}
{*------------------------------------------------------------------------------
  Loads TAmazonList collection from INI file.

  @param Filename  Name of the file to be loaded into collection.
-------------------------------------------------------------------------------}
procedure TAmazonList.LoadFromINIFile(const AFilename: AnsiString);
// Purpose: Loads TAmazonList collection from INI file.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var mif: TMemIniFile;
    slKeys: TStringList;
    iKey: integer;
begin
   Clear;
   mif := TMemIniFile.Create(AFilename);
   try
      slKeys := TStringList.Create;
      try
         mif.ReadSections(slKeys);
         slKeys.Sort;
         for iKey := 0 to Pred(slKeys.Count) do with Add do begin
            FASIN := slKeys[iKey];
            FReleaseDate := mif.ReadDateTime(slKeys[iKey], 'ReleaseDate', 0);
            FASIN := mif.ReadString(slKeys[iKey], 'ASIN', '');
            FProductName := mif.ReadString(slKeys[iKey], 'ProductName', '');
            FProductWebsite := mif.ReadString(slKeys[iKey], 'ProductWebsite', '');
            FCatalog := mif.ReadString(slKeys[iKey], 'Catalog', '');
            FManufacturer := mif.ReadString(slKeys[iKey], 'Manufacturer', '');
            FImageUrlSmall := mif.ReadString(slKeys[iKey], 'ImageUrlSmall', '');
            FImageUrlMedium := mif.ReadString(slKeys[iKey], 'ImageUrlMedium', '');
            FImageUrlLarge := mif.ReadString(slKeys[iKey], 'ImageUrlLarge', '');
            FPriceList := mif.ReadString(slKeys[iKey], 'PriceList', '');
            FPriceAmazon := mif.ReadString(slKeys[iKey], 'PriceAmazon', '');
            FPriceUsed := mif.ReadString(slKeys[iKey], 'PriceUsed', '');
            FAvailability := mif.ReadString(slKeys[iKey], 'Availability', '');
         end;
      finally
         FreeAndNil(slKeys);
      end;
   finally
      FreeAndNil(mif);
   end;
end;

{*------------------------------------------------------------------------------
  Saves TAmazonList collection to INI file.

  @param Filename  Name of the file the collection should be saved to.
-------------------------------------------------------------------------------}
procedure TAmazonList.SaveToINIFile(const AFilename: AnsiString);
// Purpose: Saves TAmazonList collection to INI file.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var mif: TMemIniFile;
    slKeys: TStringList;
    item: TAmazonItem;
    iKey: integer;
    sKey: AnsiString;
begin
   mif := TMemIniFile.Create(AFilename);
   try
      slKeys := TStringList.Create;
      try
         mif.ReadSections(slKeys);
         for iKey := 0 to Pred(slKeys.Count) do begin
            if FindASIN(slKeys[iKey])=nil
             then mif.EraseSection(slKeys[iKey]);
         end;
      finally
         FreeAndNil(slKeys);
      end;
      for iKey := 0 to Pred(Count) do begin
         item := GetItem(iKey);
         sKey := item.ASIN;
         mif.WriteDateTime(sKey, 'ReleaseDate', item.FReleaseDate);
         mif.WriteString(sKey, 'ASIN', item.FASIN);
         mif.WriteString(sKey, 'ProductName', item.FProductName);
         mif.WriteString(sKey, 'ProductWebsite', item.FProductWebsite);
         mif.WriteString(sKey, 'Catalog', item.FCatalog);
         mif.WriteString(sKey, 'Manufacturer', item.FManufacturer);
         mif.WriteString(sKey, 'ImageUrlSmall', item.FImageUrlSmall);
         mif.WriteString(sKey, 'ImageUrlMedium', item.FImageUrlMedium);
         mif.WriteString(sKey, 'ImageUrlLarge', item.FImageUrlLarge);
         mif.WriteString(sKey, 'PriceList', item.FPriceList);
         mif.WriteString(sKey, 'PriceAmazon', item.FPriceAmazon);
         mif.WriteString(sKey, 'PriceUsed', item.FPriceUsed);
         mif.WriteString(sKey, 'Availability', item.FAvailability);
      end;
      mif.UpdateFile;
   finally
      FreeAndNil(mif);
   end;
end;
{$ENDIF CollectionIniOps}

{$IFDEF CollectionXMLOps}
{*------------------------------------------------------------------------------
  Loads TAmazonList collection from XML file.

  @param Filename  Name of the file to be loaded into collection.
-------------------------------------------------------------------------------}
procedure TAmazonList.LoadFromXMLFile(const AFilename: AnsiString; const AsAttributes: boolean);
// Purpose: Loads TAmazonList collection from XML file.
// Date:    2007-07-03 (Function created by CollectionTemplater)
{$IF DEFINED(XMLLib)}
var xmlFile: TXMLLib;
    iItem: integer;
begin
   Clear;
   xmlFile := TXMLLib.Create;
   try
      xmlFile.LoadFromFile(AFilename);
      for iItem := 0 to Pred(xmlFile.Root.Nodes.Count)
      do with Add do begin
         if AsAttributes then try
            FASIN := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['ASIN'].Value.AsString);
            FProductName := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['ProductName'].Value.AsString);
            FProductWebsite := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['ProductWebsite'].Value.AsString);
            FCatalog := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['Catalog'].Value.AsString);
            FManufacturer := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['Manufacturer'].Value.AsString);
            FImageUrlSmall := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['ImageUrlSmall'].Value.AsString);
            FImageUrlMedium := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['ImageUrlMedium'].Value.AsString);
            FImageUrlLarge := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['ImageUrlLarge'].Value.AsString);
            FPriceList := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['PriceList'].Value.AsString);
            FPriceAmazon := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['PriceAmazon'].Value.AsString);
            FPriceUsed := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['PriceUsed'].Value.AsString);
            FAvailability := Utf8ToAnsi(xmlFile.Root.Attributes.AttributeByName['Availability'].Value.AsString);
         except
         end else try
            FASIN := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('ASIN').Value.AsString);
            FProductName := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('ProductName').Value.AsString);
            FProductWebsite := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('ProductWebsite').Value.AsString);
            FCatalog := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('Catalog').Value.AsString);
            FManufacturer := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('Manufacturer').Value.AsString);
            FImageUrlSmall := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('ImageUrlSmall').Value.AsString);
            FImageUrlMedium := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('ImageUrlMedium').Value.AsString);
            FImageUrlLarge := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('ImageUrlLarge').Value.AsString);
            FPriceList := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('PriceList').Value.AsString);
            FPriceAmazon := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('PriceAmazon').Value.AsString);
            FPriceUsed := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('PriceUsed').Value.AsString);
            FAvailability := Utf8ToAnsi(xmlFile.Root.Nodes[iItem].GetNode('Availability').Value.AsString);
         except
         end;
      end;
   finally
      FreeAndNil(xmlFile);
   end;
end;
{$ELSEIF DEFINED(JclSimpleXml)}
var xmlFile: TJclSimpleXML;
    iItem: integer;
begin
   Clear;
   xmlFile := TJclSimpleXML.Create;
   try
      xmlFile.LoadFromFile(AFilename);
      for iItem := 0 to Pred(xmlFile.Root.Items.Count)
      do with Add do begin
         if AsAttributes then try
            FASIN := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['ASIN'].Value);
            FProductName := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['ProductName'].Value);
            FProductWebsite := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['ProductWebsite'].Value);
            FCatalog := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['Catalog'].Value);
            FManufacturer := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['Manufacturer'].Value);
            FImageUrlSmall := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['ImageUrlSmall'].Value);
            FImageUrlMedium := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['ImageUrlMedium'].Value);
            FImageUrlLarge := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['ImageUrlLarge'].Value);
            FPriceList := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['PriceList'].Value);
            FPriceAmazon := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['PriceAmazon'].Value);
            FPriceUsed := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['PriceUsed'].Value);
            FAvailability := Utf8ToAnsi(xmlFile.Root.Items[iItem].Properties.ItemNamed['Availability'].Value);
         except
         end else try
            FASIN := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['ASIN'].Value);
            FProductName := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['ProductName'].Value);
            FProductWebsite := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['ProductWebsite'].Value);
            FCatalog := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['Catalog'].Value);
            FManufacturer := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['Manufacturer'].Value);
            FImageUrlSmall := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['ImageUrlSmall'].Value);
            FImageUrlMedium := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['ImageUrlMedium'].Value);
            FImageUrlLarge := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['ImageUrlLarge'].Value);
            FPriceList := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['PriceList'].Value);
            FPriceAmazon := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['PriceAmazon'].Value);
            FPriceUsed := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['PriceUsed'].Value);
            FAvailability := Utf8ToAnsi(xmlFile.Root.Items[iItem].Items.ItemNamed['Availability'].Value);
         except
         end;
      end;
   finally
      FreeAndNil(xmlFile);
   end;
end;
{$ELSEIF DEFINED(SimpleXML)}
{$ELSE}
begin
   raise Exception.Create('XML loading only available through XMLLib currently!');
end;
{$IFEND}

function TAmazonList.QueryActor(const Actor: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('ActorSearch', Actor);
      aqtAWS: Result := QueryGeneralAWS('ItemSearch', 'Keywords', Actor);
      else Result := false;
   end;
end;

function TAmazonList.QueryArtist(const ArtistName: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('ArtistSearch', ArtistName);
      aqtAWS: Result := QueryGeneralAWS('ItemSearch', 'Artist', ArtistName);
      else Result := false;
   end;
end;

function TAmazonList.QueryArtistAlbum(const Artist, Album: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: raise Exception.Create('Not supported for devtoken method!');
      aqtAWS: Result := QueryGeneralAWS('ItemSearch', 'Keywords='+ToHTMLParameter(Album)+'&Artist', ToHTMLParameter(Artist));
      else Result := false;
   end;
end;

function TAmazonList.QueryAsin(const Asin: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('AsinSearch', Asin);
      aqtAWS: Result := QueryGeneralAWS('ItemLookup', 'ASIN', Asin);
      else Result := false;
   end;
end;

function TAmazonList.QueryAuthor(const Author: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('AuthorSearch', Author);
      aqtAWS: Result := QueryGeneralAWS('ItemSearch', 'Keywords', Author);
      else Result := false;
   end;
end;

function TAmazonList.QueryDirector(const Director: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('DirectorSearch', Director);
      aqtAWS: Result := QueryGeneralAWS('ItemSearch', 'Keywords', Director);
      else Result := false;
   end;
end;

function TAmazonList.QueryGeneralAWS(const Operation, QueryName, QueryField: AnsiString): boolean;
var ms: TMemoryStream;
    xmlData: TXMLLib;
    newItem: TAmazonItem;
    nodeError, nodeItems: TXMLNode;
    slURL: TSignedAmazonURL;
    sURL: AnsiString;
    i: integer;
begin
   Clear;
   FLastErrorMessage := '';
   // sURL := 'http://webservices.amazon.de/onca/xml?';
   slURL := TSignedAmazonURL.Create;
   slURL.SecretAccessKey := FSecretAccessKey;
   try
      slURL.Add('AWSAccessKeyId=' + FAccessKeyID);
      slURL.Add('Operation=' + Operation);
      if Length(FAssociateID)>0
       then slURL.Add('AssociateTag=' + FAssociateID);
      slURL.Add('ResponseGroup=Medium');
      if Operation='ItemSearch' then begin
         slURL.Add('SearchIndex=Music');
         slURL.Add(QueryName + '=' + QueryField);
      end else begin
         slURL.Add('IdType=' + QueryName);
         slURL.Add('ItemId=' + QueryField);
      end;
   finally
      sURL := slURL.URL;
      FreeAndNil(slURL);
   end;
   { TODO 1 : Could be improved by properly masking QueryField here! }
   ms := TMemoryStream.Create;
   try
      Result := DownloadSomeFile(sURL, ms);
      if Result then begin
         ms.Seek(0, soFromBeginning);
         { DONE : Do XML parsing here! }
         xmlData := TXMLLib.Create;
         try
            xmlData.LoadFromStream(ms);
            Result := xmlData.Root.Nodes.Count>0;
            if Result then begin
               try
                  nodeError := xmlData.Root.Nodes.GetNode('OperationRequest').Nodes.GetNode('Errors').Nodes.GetNode('Error').Nodes.GetNode('Message');
                  Result := not Assigned(nodeError);
                  if not Result
                   then FLastErrorMessage := nodeError.Value.AsString;
               except
                  try
                     nodeError := xmlData.Root.Nodes.GetNode('Error').Nodes.GetNode('Message');
                     Result := not Assigned(nodeError);
                     if not Result
                      then FLastErrorMessage := nodeError.Value.AsString;
                  except
                  end;
               end;
            end;
            nodeItems := xmlData.Root.Nodes.GetNode('Items');
            Result := Assigned(nodeItems);
            if Result
             then for i := 0 to Pred(nodeItems.Nodes.Count) do begin
               if nodeItems.Nodes[i].Name='Item' then begin
                  newItem := Add;
                  newItem.LoadFromXmlNode(nodeItems.Nodes[i]);
               end;
            end;
         finally
            FreeAndNil(xmlData);
         end;
      end else begin
         FLastErrorMessage := 'Could not download from ' + sURL;
      end;
   finally
      FreeAndNil(ms);
   end;
end;

function TAmazonList.QueryGeneralDevToken(const QueryName, QueryField: AnsiString): boolean;
var sURL: AnsiString;
    ms: TMemoryStream;
    xmlData: TXMLLib;
    newItem: TAmazonItem;
    i: integer;
    nodeError: TXMLNode;
begin
   Clear;
   FLastErrorMessage := '';
   sURL := 'http://xml.amazon.de/onca/xml3?dev-t=' + FDeveloperToken
         + '&f=xml'
         + '&mode=music'
         + '&page=1'
         + '&locale=de'
         + '&t=' + FAssociateID
         + '&type=lite'
         + '&v=1'
         + '&' + QueryName + '=' + QueryField;
   { TODO 1 : Could be improved by properly masking QueryField here! }
   ms := TMemoryStream.Create;
   try
      Result := DownloadSomeFile(sURL, ms);
      if Result then begin
         ms.Seek(0, soFromBeginning);
         { DONE : Do XML parsing here! }
         xmlData := TXMLLib.Create;
         try
            xmlData.LoadFromStream(ms);
            Result := xmlData.Root.Nodes.Count>0;
            if Result then begin
               try
                  nodeError := xmlData.Root.Nodes.GetNode('ErrorMsg');
                  Result := not Assigned(nodeError);
                  if not Result
                   then FLastErrorMessage := nodeError.Value.AsString;
               except
               end;
            end;
            if Result
             then for i := 0 to Pred(xmlData.Root.Nodes.Count) do begin
               if xmlData.Root.Nodes[i].Name='Details' then begin
                  newItem := Add;
                  newItem.LoadFromXmlNode(xmlData.Root.Nodes[i]);
               end;
            end;
         finally
            FreeAndNil(xmlData);
         end;
      end else begin
         FLastErrorMessage := 'Could not download from ' + sURL;
      end;
   finally
      FreeAndNil(ms);
   end;
end;

function TAmazonList.QueryManufacturer(const Manufacturer: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('ManufacturerSearch', Manufacturer);
      aqtAWS: Result := QueryGeneralAWS('ItemSearch', 'Keywords', Manufacturer);
      else Result := false;
   end;
end;

function TAmazonList.QuerySimilarities(const Similarities: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('SimilaritiesSearch', Similarities);
      aqtAWS: Result := QueryGeneralAWS('ItemSearch', 'Keywords', Similarities);
      else Result := false;
   end;
end;

function TAmazonList.QueryUpc(const Upc: AnsiString): boolean;
begin
   case QueryType of
      aqtDevToken: Result := QueryGeneralDevToken('UpcSearch', Upc);
      aqtAWS: Result := QueryGeneralAWS('ItemLookup', 'UPC', Upc);
      else Result := false;
   end;
end;

{*------------------------------------------------------------------------------
  Saves TAmazonList collection to XML file.

  @param Filename  Name of the file the collection should be saved to.
-------------------------------------------------------------------------------}
procedure TAmazonList.SaveToXMLFile(const AFilename: AnsiString; const AsAttributes: boolean);
// Purpose: Saves TAmazonList collection to XML file.
// Date:    2007-07-03 (Function created by CollectionTemplater)
const XMLHeader = '<?xml version="1.0" encoding="utf-8"?>'#13#10;
var fs: TFileStream;
procedure AddLine(const ALine: AnsiString);
begin
   fs.Write(ALine[1], Length(ALine));
end;
var iItem: integer;
    item: TAmazonItem;
begin
   fs := TFileStream.Create(AFilename, fmCreate or fmShareDenyWrite);
   try
      AddLine(XMLHeader);
      AddLine('<TAmazonList>'#13#10);
      for iItem := 0 to Pred(Count) do begin
         item := GetItem(iItem);
         if AsAttributes then begin
            AddLine('  <TAmazonItem'#13#10);
            AddLine('    ASIN="' + XMLify(AnsiToUtf8(item.ASIN)) + '"'#13#10);
            AddLine('    ProductName="' + XMLify(AnsiToUtf8(item.ProductName)) + '"'#13#10);
            AddLine('    ProductWebsite="' + XMLify(AnsiToUtf8(item.ProductWebsite)) + '"'#13#10);
            AddLine('    Catalog="' + XMLify(AnsiToUtf8(item.Catalog)) + '"'#13#10);
            AddLine('    Manufacturer="' + XMLify(AnsiToUtf8(item.Manufacturer)) + '"'#13#10);
            AddLine('    ImageUrlSmall="' + XMLify(AnsiToUtf8(item.ImageUrlSmall)) + '"'#13#10);
            AddLine('    ImageUrlMedium="' + XMLify(AnsiToUtf8(item.ImageUrlMedium)) + '"'#13#10);
            AddLine('    ImageUrlLarge="' + XMLify(AnsiToUtf8(item.ImageUrlLarge)) + '"'#13#10);
            AddLine('    PriceList="' + XMLify(AnsiToUtf8(item.PriceList)) + '"'#13#10);
            AddLine('    PriceAmazon="' + XMLify(AnsiToUtf8(item.PriceAmazon)) + '"'#13#10);
            AddLine('    PriceUsed="' + XMLify(AnsiToUtf8(item.PriceUsed)) + '"'#13#10);
            AddLine('    Availability="' + XMLify(AnsiToUtf8(item.Availability)) + '"'#13#10);
            AddLine('   />'#13#10);
         end else begin
            AddLine('  <TAmazonItem>'#13#10);
            AddLine('    <ASIN PascalType="string">' + XMLify(AnsiToUtf8(item.ASIN)) + '</ASIN>'#13#10);
            AddLine('    <ProductName PascalType="string">' + XMLify(AnsiToUtf8(item.ProductName)) + '</ProductName>'#13#10);
            AddLine('    <ProductWebsite PascalType="string">' + XMLify(AnsiToUtf8(item.ProductWebsite)) + '</ProductWebsite>'#13#10);
            AddLine('    <Catalog PascalType="string">' + XMLify(AnsiToUtf8(item.Catalog)) + '</Catalog>'#13#10);
            AddLine('    <Manufacturer PascalType="string">' + XMLify(AnsiToUtf8(item.Manufacturer)) + '</Manufacturer>'#13#10);
            AddLine('    <ImageUrlSmall PascalType="string">' + XMLify(AnsiToUtf8(item.ImageUrlSmall)) + '</ImageUrlSmall>'#13#10);
            AddLine('    <ImageUrlMedium PascalType="string">' + XMLify(AnsiToUtf8(item.ImageUrlMedium)) + '</ImageUrlMedium>'#13#10);
            AddLine('    <ImageUrlLarge PascalType="string">' + XMLify(AnsiToUtf8(item.ImageUrlLarge)) + '</ImageUrlLarge>'#13#10);
            AddLine('    <PriceList PascalType="string">' + XMLify(AnsiToUtf8(item.PriceList)) + '</PriceList>'#13#10);
            AddLine('    <PriceAmazon PascalType="string">' + XMLify(AnsiToUtf8(item.PriceAmazon)) + '</PriceAmazon>'#13#10);
            AddLine('    <PriceUsed PascalType="string">' + XMLify(AnsiToUtf8(item.PriceUsed)) + '</PriceUsed>'#13#10);
            AddLine('    <Availability PascalType="string">' + XMLify(AnsiToUtf8(item.Availability)) + '</Availability>'#13#10);
            AddLine('  </TAmazonItem>'#13#10);
         end;
      end;
      AddLine('</TAmazonList>'#13#10);
   finally
      FreeAndNil(fs);
   end;
end;
{$ENDIF CollectionXMLOps}

{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AASIN  The contents that is to be searched for.
  @return  Found item that matches ASIN, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindASIN(const AASIN: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).ASIN=AASIN then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AProductName  The contents that is to be searched for.
  @return  Found item that matches ProductName, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindProductName(const AProductName: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).ProductName=AProductName then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AProductWebsite  The contents that is to be searched for.
  @return  Found item that matches ProductWebsite, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindProductWebsite(const AProductWebsite: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).ProductWebsite=AProductWebsite then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   ACatalog  The contents that is to be searched for.
  @return  Found item that matches Catalog, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindCatalog(const ACatalog: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).Catalog=ACatalog then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AArtists  The contents that is to be searched for.
  @return  Found item that matches Artists, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindArtists(const AArtists: TStringList): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).Artists=AArtists then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AReleaseDate  The contents that is to be searched for.
  @return  Found item that matches ReleaseDate, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindReleaseDate(const AReleaseDate: TDateTime): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).ReleaseDate=AReleaseDate then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AManufacturer  The contents that is to be searched for.
  @return  Found item that matches Manufacturer, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindManufacturer(const AManufacturer: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).Manufacturer=AManufacturer then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AImageUrlSmall  The contents that is to be searched for.
  @return  Found item that matches ImageUrlSmall, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindImageUrlSmall(const AImageUrlSmall: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).ImageUrlSmall=AImageUrlSmall then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AImageUrlMedium  The contents that is to be searched for.
  @return  Found item that matches ImageUrlMedium, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindImageUrlMedium(const AImageUrlMedium: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).ImageUrlMedium=AImageUrlMedium then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AImageUrlLarge  The contents that is to be searched for.
  @return  Found item that matches ImageUrlLarge, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindImageUrlLarge(const AImageUrlLarge: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).ImageUrlLarge=AImageUrlLarge then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   APriceList  The contents that is to be searched for.
  @return  Found item that matches PriceList, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindPriceList(const APriceList: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).PriceList=APriceList then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   APriceAmazon  The contents that is to be searched for.
  @return  Found item that matches PriceAmazon, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindPriceAmazon(const APriceAmazon: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).PriceAmazon=APriceAmazon then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   APriceUsed  The contents that is to be searched for.
  @return  Found item that matches PriceUsed, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindPriceUsed(const APriceUsed: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).PriceUsed=APriceUsed then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;
{*------------------------------------------------------------------------------
  Returns first TAmazonItem matching search parameters.

  @param   AAvailability  The contents that is to be searched for.
  @return  Found item that matches Availability, or nil if nothing found.
-------------------------------------------------------------------------------}
function TAmazonList.FindAvailability(const AAvailability: AnsiString): TAmazonItem;
// Purpose: Returns first TAmazonItem matching search parameters.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   Result := nil;
   for iItem := 0 to Pred(Count)
    do if GetItem(iItem).Availability=AAvailability then begin
       Result := GetItem(iItem);
       Exit;
    end;
end;

{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListASIN(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.ASIN)<0
       then AList.Add(item.ASIN);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListProductName(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.ProductName)<0
       then AList.Add(item.ProductName);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListProductWebsite(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.ProductWebsite)<0
       then AList.Add(item.ProductWebsite);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListCatalog(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.Catalog)<0
       then AList.Add(item.Catalog);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListManufacturer(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.Manufacturer)<0
       then AList.Add(item.Manufacturer);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListImageUrlSmall(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.ImageUrlSmall)<0
       then AList.Add(item.ImageUrlSmall);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListImageUrlMedium(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.ImageUrlMedium)<0
       then AList.Add(item.ImageUrlMedium);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListImageUrlLarge(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.ImageUrlLarge)<0
       then AList.Add(item.ImageUrlLarge);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListPriceList(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.PriceList)<0
       then AList.Add(item.PriceList);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListPriceAmazon(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.PriceAmazon)<0
       then AList.Add(item.PriceAmazon);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListPriceUsed(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.PriceUsed)<0
       then AList.Add(item.PriceUsed);
   end;
end;
{*------------------------------------------------------------------------------
  Creates a list of all item values.

  @param   AList List to be filled.
-------------------------------------------------------------------------------}
procedure TAmazonList.ListAvailability(const AList: TStrings);
var iItem: integer;
    item: TAmazonItem;
begin
   for iItem := 0 to Pred(Count) do begin
      item := GetItem(iItem);
      if AList.IndexOf(item.Availability)<0
       then AList.Add(item.Availability);
   end;
end;

{*------------------------------------------------------------------------------
  Returns one TAmazonItem by index. 

  @param   Index  Index of item to be retrieved.
  @return  Item of type TAmazonItem identified by Index.
-------------------------------------------------------------------------------}
function TAmazonList.GetItem(Index: Integer): TAmazonItem;
// Purpose: Returns one TAmazonItem by index.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := TAmazonItem(inherited GetItem(Index));
end;

{*------------------------------------------------------------------------------
  Returns whether a column should be visible.

  @param   Index  Index of column to be retrieved.
  @return  Whether column should be visible.
-------------------------------------------------------------------------------}
function TAmazonList.GetColumnVisible(Index: TAmazonItemEnum): boolean;
// Purpose: Returns whether a column should be visible.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := Index in FVisibleColumns;
end;

{*------------------------------------------------------------------------------
  Sets one TAmazonItem by index.

  @param   Index  Index of item to be set.
-------------------------------------------------------------------------------}
procedure TAmazonList.SetItem(Index: Integer; Value: TAmazonItem);
// Purpose: Sets one TAmazonItem by index.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   inherited SetItem(Index, Value);
end;

{*------------------------------------------------------------------------------
  Changes visibility on a column.

  @param   Index  Index of column to be set.
-------------------------------------------------------------------------------}
procedure TAmazonList.SetColumnVisible(Index: TAmazonItemEnum; const Value: boolean);
// Purpose: Changes visibility on a column.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   if Value
    then FVisibleColumns := FVisibleColumns + [Index]
     else FVisibleColumns := FVisibleColumns - [Index];
end;

{$IFDEF CollectionSorting}
{*------------------------------------------------------------------------------
  Sorts TAmazonList by ASIN.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortASIN;
// Purpose: Sorts TAmazonList by ASIN.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareASIN);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by ProductName.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortProductName;
// Purpose: Sorts TAmazonList by ProductName.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareProductName);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by ProductWebsite.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortProductWebsite;
// Purpose: Sorts TAmazonList by ProductWebsite.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareProductWebsite);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by Catalog.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortCatalog;
// Purpose: Sorts TAmazonList by Catalog.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareCatalog);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by Artists.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortArtists;
// Purpose: Sorts TAmazonList by Artists.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareArtists);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by ReleaseDate.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortReleaseDate;
// Purpose: Sorts TAmazonList by ReleaseDate.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareReleaseDate);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by Manufacturer.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortManufacturer;
// Purpose: Sorts TAmazonList by Manufacturer.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareManufacturer);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by ImageUrlSmall.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortImageUrlSmall;
// Purpose: Sorts TAmazonList by ImageUrlSmall.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareImageUrlSmall);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by ImageUrlMedium.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortImageUrlMedium;
// Purpose: Sorts TAmazonList by ImageUrlMedium.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareImageUrlMedium);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by ImageUrlLarge.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortImageUrlLarge;
// Purpose: Sorts TAmazonList by ImageUrlLarge.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareImageUrlLarge);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by PriceList.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortPriceList;
// Purpose: Sorts TAmazonList by PriceList.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemComparePriceList);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by PriceAmazon.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortPriceAmazon;
// Purpose: Sorts TAmazonList by PriceAmazon.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemComparePriceAmazon);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by PriceUsed.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortPriceUsed;
// Purpose: Sorts TAmazonList by PriceUsed.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemComparePriceUsed);
end;
{*------------------------------------------------------------------------------
  Sorts TAmazonList by Availability.
-------------------------------------------------------------------------------}
procedure TAmazonList.SortAvailability;
// Purpose: Sorts TAmazonList by Availability.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   GetCollectionList(Self).Sort(@TAmazonItemCompareAvailability);
end;
{$ENDIF CollectionSorting}

{$IFDEF GUICollection}
{*------------------------------------------------------------------------------
  Fills a TListItem with properties of TAmazonItem specified by index in TListItem.

  @param   Item  TListItem object to be filled with properties of this TAmazonItem.
-------------------------------------------------------------------------------}
procedure TAmazonList.ToListItem(const Item: TListItem);
// Purpose: Fills a TListItem with properties of TAmazonItem specified by index in TListItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   if Item.Index<0
    then Exit;
   if Item.Index>=Count
    then Exit;
   GetItem(Item.Index).ToListItem(Item);
end;

{*------------------------------------------------------------------------------
  Fills a TListItem with properties of TAmazonItem specified by index in TListItem.

  @param   Item  TListItem object to be filled with properties of this TAmazonItem.
-------------------------------------------------------------------------------}
procedure TAmazonList.ToListItemTagsRevisited(const Item: TListItem);
// Purpose: Fills a TListItem with properties of TAmazonItem specified by index in TListItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   if Item.Index<0
    then Exit;
   if Item.Index>=Count
    then Exit;
   GetItem(Item.Index).ToListItemTagsRevisited(Item);
end;
{$ENDIF GUICollection}

{$IFDEF CollectionFiltering}
{$IFDEF GUICollection}
{*------------------------------------------------------------------------------
  Fills a TListItem with properties of filtered TAmazonItem specified by index in TListItem.

  @param   Item  TListItem object to be filled with properties of this TAmazonItem.
-------------------------------------------------------------------------------}
procedure TAmazonList.ToFilteredListItem(const Item: TListItem);
// Purpose: Fills a TListItem with properties of TAmazonItem specified by index in TListItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   if Item.Index<0
    then Exit;
   if Item.Index>=Count
    then Exit;
   FilteredItems[Item.Index].ToListItem(Item);
end;
{$ENDIF GUICollection}

function TAmazonList.ToHTMLParameter(const Text: AnsiString): AnsiString;
begin
   Result := StringReplace(Text, ' ', '%20', [rfReplaceAll]);
end;

{*------------------------------------------------------------------------------
  Adds an item to the list of filtered items.

  @param   Value  Identifies the item to be added to the filtered list.
-------------------------------------------------------------------------------}
procedure TAmazonList.AddFilteredItem(Value: TAmazonItem);
// Purpose: Adds an item to the list of filtered items.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   SetLength(FFilteredItems,Length(FFilteredItems)+1);
   FFilteredItems[Length(FFilteredItems)-1] := Value;
end;

{*------------------------------------------------------------------------------
  Clears list of filtered items.
-------------------------------------------------------------------------------}
procedure TAmazonList.ClearFilterList;
// Purpose: Clears list of filtered items.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   SetLength(FFilteredItems,0);
end;

{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AASIN  The ASIN that should be filtered.
  @return  Count of items matching the given ASIN.
-------------------------------------------------------------------------------}
function TAmazonList.FilterASIN(const AASIN: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ASIN=AASIN
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AProductName  The ProductName that should be filtered.
  @return  Count of items matching the given ProductName.
-------------------------------------------------------------------------------}
function TAmazonList.FilterProductName(const AProductName: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ProductName=AProductName
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AProductWebsite  The ProductWebsite that should be filtered.
  @return  Count of items matching the given ProductWebsite.
-------------------------------------------------------------------------------}
function TAmazonList.FilterProductWebsite(const AProductWebsite: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ProductWebsite=AProductWebsite
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param ACatalog  The Catalog that should be filtered.
  @return  Count of items matching the given Catalog.
-------------------------------------------------------------------------------}
function TAmazonList.FilterCatalog(const ACatalog: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Catalog=ACatalog
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AArtists  The Artists that should be filtered.
  @return  Count of items matching the given Artists.
-------------------------------------------------------------------------------}
function TAmazonList.FilterArtists(const AArtists: TStringList): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Artists=AArtists
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AReleaseDate  The ReleaseDate that should be filtered.
  @return  Count of items matching the given ReleaseDate.
-------------------------------------------------------------------------------}
function TAmazonList.FilterReleaseDate(const AReleaseDate: TDateTime): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ReleaseDate=AReleaseDate
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AManufacturer  The Manufacturer that should be filtered.
  @return  Count of items matching the given Manufacturer.
-------------------------------------------------------------------------------}
function TAmazonList.FilterManufacturer(const AManufacturer: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Manufacturer=AManufacturer
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AImageUrlSmall  The ImageUrlSmall that should be filtered.
  @return  Count of items matching the given ImageUrlSmall.
-------------------------------------------------------------------------------}
function TAmazonList.FilterImageUrlSmall(const AImageUrlSmall: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ImageUrlSmall=AImageUrlSmall
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AImageUrlMedium  The ImageUrlMedium that should be filtered.
  @return  Count of items matching the given ImageUrlMedium.
-------------------------------------------------------------------------------}
function TAmazonList.FilterImageUrlMedium(const AImageUrlMedium: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ImageUrlMedium=AImageUrlMedium
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AImageUrlLarge  The ImageUrlLarge that should be filtered.
  @return  Count of items matching the given ImageUrlLarge.
-------------------------------------------------------------------------------}
function TAmazonList.FilterImageUrlLarge(const AImageUrlLarge: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).ImageUrlLarge=AImageUrlLarge
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param APriceList  The PriceList that should be filtered.
  @return  Count of items matching the given PriceList.
-------------------------------------------------------------------------------}
function TAmazonList.FilterPriceList(const APriceList: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).PriceList=APriceList
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param APriceAmazon  The PriceAmazon that should be filtered.
  @return  Count of items matching the given PriceAmazon.
-------------------------------------------------------------------------------}
function TAmazonList.FilterPriceAmazon(const APriceAmazon: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).PriceAmazon=APriceAmazon
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param APriceUsed  The PriceUsed that should be filtered.
  @return  Count of items matching the given PriceUsed.
-------------------------------------------------------------------------------}
function TAmazonList.FilterPriceUsed(const APriceUsed: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).PriceUsed=APriceUsed
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;
{*------------------------------------------------------------------------------
  Filters all TAmazonItem matching to FilteredItems list.

  @param AAvailability  The Availability that should be filtered.
  @return  Count of items matching the given Availability.
-------------------------------------------------------------------------------}
function TAmazonList.FilterAvailability(const AAvailability: AnsiString): integer;
// Purpose: Filters all TAmazonItem matching to FilteredItems list.
// Date:    2007-07-03 (Function created by CollectionTemplater)
var iItem: integer;
begin
   SetLength(FFilteredItems,0);
   for iItem := 0 to Pred(Count) do begin
      if GetItem(iItem).Availability=AAvailability
       then AddFilteredItem(GetItem(iItem));
   end;
   Result := Length(FFilteredItems);
end;

{*------------------------------------------------------------------------------
  Returns one specific filtered TAmazonItem.

  @param Index of item from filtered list.
  @return  Returns TAmazonItem matching the given Index.
-------------------------------------------------------------------------------}
function TAmazonList.GetFilteredItem(Index: Integer): TAmazonItem;
// Purpose: Returns one specific filtered TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := FFilteredItems[Index];
end;

{*------------------------------------------------------------------------------
  Returns number of filtered TAmazonItem.

  @return  Returns number of filtered TAmazonItem.
-------------------------------------------------------------------------------}
function TAmazonList.GetFilteredCount: integer;
// Purpose: Returns number of filtered TAmazonItem.
// Date:    2007-07-03 (Function created by CollectionTemplater)
begin
   Result := Length(FFilteredItems);
end;
{$ENDIF CollectionFiltering}

{ TSignedAmazonURL }

{*------------------------------------------------------------------------------
  Adds a properly encoded parameter to the query.

  @param ParameterName   Name to use.
  @param Value   Value; will be encoded.
------------------------------------------------------------------------------*}
procedure TSignedAmazonURL.AddParameter(ParameterName, Value: AnsiString);
begin
   Sorted := false;
   Values[ParameterName] := RFC3986CharacterEncode(Value);
   Sorted := true;
end;

{*------------------------------------------------------------------------------
  Constructor, presets server, path and timestamp.
------------------------------------------------------------------------------*}
constructor TSignedAmazonURL.Create;
begin
   inherited;
   FScript := '/onca/xml';
   FServer := 'ecs.amazonaws.com';
   Sorted := true;
   Duplicates := dupIgnore;
   StrictDelimiter := true;
   Delimiter := '&';
   Add('Service=AWSECommerceService');
   Add('Timestamp=' + RFC3986CharacterEncode(FormatDateTime('yyyy-mm-dd', Now) + 'T' + FormatDateTime('hh:nn:ss', Now) + '.000Z'));
end;

{*------------------------------------------------------------------------------
  Creates a signature for the current request.

  @return The signature, already encoded.
------------------------------------------------------------------------------*}
function TSignedAmazonURL.GetSignature: AnsiString;
var hmc: THMAC_Context;
    hmd: TSHA256Digest; // array [0..31] of byte
    s, sBin: AnsiString;
    i: integer;
begin
   hmac_SHA256_inits(hmc, FSecretAccessKey);
   s := StringToSign;
   hmac_SHA256_update(hmc, @(s[1]), Length(s));
   hmac_SHA256_final(hmc, hmd);
   SetLength(sBin, 32);
   for i := 0 to 31
    do sBin[i+1] := AnsiChar(hmd[i]);

   Result := Base64Encode(sBin);
   Result := StringReplace(Result, '+', '%2B', [rfReplaceAll]);
   Result := StringReplace(Result, '=', '%3D', [rfReplaceAll]);
end;

{*------------------------------------------------------------------------------
  Constructs the string used for signing.

  @return Signed string as used to create the signature.
------------------------------------------------------------------------------*}
function TSignedAmazonURL.GetStringToSign: AnsiString;
var slTemp: TStringList;
    i: integer;
begin
   slTemp := TStringList.Create;
   try
      try
         Self.Sorted := false;
         Self.CustomSort(ByteCompare);
         slTemp.Add('GET');
         slTemp.Add(FServer);
         slTemp.Add(FScript);
         slTemp.Add(DelimitedText);
         slTemp.Delimiter := #10;
         Result := slTemp.DelimitedText;
      finally
         FreeAndNil(slTemp);
         Self.Sorted := false;
      end;
   except
      Result := '';
   end;
end;

{*------------------------------------------------------------------------------
  Constructs a URL to access Amazon AWS.

  @return Complete URL including signature
------------------------------------------------------------------------------*}
function TSignedAmazonURL.GetURL: AnsiString;
begin
   Result := 'http://' + FServer + FScript + '?' + DelimitedText + '&Signature=' + GetSignature;
end;

{$IFDEF XMLLib}
(*
XMLLib is released as freeware for all non-commercial projects.
All commercial projects using this unit have to inform the author -
Thomas Koos (info@muetze1.de) - the usage in that projects is allowed
afterwards. If this won't be done - the usage is illegal!

Remove this block after you have agreed to the above.
*)
{$ENDIF XMLLib}

end.

