Ubuntu, Firefox e Flash

“Ubuntu: Linux for Human Beings“… yeah right. Uma das coisas mais irritantes que existe em qualquer distro linux é a inabilidade de facilmente rodar flash. Parte da culpa é da Adobe, que somente ano passado passou a oferecer um release oficial do Flash para linux. Ubuntu, por mais que tente ser user-friendly, não é uma excessão à esta regra.

Anyway, fiz uma atualização do linux ontem quando voltei das férias and guess what? A atualização do ubuntu ferrou com o flash no meu firefox-2. Procurei por diversos foruns como fazer esta joça funcionar, em vão. Removi plugins, reinstalei os plugins usando o gerenciador do firefox, usando o synaptic, dancei a macarena e fiz macumba na frente do computador… e nada do flash funcionar.

Foi então que encontrei este blog e meus problemas acabaram </valtermercado>. O cara reclama de tudo quanto é problema que ele tambem teve para habilitar o flash, e exibe a solução que finalmente funcionou para mim.

  1. Remova completamente (enfase no ‘completamente’) todos os plugins de flash que você já instalou… eles não vão servir para nada.
  2. Accesse o site do adobe http://get.adobe.com/flashplayer/
  3. Baixe a ultima versão do flash player (eu baixei o arquivo tar.gz)
  4. Extraia os arquivos para alguma pasta (de preferencia no seu home)
  5. Usando o terminal, acesse esta pasta
  6. sudo ./flashplayer-install
  7. siga as instruções do prompt, e indique qual a pasta onde está instalado o firefox

Normalmente a pasta onde o firefox está instalado é a /usr/lib/firefox. Eu atualizei meu firefox para a versão 3.0.5 agora, mas mantive o firefox-2 para testar backwards compatibility (assim como costumava manter o IE 5.5, IE 6.0 e IE 7.0 no windows), portanto a pasta onde eu deveria instalar o flash era a pasta do firefox 3.0.5, que é /usr/lib/firefox-3.0.5 (wow, tão dificil de imaginar).

Anyway, com estes passos (que não tornam o ubuntu um “linux for human beings”) consegui abilitar o flash na minha maquina. YAY. Agora, aonde está aquele CD de instalação do Debian ? Cansei de ter de atualizar meu linux diariamente e ver ele ficar ferrado ocasionalmente.

E os sprints ? E os updates ?

Vamos começar 2009 com um update neste blog então :D E o primeiro assunto do ano: a falta de updates nos meses anteriores. HUA.

Como sabem, estavamos trabalhando com Scrum, e eu estava narrando os resultados obtidos em cada sprint. Realizamos seis sprints, e então nosso Scrum Master foi para Porto Alegre realizar o curso de Certified Scrum Master com o Alexandre Magno. Ele voltou de lá dizendo que estavamos fazendo um monte de coisa errada, um monte de coisa certa, e um monte de coisa que era meio inovador para o Scrum. Decidimos então parar com o sprints até o momento em que nos fosse repassado o conteudo do curso.

Primeira coisa que percebi foi, sem sprints, sem post-its, me sentia perdido sem saber o que fazer. Foram alguns dias até a apresentação, e durante este periodo me senti um peixe fora d’agua. Passei a trabalhar em projetos paralelos e sem comprometimento com o cliente final. Saiu até uma coisa bacana, mas com pouco retorno.

Apos o curso interno, vimos tudo sobre scrum, pocker cards, as funções de cada membro do time, a organização de um time multidisciplinar, user stories, e tudo mais. Queriamos virar o mundo de ponta-cabeça, mas os clientes começaram a berrar e pouco pudemos fazer senão atende-los. Um mes se passou do curso até o momento onde voltamos a usar scrum. Para que eu pudesse me organizar, eu passei a usar um kambam pessoal com todas as tarefas que eu tinha de fazer, para garantir que as melhorias que eu percebia serem necessárias não fossem esquecidas a medida que eu ia atacar outro incendio.

Em dezembro voltamos a usar o Scrum. Reunimos toda a equipe de desenvolvimento, e começamos a atacar um cliente de cada vez, organizando os user stories, quebrando em post-its e dividindo as tarefas entre o time. Em uma semana chegamos a completar quase que uns sete projetos diferentes, todos utilizando os principios do scrum. Foi divertido, foi produtivo, e foi divertido.

