Clipper On Line • Ver Tópico - Registro não atualizada para outros usuários

Registro não atualizada para outros usuários

Discussão sobre Banco de Dados e RDDs para Clipper/[x]Harbour.

Moderador: Moderadores

 

Registro não atualizada para outros usuários

Mensagempor jelias » 04 Jan 2013 08:01

Amiguinhos,

Aproveitando a folga que o governo "SPED's" me deu para gozar, brincadeira, tenho serviço que não acaba, estou aproveitando para melhorar meus sistemas. Ontem encontrei um problema que me deixou sem entender e sem confiança nos processos que faço. Em qualquer rotina de casdastro, faço o seguinte procedimento de teste:

1 - Entro no cadastro de TIPO DE DOCUMENTO na minha máquina e seleciono o código do item, logo me aparece a descrição e um menu com as opções: alterar, excluir, próxima, anterior, outros. Fico parado nesta tela, não faço nenhum procedimento.
Dados apresentados na tela do sistema.
Código: 0001
Descrição: Teste de atualizacao
Procedimentos feito no código fonte
DBUSEAREA() -> DBSEEK() -> Criei uma matriz com o conteúdo do registro e apresentei na tela. -> Aguardando próxima passo....

2 - Peço para outro usuário entrar no sistema e fazer o mesmo procedimento, todavia, peço para que ele altere o valor do campo descrição.
Dados apresentados na tela do sistema.
Código: 0001
Descrição: Teste de atualizacao que deve ser visualizado
Procedimentos feito no código fonte
DBUSEAREA() -> DBSEEK() -> Crirei uma matriz com o conteúdo do registro e apresentei na tela. -> Usuário seleciona alterar -> Atualizo a matriz com o conteúdo do registro apresento na tela -> READ -> DBRLOCK() -> DBCOMMIT() -> DBUNLOCK()
- Peço para o mesmo confirmar se está tudo gravado e foi confirmado. Na tentativa de evitar deixa escpacar detalhes, deixei o dbu com o arquivo em questão aberto e confirmei que o registro foi modififado, aconteceu oflush no momento exato da atualização do usuário.
Ainda não satisfeito, peço para outro usuário entrar na mesma opção e me dizer o conteúdo de descrição do item código: 0001, e confirmo que foi atualizado.

3 - Após todas as confirmações, volto para a minha máquina que está com as variáveis com conteúdos antigos, seleciono alterar o item e o programa faz o mesmo processo, atualiza a matriz antes de apresentar e deixar o usuário modificar seus conteúdos e para minha surpreza: O conteúdo não é atualizado, é como se o registro não tivesse sido alterado pelo outro usuário.
- Pensei que pudesse ter algum erro na matriz, crio uma nova matriz e acontece o mesmo erro.
- Apresento o conteúdo do campo na tela e o mesmo erro, ou seja, o campo do arquivo também mostra o conteúdo desatualzado. @ 20,01 say AL_TIPOS->descri; INKEY(0)
- Mudei de matriz para variável, o mesmo erro.
- Fiz o teste na mesma máquina abrindo o programa duas vezes e executando os passos acima e acontece o mesmo erro.
- Antes de declarar a matriz, executei um comando DBCOMMIT(), mesmo sabendo que não é correto e não atualizou o conteúdo. @ 20,01 say AL_TIPOS->descri; INKEY(0)
- Quando executo um get direto no campo, FUNCIONA, o conteúdo apresenta ATUALIZADO!!!!!.

Consegui resolver "parcialmente" o problema, digo parcialmente porque nos cadastros é fácil, agora existem situações bem mais complexas como por exemplo:
Um cliente tem vários caminhos na frota e está fazendo um serviço no veículo X com o usuário João e no mesmo momento tem outro veículo Y, da mesma frota, fazendo um serviço com o usuário Pedro,vamos considerar que ambos estão na situação acima e acontece que o João termina a venda primeiro, e o limite de compra do cliente é ultrapassado no ato da atualização, no momento da venda do usuário Pedro o sistema deverá alertá-lo quanto a isso. Seguindo o acontecimento acima, isso não ocorrerá causando uma falha crítica, visto que o banco de dados já está aberto com o mesmo registro em ambos os caso, ou seja, o arquivo de CLIENTES.

