Diego Rubin http://diegorubin.com Mon, 22 Oct 2012 15:08:57 +0000 Site pessoal de Diego Rubin Run My Source e a SECCOMP 2012 http://diegorubin.com/2012/10/22/run-my-source-e-a-seccomp Introdução

Olá pessoal, sei que já faz um bom tempo que não publico nada, o motivo deste abandono é o grande volume de projetos que estou alocado e os trabalhos relacionados as disciplinas que estou cursando esse semestre. Um dos projetos que estava trabalhando antes dessa tormenta começar era o Run My Source, que nem cheguei a apresentá-lo direito. Então quero faze-lo agora e depois explicarei o motivo.

Outra coisa, escrevi esse texto bem rápido na hora do meu almoço, se houver algum erro, por favor, me avisem.

O que é o Run My Source

O Run My Source foi desenvolvido com o intuito de ser utilizado quando ministro minicursos relacionados a programação. A principal funcionalidade do sistema é compilar(se necessário) e executar códigos submetidos por usuários. Quando falei desse projeto, muitas pessoas já chegaram falando: "Mas Diego, já tem um monte de sistemas semelhantes na internet" e concordo com essas pessoas, existem muitos sistemas desse tipo na internet. Um excelente sistema, e que já cheguei a utilizar por diversas vezes é o Ideone e é muito bom. Mas de qualquer forma resolvi desenvolver o meu, pois não quero adequar as minhas ideias à ferramenta de terceiros, eu quero adequar as minhas ferramentas às minhas ideias.

O core do Run My Source foi desenvolvido utilizando Erlang e OTP. Gosto muito de linguagens funcionais, estou para escrever um artigo sobre esse paradigma de programação desde dezembro do ano passado, mas quanto mais me aprofundo, mas quero me aprofundar e ainda não me achei capaz de escrever um artigo digno. Escolhi Erlang simplesmente porque queria aprender a linguagem e estava cursando uma disciplina onde foi possível escolher uma linguagem para desenvolver um projeto, então resolvi unir tudo.

O front-end do sistema foi desenvolvido utilizando Ruby On Rails, hoje Ruby é a linguagem que tenho maior facilidade e experiencia, não é minha preferida como gosto sempre de salientar, mas é a que utilizo quando quero desenvolver algo rapido e bem feito.

O Run My Source disponibiliza hoje as seguintes linguagens para serem utilizadas: C, C++, Erlang, Fortran, Lua, Objective-c, Pascal, Perl, Php, Prolog, Python e Ruby.

E a Seccomp 2012?

Neste ano ministrarei um minicurso de introdução a linguagem de programação Ruby. Gosto de ministrar, e também de participar, de minicursos onde os alunos possam ter a chance de testar a tecnologia e poder criar algo.

Meu objetivo é mostrar quais são as características do Ruby que diferem de outras linguagens que utilizamos no dia-a-dia e quero que os alunos possam sentir essas diferenças na hora de codar. Para isso estou planejando uma série de pequenos problemas, mais ou menos no estilo do SPOJ para os alunos testarem o que foi apresentado.

Para cumprir este objetivo pretendo utilizar o Run My Source para criar estes pequenos problemas. Assim os alunos poderão testar e submeter seus códigos para validá-los.

Se alguém que for participar do minicurso, ou não, quiser ver como funciona e só cadastrar-se no site runmysource.com, ir em cursos e ver o problema que tenho cadastrado de exemplo. Este problema foi retirado do Spoj. Em breve estará disponível um curso com o título SECCOMP 2012, que será utilizado no dia do minicurso.

O Run My Source não está finalizado, ele ainda possui alguns bugs e alguns erros de interface, mas aos poucos eu vou corrigindo.

]]>
Mon, 22 Oct 2012 15:08:57 +0000 /2012/10/22/run-my-source-e-a-seccomp rubin.diego@gmail.com (Diego Rubin)
Criando uma Jail utilizando Chroot no Fedora ou CentOS http://diegorubin.com/2012/06/22/criando-uma-sandbox-utilizando-chroot-no-fedora-ou-centos Introdução

Uma das técnicas utilizadas em segurança de servidores é a criação de um sistema isolado que dificultam o ataque de usuário que possuem acesso a determinado recurso mas que não devem ter acesso a outros.

Estou trabalhando em um projeto que já queria ter desenvolvido a algum tempo. Neste semestre tive uma disciplina onde o trabalho final era de tema livre, a única restrição era a linguagem em que o sistema deveria ser implementado, no meu caso, Erlang. Já faz algum tempo que estou estudando linguagens funcionais e achei esta uma bela oportunidade.

O sistema desenvolvido consiste em uma aplicação web onde os usuários submetem códigos, a aplicação compila-os e retorna a saída. Funciona como ideone.com. Meus objetivos com ele são:

  • Aprender novas técnologias e resolver novos problemas;

  • Me divertir. Objetivo de quase todos os projetos que desenvolvo sozinho;

  • Utiliza-lo em minicurso que eventualmente eu apresento por ai.

Bom, meu objetivo 1 foi logo alcançado. Encontrei um problema que ainda não tinha resolvido em algum outro projeto que desenvolvi. Eu estaria dando acesso a um profissional qualificado(ou não) ao meu servidor para ele poder criar uma aplicação. Esse é um tremendo problema de segurança.

No primeiro momento pensei em virtualização, porém meus recursos financeiros não permitem-me chegar muito longe nesta solução. Realizei algumas pesquisas e resolvi adotar o modo que descreverei neste artigo. Talvez haja um jeito bem melhor, porém acredito que o jeito implementado seja válido.

Sandbox e um pouco sobre segurança

Em segurança da informação, o termo Sandbox é utilizado para referenciar um ambiente controlado onde são executados procedimentos que podem danificar um sistema.

Bom, realmente não sou especialista nesta área. Por isso adicionei mais alguns artigos sobre o assunto abaixo do post.

O Chroot

Minha experiencia com esse aplicativo dava-se apenas a alteração da senha de root de sistemas instalados em laboratórios onde tive aula. Sempre gostei de ser root, poder instalar minhas aplicações favoritas e não ter que depender do técnico para executar um simples fsck.

O comando Chroot altera o diretório raiz do processo corrente e dos processos filhos do processo corrente para o diretório passado como argumento. Assim, todos os comandos executados e todas as suas implicações serão relativas ao novo sistema de arquivos.

Por exemplo, ao executarmos o comando # chroot /jails/root o diretório passado como argumento torna-se o "/", sendo assim, no processo filho, quando eu executo um ls na raiz, na verdade eu estarei executando /jails/root/bin/ls /jails/root. E isto ajuda-me a resolver o problema descrito na Introdução.

Como todo sistema e aplicação, existem falhas nesta técnica, existe como um usuário preso na jail sair(também adicionei um artigo falando sobre isso no final do post), mas no caso em que estarei executando o processo dentro do jail, até o usuário rodar a aplicação que quebre, provavelmente o sistema já terá encerrado sua execução.

Criando a estrutura de diretórios

Neste ponto começarei a ser um pouco especifico. Neste exemplo irei mostrar um jeito fácil de criar a estrutura de diretórios tendo como base os sistemas operacionais Fedora ou CentOS(provavelmente não diferenciar muito em um Red Hat, mas como eu não cheguei a testar eu não vou afirmar nada).

Podemos criar os todos os diretórios base manualmente e copiar os aplicativos e libs para eles ou podemos pegar um pronto. Neste artigo, como escrevi anteriormente, irei mostrar como fazer isso de forma simples com o auxilio do yum.

Vamos criar o diretório destino:

# mkdir -p /jails/root/var/lib/rpm

Agora vamos iniciar a base de dados do yum:

# rpm --root /jails/root --initdb

O próximo passo é obter o pacote rpm base do sistema. Se você tiver instalado o programa yumdownloader você pode utilizá-lo para obter este pacote:

yumdownloader --destdir=/tmp fedora-release: No Fedora

yumdownloader --destdir=/tmp centos-release: No CentOS

Ou pode obte-lo fazendo download:

wget http://archive.fedoraproject.org/pub/fedora/linux/releases/17/Fedora/x86_64/os/Packages/f/fedora-release-17-1.noarch.rpm: No caso do Fedora 17