Semana que vem voltamos a trabalhar (férias é bom as vezes), e vou tentar manter este blog mais atualizado então.

Second Sprint

mais duas semanas se passaram, e encerramos nosso segundo sprint.

esta segunda iteração tinha como meta realizar avaliações de programadores à distância, e otimizar as funcionalidades de dois sistemas que possuimos. Desta vez não tivemos tantos impedimento quanto da última vez, mas mesmo assim somente conseguimos dar cabo de metade das metas previstas.

Será falta de pessoal ? Será falta de organização ? Será falta de experiencia ? Talvez seja simplesmente um pouco dos três. Por ser recem nosso segundo sprint, falta ainda experiencia para precisar exatamente o que nosso diminuto time será capaz de realizar no periodo de duas semanas disponivel. ‘Incendios’ sempre terão de ser apagados, muitas vezes pelo pessoal com mais capacidade que justamente compunha o time. De qualquer modo, ainda é muito cedo (afinal, foi recem nosso segundo sprint) para esperar que consigamos acertar nossas previsões e metas plenamente.

Para piorar o que já estava ruim, um de nossos colaboradores decidiu começar a trabalhar em casa, o que limita a troca de conhecimento e a agilidade de comunicação. Era um colaborador com quem eu possuia certos desentendimentos ocasionalmente, mas ainda assim um programador competente e capaz. Fará falta.

Iniciamos o terceiro sprint aonde o primeiro terminou, com a refatoração plena do sistema em plugins. Deste modo poderemos trabalhar com cada sub-sistema como uma unidade individual e independente das demais, com um sistema mais simples e melhor organização de controle de versões que o utilizado atualmente.

Desacoplamento dos sub-sistemas e do banco de dados hoje, TDD e Trac amanhã.

First Sprint

Depois de muita conversa e discussão, finalmente conseguimos dar principio às práticas Agile em nossa empresa. Adotamos SCRUM, e iniciamos o primeiro sprint duas semanas atrás, sendo que sua conclusão ocorreu nesta segunda.

A primeira vista, a prática foi um fracasso. Prometiamos mil maravilhas, que os projetos seriam entregues em tempo, que tudo iria funcionar perfeitamente. Porém imprevistos que sequer o Scrum Master conseguiu se livrar acabou por atrasar o projeto completamente. Dos 10 dias que havia reservado para o projeto, somente produzi em metade deles. Prometemos entregar 27 itens, e somente concluimos seis deles.

Fracasso ?! Eu vejo sucesso nisso tudo. Sim, falhamos em atingir nossas metas, mas pela primeira vez mensuramos exatamente o que ocorreu, por que as metas não foram atingidas, e mantivemos a comunicação entre todos os integrantes do time. Os itens entregues, tambem, foram pesquisados, desenvolvidos e avaliados de maneira que há garantia de que não teremos de voltar a eles tão cedo.

Iniciamos o segundo Sprint na segunda a tarde, definindo 21 itens por serem entregues. Vamos ver como nos saimos agora.

programe simples

Durante o 9o. Forum de Software Livre tive a oportunidade de assistir uma palestra do criador do PHP, Rasmus Lerdorf. Entre diversas pérolas de conhecimento que ele distribuiu para os presentes, uma que realmente tenho cultuado desde então é a seguinte:

Passar horas, dias, escrevendo um código altamente complexo para resolver um problema pela primeira vez não é ser esperto, é ser idiota. Se você teve grande dificuldade em escrever o código da primeira vez, multiplique por 1000 e e este será o grau de dificuldade para fazer manutenção neste mesmo código1

Desde então tenho evitado soluções “mágicas” e ofuscadas para criar soluções inovadoras, sempre buscando na medida do possível simplificar todo e qualquer raciocionio que crio.

____

1 Tradução livre e independente das reais palavras de Rasmus Lerdorf.A palestra foi em abril, e não lembro exatamente quais foram as palavras utilizadas por ele.

onsubmit on form.submit()

Descobri recentemente que os padrões da W3C não permitem que, ao executar a função javascript form.submit(), as funções inseridas no parametro onsubmit de um formulário sejam executadas, somente realizando onsubmit através do input especifico (<input type=submit>). Achei isso muito estranho, e as circunstâncias em que eu trabalhava exigia que eu fosse capaz de criar um botão de submit que pudesse ser utilizado com qualquer formulario, esteja o botão declarado dentro ou fora do formulario, quer existam ou não funções declaradas no campo onsubmit;

