4 de novembro de 2011

Trabalhando com emails em uma caixa postal usando IMAP4 em Delphi

Mostrei num outro post aqui no blog como ter acesso aos emails numa caixa postal através do protocolo POP3 no Delphi. O POP3 é um protocolo amplamente adotado para permitir o acesso remoto aos emails de uma caixa postal mas ele é bastante limitado. Seus recursos estão restritos basicamente à manipulação das mensagens existentes na caixa postal, permitindo lê-las, apagá-las ou arquivá-las.

Se precisar de recursos mais avançados - tais como gerenciar pastas, localizar uma mensagem pesquisando o texto dela, compartilhar caixas postais com um grupo de trabalho, entre outras - temos que apelar para o IMAP4, um protocolo mais completo.

O primeiro passo para trabalhar com o IMAP4, assim como acontece com os outros protocolos baseados no TCP/IP, é estabelecer uma conexão com o servidor. Aqui também isso exige que alguns parâmetros sejam informados, tais como o endereço do servidor, a porta onde ele estará aguardando requisições e um usuário com permissão de acesso ao serviço, bem como sua respectiva senha:
var iMap TIdIMAP4;
begin
iMap := TIdIMAP4.Create (Nil);

{ Atribui os dados fornecidos pelo usuário e tenta se conectar }
iMap.Host := _ServerIMAP; { Endereço do servidor }
iMap.Port := _PortaIMAP; { Porta que o servidor está ouvindo }
iMap.Username := _UserIMAP;
iMap.Password := _SenhaIMAP;

iMap.Connect(100000);

{ Realiza aqui algumas tarefas com o IMAP conectado}

{ Descarta a conexão e outros recursos qdo não forem mais necessários }
iMap.Disconnect();

iMap.Free ();
end;
Uma grande diferença em relação ao funcionamento do POP3 é que o IMAP4 pode gerenciar várias caixas postais numa única conexão. Do ponto de vista do usuário, cada caixa postal é normalmente encarada como uma pasta. Como os comandos do IMAP4 são direcionados a uma caixa postal, isso nos obriga a selecionar uma antes de submeter os comandos.

Ao menos a caixa chamada INBOX (caixa de entrada) deve existir mas você pode levantar uma lista completa com todos os nomes das caixas que existem na conta usando a função ListMailBoxes. Um usuário pode, então, ser apresentado a essa lista para que escolha a caixa mais apropriada para as necessidades dele. Considerando o iMap criado e conectado no quadro anterior, fazer o levantamento é bem simples:
var lMaixBoxes : TStringList;
lCaixaSel : String;
begin
{ ... }
lMaixBoxes := TStringList.Create();

iMap.ListMailBoxes(lMaixBoxes);

{ Carrega a lista na tela para o usuário escolher uma delas. }
lCaixaSel := SelecionaCaixaPostal (lMaixBoxes);

lMailBoxes.Free();

if (not iMap.SelectMailBox (lCaixaSel) ) then
raise Exception.Create('Erro na seleção da caixa postal');
{ ... }
end;
Uma vez que a caixa foi escolhida, precisamos repassá-la ao iMap para que possamos operar com a caixa específica. É o que faz a chamada à função SelectMailBox no código acima. Essa chamada faz com que o TidIMAP4 alimente sua propriedade MailBox com um resumo do estado atual da caixa selecionada, disponibilizando informações como a quantidade total de mensagens e quantas dessas permanecem não lidas, entre outras informações.