wget http://mirror.centos.org/centos/6.2/os/x86_64/Packages/centos-release-6-2.el6.centos.7.x86_64.rpm: No caso do CentOs 6.2

Eu adicionei os links para o pacote mais recente de cada distribuição, se você precisar de outra versão, a organização dos diretórios é bem intuitiva e podem ser obtidos seguindo o mesmo caminho.

rpm -i --root=/jails/root --nodeps os-release.rpm

Para instalar novos pacotes através do yum podemos utilizar o comando passando o parametro installroot, como o exemplo:

yum --installroot=/jails/root install bash

Para executarmos o jail basta executarmos o comando.

# chroot /jails/root

Fakechroot

Para alterar o diretório raiz utilizando o comando chroot você precisa estar logado como root, porém podemos acessá-lo como um usuário normal através do comando fakechroot. O comando fakechroot torna possibilita um usuário criar sua própria sandbox.

Para executarmos o bash, ou outro comando qualquer, dentro de uma jail utilizando o fakechroot podemos utilizar o seguinte comando.

fakechroot /path/to/command/chroot /path/to/directory/jail /bin/bash

Conclusão

Fazia muito tempo que eu não gastava tanto tempo para resolver um problema de infra. Gosto muito de estudar essa área, porém não estou com muito tempo para o mesmo.

Se alguém tiver alguma dúvida peço que utilize o sistema de comentários do site. Mesmo que eu não saiba responder de imediato, irei procurar a resposta pois tenho muito interesse nestes tipos de problemas e colocarei a resposta aqui.

]]>
Fri, 22 Jun 2012 11:10:22 +0000 /2012/06/22/criando-uma-sandbox-utilizando-chroot-no-fedora-ou-centos rubin.diego@gmail.com (Diego Rubin)
Um ano de existência http://diegorubin.com/2012/04/16/um-ano-de-existencia Introdução

Neste post offtopic gostaria de falar um pouco sobre o site, o sistema que estou programando e a apresentação de alguns números.

Evolução

Inicialmente hospedei o site no wordpress.com, ainda estava trabalhando na Ci&t e viajando todos os dias e tinha muito tempo para fazer algo novo. Em agosto aluguei meu próprio espaço e movi minha instancia do wordpress para lá. Até então não podia utilizar o Google Analytics, portanto os dados que irei apresentar não foram computados desde abril mas desde de agosto.

Em setembro, no feriado da independência, pensei em criar meu próprio cms e assim poderia de forma mais fácil colocar no ar coisas que eu tinha em mente já que meu forte não é PHP. No inicio pensei em utilizar o framework Catalyst desenvolvido em Perl, gosto muito dessa linguagem e queria aprender coisas novas. Depois de algumas brincadeiras percebi que iria demorar demais para colocar alguma coisa em produção sendo que teria que aprender a usar um framework totalmente novo para mim. Além disso queria usar algumas tecnologia que iriam complicar um pouco mais o desenvolvimento, não por causa da tecnologia em si, mas sim por ser algo que sai da maioria dos padrões.

Em 14 de setembro iniciei o desenvolvimento do cms utilizando o framework Ruby on Rails, que conheço bastante, e o banco de dados nosql MongoDB. Em 18 de outubro coloquei a primeira versão em produção. Importei todos os posts e comentários que foram feitos no worpress para o novo sistema.

Após a primeira versão em produção comecei a criar funcionalidades e melhorias que eu tinha na planeja. Então criei meu próprio sistema de exibição das minhas apresentações, faço o upload de um arquivo pdf e a apresentação é exibida no site, inclusive em fullscreen utilizando as teclas padrões. Construí meu próprio sistema de snippets, "estendi" o markdown utilizado no sistema e criei um comando para exibir snippets, assim os exemplos dos artigos também serão exibidos em sua própria sessão. Adicionei um cadastro de links onde salvo meus favoritos e para facilitar o uso do recurso criei um plugin para o Firefox, onde é exibido um menu com uma lista dos favoritos dividido em tags, opção de adicionar um novo favorito e opção de adicionar um novo snippet.

O tempo médio de resposta do sistema para cada request está em torno de 170ms, e estou monitorando sempre para melhorar o sistema de cache do site. Neste contexto foi incorporado o uso do banco nosql Redis para meu próprio controle de pageviews. Tenho essa preocupação com o frontend também, por isso tenho todos os meus códigos javascript e css compactados e adicionados em um único arquivo, esse processo é realizado no deploy. Além disso também tenho adotado a utilização de css sprites.

Também criei uma funcionalidade para facilitar a criação de novos widgets que são utilizados na barra lateral. Os widgets criados até agora utilizando este recurso são:

  • Exibição de QRCode contendo informações de contato [RW]

    Na barra lateral é exibido uma imagem contendo um QRCode que é gerado a partir da administração do site. Após edição das informações a imagem é automaticamente gerada.

  • Últimos posts publicados [R]

    Este é um widget que não possui opção de edição. Seu conteúdo é gerado dinamicamente quando um novo post é publicado.

  • Últimos links adicionados aos favoritos [R]

    Este é um widget que não possui opção de edição. Seu conteúdo é gerado dinamicamente quando um novo post é publicado.

  • Exibição do meu status no Spoj [R]

    Criei um script que de tempos em tempos ele recupera minhas informações no Spoj e exibe através do widget na barra lateral.

Ainda há muitas coisas que quero fazer, mas já estou feliz com o resultado atual.

Dados de Visitas

Relembrando, os dados que serão exibidos foram computados após agosto de 2011. Aĺem disso, todos nós sabemos que números muitas vezes não dizem nada e que também são facilmente manipulados, porém estou tentando fazer com que essas informações sejam úteis para mim, Por isso adoto atitudes, como por exemplo, quando estou logado no sistema o código do analytics e outras ferramentas que utilizo para gerar estáticas são desativados, portanto minhas próprias visitas não contam.

Os 5 posts mais visitados

Título Visualizações
Desenvolvimento para Android - Carregando um banco SQLite criado externamente 351
Abandonando a IDE do Arduino 273
Desenvolvimento para Android - Trabalhando com SQLite 247
Criação de Bibliotecas Dinâmicas em C 240
Desenvolvimento para Android - Utilizando uma ListView 202

Futuro

Ainda tenho um backlog um pouco grande de coisas para fazer. Muita coisa está feita apenas visando a minha utilização, que tornar estes pontos mais genéricos e assim que estiverem prontos eu quero liberar o sistema com a licença AGPL. Mas por enquanto estou arrumando a casa.

Quero adotar mais o uso de html5 para demarcar conteúdos do site, terminar meu sistema de busca, parametrizar algumas coisas que estão fixas no código, etc...

Conclusão

Estou bem feliz com o resultado que tive neste primeiro ano de existência do site. No inicio acreditava que seria apenas mais uma tentativa de manter um blog como outras que tive anteriormente.

Tenho recebido alguns feedbacks através de email e redes sociais, porém gostaria de tê-los em forma de comentários no site, por isso eu peço que usem o espaço que foi construído para isso. Se alguém está tendo algum problema ou sabe de algum ponto que precisa ser melhorado, por favor, peço que avisem-me.

]]>
Tue, 17 Apr 2012 00:28:07 +0000 /2012/04/16/um-ano-de-existencia rubin.diego@gmail.com (Diego Rubin)
Recebendo Argumentos via Linha de Comando em Erlang Script http://diegorubin.com/2012/04/09/recebendo-argumentos-via-linha-de-comando-em-erlang Introdução

Atualmente estou estudando a linguagem de programação Erlang, ainda estou acostumando-me com a linguagem, porém este mundo já é conhecido, porque há um bom tempo venho estudando linguagens funcionais(há um post que estou escrevendo sobre o assunto, comecei ano passado e ainda sinto que não estou totalmente preparado para terminá-lo). Além disso, em uma das disciplinas que estou cursando esse semestre pude escolher esta linguagem como foco dos meus estudos.

Como primeiros testes com algo útil, quero reescrever em Erlang o meu projeto FrontFile, que para mim talvez seja os scripts mais úteis que criei até hoje, que foi escrito em Perl.