Primeira Solução

Após uma simples busca encontrei a seguinte função:

if(form.onsubmit()) {
return form.submit();
}

Excelente e simples solução para o problema. Ele tenta executar as funções descritas no onsubmit, e então executa o submit.

Mas e se o formulário não possuir um onsubmit ? Oops… um erro ocorre…

Solução Final

Para gerar uma função genérica, que pudesse com qualquer formulário, tive de decair ao nivel do gambi-development, os quais quem me conhece sabe que repudio com fervor. A função final ficou parecida com isto:

if(typeof form.onsubmit == "function") {
if(!form.onsubmit()) {
return false;
}
}
return form.submit();

Ele verifica se existe a função onsubmit, executa ela se existente, retorna false se o retorno dela for false, e por fim executa o submit se o resultado do onsubmit for verdadeiro ou a função não existir

Não me orgulho do resultado encontrado, nem um pouco elegante, mas resolve o problema de minha aplicação.

Deixando a Solução mais Elegante

Quando eu idealizei a função acima, em julho de 2008, eu a utilizava em apenas um local que era invocado automaticamente com o template de formulários do gerenciador de nosso sistema. Porém, esta função facilita muito a vida de todos em qualquer outra ocasião onde um formulario deve ser preenchido e tu tem usar a função form.submit() para envia-lo. A solução, portanto, permaneceu não-elegante devido ao fato de não ter ser reutilizada em outro lugar.

Anyway, um jeito bem simples de deixar a solução elegante é criar uma função que a executa passando o formulario por parâmetro.


// form_submit
// submits a form, checking if there is a onsubmit function defined for it
//
// @author hagnat
// @see http://hagnat.wordpress.com/2008/07/22/onsubmit-on-formsubmit
// @param object[form] the form being submitted
function form_submit(form)
{
if(typeof form.onsubmit == "function") {
if(!form.onsubmit()) {
return false;
}
}
return form.submit();
}

Eu podia meter ainda uns testes para prevenir erros usando try-catch, mas dae é complicar desnecessariamente a função. Imagino que quem utilizar esta função saberá colocar os pontos nos i’s e passar os parâmetros corretos para a função :P

Tadah! Agora basta invocar a função form_submit(form) dentro do onClick de um a ou input. E aquela solução tosca fica um pouco menos tosca :)

Sem comentários

Se você não comenta seus códigos, é melhor começar: cada função não comentada que você escreve é uma incomodação a mais que você está criando, gratuitamente, na hora de realizar manutenção no sistema que está desenvolvendo.

Comentários possuem diversos beneficios na manutenção de código, como explicar qual a linha de raciocínio que o programador possuia ao desenvolver uma função, quais os parametros de entrada e saída de uma função, alem de descrever em linguagem humana o que determinada função realiza. Basicamente, comentários funcionam como uma documentação rudimentar para sistemas que não possuem uma documentação redigida.

Imaginem um sistema com dezenas de milhares de linhas de código, e você tendo de realizar manutenção neste. Por onde começar ? Você procura a documentação, mas ela não existe; Você segue seus instintos e encontra os arquivos que iniciam o processo do sistema, e consegue encontrar (com dificuldade ou não) os módulos do sistema que precisa realizar manutenção. Analisando o código, você encontra referencias para dezenas de classes e funções que você não tem a menor idéia de onde se localizam, ou o que estas realizam defact.

É importante não apenas comentar, mas faze-lo direito. Um código mal-comentado é quase tão ruim quanto um não-comentado. A ausência de comentários retira a total esperança de uma fácil compreensão do código, enquanto o mal-comentado dá um pouco de esperança apenas para retira-la mais tarde, porém, ainda assim existem comentários com o qual o programador terá com que se basear na manutenção do código.

Programando orientado a objetos, faço questão de que cada função que desenvolvo tenha pelo menos um cabeçalho com o nome da função e uma descrição simples do que esta faz, quando o nome da função não for o suficiente para descrever a função. Cada bloco de código é comentado, determinando o que estou codificando e aonde estas informações serão utilizadas. Existem funções onde comentários são a maior parte referente à estas, de modo que qualquer pessoa que tiver de realizar manutenção em um módulo onde estas funções são invocadas terá pequena dificuldade em compreender o que a função faz.