A solução foi usando DBSKIP(0) antes de atualizar a matriz. Claro que isso não vale somente para a matriz, pois o próprio conteúdo campo do banco apresentava desatualizado.
O que pude entender é que o conteúdo do registro após o DBSEEK() fica armazenado na memória na hora de declarar o conteúdo ele pega o que esta na memória. Ratificando, fiz o seguinte teste. Antes de declarar o conteúdo da matriz, enviei novamente um DBSEEK() e o conteúdo do campo é atualizado.
Será muito trabalhoso entrar em todas as rotinas do sistema e antes de pegar qualquer conteúdo dos campos acrescentar um DBSKIP(0). Nunca aprendi que isso fosse necessário!

Amigos, dentro do meu entendimento acredito que isso não possa ocorrer. Talvez seja um BUG no RDD DBFCDX do xHarbour.
Não testei nas versões mais antigas, estou extenuado no momento. :)
Uso xHarbour 1.2.1Intl. (Simplex) (Rev. 9421)
Borland C++ 5.8
RDD DBFCDX

Preciso muito dos colegas para entender estes fatos e me sentir "seguro" novamente com a integridade dos dados do sistema.

Saudações,

Júlio.

Segue abaixo o código do cadastro e rotinas que julgo serem importantes:
#include "INKEY.CH"
#include "CLUBE.CH"
FUNCTION CAD_LINHA(n_cod)
LOCAL Atel:=SAVESCREEN(00,00,24,79)
LOCAL wCadastro:=.F.
if n_cod==NIL
   ABREARQ(,"AL_LINHA")
else
   if VALTYPE(n_cod)=="L"
      KEYBOARD(CHR(18)+CHR(65))
   else
      KEYBOARD(CHR(13))
   endif
endif
do whil .t.
   rest screen from aw_tel_master
   Wcadastro=.F.
    cadastro=.F.
   v_cod=IIF(VALTYPE(n_cod)!="C",0,val(n_cod))
   MOLDURA(03,02,05,22,.F.,"",.T.)
   aCoordenadas := { 07,05,16,76 }
   @ 04,03 say "C¢digo    = " get v_cod pict'99' VALID( iif(LASTKEY()==13 .and. v_cod=0,VISUALIZA("AL_LINHA", aCoordenadas, {"v_cod","val(codigo)"} ),.T.) )
   read
   if (LASTKEY()==27)
      if n_cod=NIL; ABREARQ(,.F.); endif
      RESTSCREEN(00,00,24,79,Atel)
      retu
   elseif (LASTKEY()==530)
      Wcadastro=.T.
   elseif (LASTKEY()!=530 .and. v_cod>0)
      v_cod=STRZERO(v_cod,2)
   end
   if !Wcadastro
      @ 04,03 say "C¢digo    = " get v_cod pict"99"
      GetList:={}
   end
   sele AL_LINHA
   DBSETORDER(1)
   if (!Wcadastro .and. !DBSEEK(v_cod))
      MENSAGEM("C¢digo da linha n„o foi encontrado, Verifique !!!",5)
      loop
   elseif (!Wcadastro .and. DBSEEK(v_cod))
      cadastro=.f.
      A:=CRIAVAR()
   elseif Wcadastro
      IF PERG("Aten‡Æo, deseja incluir dados ? ")="N"
         loop
      end
      A:=NEWVAR()
      cadastro=.t.
   end
   do whil .t.
      MOLDURA(06,02,08,65,.F.,"",.T.)
      @ 04,03 say "C¢digo    = " get A[1] pict'99'
      GetList:={}
      @ 07,03 say "Descri‡Æo = " get A[2]  pict"@!" MSG("Entre com a nome da linha")
      if cadastro
         read
         if PERG("Confirma os dados digitados acima ? ")="N"
            exit
         end
         if UPDATED()
            sele AL_LINHA
            if Wcadastro
               AL_LINHA->(DBSETORDER(1)); AL_LINHA->(DBGOBOTTOM()); A[1]=STRZERO((VAL(AL_LINHA->codigo)+1),2)
               ADDREC(10)
               DBUNLOCK()
            end
            if !RECLOCK(10)
               exit
            end
            REPLVAR(A)
            DBUNLOCK()
            DBCOMMIT()
            if Wcadastro
               if PERGUNTA("C¢digo da linha ‚: "+A[1]+", Deseja continuar ?") == "N"
                  EXIT
               end
            end
         end
         exit
      else
         GetList:={}
         trab_ckey=NAVEGATE(10,55)
         if (trab_ckey='A')
            DBSKIP(0)   // Consegui resolver usando este comando.
            A:=CRIAVAR()
            cadastro=.T.
         elseif (trab_ckey='E')
            if !RECLOCK(10)
               MENSAGEM("Registro nÆo liberado para exclusÆo ! ! !",2)
               exit
            end
            DBDELETE()
            DBUNLOCK()
            exit
         elseif (trab_ckey='O')
            exit
         elseif (trab_ckey="P" .or. trab_ckey="A")
            A:=CRIAVAR()
         end
      end
   end
   if n_cod!=NIL; exit; endif