O Erlang é uma linguagem desenvolvida pela Ericsson visando aplicações para telefonia que são altamente concorrentes. Também é bastante utilizado em aplicações financeiras e servidores de mensagens instantâneas. Porém ele também pode ser utilizado para desenvolver script.

Passagem de parâmetros via linha de comando

Existe um modelo de para passagem de parâmetros via linha de comando para programas chamada getopt. Ela foi introduzida no Bash em 1986 e vem sendo utilizada por diversos programas. Veja um exemplo utilizando o comando ls.

ls --all -B

Nos links abaixo do post é possível encontrar mais informações sobre esse padrão de passagem de parâmetros via linha de comando.

Parseando os Parâmetros em Erlang

O Erlang Script não possui esse tipo de recurso nativamente, porém existe um projeto no githup que implementa o getopt. Para utilizá-lo em sua aplicação basta baixar os arquivos do repositório e compilar utilizando o make. Veja os comandos necessário, lembrando que o git deve estar instalado, se não estiver você pode fazer o download do pacote do projeto na própria página do github:

$ git clone https://github.com/jcomellas/getopt

$ cd getopt

$ make

Para podermos utilizar o módulo compilado temos que torná-lo visível ao nosso script, para isso temos algumas opções, as que eu conheço são as seguintes.

  • Colocar no mesmo diretório onde o comando será executado

    Essa é a forma mais simples, porém ela exige que você sempre execute o seu script do diretório onde estiver a biblioteca que será invocada. Se você executar o comando de outro diretório o script não irá conseguir carregar a biblioteca. Nós utilizaremos esta abordagem em nosso exemplo.

  • Adicionar no diretório de libs do Erlang

    Os arquivos da lib podem ser adicionadas no diretório padrão de libs que fica em /usr/lib/erlang/lib ou /usr/lib64/erlang/lib para sistemas de 64 bits. Esse é o caminho para sistemas unix-like (Me desculpem pessoal ai do Windows, não conheço muito deste sistema). O problema desta abordagem e que muitas vezes não iremos ter permissão de escrita nestes diretórios.

  • Utilizar o comando code:add_path("CaminhoParaDiretorio")

    Talvez esse modo seja o mais flexível. O diretório passado será carregado em tempo de execução.

  • Setar a variável de ambiente $ERL_LIBS

    Tive alguns problemas com esse modo, parece que nem todos os sistemas utilizam está variável. Ainda irei testar mais vezes e qualquer descoberta eu adiciono neste post. Talvez ela seja utilizadas apenas em aplicações que foram construídas utilizando o OTP.

Implementando

Criei um exemplo simples para demonstrar o uso de passagem de parâmetros. O exemplo lê um arquivo do tipo csv com notas de alunos e calcula a média de cada aluno. Os parâmetros que o script pode receber é a mensagem quando o aluno é aprovado, quando é reprovado e a média que o aluno precisa obter para ser aprovado. Esses são parâmetros opcionais que se não forem informados receberão um valor padrão informado no código. O caminho até o arquivo também deve ser informado.

Como de costume, tentei comentar o bem o código, explicando tudo que o que foi feito no código e qualquer dúvida peço que utilize o sistema de comentário do site.

#!/usr/bin/env escript
% O arquivo que será lido é um csv como o descrito abaixo
% Nome, nota1, nota2, nota3

-module(exemplo).
-author('rubin.diego@gmail.com').

% Função que é chamada quando o script é executado.
% A primeira assinatura é executada quando nenhum parametro é passado.
main([]) ->
  usage();
main(Args) ->
  {ok, {Options, Arquivo}} = getopt:parse(option_spec_list(),Args),
  io:format("Executando o script ~n"
            "Args: ~p~n~n"
            "Options: ~p~n~n"
            "Arquivo: ~p~n~n", [Args, Options, Arquivo]),

  [Media|_] = [X || {media, X} <- Options],
  [Aprovado|_] = [X || {aprovado, X} <- Options],
  [Reprovado|_] = [X || {reprovado, X} <- Options],

  read(Arquivo, Media, Aprovado, Reprovado).

% Se não houver nenhum parametro passado via linha de comando
% essa função será executada.
usage() ->
  usage(escript:script_name()).

usage(Name) ->
  getopt:usage(option_spec_list(), Name, "arquivo", 
               [{"arquivo", "Arquivo csv contendo as informações descritas no código."}]).

% Uma simples função que retorna a configuração dos parametros esperados.
option_spec_list() ->
  [
    % {Nome, NomeCurto,  NomeLongo,  Tipo Esperado, Mensagem de Ajuda}
    {aprovado, $a, "aprovado", {string, "Aprovado"}, "Mensagem que será exibida se o aluno foi aprovado"},
    {reprovado, $r, "reprovado", {string, "Reprovado"}, "Mensagem que será exibida se o aluno foi reprovado"},
    {media, $m, "media", {float, 5}, "Média minima para o aluno ser aprovado"}
  ].

% Leitura do arquivo com os dados dos alunos e calculo das média.
read(Filename, Media, Aprovado, Reprovado) ->
  {ok, Fd} = file:open(Filename, [read]),
  read(io:get_line(Fd, ""), Fd, Media, Aprovado, Reprovado).

read(eof, File, _Media, _Aprovado, _Reprovado) ->
  file:close(File);
read(Line, File, Media, Aprovado, Reprovado) ->
  {Nome, Resultado} = calculate(string:strip(Line, right, $\n)),
  resultado(Nome, Resultado, Media, Aprovado, Reprovado),
  Content = io:get_line(File, ""),
  read(Content, File, Media, Aprovado, Reprovado).

% A função pega as informações de uma linha e trata as mesmas
calculate(Line) ->
  % As informações no arquivo estão dispostas como o exemplo no comeco do arquivo.
  % Iremos quebrar as informações separadas por vírgulas.
  Infos = string:tokens(Line, ","),
  [Nome|Notas] = Infos,
  Resultado = (lists:sum([list_to_float(string:strip(X)) || X <- Notas ])/3.0),
  {Nome, Resultado}.

% Verifica se o aluno foi aprovado ou não e imprime o resultado na tela.
resultado(Nome, Resultado, Media, Aprovado, Reprovado) ->
  Mensagem = if 
    Resultado >= Media ->
      Aprovado;
    true ->
      Reprovado
    end,
  io:format("~s ~s ~.1f~n", [Nome, Mensagem, Resultado]).

O script pode ser executado com o seguinte comando:

./exemplo.escript -a "foi aprovado com média" -r "foi reprovado com média" -m 4.5 notas

Lembrando que o arquivo deve ter permissão de execução.

Conclusão

Estou iniciando minha jornada no mundo do Erlang, porém tenho alguns projetos onde espero utilizar essa tecnologia e muito em breve quero escrever mais artigos abordando a linguagem.

Também gostaria de deixar um pedido, se alguém puder dar alguma dica, alguma boa prática da tecnologia que está ausente no código, por favor, deixe nos comentários.

E lembrando também que o código do exemplo pode ser encontrado no repositório do blog no github.

]]>
Mon, 09 Apr 2012 23:40:53 +0000 /2012/04/09/recebendo-argumentos-via-linha-de-comando-em-erlang rubin.diego@gmail.com (Diego Rubin)
HTML5 - File Upload utilizando Drag And Drop http://diegorubin.com/2012/03/15/html5-file-upload-utilizando-drag-and-drop Introdução

Apesar do HTML 5 ainda não ter sido totalmente especificado e por isso ainda não ter sido liberado pela W3C, ele já está sendo adotado por diversos navegadores e com isso várias aplicações web estão fazendo uso destes novos recursos.

Neste artigo irei mostrar uma forma de realizar upload de arquivos utilizando a File API do HTML 5 simplesmente arrastando um arquivo do desktop em um área pré-determinada da página. Além disso, também será utilizado o FileReader que possibilita o ler o conteúdo do arquivo que é carregado do file system.

ATENÇÃO: Se você está visualizando este post através do feed, eu peço que visite a página do conteúdo no site para ver o exemplo funcionando em seu navegador.

No exemplo, um arquivo arrastado a partir do desktop é carregado pelo navegador e é exibida no frame. É um exemplo simples mas que exemplifica bem o uso desses novos recursos disponíveis pelo Html 5. Se o arquivo arrastado não for uma imagem nada será exibido.