Escrever comentários pode parecer uma grande perda de tempo no curto prazo, mas no longo prazo acaba se revelando num grande beneficio para os programadores que tiverem de trabalhar neste sistema.

Imaginava que isto já fazia parte do bom-senso da sub-cultura dos programadores, mas alguns códigos que vi recentemente me provaram o contrario. A seguir: sobre a importância de boa nomenclatura de funções.

Como a Apple conseguiu fazer tudo certo fazendo tudo errado

A wired escreveu uma interessante reportagem sobre como a Apple conseguiu se tornar uma empresa muito bem sucedida indo completamente contra a maré, e como o culpado por isto funcionar é uma pessoa apenas: steve jobs.

Enquanto a maioria das empresas trata seus funcionários como deuses, oferecendo as maiores regalias para eles, na apple eles são tratados como lixo por jobs. Basta lembrar o filme “Piratas do Vale do Silicio” para imaginar como ele trata seus subordinados. Todos são humilhados, ridicularizados e temem por seus empregos, porem todos apreciam seu chefe, e fazem de tudo para agradá-lo.

Na apple todos são iguais. Se o vice-presidente chegar atrasado, ele terá de buscar por uma vaga no estacionamento como qualquer outro funcionario. Porem, Steve Jobs não. Se ele estiver atrasado e a vaga de deficientes estiver vaga, é ela que ele irá ocupar. E ele geralmente o faz, a ponto de que os funcionarios da empresa trocaram o emblea de deficientes pelo emblema da Mercedes.

Empresas como a microsoft produzem apenas o sistema operacional, empresas como a macromedia produzem software de qualidade e empresas como a dell produzem o processador. Ou seja, cada empresa se especializa em uma área e nela produz os melhores resultados. Não na apple. Na apple todo o pacote é desenvolvido. Do mouse ao processador, do sistema operacional ao emoticon. Isto faz com que todo o pacote possua a qualidade que a Apple espera de seus produtos, e se seus clientes estão insatisfeitos é por sua falha, não por que terceiros relaxaram no desenvolvimento de produtos.

A maioria das empresas ve blogs e fãs como publicidade gratuita. Diversos presidentes de empresas possuem blogs aonde anunciam quais os futuros projetos da companhia, e incentivam seus funcionários a comentarem sobre suas atividades em seus blogs pessoais, mesmo que seje para reclamar. Na apple, por outro lado, cobre-se de segredo todo e qualquer novo projeto da empresa. Seus funcionarios tem de assinar contratos aonde se prontificam a não comentar com ninguem, até mesmo parentes, no que estão trabalhando. Até o próprio Steve Jobs cuida para manter o trabalho da empresa em segredo, chegando a cobrir com um pano o protótipo do iPod Hi-Fi que levara para casa para experimentar. Tal absurdo chega ao ponto de que os funcionários de um projeto são proibidos de comentar com outros funcionários da empresa sobre o que estão a desenvolver. Muitos sequer sabiam que existia algo chamado iPod sendo desenvolvido na empresa até pouco antes deste haver sido lançado para o publico,

Curioso ver como uma pessoa com uma mentalidade tão retrograda de administração consegue produzir tanto sucesso. Desde que retornou à Apple em 1997 (ele havia sido demitido da empresa que ele próprio fundara), Steve Jobs consguiu tirar a empresa do fundo do poço e fazer com que ela se tornasse um gigante, lider no mercado de mp3 players, inovadora no ramo de celulares e alvo do desejo de dezenas de pessoas em termo de sistema operacional (eu sempre quis ter um OsX pr’eu).

Talvez seja sua aura de realidade distorcida. Steve Jobs faz com que mesmo o mais rudimentar projeto pareça inovador, com que a mais entediante tarefa seja uma tarefa vinda de deus. Jobs possui um estilo quase único de administração, aonde ele tenta se envolver em todas as camadas de desenvolvimento. Do angulo do monitor, à quantos parafusos vao haver na base do notebook, chegando ao cumulo de discutir coisas ao nivel do pixel!

É uma antitese esta empresa, que se propóe a pensar diferente com algumas táticas tão antiguadas de gerncia.

Fonte: Wired