Com uma caixa selecionada, podemos realizar operações como pesquisar os emails contidos nela. Nesse quesito, o IMAP4 é muito mais flexível que o POP3 já que nos fornece uma opção para filtrar as mensagens desejadas usando a estrutura TIdIMAP4SearchRec. O principal recurso dessa estrutura é o campo SearchKey. É esse campo que determina quais critérios devem ser respeitados na consulta e, portanto, quais dos outros campos da estrutura serão considerados quando a consulta for submetida pela função SearchMailBox. O exemplo abaixo usa o iMap conectado para procurar todas as mensagens que ainda não foram lidas na caixa postal atual e varre a lista encontrada, recuperando cada uma delas.
var rec : array of TIdIMAP4SearchRec;
TheMsg : TIdMessage;
contMsg, i, idMsg : Integer;
begin
{ Procura por emails do tipo skUnseen - ou seja, somente os "não lidos" }
SetLength(rec,1);
rec[0].SearchKey := skUnseen;
if not iMap.SearchMailBox (rec) then
raise Exception.Create('Erro na pesquisa da caixa postal');

{ Quantas mensagens foram encontradas ? }
contMsg := iMap.MailBox.SearchResult.Length;

for i := 0 to contMsg - 1 do begin
TheMsg := TIdMessage.Create(Nil);

{ Obtem a identificação da mensagem ... }
idMsg := iMap.MailBox.SearchResult[i];

{ ... e a usa para recuperar o email completo }
iMap.Retrieve (idMsg, TheMsg);

{ Analisa anexos e outros procedimentos }
end;
end;
Veja que a a função de pesquisa aceita um array de filtros como parâmetro. Isso significa que podemos construir uma combinação de filtros para que uma lista mais refinada seja retornada. Outras possibilidades de filtros para pesquisar a caixa incluem o texto contido nas mensagens ou seus cabeçalhos, a data de recepção delas ou até mesmo seu tamanho.

Um outro ponto importante a destacar é que o resultado da pesquisa é armazenado na propriedade SearchResult do MailBox. O resultado fica acessível como uma lista de identificadores das mensagens que atenderam o critério estipulado para a busca. Esses identificadores são números inteiros que podem ser usados em diversas funções da caixa postal, como a que recupera o conteúdo da mensagem ou que permite excluí-la.

Uma vez que uma mensagem foi recuperada com sucesso, a instância da classe TIdMessage nos dá acesso a todas as informações pertinentes a ela, incluindo os anexos, caso ela possua algum. O trecho que trata os anexos foi removido pra não poluir muito o quadro. Como a recuperação de anexos funciona exatamente igual ao do POP3, o processo descrito no post Recuperando emails de uma caixa postal com Delphi pode ser usado para realizar esse trabalho.

Um outro recurso do IMAP4 é que você pode copiar mensagens da caixa atualmente selecionada pra uma outra caixa usando a função CopyMsgs. Ela aceita 2 parâmetros : um array com os identificadores das mensagens a serem copiadas e o nome da caixa postal que receberá as mensagens. Caso precise mover as mensagens, use essa função combinada com a DeleteMsgs, que exclui uma lista de mensagens da caixa atual.

O IMAP4 ainda disponibiliza funções para gerenciar as caixas postais. É possível criar uma caixa nova, renomer uma caixa e excluir caixas existentes. Os nomes das funções com esses objetivos são bastante intuitivos : CreateMailBox, DeleteMailBox e RenameMailBox.

21 de outubro de 2011

Trabalhando com a tag Canvas do HTML5 - parte IV

Até agora , nos posts que escrevi sobre o uso do canvas do HTML5, quando precisei me referir a uma cor nos scripts de exemplo, eu sempre usei a representação tradicional para cores estabelecida pelo CSS (Cascading Style Sheet). Isto é, as cores sempre foram especificadas como uma mistura da intensidade de vermelho, verde e azul (o código RGB) que resultasse na cor desejada. Neste post, mostro que o Canvas permite aplicar um grau de transparência às cores e também trabalhar com gradientes de cores.