Arraste a imagem aqui
Digite um titulo

A estrutura HTML e o Estilo

Neste exemplo iremos utilizar uma simples div para delimitarmos a área em que o arquivo será arrastado. E colocaremos um elemento span para avisarmos nosso usuário que o arquivo deve ser arrastado para este lugar.

<html>
  <head>
    <link href="html5-fileapi.css" media="all" rel="stylesheet" type="text/css" />
  </head>
  <body>

    <div id="example-content">

      <div id="frame">

        <div id="image-area">

          <span id="drop-message"> Arraste a imagem aqui </span>

        </div>

        <span id="title"> Digite um titulo </span>
      </div>

    </div>

  </body>
  <script src="html5-file-upload-code.js" type="text/javascript"></script>
</html>

O estilo utilizado no exemplo é bem simples e pode ser visto logo abaixo:

#example-content { text-align: center; }
#frame { background-color: #000; color: #fff; margin: 50px; width: 400px; height: 450px; padding: 50px; }
#image-area { width: 100%; border: 1px dotted #fff; padding-top: 50%; padding-bottom: 50%; margin-bottom: 15px; overflow:hidden; }

.hover { border: 1px solid #fff !important; }

O Código Javascript

Abaixo irei colocar o código comentado que será utilizado neste exemplo. Há bastante comentários no código, mas como de costume, quero reforçar que qualquer dúvida utilize o sistema de comentários do site.

// Criação da nossa "classe" de controle.
// Não entrarei neste detalhe aqui, mas por favor,
// se você ouvir alguém falar que javascript é uma
// linguagem orientada a objetos, no mínimo, não
// acredite nele. Javascript é uma linguagem prototipada.
// Essa maneira de programar javascript é uma preferencia
// minha. Prefiro este estilo a criar várias funções perdidas.
function FileFrame(fileArea, fileTitle) {
  var self = this;

  this.fileArea = fileArea;
  this.fileTitle = fileTitle;

  this.init = function() {
    // Registrando eventos de drag and drop
    self.fileArea.addEventListener("dragleave", self.dragHover, false);
    self.fileArea.addEventListener("dragover", self.dragHover, false);
    self.fileArea.addEventListener("drop", self.drop, false);
 
  };

  this.dragHover = function(e) {
    // Impede possíveis tratamentos dos arquivos
    // arrastados pelo navegador, por exemplo, exibir
    // o conteudo do mesmo.
    e.stopPropagation();  
    e.preventDefault();  

    // Quando o arquivo está sobre área alteramos o seu estilo
    self.fileArea.className = (e.type == "dragover" ? "hover" : "");  
  };

  this.drop = function(e) {
    self.dragHover(e);  

    // Volta um array com os arquivos arratados,
    // porém neste exemplo iremos tratar apenas
    // o primeiro arquivo
    self.file = e.dataTransfer.files[0];  
   
    // Recupera nome do arquivo
    self.fileTitle.innerHTML = self.file.name;


    self.read(self.file);
    
    // Neste ponto podemos implementar uma função para
    // enviar os arquivos via ajax.
    // Irei deixar um exemplo, qualquer dúvida eu peço
    // que utilize o sistema de comentários do site.
    /*
    self.sendFile(self.file);
    */
  };

  // Esse método irá ler o arquivo na memória,
  // depois iremos mostrá-lo no nosso frame
  this.read = function(file) {
    // Iremos ler apenas imagens nesse exemplo
    // e iremos exibi-lo no frame
    if (file.type.match('image.*')) {
      var reader = new FileReader();

      // Callback que será executado após a leitura do arquivo
      reader.onload = function(f) {
        self.fileArea.innerHTML = "";
        self.fileArea.setAttribute("style", "padding: 0px !important;");
        
        // Criação do elemento que será utilizado para exibir a imagem
        var img = document.createElement("img");
        img.setAttribute("src", f.target.result);
        img.setAttribute("height", "350");

        self.fileArea.appendChild(img);
      }

      // Irá ler o arquivo para ser acessado através de uma url
      reader.readAsDataURL(file);
    }
  }

  // Essa função pode ser utilizada como 
  this.sendFile = function(file) {

    // Criaremos um formulário
    var f = new FormData();
    // Passando o arquivo para o formulário
    f.append("file", file);

    // Chamada async para realizar o upload da imagem
    var request = new XMLHttpRequest();
    request.open("POST", "", true);
    request.send(f);
    request.onreadystatechange=function(){
      // Término do envio do formulário
      if(request.readyState==4) {
      }
    }
  };

}

// Recupera a div que conterá a imagem
// e o span com o título de nosso arquivo
var area = document.getElementById("image-area");
var title = document.getElementById("title");

var fileFrameArea = new FileFrame(area, title);
fileFrameArea.init();

Conclusão

Infelizmente o uso deste tipo de recurso em produção ainda dever ser utilizado em aplicações bastante especificas ou devemos também criar alternativas a elas já que uma bela parcela de usuários ainda estão utilizando navegadores que não suportam esse tipo de recurso. Mas é legal ver o que está por vir.

Os arquivos utilizados neste exemplo podem ser encontrados no repositório do blog no github.

]]>
Thu, 15 Mar 2012 23:46:34 +0000 /2012/03/15/html5-file-upload-utilizando-drag-and-drop rubin.diego@gmail.com (Diego Rubin)
Desenvolvimento para Android - Utilizando o botão físico de busca http://diegorubin.com/2012/02/20/desenvolvimento-para-android-utilizando-o-botao-fisico-de-busca Introdução

Alguns smartphones com Android possuem botões físicos. Um deles dispara um evento que o Android trata como uma solicitação de busca. Se a aplicação não tratar esse tipo de evento o sistema operacional irá tratá-la da forma que estiver configurado, na maioria dos casos um navegador de internet irá abrir com um textfield para inserir alguma informação para ser buscada.

O que eu quero mostrar neste artigo é uma forma da aplicação que você estiver desenvolvedor tratar esse evento de busca ao clicar no botão físico de busca.

Método para busca no banco

Iremos criar um novo método em nossa classe LinguagemDataSource que recebe um string. Essa string servirá de filtro para o nome das linguagens cadastradas no banco.

Arquivo: LinguagemDataSource.java

package com.diegorubin.search_list;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public class LinguagemDataSource {

  private SQLiteDatabase db;
  private DatabaseHelper helper;
  
  public LinguagemDataSource(Context context) {
    helper = new DatabaseHelper(context);
    db = helper.getDatabase();
  }
  
  /**
   * Filtrando as linguagens cadastradas no banco.
   * Este método será utilizado ao pressionar o botão de busca.
   */
  public List<JSONObject> filterByNome(String nome) {
    List<JSONObject> result = new ArrayList<JSONObject>();
    Cursor cursor = 
      db.query("linguagens", new String[]{"nome", "descricao"}, 
               "nome like '%" + nome + "%'",
               null, null, null, "nome ASC" /*ordenando pelo nome*/);

    cursor.moveToFirst();
    while(!cursor.isAfterLast()) {
      result.add(readRow(cursor));
      
      cursor.moveToNext();
    }
    
    cursor.close();
    return result;
  }
  
  /**
   * Recuperando todas a linguagens cadastradas no nosso
   * banco de dados.
   * Iremos retorná-los em List<JSONObject>, pois é o 
   * formato que o nosso adapter espera.
   */
  public List<JSONObject> allLinguagens() {
    List<JSONObject> result = new ArrayList<JSONObject>();
    
    // Iremos buscar todas as linguagens cadastradas no banco
    // As colunas que iremos selecionar serão nome e descricao
    // O objeto de retorno contém a referencias das linhas retornadas
    Cursor cursor = 
      db.query("linguagens", new String[]{"nome", "descricao"}, 
               null, /* buscaremos todas, nao precisamos de nenhuma condicao*/
               null, null, null, "nome ASC" /*ordenando pelo nome*/);
    
    cursor.moveToFirst();
    while(!cursor.isAfterLast()) {
      result.add(readRow(cursor));
      
      cursor.moveToNext();
    }
    
    cursor.close();
    return result;
  }
  
  /**
   * Método auxiliar para ler resultado da query
   */
  private JSONObject readRow(Cursor cursor) {
    JSONObject obj = new JSONObject();
    
    try{
      // As colunas são recuperadas na ordem que foram selecionadas
      obj.put("nome", cursor.getString(0));
      obj.put("descricao",cursor.getString(1));
    }catch (JSONException e) {
    }
    
    return obj;
  }
}

