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.

2 respostas

Deixe uma resposta

Want to join the discussion?
Feel free to contribute!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *


Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.