As regras para cores no canvas são as mesmas definidas para o CSS. Portanto, podemos usar aqui também a função rgba(), que introduz um quarto parâmetro para controlar o nível de transparência de cores. Os 3 primeiros parâmetros são os conhecidos níveis de intensidade do vermelho, verde e azul (representados por números de 0 a 255). O último valor – chamado de alpha – corresponde ao grau de transparência a ser aplicado aos desenhos subsequentes. Ele pode assumir valores entre 0 (completamente transparente) e 1 (desenha a imagem opaca, sobrepondo totalmente o fundo, sendo esse o efeito padrão obtido até aqui).
var myCanvas = document.getElementById("cnvCores2");
var ctx = myCanvas.getContext("2d");
var incrY = myCanvas.height / 10;
var grausFin = 360;
var radFin = (Math.PI/180)* grausFin;
var meioCompr = myCanvas.width / 2;

ctx.font = "11px Arial";

/* Primeiro, preenche com cor do fundo opaca */
ctx.fillStyle = 'rgb(20,230,20)';
ctx.fillRect(0, 0, meioCompr, myCanvas.height / 2);
ctx.fillRect(meioCompr, myCanvas.height / 2, meioCompr, myCanvas.height / 2);

for (var i = 0; i < 10;i++) { /* agora, pinta por cima usando branco mas variando a transparência */
ctx.fillStyle = 'rgba(255,255,255,' + (1.0 - (i / 10.0)) + ')';
ctx.fillRect(0, i*incrY, meioCompr, incrY);

ctx.fillStyle = "navy";
ctx.fillText (Math.ceil (10.0 - i) / 10.0, 8, (i+1)*incrY - 4);
}

/* preenche no centro um círculo marrom com alto grau de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgba(128,0,0,0.4)';
ctx.arc(meioCompr, myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();

/* Como seria sem o parâmetro de transparência */
ctx.beginPath();
ctx.fillStyle = 'rgb(128,0,0)';
ctx.arc(meioCompr, 3 * myCanvas.height / 4, meioCompr, 0, radFin, false);
ctx.fill();
ctx.closePath();
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do código é dedicada a escrever o grau de transparência de cada uma das faixas horizontais. Como o desenho de texto também é afetado pelo valor de fillStyle - incluindo a transparência configurada nele -, eu atribuo a ela uma outra cor sólida para que o texto esteja plenamente visível, sem qualquer interferência do fundo previamente desenhado.

Há uma propriedade do contexto de pintura chamada globalAlpha que permite mudar o grau de transparência dos desenhos, influenciando preenchimentos e contornos num único comando. Essa característica tem sua utilidade, mas é menos flexível do que controlar separadamente pelo tipo de desenho.

Uma outra forma de trabalhar com cores prevista no canvas do HTML5 é o preenchimento de áreas com um gradiente, isto é, a pintura é iniciada com uma cor e vai gradualmente se aproximando de uma segunda cor, que é finalmente usada para concluir a pintura. Estão previstos dois tipos de gradiente : um linear e outro radial. Para usar um deles, basta criar o tipo desejado, alimentar as cores que comporão o gradiente e então associá-lo à propriedade fillStyle do contexto do Canvas, exatamente como fizemos com cores e transparências.

O tipo linear é instanciado através da função createLinearGradient enquanto para o gradiente radial usamos createRadialGradient. Ambos os métodos criam objetos que respeitam a especificação da interface CanvasGradient, atualmente composta por apenas uma função cujo objetivo é adicionar pontos de parada para as cores no gradiente. Ou seja, essa função define quantas cores básicas haverão no gradiente resultante e quanto espaço cada uma ocupará nele. As cores intermediárias são calculadas automaticamente de modo a preencher toda a área definida pela função de criação.
/* Gradiente linear simples com 2 cores: uma vai do início do retângulo até o meio e a outra do meio até o fim */
var lineargradient = ctx.createLinearGradient(0,0,120,120);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.5, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'rgb(120,120,220)');

/* Preenche o retângulo real com o padrão gradiente criado acima */
ctx.fillStyle = lineargradient
ctx.fillRect (0,0,180,180);

/* Outro gradiente, agora com mais cores ... */
lineargradient = ctx.createLinearGradient(0,200,180,280);
lineargradient.addColorStop (0, 'navy');
lineargradient.addColorStop (0.2, 'red');
lineargradient.addColorStop (0.8, 'rgb(50,50,180)');
lineargradient.addColorStop (1, 'green');