Alterações no SearchListActivity

Temos que alterar a classe SearchListActivity. Agora iremos checar se foi chamado após o callback de busca ou não. Se ele atender o primeiro caso, usaremos a String que foi usada como entrada no dialogo de busca para filtrar os nossos dados.

Arquivo: SearchListActivity.java

package com.diegorubin.search_list;

import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Toast;

public class SearchListActivity extends Activity {
  
  private ListView lstLinguagens;
  private List<JSONObject> linguagens;
  private LinguagemArrayAdapter adapter;
  private LinguagemDataSource source;
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    // Buscando o elemento Listview da nossa interface principal interface 
    lstLinguagens = (ListView) findViewById(R.id.lstLinguagens);
    
    source = new LinguagemDataSource(getApplicationContext());
    
    //Verifica se a activity foi chamada através do callback de busca 
    Intent intent = getIntent();
    if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
      String query = intent.getStringExtra(SearchManager.QUERY);
      
      // Recupera do banco as informações filtradas
      linguagens = source.filterByNome(query);
      
    } else {
    
      // Recupera do banco as informações que serão utilizados em nosso adapter
      linguagens = source.allLinguagens();
    }         
    
    // Passamos a lista de exemplo para gerar nosso adpater
    adapter = new LinguagemArrayAdapter(getApplicationContext(), R.layout.linguagem, linguagens);
    
    // Setando o adapter em nossa ListView
    lstLinguagens.setAdapter(adapter);
    
    // Setando callback ao selecionar um item da lista
    lstLinguagens.setOnItemClickListener(new OnItemClickListener() {
      
      public void onItemClick(AdapterView<?> parent, View view,
                              int position, long id) {
        
        try{
          JSONObject linguagem = linguagens.get(position);
          String descricao = linguagem.getString("descricao");
          Toast.makeText(getApplicationContext(), descricao, 10000).show();
        }catch (JSONException e) {
        }
      }
      
    });
    
    }
}

Configuração do manifest

Um novo arquivo deve ser criado em res/xml chamado searchconfig.xml, bom, esse nome não é obrigatório, mas acredito que seja um bom nome para ele. O conteúdo dele esta logo abaixo, com as configurações do dialogo de busca da nossa aplicação.

Arquivo: searchconf.xml

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
    android:label="@string/app_name"
    android:searchMode="showSearchLabelAsBadge"
    android:hint="Filtrar" >
</searchable>

Agora é necessário fazer algumas alterações no arquivo AndroidManifest.xml

Arquivo: AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.diegorubin.search_list"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="7" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".SearchListActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
                
                <!-- Avisa que nossa activity pode ser utilizada na busca -->
                <action android:name="android.intent.action.SEARCH" />
            </intent-filter>
            
            <!-- Setando esta activity para ser utilizada como callback da busca -->
            <meta-data android:name="android.app.default_searchable"
                       android:value=".SearchListActivity" />
            <!-- configuração do dialogo de busca -->
            <meta-data android:name="android.app.searchable"
                     android:resource="@xml/searchconf"/> 
        </activity>
    </application>

</manifest>

O elemento meta-data com o atributo android:name igual a android.app.defaultsearchable_ deve ser setada em todas as activity's que irão tratar o evento do botão fisico de busca.

Agora, ao pressionar o botão de busca, nossa aplicação tratará o evento e não o sistema. Não será aberto, por exemplo, o navegador, mas sim um dialogo para entrada de dados e esses dados repassado a nossa Activity.

Conclusão

Este foi o último arquivo da série sobre desenvolvimento para Android no CarnAndroid. Brincadeiras a parte, acredito que tenha alcançado meu objetivo.

Nosso projeto chegou a versão final, e estará no repositório do blog no github. Qualquer dúvida ou sugestão, por favor, utilizem o sistema de comentário do site.

Agradeço as pessoas que acompanharam os posts e em breve espero escrever mais sobre o desenvolvimento de aplicações para Android.

[]'s

]]>
Mon, 20 Feb 2012 19:54:01 +0000 /2012/02/20/desenvolvimento-para-android-utilizando-o-botao-fisico-de-busca rubin.diego@gmail.com (Diego Rubin)
Desenvolvimento para Android - Carregando um banco SQLite criado externamente http://diegorubin.com/2012/02/19/desenvolvimento-para-android-carregando-um-banco-criado-externamente Introdução

No artigo anterior, Trabalhando com SQLite, ao criar o banco de dados nós também o populamos com algumas informações que eram necessárias para nossa aplicação. Neste caso eram poucas as informações que iriamos utilizar.

Agora, no caso de termos um banco de dados com um números razoável de informação seria interessante utilizarmos um arquivo sqlite criado previamente. Este artigo tem como objetivo mostrar como podemos utilizar um arquivo do sqlite criado externamente em nossa aplicação.

Criação do banco de dados

Seguindo nosso exemplo iremos criar um banco de dados no sqlite chamado linguagens.sqlite. Os comandos sql estão logo abaixo, você pode utilizar sua aplicação favorita para criar este banco ou se você não tem nenhuma experiencia com o sqlite pode seguir os seguintes passos. O sqlite3 está instalado junto com o Android SDK. O comando pode ser encontrado em android-sdk/tools/sqlite3.

./sqlite3 linguagens.sqlite

Após isso digite os comandos sql abaixo.

CREATE TABLE linguagens (
  id INT AUTO_INCREMENT,
  nome VARCHAR(100),
  descricao VARCHAR(200),
  primary key(id)
);

INSERT INTO linguagens VALUES(1, 'Ruby', 'Sobre a linguagem Ruby');
INSERT INTO linguagens VALUES(2, 'Python', 'Sobre a linguagem Python');
INSERT INTO linguagens VALUES(3, 'Perl', 'Sobre a linguagem Perl');
INSERT INTO linguagens VALUES(4, 'Lua', 'Sobre a linguagem Lua');
INSERT INTO linguagens VALUES(5, 'PHP', 'Sobre a linguagem PHP');
INSERT INTO linguagens VALUES(6, 'Go', 'Sobre a linguagem Go');

Além da tabela com nossas informações, também é necessário criarmos uma tabela de controle do android, para isso utilizaremos os seguintes comandos sql. Se essa tabela não for criada, o sistema irá disparar um exception ao abrir o arquivo.

CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'pt_BR');

INSERT INTO "android_metadata" VALUES ('pt_BR');

Após ter o arquivo criado, copie o mesmo para o diretório assets da aplicação.

Usando o banco de dados criado

Agora teremos que fazer algumas alteracões na classe DatabaseHelper para utilizar o banco que criamos ao invés de criar um novo arquivo. Mais um vez, todas as explicações sobre as mudanças realizadas estão comentadas no próprio código e qualquer dúvida pode ser envidas através do sistema de comentários do site.

Arquivo: DatabaseHelper.java

