domingo, 14 de junho de 2009

Lendo e gravando dados com Plua em arquivos pdb (palm data base)

A alguns dias houve um tópico interessante na lista de discussão sobre plua no yahoo groups (a lista é fechada, por isso passei um link alternativo).

O sujeito Jimmy Joe Jack, disse que queria fazer algo para seu palm com plua, mas não tinha muito tempo para aprender e por isso não queria ler a documentação toda (que é pequena, mas longe de ser fácil), pediu que enviássemos alguns exemplos.

Foi quando Berkant Atay respondeu, mandando um exemplo que fazia algo quando apertava um botão, e na sequecia mandou exemplos de como trabalhar com arquivos, stream e data bases.

Aproveitei a deixa para também aprender como isso afinal funcionava (obrigado Berkant). Deixo agora neste post o que aprendi com meu primeiro exemplo gravando informações em arquivos.

Fazendo a leitura de um arquivo de banco de dados

Plua disponibiliza uma série de funções, veja na documentação lá onde diz "Database I/O functions". Vamos a um trecho de códigos que fica mais fácil.
 -- open in read mode
file,NumRegs = io.open("db:/AgesNames", "r+")

-- define a variable
returnData = {}

-- Atention!in DB file, the index begin in 0 (zero)
-- but in table, array..the index begin in 1 (one)
for i=0,NumRegs-1 do
-- open data from index
file:openrec(i) -- begin in zero
-- read this data
data = file:read("*a")
temp = bin.unpack("SB",data)

-- add this data to returning variable
returnData[i+1] = temp[2].."\t"..temp[1]
end

file:close()

Logo no início eu abro o arquivo com "r+" através do "protocolo" "db", disse que o nome de meu banco de dados é "AgesNames". Assim como em outras linguagens há modos de abrir aquivos. vejamos (tradução livre do manual):
  • "r": Abre para leitura. Se o arquivo não existe não é feito nada, apenas da erro.

  • "r+": abre/cria para leitura/escrita. Se o arquivo já existe ele é preservado, caso contrário é criado. O conteúdo do arquivo é mantido.

  • "w": abre/cria para escrita. Se o arquivo existe ele é truncado (o conteúdo dele é apagado), se não existe o arquivo é criado.

  • "a": abre/cria para escrita. Se o arquivo existe ele é preservado, o ponteiro aponta para o fim do arquivo, se o arquivo não existe ele é criado.
Você deve ter notado que duas variáveis (file e NumRegs) é que recebem o io.open, isso porque Lua suporta atribuição (e retorno) múltipla. No caso io.open retorna o ponteiro para o arquivo (guardei em file) e número de registros/linhas naquele arquivo (guardei em NumRegs).

Em seguida fazemos um laço da primeira a última linha de dados do arquivo. Atenção que o primeiro índice de um arquivo é 0 (zero), enquanto que as tables (arrays, vectors) em Lua iniciam no índice 1.

Você deve ter notado que no "read" também tem uma letra, pois bem, veja o que o manual da Lua diz:
  • "*n": lê um número; este é o único formato que retorna um número ao invés de uma cadeia.
  • "*a": lê o arquivo inteiro, iniciando na posição corrente. Quando está no final do arquivo, retorna a cadeia vazia.
  • "*l": lê a próxima linha (pulando o fim de linha), retornando nil ao final do arquivo. Este é o formato padrão.
  • number: lê uma cadeia até este número de caracteres, retornando nil ao final do arquivo. Se o número fornecido é zero, a função não lê nada e retorna uma cadeia vazia ou nil quando está no fim do arquivo.
E o último e talvez mais importante detalhe na leitura de alguma informação. Como funciona esse "bin.unpack". Pois bem, o arquivo de dados é um arquivo binário, portanto ocupa menos espaço que um arquivo texto, como isso ocorre? Da mesma forma que em banco de dados, os dados são armazenados no seu tipo e não tudo como string (que ocupa mais bites). Então tanto na hora de ler como gravar devemos informar que tipo de dados é cada coluna.

No caso deste programa temos duas colunas "name" e "age", o primeiro uma string e o segundo um número inteiro positivo. Por causa dessas duas colunas é que temos duas letras dentro do bin.unpack, uma letra dizendo que tipo de dados é cada coluna. Veja no manual da Plua o que diz de cada um dos tipos (tradução livre).

  • B=8 bits integer positivos e negativos;

  • W=big endian 16 bits inteiros positivos e negativos;

  • L=big endian 32 bits inteiros positivos e negativos;

  • F=big endian 32 bits float;

  • D=big endian 64 bits double;

  • S= String variável, termina com um ASCII null.