end
if n_cod=NIL; ABREARQ(,.F.); endif
retu

FUNCTION CriaVar()
RELEASE aGets
aGets := ARRAY(FCOUNT())
AEVAL(aGets, {|x,nI| aGets[nI] := FIELDGET(nI) })
RETURN aGets[/quote]
[quote]FUNCTION ReplVar(aGets)
AEVAL(aGets, {|x,nI| FIELDPUT(nI,x)})
RETURN NIL

FUNCTION RECLOCK(nRetry)
LOCAL lForever, lRet := .T.
nRetry := IIF(nRetry == NIL, 10, nRetry)
lForever := (nRetry == 0)
WHILE !DBRLOCK()
    MENSAGEM("Tentando travar registro", 1)
    nRetry--
    INKEY(1/10)
ENDDO
RETURN lRet
xHarbour 1.2.1 (simplex) + BCC 5.8.2 + Hwgui + SQLRDD
Clipper 5.2e / Blinker 7
Júlio Cézar Elias
e-mail: jelias@tpnet.psi.br
jelias
Usuário Nível 3

Usuário Nível 3
 
Mensagens: 249
Data de registro: 27 Ago 2008 11:32
Cidade/Estado: Minas Gerais
Curtiu: 0 vez
Mens.Curtidas: 16 vezes

Registro não atualizada para outros usuários

Mensagempor alaminojunior » 05 Jan 2013 14:25

Júlio, isso a meu ver é herança do Clipper.
No PC em que você criou a matriz com o conteúdo do registro, este armazenou em buffer esse conteúdo.
Segundo o que sei, para você obter os valores atualizados, precisa de
dbgoto(recno())
antes de atualizar a matriz.

Arquivos abertos em modo compartilhado armazenam sempre o registro corrente em buffer.
Talvez tenha que rever algumas rotinas.
Compilador xHarbour 1.2.3 + Embarcadero C++ 7.30
MySQL c/ SQLRDD
HwGui + GTWVG
Avatar de usuário

alaminojunior
Colaborador

Colaborador
 
Mensagens: 1689
Data de registro: 16 Dez 2005 20:26
Cidade/Estado: Ubatuba - SP
Curtiu: 27 vezes
Mens.Curtidas: 11 vezes

Registro não atualizada para outros usuários

Mensagempor rochinha » 06 Jan 2013 00:15

Amiguinho,

No seguinte trecho eu não entendi o uso de ADDREC() e DBUNLOCK()
064               if Wcadastro
065                  AL_LINHA->(DBSETORDER(1)); AL_LINHA->(DBGOBOTTOM()); A[1]=STRZERO((VAL(AL_LINHA->codigo)+1),2)
066                  ADDREC(10)
067                  DBUNLOCK()
068               end


Tendo em vista que o código da função ADDREC() não está apresentado e supondo que ela faça uso de DBAPPEND() ou APPEND BLANK, este por si só trava já o registro e quando voce dá, logo após, um comando de destravamento voce esta permitindo a falha.
OPS! LINK QUEBRADO? Veja ESTE TOPICO antes e caso não encontre ENVIE seu email com link do tópico para fivolution@hotmail.com. Agradecido.

@braços : ? )

A justiça divina tarda mas não falha, enquanto que a justiça dos homens falha porque tarda.
Avatar de usuário

rochinha
Membro Master

Membro Master
 
Mensagens: 4547
Data de registro: 18 Ago 2003 20:43
Cidade/Estado: São Paulo - Brasil
Curtiu: 806 vezes
Mens.Curtidas: 246 vezes

Registro não atualizada para outros usuários

Mensagempor jelias » 06 Jan 2013 09:07