package com.diegorubin.search_list;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {

  /**
   * Este é o endereço onde o android salva os bancos de dados criado pela aplicação,
   * /data/data/<namespace da aplicacao>/databases/
   */
  private static String DBPATH = "/data/data/com.diegorubin.search_list/databases/";

  // Este é o nome do banco de dados que iremos utilizar
  private static String DBNAME ="linguagens.sqlite";
  
  private Context context;
  
  /**
   * O construtor necessita do contexto da aplicação
   */
  public DatabaseHelper(Context context) {
    /* O primeiro argumento é o contexto da aplicacao
     * O segundo argumento é o nome do banco de dados
     * O terceiro é um pondeiro para manipulação de dados, 
     *   não precisaremos dele.
     * O quarto é a versão do banco de dados
     */
    super(context, "linguagens.sqlite", null, 1);
    this.context = context;
  }
  
  /**
   * Os métodos onCreate e onUpgrade precisam ser sobreescrito
   */
  @Override
  public void onCreate(SQLiteDatabase db) {
    /*
     * Estamos utilizando o banco do assets, por isso o 
     * código antigo deste método não é mais necessário.
     */
  }
  
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    /*
     * Estamos criando a primeira versão do nosso banco de dados,
     * então não precisamos fazer nenhuma alteração neste método.
     * 
     */
  }
  
  /**
   * Método auxiliar que verifica a existencia do banco
   * da aplicação.
   */
  private boolean checkDataBase() {
    
    SQLiteDatabase db = null;
    
    try {
      String path = DBPATH + DBNAME;
      db = 
        SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY);
      db.close();
    } catch (SQLiteException e) {
      // O banco não existe
    }
    
    // Retorna verdadeiro se o banco existir, pois o ponteiro irá existir,
    // se não houver referencia é porque o banco não existe
    return db != null;
  }
  
  private void createDataBase() 
  throws Exception {
    
    // Primeiro temos que verificar se o banco da aplicação
    // já foi criado
    boolean exists = checkDataBase();
    
    if(!exists) {
      // Chamaremos esse método para que o android
      // crie um banco vazio e o diretório onde iremos copiar
      // no banco que está no assets.
      this.getReadableDatabase();
      
      // Se o banco de dados não existir iremos copiar o nosso
      // arquivo em /assets para o local onde o android os salva
      try {
        copyDatabase();
      } catch (IOException e) {
        throw new Error("Não foi possível copiar o arquivo");
      }
      
    }
  }
  
  /**
   * Esse método é responsável por copiar o banco do diretório
   * assets para o diretório padrão do android.
   */
  private void copyDatabase()
  throws IOException {
    
    String dbPath = DBPATH + DBNAME;
    
    // Abre o arquivo o destino para copiar o banco de dados
    OutputStream dbStream = new FileOutputStream(dbPath);
    
    // Abre Stream do nosso arquivo que esta no assets
    InputStream dbInputStream = 
      context.getAssets().open("linguagens.sqlite");
    
    byte[] buffer = new byte[1024];
    int length;
    while((length = dbInputStream.read(buffer)) > 0) {
      dbStream.write(buffer, 0, length);
    }
    
    dbInputStream.close();
    
    dbStream.flush();
    dbStream.close();
    
  }
  
  public SQLiteDatabase getDatabase() {
    
    try{
      // Verificando se o banco já foi criado e se não foi o
      // mesmo é criado.
      createDataBase();
      
      // Abrindo database
      String path = DBPATH + DBNAME;
      
      return SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READWRITE);
    }catch (Exception e) {
      // Se não conseguir copiar o banco um novo será retornado
      return getWritableDatabase();
    }

  }
}

Consideração importante

Lembrando que estamos em um dispositivo com bem menos recursos de memória que um computador temos que levar em consideração o tamanho do arquivo que iremos copiar no diretório assets. Tive problemas ao abrir um arquivo com 4mb. Por isso recomendo que para evitar problemas, se você for utilizar essa técnica divida o arquivo em pedaços menores e ao copiar o banco leia um pedaço por vez e escreva-os no dbStream. No GNU/Linux essa divisão pode ser feita utilizando o comando split, como no exemplo abaixo:

split arquivo.sqlite -b 1000000

O comando acima divide o arquivo.sqlite em pedaços de 1000000 bytes ou quase 1 mega.

Então o código do método copyDatabase() ficaria assim:

private void copyDatabase()
throws IOException {
  
  String dbPath = DBPATH + DBNAME;
  
  // Abre o arquivo o destino para copiar o banco de dados
  OutputStream dbStream = new FileOutputStream(dbPath);
  
  // NUMERO_PEDACOS: número total de arquivos gerado após a divisão
  // PREFIX: string que prefixa cada pedaço
  for(int i = 1; i <= NUMERO_PEDACOS; i++){
    // Abre Stream do nosso arquivo que esta no assets
    InputStream dbInputStream = 
      context.getAssets().open(PREFIX + i);
  
    byte[] buffer = new byte[1024];
    int length;
    while((length = dbInputStream.read(buffer)) > 0) {
      dbStream.write(buffer, 0, length);
    }
  
    dbInputStream.close();
  }
  
  dbStream.flush();
  dbStream.close();
  
}

Conclusão

Minha intenção com este arquivo era demonstrar como utilizar um banco de dados criado previamente em uma aplicação android. Esse tipo de procedimento foi necessário em uma aplicação que desenvolvi e espero que seja útil para mais alguém.

Para esse exemplo foi necessário a alteração apenas de um arquivo do projeto que estamos utilizando de exemplo. Criei uma tag no repositório do blog e o projeto completo pode ser obtido lá.

Qualquer dúvida utilizem o sistema de comentário do site, terei o maior prazer em respondê-las.

]]>
Sun, 19 Feb 2012 13:46:21 +0000 /2012/02/19/desenvolvimento-para-android-carregando-um-banco-criado-externamente rubin.diego@gmail.com (Diego Rubin)
Desenvolvimento para Android - Trabalhando com SQLite http://diegorubin.com/2012/02/18/desenvolvimento-para-android-trabalhando-com-sqlite Introdução

Este é o segundo artigo da série sobre desenvolvimento de aplicações para Android. Neste artigo quero abordar o uso SGBD SQLite. Apesar de suas limitações em ralação a outros SGBD's, ele pode ser extremamente útil em pequenas aplicações.

Uma nova tag foi criada no repositório do blog e projeto inteiro pode ser encontrado no github.

Classe de criação do banco

O primeiro passo será a criação de uma classe que nos ajudará com a criação do banco de dados de nossa aplicação. Essa classe herdará de android.database.sqlite.SQLiteOpenHelper. Ela será responsável pela criação das tabelas e por popular com informações de exemplo. Iremos chamar essa classe de DatabaseHelper.

Arquivo: DatabaseHelper.java

package com.diegorubin.search_list;

import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DatabaseHelper extends SQLiteOpenHelper {

  /*
   * O construtor necessita do contexto da aplicação
   */
  public DatabaseHelper(Context context) {
    /* O primeiro argumento é o contexto da aplicacao
     * O segundo argumento é o nome do banco de dados
     * O terceiro é um pondeiro para manipulação de dados, 
     *   não precisaremos dele.
     * O quarto é a versão do banco de dados
     */
    super(context, "linguagens.sqlite", null, 1);
  }
  
  /*
   * Os métodos onCreate e onUpgrade precisam ser sobreescrito
   */
  @Override
  public void onCreate(SQLiteDatabase db) {
    db.execSQL("CREATE TABLE linguagens(" +
                   "id INT AUTO_INCREMENT," +
                   "nome VARCHAR(100)," +
                   "descricao VARCHAR(200)," +
                   "primary key(id));");
   
    ContentValues values = new ContentValues();
    
    /*
     * Inserindo os valores de exemplo em nosso
     * banco de dados.
     * 
     * É, estou sem muita imaginação :)
     */
    values.put("nome", "Ruby");
    values.put("descricao", "Sobre a linguagem Ruby");
    db.insert("linguagens", null, values);
    
    values.put("nome", "Python");
    values.put("descricao", "Sobre a linguagem Python");
    db.insert("linguagens", null, values);
    
    values.put("nome", "Perl");
    values.put("descricao", "Sobre a linguagem Perl");
    db.insert("linguagens", null, values);
    
    values.put("nome", "Lua");
    values.put("descricao", "Sobre a linguagem Lua");
    db.insert("linguagens", null, values);
  }
  
  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    /*
     * Estamos criando a primeira versão do nosso banco de dados,
     * então não precisamos fazer nenhuma alteração neste método.
     * 
     */
  }
  
  public SQLiteDatabase getDatabase() {
    return this.getWritableDatabase();
  }
}

Classe de conexão com o banco de dados

Tendo criado nosso banco, agora iremos criar uma classe contendo as métodos para buscar nossas informações no banco. O principal intuido da criação dessa classe é organizar nosso código. Todas as manipulações da tabela ficarão nesta classe.

A classe se chamará LinguagemDataSource.

Arquivo: LinguagemDataSource.java