Sincero Pedido de Desculpas

Lembro quando eu estava apenas engatinhando em programação, quando eu trabalhava no laboratório de pesquisas, a famosa Sala 1734. Lá eu dedicava parte da minha vida acadêmica aos estudos de algoritmos genéticos, vida artificial, resolução de problemas de quimiometria, além das normais atividades ligadas à jogatina, bebida e festas. Somente depois de três anos estudando computação foi que finalmente pude dedicar de corpo e alma à esta profissão.

Apesar da minha inexperiência, produzi excelente resultados, otimizando algoritmos já existentes para que aproveitassem melhor os recursos disponíveis, alem de ajudar no desenvolvimento de versões mais rápidas destes (o processo de otimização de um algoritmo genético levava horas, e tão cedo terminava a máquina ficava osciosa. Eu ajudei a reduzir o tempo consumido e a criar filas de testes de modo a permitir melhor aproveitamento das máquinas). Dediquei-me de corpo e alma, e por vezes fazia da minha univesidade minha segunda casa defacto.

Foi então que entrei em discussão com um de meus professores, na cadeira de Engenharia de Software. Me fervia a mente ter de me sujeitar a preencher os sem números de diagramas e organizar as dezenas de tabelas de documentação que meu professor ensinava como ferramentas básicas de um bom e estruturado projeto. Lembrava dos meus códigos, bem estruturados em arquivos ordenados, encapsulados de modo a permitir que um pudesse ser substituido por outro conforme a necessidade, e como para nenhum deles havia um diagrama sequer, uma tabela de documentação (apesar de que eles possuiam alguma documentação… eu SEMPRE documentei meus codigos), uma página de referencia citando o que já havia sido feito e o que poderia ser feito. Meu raciocinio era de que “O trabalho de pesquisa as vezes é algo momentaneo. Eu tenho uma idéia e preciso verificar se ela está correta ou não, e não posso perder tempo documentando isso quando poderia estar programando defacto”.

Alguns semestres mais tarde me vi frente a um problema onde eu sabia a resposta, porem não possuia as habilidades de programação necessárias para realizá-lo. Um de meus colegas possuia as habilidades para realizá-lo, mas não sabia como responder. Juntou-se a fome com a vontade de comer. Tentei explicar para ele como fazer, mas somente depois que rabisquei em uma folha de papel um diagrama com todos os passos que o sistema deveria realizar que este foi programado corretamente. Um excelente sucesso. Mais tarde, com o papel ainda em mãos, percebi o quão errado eu estivera na discussão com meu professor.

Alguns anos mais tarde, quando iniciei minha recente carreira como programador web, me vi novamente frente a diversos problemas onde a falta de documentação dificultava a capacidade de dar manutenção à códigos antigos. E me vi eu sendo o defensor de padronização, reutilização e documentação de código, enfrentando resistência daqueles que acreditam que “perder tempo documentando” iria somente atrasar suas já atrasadas tarefas de programação.

O mundo dá voltas. Nunca substime qualquer ensinamento que lhe for dado.

Estágios de Programação

Programando profissionalmente para uma pequena empresa web me perimtiu visualizar os mais adversos estágios de programação que um poderia imaginar. Os estágios iniciais foram-me apenas relatados, como se fossem estórias de terror de tempos imemorávis.

Inicialmente cada programador era responsável por um projeto. Ele recebia o layout do design, cortava-o do jeito que o convinha programar, organizava a base de dados de acordo com sua lógica, sempre sem utilizar chaves estrangeiras uma vez que estas não existiam nas rudimentares versões de mysql utilizadas outr’ora. As ferramentas de gerência da página de internet eram geradas com um script, que era basicamente um copy-pasta de outros projetos. Bugs eram resolvidos com dificuldade, e toda melhoria no sistema gerava uma quantidade enorme de re-trabalhos e bugs causados porque algo foi mudado que não deveria. Este é o estágio “Cada um por si, e o manual nos acuda!