Edson, de acordo com sua importantíssima contribuição.
alaminojunior escreveu:No PC em que você criou a matriz com o conteúdo do registro, este armazenou em buffer esse conteúdo.

alaminojunior escreveu:Arquivos abertos em modo compartilhado armazenam sempre o registro corrente em buffer.

Minhas suspeitas se tornaram fatos.
jelias escreveu:O que pude entender é que o conteúdo do registro após o DBSEEK() fica armazenado na memória na hora de declarar o conteúdo ele pega o que esta na memória. Ratificando, fiz o seguinte teste. Antes de declarar o conteúdo da matriz, enviei novamente um DBSEEK() e o conteúdo do campo é atualizado.

Testei sua sugestão e funcionou certinho o comando DBGOTO(RECNO()) antes de declarar novamente o conteúdo da matriz. Eu já havia usado DBSKIP(0) que também funciona.
Sendo assim, podemos "afirmar" que antes de pegar o conteúdo de um campo de um registro já selecionado do banco de dados "DBF" temos que atualizar o buffer?

Mestre Rochinha, você está correto quanto a função ADDREC(), ela executa um DBAPPEND(). Após destravar o registro, volto a travar o mesmo antes de atualizar usando um RECLOCK(). Acredtita que mesmo assim possa haver uma falha? Segue abaixo a função ADDREC()
No Harbour usando DBFCDX também existe a necessidade atualizar o buffer de um registro já selecionado?

FUNCTION ADDREC(nRetry)
LOCAL lForever, nOrder := INDEXORD(), lRet := .T.
nRetry := IIF(nRetry == NIL,10,nRetry)
lForever := (nRetry == 0)
WHILE nRetry > 0 .OR. lForever
   DBAPPEND()
   IF !NETERR()
      EXIT
   ENDIF
   MENSAGEM("Tentando abrir registro",1)
    nRetry--
ENDDO
IF NETERR()
   BEEP() ; ALERT("N„o consegui abrir registro, opera‡„o cancelada !")
   lRet := .F.
ENDIF
RETURN lRet


Saudações,

Júlio.
xHarbour 1.2.1 (simplex) + BCC 5.8.2 + Hwgui + SQLRDD
Clipper 5.2e / Blinker 7
Júlio Cézar Elias
e-mail: jelias@tpnet.psi.br
jelias
Usuário Nível 3

Usuário Nível 3
 
Mensagens: 249
Data de registro: 27 Ago 2008 11:32
Cidade/Estado: Minas Gerais
Curtiu: 0 vez
Mens.Curtidas: 16 vezes

Registro não atualizada para outros usuários

Mensagempor rochinha » 07 Jan 2013 11:05

Amiguinho,

Como visto na função, caso ela tenha sucesso na criação do registro ela já produz a trava e o seu comando de destravamento logo após ela, desfaz isto.

O fator de buferização de registros, geralmente usado em rede, deve prever um tempo minimo de travamento de registros, ou seja, voce buferriza os dados sem produzir travamentos, faz uso dos dados buferizados e se optar por salvar as alterações processa o travamento e em seguida o salvamento, não dando tempo para acontecer algum erro por acesso de outra maquina.

Dificilmente ou nunca haveria a possibilidade de uso de um registro por outra maquina enquanto outra esta criando o registro, mas no caso de registros ja existentes, travar enquanto se mantém os dados é perigoso.

Veja as funções abaixo e perceba um meio de buferização em poucas linhas
...
   lEstouIncluindo := .t.
   use clientes shared new
   ...
   // Se for pesquisar coloque o trecho de procura aqui
   nRegistro := recno()
   ...
   InitVars() // Cria variaveis conforme os nomes de campos da tabela ativa
   if lEstouIncluindo
      dbGoBottom(); dbSkip() // Salta para registro fantasma
   endif
   EqualVars() // Carrega em variaveis o conteudo dos campos da tabela ativa
   ...
   // Processa os GETs de manutenção
   ...
   if Pergunta( "Deseja salvar?" ) = "S"
      if lEstouIncluindo
         dbAppend()
      else
         go nRegistro
         RLock()
      endif
      ReplaceVars() // Repassa os dados das variáveis para os respectivos campos
      dbCommit()
      dbRUnlock()
   endif
   ReleaseVars() // Libera os conteudos das variáveis em memória
   close clientes


O codigo foi ilustrativo apenas para evidenciar que os travamentos devem ser processados somente no ultimo momento.