package com.diegorubin.search_list;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public class LinguagemDataSource {

  private SQLiteDatabase db;
  private DatabaseHelper helper;
  
  public LinguagemDataSource(Context context) {
    helper = new DatabaseHelper(context);
    db = helper.getDatabase();
  }
  
  /*
   * Recuperando todas a linguagens cadastradas no nosso
   * banco de dados.
   * Iremos retorna-los em List<JSONObject>, pois é o 
   * formato que o nosso adapter espera.
   */
  public List<JSONObject> allLinguagens() {
    List<JSONObject> result = new ArrayList<JSONObject>();
    
    // Iremos buscar todas as linguagens cadastradas no banco
    // As colunas que iremos selecionar serão nome e descricao
    // O objeto de retorno contém a referencias das linhas retornadas
    Cursor cursor = 
      db.query("linguagens", new String[]{"nome", "descricao"}, 
               null, /* buscaremos todas, nao precisamos de nenhuma condicao*/
               null, null, null, "nome ASC" /*ordenando pelo nome*/);
    
    cursor.moveToFirst();
    while(!cursor.isAfterLast()) {
      JSONObject obj = new JSONObject();
      
      try{
        // As colunas são recuperadas na ordem que foram selecionadas
        obj.put("nome", cursor.getString(0));
        obj.put("descricao",cursor.getString(1));
      }catch (JSONException e) {
      }
      
      result.add(obj);
      
      cursor.moveToNext();
    }
    
    cursor.close();
    return result;
  }
}

Alterando o Activity principal

O ultimo passo será alterar a fonte de dados de nosso exemplo anterior. Agora as informações que popularam a ListView irão vir do nosso banco de dados. O SearchListActivity após a alteração está logo abaixo.

Arquivo: SearchListActivity.java

package com.diegorubin.search_list;

import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Toast;

public class SearchListActivity extends Activity {
  
    private ListView lstLinguagens;
    private List<JSONObject> linguagens;
    private LinguagemArrayAdapter adapter;
    private LinguagemDataSource source;
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        source = new LinguagemDataSource(getApplicationContext());
        
        // Recupera do banco as informações que serão uitlizados em nosso adapter
        linguagens = source.allLinguagens();
        
        // Passamos a lista de exemplo para gerar nosso adpater
        adapter = new LinguagemArrayAdapter(getApplicationContext(), R.layout.linguagem, linguagens);
        
        // Buscando o elemento Listview da nossa interface principal interface 
        lstLinguagens = (ListView) findViewById(R.id.lstLinguagens);
        // Setando o adapter em nossa ListView
        lstLinguagens.setAdapter(adapter);
        
        // Setando callback ao selecionar um item da lista
        lstLinguagens.setOnItemClickListener(new OnItemClickListener() {
          
          public void onItemClick(AdapterView<?> parent, View view,
                                  int position, long id) {
            
            try{
              JSONObject linguagem = linguagens.get(position);
              String descricao = linguagem.getString("descricao");
              Toast.makeText(getApplicationContext(), descricao, 10000).show();
            }catch (JSONException e) {
            }
          }
          
        });
        
    }
}

Conclusão

No próximo artigo quero escrever um pouco sobre como carregar um banco de dados criado externamente em nossas aplicações.

Mais uma vez quero deixar-lhes a vontade para tirarem eventuais dúvidas sobre as informações passadas neste artigo. Os comentários do post estão ai para isso.

[]'s

]]>
Sat, 18 Feb 2012 15:12:10 +0000 /2012/02/18/desenvolvimento-para-android-trabalhando-com-sqlite rubin.diego@gmail.com (Diego Rubin)
Desenvolvimento para Android - Utilizando uma ListView http://diegorubin.com/2012/02/17/desenvolvimento-para-android-utilizando-uma-listview Introdução

Pretendo neste carnaval escrever uma série de artigos sobre desenvolvimento de aplicações Android. Como em todos os artigos sobre desenvolvimento que escrevo, os códigos utilizados estão disponíveis no github do site.

Pretendo escrever cerca de três posts, sempre incrementando o mesmo exemplo. No repositório, irei criar uma tag separando as mudanças em cada post.

Objetivo deste artigo

Este primeiro artigo da série irá mostrar uma forma de criar uma lista persionalizada. No exemplo será apresentado uma lista de linguagens de programação com o título da linguagem em destaque e logo abaixo, como uma fonte menor, com uma pequena descrição. Bom, de qualquer forma o conteúdo é irrelevante, o importante é a forma de fazê-lo.

Estou levando em consideração que você já tem alguma familiaridade com o desenvolvimento de aplicações para Android e que você já tem o seu ambiente rodando.

Implementação

O primeiro passo é criar um novo arquivo de layout. Abaixo está o arquivo criado para esse exemplo. É algo bem simples, um LinearLayout contento dois TextView's, o primeiro exibirá o nome da linguagem e o segundo a descrição.

Os TextView's deveram conter o atributo "android:id", é por meio dele que iremos recuperar a referência destes elementos.

Arquivo: linguagem.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_margin="5dip"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/txtLinguagem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Large Text"
        android:textAppearance="?android:attr/textAppearanceLarge" />

    <TextView
        android:id="@+id/txtDescricao"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Small Text"
        android:textAppearance="?android:attr/textAppearanceSmall" />

</LinearLayout>

Temos que criar uma classe que irá ligar nossos dados à interface. Essa classe deve herdar a classe ArrayAdapter, essa classe realmente servirá como adaptador das nossas informações com a interface. Vamos criar a classe chamada LinguagemArrayAdapter. O código está comentado, mas se houver alguma dúvida, por favor, utilize os comentários do post.

Arquivo: LinguagemArrayAdapter.java

package com.diegorubin.search_list;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONObject;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

/*
 * Criaremos um ArrayAdapter para uma lista de objetos json.
 * Podemos criar um ArrayAdapter para qualquer tipo de objeto.
 * 
 */
public class LinguagemArrayAdapter extends ArrayAdapter<JSONObject> {
  private static final String tag = "LinguagemArrayAdapter";
  private Context context;
  private String directory;

  // Elementos de cada linha da lista
  private TextView txtLinguagem;
  private TextView txtDescricao;
  
  private List<JSONObject> elements = new ArrayList<JSONObject>();

  public LinguagemArrayAdapter(Context context, int textViewResourceId,
      List<JSONObject> objects) {
    super(context, textViewResourceId, objects);
   
    // Salva o contexto da aplicação
    this.context = context;
    
    // Lista de elementos que serão utilizados para a contrução da lista
    this.elements = objects;
  }

  public int getCount() {
    return this.elements.size();
  }
  
  public JSONObject getItem(int index) {
    return this.elements.get(index);
  }
  
  /*
   * Setando os valores dos objetos em cada linha da lista
   */
  public View getView(int position, View convertView, ViewGroup parent) {
    View row = convertView;
    if (row == null) {
      LayoutInflater inflater = (LayoutInflater) this.getContext()
          .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
      
      // layout que será utilizado na criação de cada linha da lista
      row = inflater.inflate(R.layout.linguagem, parent, false);
    }

    // Get item
    JSONObject category = getItem(position);

    // Recuperando referencia dos elementos da interface
    txtLinguagem = (TextView) row.findViewById(R.id.txtLinguagem);
    txtDescricao = (TextView) row.findViewById(R.id.txtDescricao);
    
    // Setando valores
    txtLinguagem.setText(category.optString("nome", ""));
    txtDescricao.setText(category.optString("descricao", ""));
    
    return row;
  }
}

Feito isso, nossa próxima etapa será exibir nossas informações na tela. Para isso iremos adicionar ao nosso arquivo de layout principal um widget do tipo ListView. O arquivo de layout em questão é o main.xml.

Arquivo: main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ListView android:id="@+id/lstLinguagens"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:dividerHeight="1dip">
    </ListView>

</LinearLayout>

Agora em nossa classe principal, no método onCreate iremos criar um lista de dados fake para podermos testar nossa lista.

Arquivo: SearchListActivity.xml

import java.util.ArrayList;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Toast;

public class SearchListActivity extends Activity {
  
    private ListView lstLinguagens;
    private List<JSONObject> linguagens;
    private LinguagemArrayAdapter adapter;
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // Criação de uma lista quer será utilizada em nosso adapter
        linguagens = new ArrayList<JSONObject>();
        
