Trabalhos de MAC 441/5714
Além dos trabalhos realizados em sala de aula, teremos um
projeto em várias fases para realizar em casa.
Datas de Entrega dos Trabalhos:
Sistema de Webmail
O trabalho deste semestre deverá ser realizado em grupos de 2 a 4 alunos e
será um pequeno cliente Web para gerenciamento de e-mails. O cliente será multi-usuário e
deve prover funcionalidades básicas para o envio e recebimento
de mensagens que contenham formas restritas de attachments.
E-mails simples, multiparte e multinível para vocês testarem.
Fase 1:
Esta fase cumpre dois propósitos. O primeiro deles é
aclimatá-los ao desenvolvimento no ambiente Squeak. O segundo
é o desenvolvimento e a implementação do modelo
que deve fundamentar o sistema de Web mailnas
fases subseqüentes. Como nós nunca implementamos um
sistema de Web mail do início ao fim, este modelo deve servir
apenas como ponto inicial, e pode mudar caso apresente algum problema
ou inconsistência. Essas mudanças serão devidamente
discutidas e divulgadas na lista da disciplina. Note que os programas
entregues devem implementar rigorosamente as interfaces aqui descritas
(o que não quer dizer, de maneira alguma, que o seu programa
deve se restringir às classes especificadas abaixo).
Note também que as assinaturas de métodos em Smalltalk
são case-sensitive. O formato de entrega é descrito na sessão formato de entrega.
Se você estiver desesperado e emperrado, visite a sessão de dicas.
Nosso sistema organiza os e-mails de acordo com o padrão Maildir. Para mais informações a respeito do Maildir, visite a sessão de referências. Não esqueça das considerações a respeito do Maildir que fizemos na aula de 10/04.
A API pública do sistema é composta por oito classes,
sumarizadas a seguir. Para os graficamente orientados, há um diagrama de classes UML disponível.
WebMail: Representa uma
instância do sistema de Web mail. Um WebMail vem sempre
associado a um diretório local. Para criar um novo WebMail, o
usuário utiliza o método novoEm:
"Instala um novo núcleo no diretório /home/giuliano/webmail".
umWebMail := WebMail novoEm: '/home/giuliano/webmail'.
A
título de simplicidade, o gerenciamento de usuários fica,
inicialmente, a cargo de WebMail. Isso reforça a idéia
de que um usuário faz sentido no âmbito de uma instância de WebMail.
Novos usuários (a classe que representa os usuários
será discutida mais adiante) podem ser criados por meio da
mensagem:
"Devolve uma nova instância de Usuario usuário cujo login é gmega."
umUsuario := umWebMail usuarioComLogin: 'gmega'.
Note que esse método só cria um novo usuário com
login 'gmega' caso um já não exista. A
adição de novos usuários deve corresponder
à criação de um Maildir como um
subdiretório do diretório ao qual o núcleo
está associado (home/giuliano/webmail
no nosso exemplo). WebMail deve ainda permitir que usuários
previamente cadastrados sejam removidos. Isso pode ser feito por meio
do método:
umWebMail removeUsuarioComLogin: umLogin.
Além disso, WebMail deve dar acesso ao carteiro, discutido a
seguir, por meio da mensagem:
umCarteiro := umWebMail carteiro.
Carteiro: O carteiro é
responsável pelo envio de mensagens e verificação
das caixas postais. Ele implementa duas mensagens principais:
"Procura por novos e-mails em
todas as caixas postais associadas a umUsuario. Novos e-mails
deverão ser gravados no Maildir do usuário. Esta mensagem
devolve uma coleção cujo conteúdo são
instâncias de Mensagem que representam os novos e-mails
recebidos."
novasMensagens := carteiro checaCaixas: umUsuario.
"Envia uma mensagem usando uma conta SMTP."
carteiro enviaMensagem: umaMensagem usandoConta: umaContaSMTP.
Usuario: Representa um
usuário do sistema. Usuários possuem
múltiplas contas POP3 e SMTP, bem como uma pasta (abstrata)
privativa onde serão armazenados seus e-mails.
umUsuario adicionaConta: umaConta. "Associa umUsuario a uma conta (POP ou SMTP)."
umUsuario removeConta: umaConta. "Remove uma conta previamente registrada a umUsuario"
umUsuario
contasPOP3.
"Devolve todas as contas POP3 associadas a
umUsuario"
umUsuario contasSMTP. "Devolve todas as contas SMTP associadas a umUsuario"
umUsuario pastaRaiz.
"Devolve a pasta raiz
deste usuário".
Conta: Representam as contas SMTP e POP.
Uma Conta SMTP, por exemplo, pode ser criada da seguinte forma:
Conta novaConta: #SMTP noServidor: umEndereco comLogin: umLogin eSenha: umaSenha.
Contas POP3 são criadas substituindo-se #SMTP por #POP3.
Pasta: Pasta abstrata para a armazenagem de e-mails. Devem ser
mapeadas em pastas no Maildir. Pastas podem conter subpastas e
mensagens. Mensagens podem ser depositadas numa Pasta pela
ação do carteiro ou de um agente externo (i.e., a nossa
futura interface gráfica). A interface de Pasta é
descrita a seguir:
"Cria uma subpasta de umaPasta cujo nome é 'inbox'. Devolve uma referência para inbox."
inbox := umaPasta criaPasta: 'inbox'.
"Devolve a pasta 'inbox', previamente armazenada"
inbox := umaPasta pastaComNome: 'inbox'.
"Move uma subpasta ou uma mensagem da pasta umaPasta para a pasta inbox."
umaPasta move:umaPastaOuMensagem para: inbox.
umaPasta mensagens.
"Devolve todas as mensagens guardadas em umaPasta"
umaPasta
subpastas.
"Devolve todas as subpastas de umaPasta."
umaPasta apagaPasta: umaSubpasta. "Apaga uma subpasta de umaPasta."
umaPasta apagaMensagem: umaMensagem. "Apaga uma mensagem da pasta umaPasta."
umaPasta guardaMensagem: umaMensagem."Adiciona uma mensagem à pasta umaPasta."
Por
padrão, o carteiro coloca novas mensagens na pasta raiz.
Eventuais mecanismos de filtragem ficam a critério de
vocês (i.e., colocar os e-mails novos numa pasta especial chamada
'inbox'), mas eles devem vir desabilitados por default. As mensagens contidas nas Pastas são instâncias
de Mensagem.
Mensagem: Representam os e-mails no nosso sistema. Uma mensagem é, na verdade, um Conteudo especializado, acrescido de algumas restrições semânticas (campos obrigatórios e um remetente).
Mensagens podem ser simples ou multiparte. Uma mensagem
simples é composta por um cabeçalho e um corpo de texto (um conteúdo simples). Uma
mensagem multiparte é composta por um cabeçalho e por uma
coleção de conteúdos (um conteúdo multiparte). Um conteúdo pode ser
um documento word, um trecho
de texto, um arquivo binário codificado em Base64, entre outros.
Em particular, um conteúdo pode ser multiparte, sendo neste caso
ele também composto por um cabeçalho e uma coleção de
conteúdos. O resultado final é que uma mensagem
multiparte assume, na verdade, o papel de raiz de uma árvore de
conteúdos. Os nós externos (folhas) dessa árvore são compostos por conteúdos
não-multiparte, enquanto que os nós internos são
compostos por conteúdos multiparte. Conceitualmente, as mensagens devem ser associadas a um remetente (Usuario).
O cabeçalho é composto por uma série de atributos no formato chave:valor. Atributos
podem ocupar múltiplas linhas, o cabeçalho de uma
mensagem é separado de seu corpo (ou árvore de
conteúdos) por uma linha em branco. Mensagens simples são
diferenciadas de mensagens multiparte por meio do
atributo "Content-Type" em seus cabeçalhos. Uma mensagem
multiparte
sempre carrega um "Content-Type" da forma "multipart/*", onde * pode
assumir um certo número de valores (que não nos interessa
agora).
Mensagens simples terão Content-Type: * - {multipart/*} ou
virão sem
Content-Type. No exemplo abaixo
(tirado deste sítio), uma mensagem multiparte é mostrada com o cabeçalho em destaque:
From: Laura Rios <laura@oara.sf.ca.us>
To: steve@ntecom.za
Subject: Sara is two!
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary="--Snip snip snip"
Prólogo: qualquer texto colocado aqui deve ser ignorado.
--Snip snip snip
Content-Type: text/enriched; charset="us-ascii"
Hi, Steve. Sara had her second birthday yesterday. Can you believe it?
She is so <bold>big</bold>! Now if we can just live through the
"terrible twos" for a year, <italic>sigh</italic>.
Here are a few words from her. I've also scanned in a picture of her
party. Tell your boss thanks for letting us keep in touch by email.
Laura
--Snip snip snip
Content-type: audio/basic
Content-transfer-encoding: base64
Content-description: Sara says "I love you, Steve" (awww)
/Xr++/hoX2lqeXt8d/7z8/D5+PLw7/b+9fD09319/vz5f3j//Pz9fHp7fvrs9Wz/eH59d
omitido...
/////////////////y8=
--Snip snip snip
Content-type: image/gif
Content-transfer-encoding: base64
Content-description: Cutting the cake, sort of
R0lGODdhQAHIAKMAAAAAAP+2bQAAACQAAAAASEgAAAAkSEgkJG0kJJEkAABIkZFIJCRttrZt
omitido...
l7Vry4B9aM+yKjifMosAADs=
--Snip snip snip--
Epílogo: Qualquer texto colocado aqui deve ser ignorado.
Mensagens podem ser criadas através do método:
umaMensagem := Mensagem de:umUsuario comConteudo:umConteudo.
Mensagens válidas contêm um campo From:, um campo Date: e ao menos um campo To: ou Bcc: ou Cc:. O método de:comConteudo: deve produzir um erro caso o conteudo passado como parâmetro seja
inválido. Os atributos manipulados pelos métodos abaixo
são instâncias de Atributo.
"Devolve o atributo obrigatório Date:, presente em qualquer cabeçalho."
umaMensagem date.
"Idem para o atributo obrigatório From:"
umaMensagem from.
"Idem
para o atributo obrigatório To:. Este método devolve uma
OrderedCollection já que uma mensagem de e-mail pode conter
múltiplas instâncias de To: em seu cabeçalho."
umaMensagem to.
"Idem para o atributo Bcc:. Este método também devolve uma OrderedCollection."
umaMensagem bcc.
"Idem para o atributo cc:. Este método também devolve uma OrderedCollection."
umaMensagem cc.
"Devolve a coleção de atributos cuja chave é Receive, na ordem em que aparecem no cabeçalho."
umaMensagem atributosComChave: 'Receive'.
"Devolve a coleção de conteúdos que são
filhos desta mensagem, ou uma coleção vazia caso
não haja nenhum."
umaMensagem conteudos.
"Devolve o corpo desta mensagem (i.e., tudo o que não for o cabeçalho desta mensagem)."
umaMensagem corpo.
"Devolve o remetente associado a esta Mensagem."
umaMensagem remetente.
"Devolve esta mensagem em formato texto (com cabeçalho e tudo)."
umaMensagem asString.
"Devolve se esta mensagem está marcada como lida (true) ou não lida (false)."
umaMensagem lida.
"Modifica o estado desta mensagem para lida ou não lida. Aceita true ou false."
umaMensagem lida: umBooleano.
Conteudo: Generalização
de Mensagem, representa o
conteúdo de um e-mail. Conteúdos são compostos por
um cabeçalho (uma coleção de atributos no estilo
chave:valor) e por um corpo de texto. O corpo de texto pode representar
subconteúdos, caso o Conteudo seja multiparte. A
classe Conteudo pode ser
implementada como uma superclasse de Mensagem, ou não. A
interface de conteúdo inclui os métodos atributosComChave:, conteudos, corpo e asString de Mensagem (com semântica idêntica), mais os seguintes métodos:
"Adiciona outroConteudo como subconteúdo de umConteudo."
umConteudo adicionaConteudo: outroConteudo.
"Remove o subconteúdo outroConteudo de umConteudo."
umConteudo removeConteudo: outroConteudo.
"Faz de umaString o novo corpo de umConteudo. Se umConteudo for
multiparte, umaString deve ser interpretada como um conteúdo
multiparte. Se umaString for um conteudo multiparte mal-formado, este
método deve produzir um erro."
umConteudo corpo: umaString.
"Adiciona o atributo umAtributo ao cabeçalho."
umConteudo adicionaAtributo: umAtributo.
"Remove o atributo umAtributo do cabeçalho."
umConteudo removeAtributo: umAtributo.
"Devolve uma OrderedCollection com todos os atributos do cabeçalho deste Conteudo."
umConteudo todosAtributos.
Além
disso, a classe conteúdo disponibiliza um método que
permite instanciar novos conteúdos a partir de uma
representação textual. Esse método chama-se
'com:'. Exemplo:
"Cria uma stream de leitura num arquivo que contém uma mensagem"
readStream := CrLfFileStream fileNamed: '/home/giuliano/Maildir/cur/1144263147.000001.mbox:2,S'
readStream converter: (Latin1TextConverter new).
"Instancia um conteúdo a partir dessa mensagem."
novoConteudo := Conteudo com: (aReadStream contents).
"Cria um clone de 'novoConteudo'".
conteudoClone := Conteudo com: (novoConteudo asString).
"Cria uma mensagem cujo conteúdo é o dado por novoConteudo"
umaMensagem := [Mensagem de:umUsuario comConteudo:novoConteudo]
ifError: [:err :rcvr |
('ConteudoInvalido' = err) ifTrue:
[Transcript show: (rcvr
ultimoErro). nil]]
Atributo: Um par chave-valor. Atributos são imutáveis, e criados pelo método:
umAtributo := Atributo novoComChave: umaChave eValor: umValor.
Além disso, atributos disponibilizam sua chave e valor por meio dos métodos chave e valor.
Dicas:
Dicas e pacotes úteis que discutimos na aula de 10/04:
- Vassili's RegEx (Categoria VB- na imagem 3.8).
- Regular Expression Plug-in (está no SqueakMap).
- Small Compiler Compiler (SmaCC, também no SqueakMap).
- Recomendo o uso da CrLfFileStream para a leitura de arquivos
pré-gravados. Não esqueçam de instalar o Latin1TextConverter na stream (método converter:),
ou algumas surpresas desagradáveis podem ocorrrer.
- As classes mais importantes para manipulação do sistema de arquivos são FileDirectory e DirectoryEntry.
Fase 2:
Nesta fase, vocês deverão implementar as funcionalidades mais
fundamentais da interface do sistema de Web Mail. A interface deve, obrigatoriamente, ser uma interface Web baseada no Seaside. Tanto o projeto
quanto a implementação são livres. Deve ser possível, no entanto:
- Visualizar todas as pastas criadas;
- criar novas pastas e remover pastas antigas;
- criar e enviar novas mensagens;
- apagar mensagens antigas;
- pegar mensagens de uma conta POP3 por meio de um botão ou link;
- responder a qualquer mensagem;
- listar as mensagens guardadas por pasta, exibindo X mensagens por página (X é um número fixo);
- abrir mensagens guardadas para visualização
(basta mostrar o corpo completo do conteúdo-raiz, não
precisam se preocupar com attachments ainda);
- inspecionar de forma menos crua os principais campos da mensagem (From:, To:, CC:, Date:, Subject:).
Esses são os requisitos obrigatórios. Vamos especificar
ainda um pequeno conjunto de requisitos não-essenciais que
poderiam enriquecer o sistema neste estágio do desenvolvimento.
Esses requisitos são opcionais e valem pontos extras. São
eles:
- Visualização de mensagens enviadas (vale 0,25 ponto extra);
- demarcação de mensagens como lidas/não-lidas (vale 0,75 ponto extra);
- configuração do número de mensagens listadas por página no requisito 7 (vale 0,5 ponto extra);
- acesso à representação textual da mensagem
em sua íntegra; cabeçalho incluso (vale 0,25 ponto extra);
- <coloque seu requisito aqui - envie uma proposta para mim> (vale X,X ponto(s) extra(s)).
A interface que esperamos para esta fase é algo bem simples. Para os
pouco criativos, há algumas sugestões de interfaces disponíveis (modo de visualização de mensagem, modo de edição, modo de listagem). Nesta fase, vocês não devem se preocupar com gerenciamento de sessões/usuários/login, com anexos (attachments)
e nem com a edição de rascunhos (mensagens criadas e não-enviadas). O
sistema deve usar um usuário padrão, bem como uma conta POP e uma conta
SMTP padrão. Para tanto, vocês devem incluir em WebMail os seguintes
métodos provisórios:
“Inicializa o Maildir do usuário padrão.”
webMail inicializaUsuarioPadrao.
“Registra umaContaSMTP como a conta SMTP padrão, que será usada para o envio de e-mails”
webMail contaSMTPPadrao: umaContaSMTP.
“Registra umaContaPOP3 como conta a conta POP3 padrão, que será usada para a checagem de mensagens.”
webMail contaPOP3Padrao: umaContaPOP3.
Esses métodos serão úteis apenas para esta fase.
Fase 3:
Nesta fase vocês deverão implementar as funcionalidades
que ainda restam para que o sistema de vocês aproxime-se um pouco
mais de um sistema de Web Mail real. Para tanto, vocês
deverão:
- Tornar o sistema de Web Mail multiusuário. Isso envolve, essencialmente:
- Adicionar uma tela de autenticação ao sistema. A
identificação do usuário deve ser feita mediante a
apresentação de um login e de uma senha. Exemplo de tela de autenticação.
- Adicionar um botão (ou link) de logout, facilmente acessível, à interface do sistema. O botão de logout deve encerrar a sessão em curso. Exemplo de botão de logout na tela de listagem de mensagens.
O sistema não pode apresentar restrições com
relação ao número de usuários
simultâneos (não vale entregar um sistema que só
funcione com um usuário por vez).
Nós não vamos tratar da adição de novos
usuários. Usuários serão adicionados diretamente
ao núcleo, por meio do método usuarioComLogin:umLogin. O login em umLogin
deve identificar o usuário na tela de
autenticação. Nós também não vamos
tratar da remoção de usuários.
Vocês devem adicionar um método de instância senha: à classe Usuario. O método deve receber uma ByteString
contendo a senha do usuário em questão. Não
se preocupem com a codificação de caracteres -
certifiquem-se apenas de que senhas aceitas por [A-Za-z]+ funcionem.
- Permitir que o usuário gerencie suas contas POP/SMTP. O
usuário deve ser capaz de adicionar/remover contas POP/SMTP,
idealmente por meio de uma interface especial acessível por meio
de um botão. Exemplo de botão 'configurações' na tela de listagem de mensagens.
A interface de configuração também deve permitir
que o usuário especifique quais contas POP incluir na
operação de checagem de caixas postais. Exemplo de interface de configuração.
- Permitir o uso de múltiplas contas SMTP. Na fase anterior, o método WebMail>>contaSMTPPadrao: umaContaSMTP era
usado para definir a conta SMTP padrão utilizada por todas
as operações de envio. Nesta fase, vocês
deverão permitir que o usuário escolha qual conta SMTP
usar a cada envio. Isso pode ser feito com a adição de
uma listbox à tela de envio de mensagens, ou de alguma outra forma qualquer.
- As configurações acessíveis por meio da interface de configuração devem ser persistidas.
Além disso, vocês poderão implementar os seguintes extras:
- Download de algumas formas restritas de anexos (2,5). Este
extra envolve a implementação de um mecanismo que permita
que algumas formas restritas de attachments sejam baixadas. Vocês
podem assumir que o e-mail vai obedecer ao seguinte formato:
- O primeiro conteúdo é o conteúdo a ser
mostrado na área de texto. Esse conteúdo será do
tipo text/plain.
- Os conteúdos subseqüentes compõem a
lista de anexos que podem ser baixados. Os anexos apresentam as
seguintes restrições:
- Codificados em base 64.
- Atributo Content-Disposition no formato:
Content-Disposition: <algo>; filename="<filename>"
Exemplo:
Content-Disposition: attachment; filename="redball.gif"
Vocês deverão mostrar a lista de attachments
numa área especial com links (de onde eles podem ser baixados).
Deve ser possível abrir os anexos baixados nos programas de
origem; isto é, eles devem ser decodificados corretamente. E-mail exemplo. Exemplo de tela de visualização.
- Visualização de imagens gif e jpeg
na própria interface do sistema de Web Mail (1,0) - prá
quem implementou o primeiro extra, este aqui vem quase de graça.
- Visualização de e-mails html (Content-Type: multipart/related) que contenham imagens gif e jpeg (2,0) - prá quem implementou o primeiro extra, este aqui também vem quase de graça.
Formato de entrega:
O formato de entrega será o Squeak ARchive. Um Squeak ARchive
é um arquivo ZIP (pode ser criado com o utilitário ZIP no
Linux, com o WinZIP no Windows 9x/NT e como uma pasta compactada no
Windows XP) cuja extensão foi trocada para .sar. Ele apresenta
uma estrutura simples, que segue algumas convenções:
./install/preamble - trecho de código executado primeiro
./install/postscript
- trecho de código executado após o preamble
(hã... também não sei muito bem prá que
serve isto).
Esses arquivos dizem ao SARInstallercomo
processar os arquivos de projeto providos junto com o SAR e podem
conter
código de inicialização arbitrário.
Os arquivos de projeto são aqueles que contêm o
código do programa de fato. Esses arquivos podem ser o resultado
de file outs (.st), pacotes monticello (.mc/.mcz), change
sets(.cs), entre outros. Eu recomento o uso do Monticello,
já que é mais fácil organizar e exportar pacotes
com a ajuda dele. O código inserido nos arquivos preamble e
postscript será executado no contexto de uma instância
de SARInstaller. Cabe a você dizer ao SARInstaller quais arquivos processar e como. Por exemplo, o seguinte preamble:
self openGraphicsFile: 'umGrafico.jpg'.
self fileInMonticelloZipVersionNamed: 'projeto.mcz'.
self openTextFile: 'instruções-de-uso.text'.
MyWeirdClass doSomeInitialization.
Abre uma imagem na tela, carrega um pacote Monticello, abre uma janela
com o conteúdo de um arquivo de texto e chama o método
'doSomeInitialization' na classe 'MyWeirdClass'.
Além do arquivo SAR, eu gostaria que vocês entregassem:
- Um pequeno relatório descrevendo as partes que julgarem
relevantes da implementação. Em particular, eu gostaria
de ver nesse relatório como vocês implementaram o Maildir.
Informações de instalação e outras
considerações também devem ser inseridas aqui.
- Um diagrama UML, opcional, do design que vocês bolaram. Esse aqui é só para quem quiser que eu dê uma olhada e emita uma opinião.
Ambos os documentos devem ser entregues como arquivos PS ou PDF, para evitar problemas com formatos.
Usando o Monticello:
Eu ia fazer um tutorial, mas encontrei um muito bom já feito.
Referências:
Com relação ao Maildir, vale o que discutimos na aula de 10/04:
na ausência de link/unlink, me contento com cópias. A
organização de e-mails pode ser baseada na estrutura
cur/new ou em atributos codificados no próprio nome do arquivo
(daí o new fica
inútil). Não esqueça de documentar o que
você fez. Lembre-se que o Carteiro faz o papel do MTA no nosso
caso.
Página de MAC 441/5714
Página do Fabio
Página do DCC