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:

  1. Visualizar todas as pastas criadas;
  2. criar novas pastas e remover pastas antigas;
  3. criar e enviar novas mensagens;
  4. apagar mensagens antigas;
  5. pegar mensagens de uma conta POP3 por meio de um botão ou link;
  6. responder a qualquer mensagem;
  7. listar as mensagens guardadas por pasta, exibindo X mensagens por página (X é um número fixo);
  8. abrir mensagens guardadas para visualização (basta mostrar o corpo completo do conteúdo-raiz, não precisam se preocupar com attachments ainda);
  9. 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:
  1. Visualização de mensagens enviadas (vale 0,25 ponto extra);
  2. demarcação de mensagens como lidas/não-lidas (vale 0,75 ponto extra);
  3. configuração do número de mensagens listadas por página no requisito 7 (vale 0,5 ponto extra);
  4. acesso à representação textual da mensagem em sua íntegra; cabeçalho incluso (vale 0,25 ponto extra);
  5. <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:
    1. 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.
    2. 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.
Além disso, vocês poderão implementar os seguintes extras:
      1. Codificados em base 64.
      2. 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 exemploExemplo de tela de visualização.

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