O segundo estágio envolve os primeiros passos de centralização do processo de trabalho. Todos os projetos são reunidos e os banco de dados centralizados em um um único servidor. Ferramentas “padronizadas” e testadas são reutilizadas em novos projetos. A programação, porem, continua caótica. Uma vez que os arquivos são abertos diretos do servidor, se duas pessoas tiverem de trabalhar em um mesmo projeto e tiverem de editar um arquivo centralizado, como o arquivo de CSS do site, haverá conflitos por perca do trabalho do primeiro tão cedo o segundo sobre-escrever o arquivo salvo deste. A aparente organização do servidor defato torna dificil trabalhar em time, fazendo os programadores a se forçarem a trabalhar sozinhos para evitarem as dores de cabeça de ter de compartilhar os recursos do servidor. Este é o estágio “Um servidor para todos governar e na desorganização aprisioná-los

Até o momento não falei em programação orientada a objetos. Esta incrivel opção, introduzida no PHP 3, melhorada no PHP4, divinizada no PHP5, e em processo de trancedencia no PHP6, permite que dados e funções sejam encapsulados de acordo com o contexto destes. $contato_sexo não mais irá receber qualquer valor, mas $contato->setSexo($value) irá tratar o valor de acordo que somente valores válidos sejam retornados com um $contato->getSexo(). Uma vez que as funções do objeto ficam centralizadas num arquivo apenas, é possível organizar o código de modo a re-aproveitar o código em diversos outras funções do site. O programador fica todo bobo com as possibilidades da progarmação orientada à objetos que começa a fazer tudo, por mais ridiculo que seja, utilizando objetos. Este é o estágio “$this->programacao->incrementarEstagio()

Outro estágio é o de separar o código em camadas. Temos uma camada que conecta com o banco de dados, outra camada que trata de processar as requisições do usuário e organizar os dados vindos do servidor, e uma terceira camada que exibe estas informações seguindo um código simples e fácil de compreender em HTML com pouca inclusão de código PHP (pois a camada de processamento dos dados já deixou tudo arrumadinho para esta). Aqui somos defato programadores profissionais, com códigos limpos e de fácil leitura. Separando as camads, é possivel substituir os códigos de uma de modo a atender as necessidades e limitaçõs do servidor. Programamos o site com mysql, mas o servidor utiliza Oracle ? Sem problemas, basta trocar a camada de banco de dados para Oracle. Esta é a fase “Quer programar como?

O próximo passo consiste na sequencia do segundo. A estruturação da base de dados agora deixa de ser função do programador, e passa para as competentes mãos de um analista. Chaves estrangeiras são respeitadas, nomes de variáveis padronizadas, alem de todo um trabalho de indexar as tabelas de modo a agilizar sua consulta. O servidor centralizado deixa de simplsmente armazenar os arquivos, mas passa a controlar estes com ferramentas de controle de versão, como o CVS e o SVN. Vemos a divisão da equipe de desenvolvimento em progamadores de módulos e macacos de programação, onde os primeiros desenvolvem as ferramentas que serão utilizadas em todos os projetos que os segundos irão processar. Este é o estágio “Dividir para programar“.

Antes de dar seqüência, quero deixar claro que não há nenhum preconceito para com os macacos de programação. Estes são parte vital da companhia, uma vez que sem eles para finalizarem projetos baseado nos frutos do trabalho dos programadores de módulos não haveria dinheiro algum entrando na empresa. Um necessita do outro para realizar suas funções.

Muito bem, muito bem. Temos agora um ambiente bem organizado de trabalho. Porém ainda não tratamos do árduo processo de personalizar a dar manutenção ao site do cliente. A vantagem de ter o código quebrado em dezenas de arquivos diferentes é que se for necessário corrigir um, basta substituí-lo sem grandes danos aos demais. Porém, o que ocorre quando uma melhoria num módulo tem de ser aplicada em dezenas, centenas!, de clientes diferentes, cada um com uma personalização diferente para o módulo ? E se o site tiver de ser traduzido para um novo idioma, o que fazer ? Para resolver este dilema passamos a nos utilizar de Markup Languages, como XML e YAML. Organizando o código de modo a buscar nestes arquivos os dados personalizados do cliente, podemos muito bem alterar nossa programação sem temer que pornografia apareça em meio ao site daquele cliente filiado à igreja. Este é o estágio “Admirável Configuração Nova“.

Estes são os estágios perceptiveis da organização do processo de trabalho desta empresa. A cada passo que damos é mais um passo para a maturidade desta. Nossos futuros passos, porém, ainda são muitos, e não temos uma noção concreta de para qual direção eles nos levam.


Categories

RSS The Kennel

Archives