CloudAPI – Envio de arquivos para Amazon S3 e Windows Azure – Parte 1
No meu recente artigo Shell Extension para Windows 32-bit e Windows 64-bit com Delphi XE expliquei passo a passo como criar extensões para o Windows Explorer, possibilitando o envio de arquivo para Amazon S3 e Windows Azure, neste artigo vamos ver como utilizar o Cloud API para enviar os arquivos para Amazon S3 e Windows Azure Cloud com Delphi XE2, caso você não tenha lido o artigo anterior, recomendo ler para melhor compreensão deste artigo.
O Cloud API é um conjunto de API’s que permite a interação com os serviços de tabela, armazenamento e mensagens do Amazon S3 e Windows Azure, está disponível para Delphi e C++Builder.
Utilizando o exemplo do artigo anterior como ponto inicial, foi implementado no mesmo um DataModule (CloudDM.pas) que contém a configuração para acesso as nuvens através dos componentes AmazonConnection e AzureConnection, além disso os métodos responsáveis pelo envio de arquivos para as nuvens, assim sendo você precisa ter uma conta no Amazon e no Azure para utilizar este exemplo, a baixo um resumo breve para obter sua conta a qual não é gratuita, o custo é bem baixo e baseado no quanto você utiliza.
Criando uma conta Windows Azure Storage
Através do link http://windows.azure.com você pode criar sua conta Azure Storage seguindo os passos fornecidos na página, abaixo um screenshot da minha conta e onde estão localizados os dois parâmetros (AccountName e AccountKey) requeridos pelo componente AzureConnection.
Criando uma conta Amazon (AWS)
Criar uma conta no Amazon também é bem simples, acesse o site https://console.aws.amazon.com/s3/home e siga os passos, para obter o AccountKey e AccountName a ser utilizado no componente AmazonConnection, acesse https://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key, abaixo um screenshot.
Arquivo INI para configuração da DLL
As configurações de acesso as respectivas nuvens estão sendo armazenadas no arquivo ini CloudUpload-Extension.ini localizado no diretório Home de cada usuário, eu não vou detalhar como utilizar arquivos ini aqui, você pode estudar esta parte após baixar os código fonte do exemplo aqui utilizado.
Bucket vs Container e Object vs Blob
Todo arquivo enviado para as nuvens será armazenado em uma pasta, Amazon chama pasta de Bucket e Windows Azure de Container, no final das contas Bucket e Container são pastas, cada nuvem tem suas regras e limitações de como nomear as pastas, por exemplo o nome de uma pastas no Amazon deve ser único em toda a nuvem, isso mesmo não único na sua conta, ou seja, criar um diretório imagens esquece, alguém certamente já criou um.
A diferença de nomenclatura também ocorre para arquivos, que na Amazon é chamado do Object e no Windows Azure chama-se Blog.
Cloud API respeita essas diferenças, assim para criar um Bucket no Amazon usamos o método CreateBucket, para criar um Container no Windows Azure utilizamos o método CreateContainer.
Envio de arquivos para Windows Azure
A implementação do Shell Extension utiliza o método UploadtoAzure (CloudDM.pas) para o envio de arquivos para o Windows Azure, antes de enviar o arquivo temos definir em qual pasta (Container) será armazenado este arquivo.
Como o container pode não existir, ou o usuário pode querer criar um novo, implementei um formulário que permite o usuário selecionar um container já existente ou criar uma nova.
Abaixo parte do código responsável pelo envio do arquivo, as linhas em destaque refletem o código que irá listar e criar ou selecionar o container.
Ao fazer uma solicitação ao Windows Azure através do CloudAPI a maioria dos métodos retorna um objeto do tipo TCloudResponseInfo, este objeto contém o status do retorno referente a sua solicitação, todas estas solicitações são chamadas REST.
Para obter a lista de containers utilizando o método ListContainers que é parte do objeto BlobService instanciando anteriormente, todos os serviços relacionados a Blob fazem parte desta classe, assim como a criação do container através do método Create Container.
No caso da criação do container, podemos passar informações adicionais a qual chamamos de metadata, no exemplo abaixo passamos o nome do computador utilizado para solicitar a criação deste container, esta informação estará disponível como parte do metadata deste container, você pode definir várias outras informações e adiciona-las como parte do seu metadata que é do tipo TStringList;
function TCloud.UploadtoAzure(FileName: String): TCloudResponseInfo; var BlobService: TAzureBlobService; SList, Metadata: TStrings; ContainerList: TList; ContainerObj: TAzureContainer; Content: TBytes; BlobName, ContainerName: String; ResponseList: TCloudResponseInfo; FrmList: TFrmContainerList; Act: TContainerAction; begin Result := TCloudResponseInfo.Create; if (FileName = EmptyStr) then Exit; BlobService := TAzureBlobService.Create(AzureConnection); try ResponseList := TCloudResponseInfo.Create; ContainerList := BlobService.ListContainers(nil, ResponseList); Metadata := TStringList.Create; if ResponseList.StatusCode = 200 then begin SList := TStringList.Create; for ContainerObj in ContainerList do SList.Add(ContainerObj.Name); ContainerList.Free; FrmList := TFrmContainerList.Create(nil, SList, TClouds[AzureIndex]); try FrmList.ShowModal; Act := FrmList.Action; case Act of caCreate: begin Metadata.Values[SMDCreateby] := GetComputerandUserName; if BlobService.CreateContainer(FrmList.Container, Metadata, bpaBlob, Result) then ContainerName := FrmList.Container; end; caUpload: begin ContainerName := FrmList.Container; end; end; finally SList.Free; FrmList.Free; end; if Act = TContainerAction.caNone then Exit; end;
Com a definição do container podemos seguir para o próximo passo que é o envio do arquivo.
Para o envio do arquivo utilizamos o método BlobService.PutBlockBlob que contém diversos parâmetros como: nome e o conteúdo do arquivo em TBytes, metadata e alguns outros que não são necessários para este exemplo.
Assim como para containers podemos adicionar metadata ao arquivo, neste exemplo adiciono o nome do computador\usuário e a localização original do arquivo a ser enviado.
Metadata.Clear; Metadata.Values[SMDPath] := ExtractFilePath(FileName); Metadata.Values[SMDFrom] := GetComputerandUserName; Content := ContentOf(FileName); BlobName := ExtractFileName(FileName);
O método ContentOf retorna o conteúdo do arquivo em TBytes.
function TCloud.ContentOf(const FileName: String; RequiredSizeMod: Integer): TBytes; var fs: TFileStream; begin if FileName <> EmptyStr then begin fs := TFileStream.Create(FileName, fmOpenRead); try if RequiredSizeMod < 1 then SetLength(Result, fs.Size) else SetLength(Result, ((fs.Size div RequiredSizeMod) + 1) * RequiredSizeMod); fs.ReadBuffer(Result[0], fs.Size); finally fs.Free; end; end else SetLength(Result, 0); end;
O resultado do envio será um parâmetro de saída do tipo TCloudResponseInfo, este contém a propriedade StatusCode, requisições realizadas com sucesso retornam o código 200 ou 201.
BlobService.PutBlockBlob(ContainerName, BlobName, Content, '', nil, Metadata, Result); ShowResponseInfo(TClouds[AzureIndex], Result);
Abaixo a implementação completa da unit CloudDM.pas.
unit CloudDM; interface uses System.SysUtils, System.Classes, Data.Cloud.AzureAPI, Data.Cloud.CloudAPI, Data.Cloud.AmazonAPI, Winapi.Windows, IniFiles, IOUtils, System.Generics.Collections; type TCloud = class(TDataModule) AzureConnection: TAzureConnectionInfo; AmazonConnection: TAmazonConnectionInfo; private { Private declarations } FHandle: HWND; function GetComputerandUserName: String; function UploadtoAmazon(FileName: String): TCloudResponseInfo; function UploadtoAzure(FileName: String): TCloudResponseInfo; function ContentOf(const FileName: String; RequiredSizeMod: Integer = 0): TBytes; procedure ShowResponseInfo(Title: String; Response: TCloudResponseInfo); public { Public declarations } function LoadConfiguration: Boolean; function Upload(FileName: String; Cloud: Integer): TCloudResponseInfo; class procedure SaveLog(s: String); constructor Create(AOWner: TComponent; Handle: HWND); reintroduce; overload; end; var Cloud: TCloud; Const AmazonIndex = 0; AzureIndex = 1; TClouds: Array [0 .. 1] of PWideChar = ('Amazon S3', 'Microsoft Azure'); implementation uses CloudContainerList; Const SMyAzureKey = 'MyAzureKey'; // do not translate SMyAmazonKey = 'MyAmazonKey'; // do not translate SInternetConfig = 'Internet Access Configuration'; // do not translate SAccountName = 'AccountName'; // do not translate SAccountKey = 'AccountKey'; // do not translate SProxyHost = 'ProxyHost'; // do not translate SProxyPort = 'ProxyPort'; // do not translate SStorageEndpoint = 'StorageBlobEndpoint'; // do not translate SMDPath = 'originalfilepath'; SMDFrom = 'uploadfrom'; SMDCreateby = 'createdby'; {$R *.dfm} { TCloudContainer } function TCloud.ContentOf(const FileName: String; RequiredSizeMod: Integer): TBytes; var fs: TFileStream; begin if FileName <> EmptyStr then begin fs := TFileStream.Create(FileName, fmOpenRead); try if RequiredSizeMod < 1 then SetLength(Result, fs.Size) else SetLength(Result, ((fs.Size div RequiredSizeMod) + 1) * RequiredSizeMod); fs.ReadBuffer(Result[0], fs.Size); finally fs.Free; end; end else SetLength(Result, 0); end; constructor TCloud.Create(AOWner: TComponent; Handle: HWND); begin inherited Create(AOWner); FHandle := Handle end; function TCloud.GetComputerandUserName: String; var cName, uName: array [0 .. 255] of char; c: DWORD; begin c := SizeOf(cName); GetComputerName(cName, c); GetUserName(uName, c); Result := StrPas(cName) + '\' + StrPas(uName); end; function TCloud.LoadConfiguration: Boolean; var IniFile: string; Ini: TIniFile; Port: Integer; SUrl: String; begin IniFile := GetHomePath + '\CloudUpload-Extension.ini'; Ini := TIniFile.Create(IniFile); try // The ini file will be created in case it doesn't exist if not FileExists(IniFile) then begin // Azure parameters Ini.WriteString(SMyAzureKey, SAccountName, EmptyStr); Ini.WriteString(SMyAzureKey, SAccountKey, EmptyStr); Ini.WriteString(SMyAzureKey, SStorageEndpoint, EmptyStr); // Amazon parameters Ini.WriteString(SMyAmazonKey, SAccountName, EmptyStr); Ini.WriteString(SMyAmazonKey, SAccountKey, EmptyStr); Ini.WriteString(SMyAmazonKey, SStorageEndpoint, EmptyStr); // Internet access configuration Ini.WriteString(SInternetConfig, SProxyHost, EmptyStr); Ini.WriteString(SInternetConfig, SProxyPort, EmptyStr); Ini.UpdateFile; Result := False; end else begin TryStrToInt(Ini.ReadString(SInternetConfig, SProxyPort, '0'), Port); with AzureConnection do begin AccountName := Ini.ReadString(SMyAzureKey, SAccountName, EmptyStr); AccountKey := Ini.ReadString(SMyAzureKey, SAccountKey, EmptyStr); RequestProxyHost := Ini.ReadString(SInternetConfig, SProxyHost, EmptyStr); RequestProxyPort := Port; SUrl := Ini.ReadString(SMyAzureKey, SStorageEndpoint, EmptyStr); UseDefaultEndpoints := SUrl = EmptyStr; BlobEndpoint := SUrl; end; with AmazonConnection do begin AccountName := Ini.ReadString(SMyAmazonKey, SAccountName, EmptyStr); AccountKey := Ini.ReadString(SMyAmazonKey, SAccountKey, EmptyStr); RequestProxyHost := Ini.ReadString(SInternetConfig, SProxyHost, EmptyStr); RequestProxyPort := Port; SUrl := Ini.ReadString(SMyAmazonKey, SStorageEndpoint, EmptyStr); UseDefaultEndpoints := SUrl = EmptyStr; StorageEndpoint := SUrl; end; Result := True; end; finally Ini.Free; end; end; class procedure TCloud.SaveLog(s: String); var LogFile: TextFile; begin { open the log file } AssignFile(LogFile, 'c:\Shell.txt'); if FileExists('c:\Shell.txt') then Append(LogFile) else Rewrite(LogFile); WriteLn(LogFile, s + #13#10); { write and close the logfile } CloseFile(LogFile); end; procedure TCloud.ShowResponseInfo(Title: String; Response: TCloudResponseInfo); begin case Response.StatusCode of 200, 201: MessageBox(FHandle, 'File Uploaded', PWideChar(Title), MB_OK); else MessageBox(FHandle, PWideChar('Error ' + Response.StatusMessage), PWideChar(Title), MB_ICONWARNING); end; end; function TCloud.Upload(FileName: String; Cloud: Integer): TCloudResponseInfo; begin case Cloud of AmazonIndex: Result := UploadtoAmazon(FileName); AzureIndex: Result := UploadtoAzure(FileName); else Result := TCloudResponseInfo.Create; end; end; function TCloud.UploadtoAmazon(FileName: String): TCloudResponseInfo; var StorageService: TAmazonStorageService; BucketList, Metadata: TStrings; Content: TBytes; ResponseList: TCloudResponseInfo; FrmList: TFrmContainerList; Act: TContainerAction; BucketName: String; I: Integer; begin Result := TCloudResponseInfo.Create; if (FileName = EmptyStr) then Exit; StorageService := TAmazonStorageService.Create(AmazonConnection); {$REGION 'Define the Bucket'} ResponseList := TCloudResponseInfo.Create; BucketList := StorageService.ListBuckets(ResponseList); if ResponseList.StatusCode = 200 then begin // Amazon return date/time information for each Bucket // this for is required to remove that information for I := 0 to BucketList.Count - 1 do BucketList[I] := BucketList.Names[I]; FrmList := TFrmContainerList.Create(nil, BucketList, TClouds[AmazonIndex]); try FrmList.ShowModal; Act := FrmList.Action; case Act of caCreate: begin if StorageService.CreateBucket(FrmList.Container, amzbaNotSpecified, amzrNotSpecified, Result) then BucketName := FrmList.Container; end; caUpload: begin BucketName := FrmList.Container; end; end; finally FrmList.Free; end; if Act = TContainerAction.caNone then Exit; end; {$ENDREGION} try Metadata := TStringList.Create; Metadata.Values[SMDPath] := ExtractFilePath(FileName); Metadata.Values[SMDFrom] := GetComputerandUserName; Content := ContentOf(FileName); FileName := StringReplace(FileName, ' ', '%20', [rfReplaceAll, rfIgnoreCase]); StorageService.UploadObject(BucketName, ExtractFileName(FileName), Content, False, Metadata, nil, amzbaPublicRead, Result); ShowResponseInfo(TClouds[AmazonIndex], Result); except on E: Exception do begin MessageBox(FHandle, PWideChar('Error ' + E.Message), TClouds[AmazonIndex], MB_OK); end; end; FreeAndNil(Metadata); FreeAndNil(StorageService); end; function TCloud.UploadtoAzure(FileName: String): TCloudResponseInfo; var BlobService: TAzureBlobService; SList, Metadata: TStrings; ContainerList: TList; ContainerObj: TAzureContainer; Content: TBytes; BlobName, ContainerName: String; ResponseList: TCloudResponseInfo; FrmList: TFrmContainerList; Act: TContainerAction; begin Result := TCloudResponseInfo.Create; if (FileName = EmptyStr) then Exit; BlobService := TAzureBlobService.Create(AzureConnection); try {$REGION 'Define the container'} ResponseList := TCloudResponseInfo.Create; ContainerList := BlobService.ListContainers(nil, ResponseList); Metadata := TStringList.Create; if ResponseList.StatusCode = 200 then begin SList := TStringList.Create; for ContainerObj in ContainerList do SList.Add(ContainerObj.Name); ContainerList.Free; FrmList := TFrmContainerList.Create(nil, SList, TClouds[AzureIndex]); try FrmList.ShowModal; Act := FrmList.Action; case Act of caCreate: begin Metadata.Values[SMDCreateby] := GetComputerandUserName; if BlobService.CreateContainer(FrmList.Container, Metadata, bpaBlob, Result) then ContainerName := FrmList.Container; end; caUpload: begin ContainerName := FrmList.Container; end; end; finally SList.Free; FrmList.Free; end; if Act = TContainerAction.caNone then Exit; end; {$ENDREGION} Metadata.Clear; Metadata.Values[SMDPath] := ExtractFilePath(FileName); Metadata.Values[SMDFrom] := GetComputerandUserName; Content := ContentOf(FileName); BlobName := ExtractFileName(FileName); BlobService.PutBlockBlob(ContainerName, BlobName, Content, '', nil, Metadata, Result); ShowResponseInfo(TClouds[AzureIndex], Result); except on E: Exception do begin MessageBox(FHandle, PWideChar('Error ' + E.Message), TClouds[AzureIndex], MB_OK); end; end; FreeAndNil(ResponseList); FreeAndNil(Metadata); FreeAndNil(BlobService); end; end.
O código fonte completo está disponível no repositório do RAD Studio XE2 no sourceforge, no artigo anterior você pode obter as informações de como obter os fontes.
Até o próximo artigo que irá abordar o envio de arquivos para Amazon S3.
Trackbacks & Pingbacks
[…] CloudAPI – Envio de arquivos para Amazon S3 e Windows Azure – Parte 1 […]
[…] CloudAPI – Envio de arquivos para Amazon S3 e Windows Azure – Parte 1 […]
Deixe uma resposta
Want to join the discussion?Feel free to contribute!