E perceba também como o seu código fica menor e o fator de manutenção das tabelas onde campos podem ser inclusos durante o desenvolvimento quase não requerem manutenção do código.

Voce com certeza pode buferizar usando vetores:
FUNCTION InitVars()
   local field_cnt := fcount(), counter := 0
   private field_name
   field_cnt = fcount()
   for counter = 1 to field_cnt
       field_name = lower(field(counter))
       public &field_name
   next
   return(.t.)

FUNCTION EqualVars
   local field_cnt := fcount(), counter := 0
   private field_name, retorno := {}
   field_cnt = fcount()
   for counter = 1 to field_cnt
       field_name = LOWER(FIELD(counter))
       M->&field_name := _FIELD->&field_name
   next
   return retorno

FUNCTION ReplaceVars
   local field_cnt := fcount(), counter := 0
   private field_name, retorno := {}
   field_cnt = fcount()
   for counter = 1 to field_cnt
       field_name = LOWER(FIELD(counter))
       _FIELD->&field_name := m->&field_name
   next
   return retorno

FUNCTION ReleaseVars
   local field_cnt := fcount(), counter := 0
   private field_name, retorno := {}
   field_cnt = fcount()
   for counter = 1 to field_cnt
       field_name = LOWER(FIELD(counter))
       RELEASE m->&field_name
   next
   return retorno
OPS! LINK QUEBRADO? Veja ESTE TOPICO antes e caso não encontre ENVIE seu email com link do tópico para fivolution@hotmail.com. Agradecido.

@braços : ? )

A justiça divina tarda mas não falha, enquanto que a justiça dos homens falha porque tarda.
Avatar de usuário

rochinha
Membro Master

Membro Master
 
Mensagens: 4547
Data de registro: 18 Ago 2003 20:43
Cidade/Estado: São Paulo - Brasil
Curtiu: 806 vezes
Mens.Curtidas: 246 vezes

Registro não atualizada para outros usuários

Mensagempor jelias » 07 Jan 2013 11:37

Mestre Rochinha,

Analisando o código ilustrativo pude perceber que minha rotina de cadastro está bem próxima do código apresentado. A diferença está no momento de incluir um registro no banco de dados. Já estou fazendo a correção. Muito Obrigado mais uma vez!

Uma coisa que não ficou clara ainda pra mim, "me perdoe se já tenha exclarecido, mais estou meio lento ultimamente".
- É um processo comum ter que atualizar o buffer antes de pegar o conteúdo de um campo? Se for, qual a maneira correta?
- No Harbour usando DBFCDX também existe a necessidade atualizar o buffer de um registro já selecionado?

Saudações,
Júlio.
xHarbour 1.2.1 (simplex) + BCC 5.8.2 + Hwgui + SQLRDD
Clipper 5.2e / Blinker 7
Júlio Cézar Elias
e-mail: jelias@tpnet.psi.br
jelias
Usuário Nível 3

Usuário Nível 3
 
Mensagens: 249
Data de registro: 27 Ago 2008 11:32
Cidade/Estado: Minas Gerais
Curtiu: 0 vez
Mens.Curtidas: 16 vezes

Registro não atualizada para outros usuários

Mensagempor rochinha » 07 Jan 2013 11:54

Amiguinho,

Obrigado, como um dos muitos parafusos que compoe este forum, o forum se sente feliz em servir de fonte de auxilio, aprendizado e informação.

É um processo comum ter que atualizar o buffer antes de pegar o conteúdo de um campo? Se for, qual a maneira correta?


Buffer é qualquer meio que usamos para manter algo por um certo tempo, seja uma variável, um vetor, etc. Para manipular um registro, não basta estar posicionado sobre ele, pois o posicionamento em si, não caracteriza o seu controle. Portanto, posicionar sobre um registro, buferizar os campos que iremos alterar, travar o registo e salvar é a sequencia imutável.

No Harbour usando DBFCDX também existe a necessidade atualizar o buffer de um registro já selecionado?


Com certeza sim, pois o fato de estar sobre o registro não caracteriza a posse de seus dados para manipulação.

Veja a seguinte imagem:
Imagem

Perceba que existe um browse no lado esquerdo e do lado direito uma série de abas contendo dados dos campos do registro selecionado.