Bom, chegamos em um nível em que conforme sua estrutura de dados, você pode prever quanto espaço na memória um registro vai ocupar, e fazer projeções de quantos registros a memória do seu Palm vai suportar.

A última parte do código, eu concateno as duas colunas de dados em uma, e armazeno então uma matriz em um vector, que depois vai ser usado como fonte de dados para uma gui.list.

Gravando em um arquivo

Agora que você já sabe o que cada coisa significa, a gravação é fácil. Primeiro eu pego os dados dos meus campos da interface (gui.field), coloco isso em uma table (vector), dou um "bin.pack", aloco o espaço necessário para aqueles dados e mando gravar, e claro, fecho o arquivo.

 -- retrive values from fields
name = gui.gettext(dataName)
age = gui.gettext(dataAge)
dataToWrite = {name,age}

-- open for write the data base file
file = io.open("db:/AgesNames","w")

-- encode, acording the data tyes
packed = bin.pack("SB",dataToWrite)
-- how many size is this data?
index = file:createrec(string.len(packed))
-- alloc this size on file
file:openrec(index)
-- finaly write
file:write(packed)
-- and close file
file:close()

O desenho da interface, e como saber que o botão "add" foi pressionado

Tenha bons costumes, sempre que inicia a desenhar uma tela, destrua o que já há, fica mais fácil de controlar. Em seguida mando limpar a tela.
 gui.destroy()
screen.clear()

gui.title("Simple read and write")
gui.nl();
gui.label("Name:");
dataName = gui.field(1,10,20)

gui.label("Age:");
dataAge = gui.field(1,3,3)

gui.control{type="button", text="Add", handler=saveData}

gui.nl()
data = read()
gui.list(10,32,data)

Digo qual é o título, vou uma linha para baixo (gui.nl) e adiciono as labels e os fields. O detalhe está no botão, em que associo ele a função "saveData" que é o código de gravação do logo acima.

Nova linha, e a função que lê o banco de dados traz os dados em "data" que é atribuido a "gui.list". Quando um registro é gravado, perceba que eu chamo novamente a função que redesenha a interface e faz novamente então a leitura dos dados.

Código na íntegra abaixo:
-- this file write and read data using a palm db

function read()
-- open in read mode
file,NumRegs = io.open("db:/AgesNames", "r+")

-- define a variable
returnData = {}

-- Atention!in DB file, the index begin in 0 (zero)
-- but in table, array..the index begin in 1 (one)
for i=0,NumRegs-1 do
-- open data from index
file:openrec(i) -- begin in zero
-- read this data
data = file:read("*a")
temp = bin.unpack("SB",data)

-- add this data to returning variable
returnData[i+1] = temp[2].."\t"..temp[1]
end

file:close()

-- if are empty, return this information
if(returnData == nil) then
returnData[1] = "No data writed"
end

return returnData
end

function saveData(e, id, arg)

-- retrive values from fields
name = gui.gettext(dataName)
age = gui.gettext(dataAge)
dataToWrite = {name,age}

-- open for write the data base file
file = io.open("db:/AgesNames","w")

-- encode, acording the data tyes
packed = bin.pack("SB",dataToWrite)
-- how many size is this data?
index = file:createrec(string.len(packed))
-- alloc this size on file
file:openrec(index)
-- finaly write
file:write(packed)
-- and close file
file:close()

drawGui()
end


function drawGui()

gui.destroy()
screen.clear()

gui.title("Simple read and write")
gui.nl();
gui.label("Name:");
dataName = gui.field(1,10,20)

gui.label("Age:");
dataAge = gui.field(1,3,3)

gui.control{type="button", text="Add", handler=saveData}

gui.nl()
data = read()
gui.list(10,32,data)
end

drawGui()
gui.main()

É isso amigos, até a próxima, agradescimento especial a Berkant Atay, ilustre desconhecido.

1 comentários:

  1. "I'm learning English, so it is possible that I have written incorrectly. If so, please tell me. Thanks!"

    Great article: translated it via Google Translate: http://google.com/translate

    ResponderExcluir