ctx.fillStyle = lineargradient
ctx.fillRect (0,200,180, 280);
Seu navegador (ou leitor de RSS) não suporta canvas ...
A função createLinearGradient cria uma área retangular - definida por pontos que indicam o canto superior esquerdo e o inferior direito. O gradiente é criado nesse retângulo virtual e ele então é usado para preencher o retângulo real no canvas. Como o retângulo real pode ter tamanho diferente daquele usado para criar o gradiente, o canvas faz alguns ajustes para que caiba. Se o retângulo real for maior, a última cor de parada é usada para preencher o resto da área.

Veja que para adicionarmos uma cor de parada temos que informar um valor entre 0 e 1. Esse valor é chamado de offset e indica a que distância do início do retângulo a nova cor deve começar a aparecer. Uma cor prevalece até que a próxima cor de parada seja encontrada. Então, o tanto de espaço ocupado por cada uma é definido pelo offset da parada seguinte.

Para o gradiente radial, a principal diferença é que, ao invés do efeito ser aplicado num retângulo, ele é aplicado numa área delimitada por dois círculos Os círculos são definidos na criação do gradiente, sendo que os 3 primeiros parâmetros correspondem ao ponto central e raio do círculo inicial e os 3 parâmetros seguintes correspondem ao ponto central e raio do círculo final. A área fora dos círculos é mantida com a cor original da área preenchida.
/* Círculo com gradiente */ var radgrad = ctx.createRadialGradient (105, 115, 20, 112, 130, 50);
radgrad.addColorStop(0, '#FF5F98');
radgrad.addColorStop(0.75, '#FF0188');
radgrad.addColorStop(1, 'rgba(255,1,136,0)');
/* Ao preencher retângulo, somente a área com o gradiente circular é considerada !*/
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Outro círculo com gradiente */
radgrad = ctx.createRadialGradient(95,35,15,102,40,40);
radgrad.addColorStop(0, '#00C9FF');
radgrad.addColorStop(0.8, '#00B5E2');
radgrad.addColorStop(1, 'rgba(0,201,255,0)');
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,250,500);

/* Aqui , a área circular resultante é maior que o retângulo; então, o retângulo todo é preenchido */
radgrad = ctx.createRadialGradient (200, 350, 65, 200, 350, 140);
radgrad.addColorStop (0, 'navy');
radgrad.addColorStop (0.5, 'rgb(50,50,180)');
radgrad.addColorStop (1, 'rgb(120,120,220)');
ctx.fillStyle = radgrad
ctx.fillRect (50,200,150,150);
Seu navegador (ou leitor de RSS) não suporta canvas ...
Parte do exemplo acima foi retirada do tutorial de canvas da Fundação Mozilla. Veja que a criação do gradiente aceita a função rgba(), significando que podemos aplicar transparência neste momento também.

Mais Informações
Trabalhando com a tag Canvas do HTML5 - parte I, parte II e parte III, Tutorial de Canvas na Fundação Mozilla

6 de outubro de 2011

Design Patterns com Delphi: Strategy - Parte II

No último post, eu apresentei o conceito e a aplicabilidade do Design Pattern comportamental Strategy. Aqui, vou mostrar como implementar em Delphi o exemplo prático utilizado naquele post, relacionado a pagamento de títulos. Para facilitar o entendimento, reproduzo abaixo o diagrama UML com a sugestão de modelagem proposta:
Diagrama UML para o padrão Strategy
A implementação das relações expostas no diagrama não é especialmente complexa. Ela envolve apenas conceitos comuns da programação orientada a objetos, como herança e polimorfismo.

O primeiro passo é declarar uma classe abstrata (ou interface, se preferir), definindo nela as linhas mestras que deverão ser respeitadas por todos as implementações reais do algoritmo em questão. Isto é, essa classe introduz funções que determinam a estratégia para execução da tarefa, impondo-a a todas as classes que queiram atuar como um algoritmo alternativo para a tarefa - daí o nome do padrão ser Strategy:
TWTitulo = class;