A técnica praxe de manutenção destes dados seria que finalizadora, ou seja, após atualizar os dados de alguns campos o usuário simplesmente aplicaria ou salvaria as alterações.

Outra técnica, mais trabalhosa, contaria com uso de comandos ou funções de update, que verificam após uma ação como, tabulação, enter ou salto de campos, se os dados foram atualizados e processar o salvamento automático.

Ainda na imagem voce pode verificar que o posicionamento em outro registro do browse, forçará a buferização dos dados dos campos nas variáveis e GETs.

Portanto a regra é clara de que não dá pra fugir do trivial.
OPS! LINK QUEBRADO? Veja ESTE TOPICO antes e caso não encontre ENVIE seu email com link do tópico para fivolution@hotmail.com. Agradecido.

@braços : ? )

A justiça divina tarda mas não falha, enquanto que a justiça dos homens falha porque tarda.
Avatar de usuário

rochinha
Membro Master

Membro Master
 
Mensagens: 4547
Data de registro: 18 Ago 2003 20:43
Cidade/Estado: São Paulo - Brasil
Curtiu: 806 vezes
Mens.Curtidas: 246 vezes

Registro não atualizada para outros usuários

Mensagempor alxsts » 07 Jan 2013 12:02

Olá!

Essa questão da concorrência é sempre controversa...

Neste código ilustrativo do Rochinha, o que aconteceria se, entre a leitura do registro e armazenamento em variáveis e o término da digitação dos Gets pelo usuário, outra máquina alterasse os mesmos campos do mesmo registro? As informações da outra máquina seriam sobrescritas por esta última e perdidas pois o lock e gravação só são feitos depois da digitação...

Já recebi criticas aqui por essa ideia mas, ainda penso que o certo é ler o registro, travar o registro, carregar os campos em variáveis, permitir a edição através de gets com timeout e, se o usuário confirmar, gravar, fazer o commit e unlock.
[]´s
Alexandre Santos (AlxSts)
alxsts
Colaborador

Colaborador
 
Mensagens: 2945
Data de registro: 12 Ago 2008 15:50
Cidade/Estado: São Paulo-SP-Brasil
Curtiu: 21 vezes
Mens.Curtidas: 248 vezes

Registro não atualizada para outros usuários

Mensagempor rochinha » 07 Jan 2013 12:45

Amiguinho,

Nem sempre visuzalizamos os dados do registro com intuito de alterá-los, mas para efeito de segurança possamos travar o registro usando métodos de semáforos.

Esta implementação já tem discussão aqui mesmo.

No Clipper lembro de ter problemas ao travar um registro e no mesmo momento outro travamento vindo de outra maquina destravava o primeiro.

Preciso fazer uns testes no Harbour para saber se os travamentos concorrentes podem causar transtornos.

Como apresentado no código anterior eu pego o ponteiro do registro para retorno posterior.
   nRegistro := recno()


Desta forma no momento de salvar alterações me posiciono novamente sobre o registro e processo o salvamento.
         else
            go nRegistro
            RLock()
         endif


Um erro poderia ocorrer, se outro usuário processasse uma exclusão do registro.

Portanto prever as ações dos usuários é o que tornaria o código mais seguro. A forma ou ferramenta utilizada não importa, desde que o resultado seja quase infalível.
OPS! LINK QUEBRADO? Veja ESTE TOPICO antes e caso não encontre ENVIE seu email com link do tópico para fivolution@hotmail.com. Agradecido.

@braços : ? )

A justiça divina tarda mas não falha, enquanto que a justiça dos homens falha porque tarda.
Avatar de usuário

rochinha
Membro Master

Membro Master
 
Mensagens: 4547
Data de registro: 18 Ago 2003 20:43
Cidade/Estado: São Paulo - Brasil
Curtiu: 806 vezes
Mens.Curtidas: 246 vezes




Retornar para Banco de Dados

Quem está online

Usuários vendo este fórum: Nenhum usuário registrado online e 7 visitantes


Ola Amigo, espero que meu site e forum tem lhe beneficiado, com exemplos e dicas de programacao.
Entao divulgue o link da Doacao abaixo para seus amigos e redes sociais ou faça uma doacao para o site forum...
MUITO OBRIGADO PELA SUA DOACAO!
Faça uma doação para o forum
cron
v
Olá visitante, seja bem-vindo ao Fórum Clipper On Line!
Efetue o seu login ou faça o seu Registro