        // Informações que serão utilizados no exemplo
        try {
          JSONObject ruby = new JSONObject();
          ruby.put("nome", "Ruby");
          ruby.put("descricao", "Sobre a linguagem Ruby");
          linguagens.add(ruby);
          
          JSONObject python = new JSONObject();
          python.put("nome", "Python");
          python.put("descricao", "Sobre a linguagem Python");
          linguagens.add(python);
          
          JSONObject perl = new JSONObject();
          perl.put("nome", "Perl");
          perl.put("descricao", "Sobre a linuguagem Perl");
          linguagens.add(perl);
          
        } catch (JSONException e) {
          // Tratando possivel exception ao adicionar informacoes no json
        }
        
        // Passamos a lista de exemplo para gerar nosso adpater
        adapter = new LinguagemArrayAdapter(getApplicationContext(), R.layout.linguagem, linguagens);
        
        // Buscando o elemento Listview da nossa interface principal interface 
        lstLinguagens = (ListView) findViewById(R.id.lstLinguagens);
        // Setando o adapter em nossa ListView
        lstLinguagens.setAdapter(adapter);
        
        // Setando callback ao selecionar um item da lista
        lstLinguagens.setOnItemClickListener(new OnItemClickListener() {
          
          public void onItemClick(AdapterView<?> parent, View view,
                                  int position, long id) {
            
            try{
              JSONObject linguagem = linguagens.get(position);
              String descricao = linguagem.getString("descricao");
              Toast.makeText(getApplicationContext(), descricao, 10000).show();
            }catch (JSONException e) {
            }
          }
          
        });
        
    }
}

Além de criar o adpatador iremos criar um callback para a nossa lista. Quando um item da lista receber um toque(ou clique) um pequeno "alerta" será exibido com a descrição do item selecionado. Esse é um modo de fazê-lo, se o callback for muito grande, recomento criar uma classe que herde de Adapterview.OnItemClickListener que deverá implementar o método publico onItemClick.

Conclusão

Este exemplo é bem simples porém recorrente. Ao entender o exemplo e os conceitos nele contido uma gama muito grande de possibilidades é aberta.

Esse foi o primeiro artigo da série, no próximo quero abordar o uso do banco de dados SQLite nas aplicações.

E reforçando, qualquer dúvida ou sugestão, utilize o sistema de comentários do site.

[]'s

]]>
Fri, 17 Feb 2012 18:31:38 +0000 /2012/02/17/desenvolvimento-para-android-utilizando-uma-listview rubin.diego@gmail.com (Diego Rubin)
Criação de Extensões para o Inkscape http://diegorubin.com/2011/12/18/criacao-de-extensoes-para-o-inkscape Estrutura da Extensão

Para a criação de uma extensão do Inkscape pelo menos dois arquivos são necessários, um arquivo do tipo INX, que basicamente é um xml contendo algumas descrições, como por exemplo, nome da extensão, descrição dos parâmetros que o aplicação pode receber, localização no menu do Inkscape entre outros. Abaixo encontra-se um exemplo deste arquivo.

<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
  <_name>Cartão de Natal</_name>
  <id>com.diegorubin.extensao.natal</id>
  <dependency type="executable" location="extensions">natal.py</dependency>
  <param name="de" type="string" _gui-text="De">Fulano</param>
  <param name="para" type="string" _gui-text="Para">Ciclano</param>
  <param name="mensagem" type="string" _gui-text="Mensagem">esses são os votos de</param>
  <effect>
    <object-type>all</object-type>
      <effects-menu>
        <submenu _name="Cartões"/>
      </effects-menu>
  </effect>
  <script>
    <command reldir="extensions" interpreter="python">natal.py</command>
  </script>
</inkscape-extension>

O segundo arquivo é onde implementaremos a ação da extensão. Basicamente, o que devemos fazer na implementação é manipular os elementos do documento SVG, que como explicado anteriormente, não passa de um XML.

No caso da implementação em Python, devemos criar uma classe que tenha um método publico chamado effect. Após a implementação da classe devemos instanciá-la e executar o método affect() da instancia.

Há algumas superclasses que podem ser utilizadas na implementação da extensão. O exemplo abaixo utiliza a class inkex.Effect e sua implementação encontra-se no diretório de extensões do Inkscape.

# -*- coding: utf-8 -*-

import os
import sys

# Esse é o diretório padrão de extensions
# do inkscape no linux.
# Se estiver em outro sistema, como mac
# ou windows, eese diretorio deve ser
# trocado.
sys.path.append('/usr/share/inkscape/extensions')
import inkex
from simplestyle import *


class C(inkex.Effect):
    def __init__(self):

        inkex.Effect.__init__(self)

        self.OptionParser.add_option("-d", "--de", action="store", 
                type="string", dest="de", 
                default="Fulano", help="Remetente da Mensagem")

        self.OptionParser.add_option("-p", "--para", action="store", 
                type="string", dest="para", 
                default="Destinatário", help="Destinatário da Mensagem")

        self.OptionParser.add_option("-m", "--mensagem", action="store", 
                type="string", dest="mensagem", 
                default="São os votos de", help="Mensagem personalizada")

    def effect(self):

        # Carregar elemento raiz do svg
        self.svg = self.document.getroot()

        # Criação de uma novo grupo que servirá de camada    
        self.layer = inkex.etree.SubElement(self.svg, 'g')
        self.layer.set(inkex.addNS('label', 'inkscape'), 'Mensagem')

        # Esse é o atributo que define o grupo como uma camada
        self.layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')

        self.create_message()


    def create_message(self):
        text_element = inkex.etree.Element(inkex.addNS('text', 'svg'))  
        
        # Cria um id unico para ser utilizado no elemento text
        textId = self.uniqueId('text')
        text_element.set('id', textId)

        # O texto fica em um element tspan, que fica dentro do element text
        tspan = inkex.etree.SubElement(text_element, 'tspan')
        tspanId = self.uniqueId('tspan')

        tspan.text = unicode(self.options.para) + ","
        # Indica que o span será uma nova linha
        tspan.set(inkex.addNS('role', 'sodipodi'), 'line')

        tspan = inkex.etree.SubElement(text_element, 'tspan')
        tspanId = self.uniqueId('tspan')

        tspan.text = unicode(self.options.mensagem, 'utf-8')
        tspan.set(inkex.addNS('role', 'sodipodi'), 'line')

        tspan = inkex.etree.SubElement(text_element, 'tspan')
        tspanId = self.uniqueId('tspan')

        tspan.text = unicode(self.options.de)
        tspan.set(inkex.addNS('role', 'sodipodi'), 'line')

        # Estido do texto
        style = {'font-size' : '58px', 'font-family' :'URW Chancery L',
                 '-inkscape-font-specification' : 'URW Chancery L Bold Italic',
                 'fill' : '#ff0000'}
        # Transformando o dict em uma string
        text_element.set('style', formatStyle(style))
        text_element.set('x', '362')
        text_element.set('y', '340')

        self.svg.append(text_element)

c = C()
c.affect()

Instalação

A instalação da extensão no Inkscape é algo realmente simples, basta copiar os arquivos criados dentro de um dos diretório de extensões, no caso do Linux, eles pode ser copiados em /usr/share/inkscape/extensions ou ~/.config/inkscape/extensions.

Um coisa bem bacana também é que se houver erros no script, ao ser executado, o Inkscape exibirá em uma janela de dialogo o stacktrace.

Utilização

O exemplo que criei para escrever o post é bem simples, porém serve de base para criação de um extensão mais complexa. O que o exemplo faz é pegar os parâmetros e criar um texto com três linhas contendo as informações obtidas.

Podemos utilizar a extensão de duas formas, pelo menu "Extensões" > "Cartões" > "Cartão de Natal..." no Inkscape ou por linha de comando da seguinte forma python natal.py --de "Diego Rubin" --para "Carla Viviana" --mensagem "são os votos" natal.svg > teste.svg. Os parâmetros --de, --para e --mensagem foram os parâmetros criados no __init__ da nossa classe. O argumento natal.svg é a base do cartão criado para o exemplo, este arquivo e todos os outros podem ser encontrado no repositório do blog no github.com. Este modo é muito útil se precisarmos criar vários arquivos em lote.

]]>
Sun, 18 Dec 2011 11:31:20 +0000 /2011/12/18/criacao-de-extensoes-para-o-inkscape rubin.diego@gmail.com (Diego Rubin)