TWPagamento=class
public
function efetuaPgto (ATitulo: TWTitulo) : integer;virtual;abstract;
end;
Nesse exemplo, há uma única função abstrata que obrigatoriamente terá que existir em cada nova implementação do algoritmo. Essa função aceita como parâmetro uma instância da classe Context da solução, papel encarnado aqui pelo título a pagar (TWTitulo). Com isso, qualquer algoritmo que adote a estratégia definida tem acesso às informações relevantes do título, bem como a suas operações. Permitir essa comunicação é imprescindível para que a tarefa (o pagamento do título) possa ser concretizada.

Agora que temos a interface estabelecida, podemos adicionar os diferentes algoritmos previstos no diagrama, criando cada um deles como uma herança simples que forneça código para a função abstrata da interface:
TWContaBancaria = class;

TWPgtoBoleto=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;

TWPgtoDebitoAuto=class(TWPagamento)
{ ... }
_Conta : TWContaBancaria;
public
constructor Create (AConta: TWContaBancaria);
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;

TWPgtoInternet=class(TWPagamento)
{ ... }
public
function efetuaPgto (ATitulo: TWTitulo) : integer;override;
end;

implementation

{ ... }
constructor TWPgtoDebitoAuto.Create (AConta: TWContaBancaria);
begin
_Conta := AConta;
{ ... }
end;

function TWPgtoDebitoAuto.efetuaPgto (ATitulo: TWTitulo) : integer;
begin
_Conta.Conecta;
_Conta.DebitaValor(ATitulo.ObtemValor);
ATitulo.RegistraPagto;
{ ... }
end;
A implementação da TWPgtoDebitoAuto no quadro acima mostra que podem ser necessárias outras informações para que a tarefa seja executada num determinado algorítmo. A função que executa a tarefa, no entanto, foi fixada na classe base (a interface) e não deve ser alterada já que isso implicaria que todas as outras heranças teriam que respeitar os novos parâmetros, o que nem sempre é desejável. Então, uma solução é passar as informações extras no construtor da própria classe.

Esse detalhe tem que ser levado em conta pela Factory responsável pela criação de instâncias da classe de pagamento. De acordo com o conceito de Factory, devemos criar uma função que centraliza a instanciação de uma família de classes:
TWTipoPagto = (tpBoleto, tpDebitoAuto, tpInternet);

function CriaNovoPagto (ATipo: TWTipoPagto; AContaDebito : TWContaBancaria) : TWPagamento;
begin
Result := Nil;

case (ATipo) of
tpBoleto:
Result := TWPgtoBoleto.Create;
tpDebitoAuto:
Result := TWPgtoDebitoAuto.Create (AContaDebito);
tpInternet:
Result := TWPgtoInternet.Create;
end;
end;
O último aspecto que falta abordar é com relação às características da classe de contexto TWTitulo. Além de propriedades inerentes a títulos - como seu valor, data de vencimento, código de barras, identificação do beneficiário, etc. - essa classe terá que manter uma instância da classe base de estratégia (o pagamento) para poder trocar mensagens com ela.
TWTitulo=class
protected
_Id : String;
_DataVencto : TDateTime;
_Valor : Double;
_Pagto : TWPagamento;
_FoiPago : Boolean;
procedure RegistraPagto;
{ ... }
public
function ObtemValor : Double;
function Pagar (AMeioPgto: TWTipoPagto): integer;
{ ... }
end;

{ ... }

function TWTitulo.Pagar (AMeioPgto: TWTipoPagto): integer;
begin
if (_Pagto = Nil) then
_Pagto := CriaNovoPagto (AMeioPgto, _Sessao.ObtemContaBancaria);

_Pagto.efetuaPgto (Self);
end;
Com isso, temos a classe de contexto armazenando internamente uma instância do pagamento (a estratégia) e controlando seu ciclo de vida. A instância fica disponível para que o contexto possa utilizá-la sempre que for necessário executar a tarefa embutida nela.

Note que a solução fica aberta para receber facilmente novos algoritmos, preservando o fraco acoplamento entre as classes já previstas e implementadas. Assim, poucos pontos têm que ser alterados para se introduzir uma nova forma de realizar a tarefa: basta codificar a nova classe e prever a criação de suas instâncias na função factory.

29 de setembro de 2011

Design Patterns com Delphi: Strategy - Parte I

Certas tarefas em um sistema computacional podem ser implementadas de diferentes maneiras sem que isso afete o resultado final esperado. Isto é, partindo de um determinado contexto, podemos selecionar um algoritmo entre vários possíveis e atingir o mesmo objetivo com qualquer um deles. Por exemplo, dada uma lista que precisa ser ordenada, há diversos algoritmos de ordenação que podem realizar a tarefa, como o método da bolha, quick sort ou busca binária. Independentemente do algoritmo escolhido, ao final do processo teremos a lista classificada. É claro que há diferenças de performance de um método pra outro mas isso é parte do processo de escolha daquele que melhor se adapta a uma situação. Cada situação encontrada pode levar a uma escolha distinta, sem que uma seja necessariamente preferida em relação às outras.

O programa pode, então, ter que intercambiar dinamicamente o algoritmo, seja por opção explícita do usuário ou por causa de outras questões circunstanciais encontradas durante a execução. Em projetos orientados a objetos, podemos desenhar uma solução para esse tipo de cenário usando o Design Pattern comportamental Strategy.

O objetivo do Strategy é permitir que o programa utilize de forma transparente qualquer algoritmo capaz de realizar uma determinada tarefa, construindo para isso uma estrutura de classes com baixo acoplamento. Com isso, fica fácil adicionar novos algoritmos, enquanto os códigos que os utilizarão continuam independentes da escolha feita.

Num sistema real, essa solução pode ser aplicada, por exemplo, no pagamento de títulos. Considere que um título pode ser pago levando o respectivo boleto ao banco, cadastrando a conta como débito automático ou registrando o pagamento no Internet Banking. Qualquer que seja a forma selecionada, o resultado é o título pago. O diagrama UML abaixo mostra as classes e suas relações para o Strategy aplicado a este cenário:
Diagrama UML para o padrão Strategy
A nomenclatura formal para as classes que participam da solução com o pattern Strategy é a seguinte:
A classe Strategy é uma interface onde é definido o comportamento comum a ser respeitado por todos os algoritmos. Em outras palavras, devemos incluir nesta classe funções virtuais (e provavelmente abstratas) que fixarão a forma com que a tarefa estará acessível às outras partes do programa. A classe TWPagamento do diagrama acima exerce esse papel, definindo que pagamentos deverão ser feitos através da função efetuaPgto.

São chamadas de Concrete Strategy as classes que atendem a definição introduzida no Strategy e que, portanto, implementam um algoritmo que efetivamente realize a tarefa proposta. No diagrama, esse papel cabe a três classes distintas : TWPgtoBoleto, TWPgtoDebitoAuto e TWPgtoInternete. Cada uma delas executará a tarefa a sua maneira, providenciando uma versão própria da função efetuaPgto.

O Context é a classe que usará o comportamento definido pela Strategy, invocando as funções disponibilizadas por ela. Portanto, o Context deverá armazenar internamente uma referência ao Strategy. Em geral, ele também terá que expor formas de interagir com o Strategy, publicando propriedade e funções. No nosso exemplo, o Context é a classe TWTitulo; a interação com o Strategy é conseguida passando-se para a tarefa uma instância do próprio título a ser pago.

Um Client é qualquer parte do sistema que solicite uma operação à classe Context. Esse tipo de classe foi omitido do diagrama.
No próximo post, mostro uma sugestão de como implementar na prática essa solução usando o Delphi.

Mais Informações
Posts sobre Design Patterns