View Javadoc

1   /**
2    * Copyright 2008 WebPhotos
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.webphotos.sync.FTP;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.util.ArrayList;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.StringTokenizer;
26  import net.sf.webphotos.Album;
27  import net.sf.webphotos.BancoImagem;
28  import net.sf.webphotos.PhotoDTO;
29  import net.sf.webphotos.sync.Sync;
30  import net.sf.webphotos.sync.SyncEvent;
31  import net.sf.webphotos.sync.SyncException;
32  import net.sf.webphotos.sync.SyncListener;
33  import net.sf.webphotos.util.Util;
34  import net.sf.webphotos.util.legacy.Arquivo;
35  import net.sf.webphotos.util.legacy.CacheFTP;
36  import net.sf.webphotos.util.legacy.ComandoFTP;
37  import org.apache.commons.net.ftp.FTPClient;
38  import org.apache.commons.net.ftp.FTPClientConfig;
39  import org.apache.commons.net.ftp.FTPReply;
40  import org.apache.commons.net.io.CopyStreamListener;
41  import org.apache.log4j.Logger;
42  
43  /**
44   * Interface entre o sistema e o envio de arquivos. Herda a classe FTPClient e
45   * implementa (sobrescreve) os métodos da interface Sync. Possui métodos de
46   * transferência de arquivos, mudança de diretório, criação de subdiretório,
47   * conexão e desconexão ao FTP e carregamento de arquivos de FTP.
48   *
49   * @author guilherme
50   */
51  public class SyncObject extends FTPClient implements Sync {
52  
53      private int reply;
54      private ArrayList<Arquivo> listaArquivos = new ArrayList<Arquivo>();
55      private String ftpRoot = null;
56      private List listFTP;
57      private File albunsRoot = Util.getAlbunsRoot();
58      private String usuario;
59      private char[] senha;
60      private long totalBytes = 0;
61      private long transmitido = 0;
62      private boolean enviarAltaResolucao;
63      private SyncListener syncListener = null;
64      private CopyStreamListener copyStreamListener = null;
65      private String ftpHost;
66      private int ftpPort;
67      private int retry;
68      private static final Logger log = Logger.getLogger(SyncObject.class);
69  
70      /**
71       * Cria uma nova instância de FTP
72       */
73      public SyncObject() {
74          super();
75          enviarAltaResolucao = Util.getProperty("enviarAltaResolucao").equals("true");
76      }
77  
78      /**
79       * Método baseado no storeFile da classe FTPClient, do pacote commons/net.
80       * Acrescenta um controle de processamento para saber quanto foi
81       * enviado/recebido.
82       *
83       * @param streamOrigem Arquivo de origem
84       * @param streamDestino Local de destino
85       * @param streamSize Tamanho do arquivo
86       * @throws java.io.IOException Problemas na leitura e escrita dos dados.
87       */
88      @Override
89      public void transferFile(InputStream streamOrigem, OutputStream streamDestino, long streamSize)
90              throws IOException {
91  
92          org.apache.commons.net.io.Util.copyStream(
93                  streamOrigem,
94                  streamDestino,
95                  getBufferSize(),
96                  streamSize,
97                  copyStreamListener,
98                  true);
99  
100         streamDestino.flush();
101         streamOrigem.close();
102         streamDestino.close();
103 
104         completePendingCommand();
105     }
106 
107     /**
108      * Muda de diretório. criando o diretório quando não existir. TODO: Colocar
109      * o Util.log para trabalhar no fluxo de execução.
110      *
111      * @param diretorioFilho Diretório que deve ser acessado.
112      * @throws net.sf.webphotos.sync.SyncException Erro de sincronização.
113      * @throws java.io.IOException Erro de comunicação entre os dados.
114      */
115     @Override
116     public void cd(String diretorioFilho) throws IOException, SyncException {
117         changeWorkingDirectory(getSyncFolder());
118         Util.out.println(printWorkingDirectory());
119         changeWorkingDirectory(diretorioFilho);
120         if (getReplyCode() != FTPReply.CODE_250) {
121             Util.log(getSyncFolder() + File.separator + diretorioFilho + " não existe..criando");
122             if (!makeDirectory(diretorioFilho)) {
123                 throw new SyncException("[FTP.cd]/ERRO: não foi possível criar diretório " + diretorioFilho + " verifique suas permissões com o provedor");
124             }
125         }
126         pwd();
127         Util.log("ok cd " + printWorkingDirectory());
128     }
129 
130     /**
131      * Cria um novo subdiretório no servidor FTP, no diretório atual (se um
132      * pathname relativo é dado) ou onde especificado (se um pathname absoluto é
133      * dado). Esta é uma versão recurssiva que cria os diretórios somente quando
134      * são precisos.
135      *
136      * @param pathName O nome do diretório a ser criado.
137      * @return True se completou com sucesso, ou false caso não.
138      * @exception IOException Se um erro de I/O ocorrer enquanto está enviando
139      * comando para o servidor ou recebendo resposta dele.
140      */
141     @Override
142     public boolean makeDirectory(String pathName) throws IOException {
143         if (pathName.startsWith("/")) {
144             changeWorkingDirectory("/");
145             pathName = pathName.substring(1);
146         }
147         Util.out.println(super.printWorkingDirectory());
148         String[] dirs = pathName.split("/");
149         for (String dir : dirs) {
150             if (!super.printWorkingDirectory().endsWith(dir)) {
151                 super.changeWorkingDirectory(dir);
152                 if (!FTPReply.isPositiveCompletion(super.getReplyCode())) {
153                     super.makeDirectory(dir);
154                     super.changeWorkingDirectory(dir);
155                     if (!FTPReply.isPositiveCompletion(super.getReplyCode())) {
156                         return false;
157                     }
158                 }
159             }
160         }
161         return (getReplyCode() == FTPReply.CODE_250 || getReplyCode() == FTPReply.CODE_250);
162     }
163 
164     /**
165      * Conecta ao servidor FTP. Retorna uma confirmação da conexão através de um
166      * boolean. TODO: remontar a função para que use somente dados externos a
167      * classe
168      *
169      * @return Valor lógico que confirma a conexão.
170      */
171     @Override
172     public boolean connect() {
173         boolean conectado = false;
174 
175         ftpHost = Util.getProperty("servidorFTP");
176         ftpPort = Util.getConfig().getInt("FTPport");
177 
178         String ftpProxyHost = Util.getProperty("FTPproxyHost");
179         int ftpProxyPort;
180         try {
181             ftpProxyPort = Util.getConfig().getInt("FTPproxyPort");
182         } catch (Exception e) {
183             ftpProxyPort = 0;
184         }
185 
186         Util.log("Iniciando conexão com " + ftpHost);
187         try {
188             //TODO: Preparar o suporte a múltiplas línguas
189             FTPClientConfig auxConfig = new FTPClientConfig(FTPClientConfig.SYST_NT);
190             configure(auxConfig);
191             Util.out.println("Timeout (antes): " + getDefaultTimeout());
192             setDefaultTimeout(25000);
193             Util.out.println("Timeout (depois): " + getDefaultTimeout());
194 
195             //TODO: Testar o acesso via Proxy
196             //      usando System.getProperties().put()
197             //      http://java.sun.com/j2se/1.5.0/docs/guide/net/properties.html
198             if (ftpProxyHost == null && ftpProxyPort != 0) {
199                 System.getProperties().put("ftp.proxyHost", ftpProxyHost);
200                 System.getProperties().put("ftp.proxyPort", ftpProxyPort);
201             }
202 
203             super.connect(ftpHost, ftpPort);
204             reply = getReplyCode();
205 
206             if (!FTPReply.isPositiveCompletion(reply)) {
207                 disconnect("[FtpClient.connect]/ERRO: não foi possivel conectar");
208                 return false;
209             }
210             Util.log("ok " + ftpHost + " encontrado.. autenticando..");
211 
212             SyncEvent ev = null;
213             if (syncListener != null) {
214                 ev = new SyncEvent(this);
215             }
216             reply = FTPReply.NOT_LOGGED_IN;
217             do {
218 
219                 if (syncListener != null && ev != null) {
220                     syncListener.logonStarted(ev);
221                 }
222                 Util.log("servidor: " + ftpHost + " em " + ftpRoot + "\nsolicitando conexão...");
223 
224                 login(usuario, new String(senha));
225                 Util.log("usuário: " + usuario + " senha: ***");
226                 reply = getReplyCode();
227                 retry--;
228 
229 
230             } while (reply != FTPReply.USER_LOGGED_IN && retry >= 0);
231 
232             if (reply != FTPReply.USER_LOGGED_IN) {
233                 disconnect("[FtpClient.connect]/ERRO: login/senha incorreto.");
234                 return conectado;
235             } else {
236                 // conexão bem sucedida... armazenamos o nome/login
237                 BancoImagem.getBancoImagem().setUserFTP(getUsuario());
238                 BancoImagem.getBancoImagem().setPasswordFTP(getSenha());
239             }
240 
241             Util.log("ok conexão aceita..");
242             // autenticação ok..
243 
244             setFileType(FTPClient.BINARY_FILE_TYPE);
245             //ftp.enterRemotePassiveMode();
246             // TODO: Achar uma alternativa para realizar o logging do FTP
247             //ftp.setLogFile("ftp.log");
248 
249             // tenta ir ao diretório FTPRoot... caso não consiga, tenta criar
250             changeWorkingDirectory(ftpRoot);
251             if (getReplyCode() != FTPReply.CODE_250) {
252                 Util.log(ftpRoot + " não existe..criando");
253                 if (makeDirectory(ftpRoot)) {
254                     Util.log("[FtpClient.connect]/ERRO: não foi possível criar diretório " + ftpRoot + " Retorno: " + reply);
255                     disconnect("não foi possível criar diretório");
256                     return conectado;
257                 }
258                 changeWorkingDirectory(ftpRoot);
259                 reply = getReplyCode();
260                 if (reply != FTPReply.CODE_250) {
261                     disconnect("[FtpClient.connect]/ERRO: não foi possível entrar no diretório " + ftpRoot + " que foi recém criado.");
262                     return conectado;
263                 }
264             }
265             conectado = true;
266             getSyncListener().connected(new SyncEvent(this));
267         } catch (Exception e) {
268             conectado = false;
269             log.error(e);
270             disconnect("[FtpClient.connect]/ERRO: não foi possivel manter esta conexão");
271         }
272 
273         return conectado;
274 
275     }
276 
277     /**
278      * Desconecta do servidor FTP e apresenta uma mensagem de log.
279      *
280      * @param msg Mensagem de desconexão.
281      */
282     @Override
283     public void disconnect(String msg) {
284         try {
285             Util.log("Desconectando (" + msg + ") ok");
286             super.disconnect();
287         } catch (Exception e) {
288             Util.log("Erro ao tentar desconectar.");
289         } finally {
290             Util.log(msg);
291             try {
292                 // ao finalizar, verificar se houve erros
293                 // arquivos com status <> "ok -..." serão re-enfileirados
294                 Iterator iter = listFTP.iterator();
295                 while (iter.hasNext()) {
296                     Arquivo a = new Arquivo((List) iter.next());
297                     //Arquivo a=(Arquivo) iter.next();
298                     if (!a.getStatus().startsWith("ok")) {
299                         CacheFTP.getCache().addCommand(a.getAcao(), a.getAlbumID(), a.getFotoID());
300                     }
301                 }// fim while
302             } catch (Exception e) {
303             }
304             // Dispara o evento de desconnectado
305             if (syncListener != null) {
306                 syncListener.disconnected(new SyncEvent(this));
307             }
308         }
309     }
310 
311     /**
312      * Faz um load no ArrayList CacheFTP, faz uma busca por iteração, identifica
313      * e carrega as linhas de comandos na seguinte ordem: DELETE, UPLOAD e
314      * DOWNLOAD. Carrega esses comandos através do método
315      * {@link net.sf.webphotos.sync.FTP.SyncObject#loadSyncCacheLine() loadSyncCacheLine}().
316      * Por último, após completo o load, limpa a lista do CacheFTP.
317      */
318     @Override
319     public void loadSyncCache() {
320 
321         Iterator<ComandoFTP> i = CacheFTP.getCache().iterator();
322         String linha;
323         Util.out.println("Numero de linhas: " + CacheFTP.getCache().toString());
324         // primeiro delete		
325         while (i.hasNext()) {
326             linha = i.next().toString();
327             if (linha.startsWith("3")) {
328                 loadSyncCacheLine(linha);
329             }
330         }
331         // depois upload
332         i = CacheFTP.getCache().iterator();
333         while (i.hasNext()) {
334             linha = i.next().toString();
335             if (linha.startsWith("1")) {
336                 loadSyncCacheLine(linha);
337             }
338         }
339         // depois download
340         i = CacheFTP.getCache().iterator();
341         while (i.hasNext()) {
342             linha = i.next().toString();
343             if (linha.startsWith("2")) {
344                 loadSyncCacheLine(linha);
345             }
346         }
347 
348         // limpa o CacheFTP
349         CacheFTP.getCache().clear();
350 
351     }
352 
353     /**
354      * Recebe uma linha com comando de FTP (DELETE, DOWNLOAD ou UPLOAD),
355      * processa o tipo "acao albumID foto" e a carrega em cima do ArrayList
356      * listaArquivos, que contém dados de
357      * {@link net.sf.webphotos.util.Arquivo Arquivo}.
358      *
359      * @param linha Linha de comando FTP.
360      */
361     @Override
362     public void loadSyncCacheLine(String linha) {
363         StringTokenizer tok = new StringTokenizer(linha);
364         int acao = -1;
365         int albumID = -1;
366         int fotoID = -1;
367 
368         Util.out.println("carrega: " + linha);
369 
370         if (tok.countTokens() == 3) {
371             acao = Integer.parseInt(tok.nextToken());
372             albumID = Integer.parseInt(tok.nextToken());
373             fotoID = Integer.parseInt(tok.nextToken());
374         } else {
375             // houve um erro...
376             Util.out.println("erro: " + linha);
377             return;
378         }
379 
380         // obtem uma lista do álbum (todos os arquivos)
381         File f = new File(getAlbunsRoot(), Integer.toString(albumID));
382         String[] ls = f.list();
383 
384         switch (acao) {
385             // Apagar
386             case CacheFTP.DELETE:
387             // Receber
388             case CacheFTP.DOWNLOAD:
389                 if (fotoID == 0) {
390                     // O álbum inteiro
391                     listaArquivos.add(new Arquivo(linha, acao, albumID, 0, "* todos"));
392                 } else {
393                     // Uma foto
394                     listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_a" + fotoID + ".jpg"));
395                     listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_b" + fotoID + ".jpg"));
396                     listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_c" + fotoID + ".jpg"));
397                     listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_d" + fotoID + ".jpg"));
398                     if (isEnviarAltaResolucao() == true) {
399                         listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, fotoID + ".jpg"));
400                         listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, fotoID + ".zip"));
401                     }
402                     listaArquivos.add(new Arquivo(linha, acao, albumID, 0, albumID + ".xml"));
403                     listaArquivos.add(new Arquivo(linha, acao, albumID, 0, albumID + ".js"));
404                 }
405                 break;
406             // Enviar
407             case CacheFTP.UPLOAD:
408                 if (fotoID == 0) {
409                     // O álbum inteiro
410                     Album.getAlbum().loadAlbum(albumID);
411                     for (PhotoDTO atual : Album.getAlbum().getFotos()) {
412                         fotoID = atual.getFotoID();
413                         listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_a" + fotoID + ".jpg"));
414                         listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_b" + fotoID + ".jpg"));
415                         listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_c" + fotoID + ".jpg"));
416                         listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, "_d" + fotoID + ".jpg"));
417                         if (isEnviarAltaResolucao() == true) {
418                             listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, fotoID + ".jpg"));
419                             listaArquivos.add(new Arquivo(linha, acao, albumID, fotoID, fotoID + ".zip"));
420                         }
421                     }
422                     listaArquivos.add(new Arquivo(linha, acao, albumID, 0, albumID + ".xml"));
423                     listaArquivos.add(new Arquivo(linha, acao, albumID, 0, albumID + ".js"));
424                 } else {
425                     // Uma foto
426                     Util.out.println("Upload alta: " + isEnviarAltaResolucao());
427                     for (String fileName : ls) {
428                         if ((fileName.startsWith("_") && fileName.toLowerCase().endsWith(fotoID + ".jpg"))
429                                 || (isEnviarAltaResolucao() && fileName.toLowerCase().endsWith(fotoID + ".zip"))
430                                 || (isEnviarAltaResolucao() && fileName.toLowerCase().endsWith(fotoID + ".jpg"))) {
431 
432                             Arquivo a = new Arquivo(linha, acao, albumID, fotoID, fileName);
433                             listaArquivos.add(a);
434                         }
435                     } // fim for
436                 }
437                 break;
438         }
439     }
440 
441     /**
442      * Retorna o caminho que deve usar
443      *
444      * @return Mostra o caminho base
445      */
446     @Override
447     public String getSyncFolder() {
448         return ftpRoot;
449     }
450 
451     /**
452      * Determina qual caminho usar
453      *
454      * @param ftpRoot Parâmetro que recebe a informação
455      */
456     @Override
457     public void setSyncFolder(String ftpRoot) {
458         this.ftpRoot = ftpRoot;
459     }
460 
461     /**
462      * Retorna o ouvinte syncListener.
463      *
464      * @return Retorna um listener de sincronização.
465      */
466     @Override
467     public SyncListener getSyncListener() {
468         return syncListener;
469     }
470 
471     /**
472      * Seta o ouvinte syncListener.
473      *
474      * @param listener Um listener de sincronização.
475      */
476     @Override
477     public void setSyncListener(SyncListener listener) {
478         this.syncListener = listener;
479     }
480 
481     /**
482      * Retorna o objeto copyStreamListener.
483      *
484      * @return Retorna copyStreamListener.
485      */
486     @Override
487     public CopyStreamListener getCopyStreamListener() {
488         return copyStreamListener;
489     }
490 
491     /**
492      * Seta o objeto copyStreamListener.
493      *
494      * @param copyStreamListener Objeto da classe CopyStreamListener.
495      */
496     @Override
497     public void setCopyStreamListener(CopyStreamListener copyStreamListener) {
498         this.copyStreamListener = copyStreamListener;
499     }
500 
501     /**
502      * Retorna o usuário.
503      *
504      * @return Retorna um usuário.
505      */
506     @Override
507     public String getUsuario() {
508         return usuario;
509     }
510 
511     /**
512      * Seta um nome para usuário.
513      *
514      * @param usuario Usuário.
515      */
516     @Override
517     public void setUsuario(String usuario) {
518         this.usuario = usuario;
519     }
520 
521     /**
522      * Retorna a senha do usuário.
523      *
524      * @return Retorna uma senha.
525      */
526     @Override
527     public char[] getSenha() {
528         return senha;
529     }
530 
531     /**
532      * Seta uma senha para o usuário.
533      *
534      * @param senha Senha do usuário.
535      */
536     @Override
537     public void setSenha(char[] senha) {
538         this.senha = senha;
539     }
540 
541     /**
542      * Retorna o ArrayList listaArquivos.
543      *
544      * @return Retorna listaArquivos.
545      */
546     @Override
547     public ArrayList<Arquivo> getListaArquivos() {
548         return listaArquivos;
549     }
550 
551     /**
552      * Seta uma lista para a variável listaArquivos.
553      *
554      * @param _listaArquivos Lista de arquivos.
555      */
556     public void setListaArquivos(ArrayList<Arquivo> _listaArquivos) {
557         this.listaArquivos = _listaArquivos;
558     }
559 
560     /**
561      * Retorna o valor de enviarAltaResolucao. Especifica se serão enviadas ou
562      * não, as imagens originais.
563      *
564      * @return Retorna um valor lógico.
565      */
566     @Override
567     public boolean isEnviarAltaResolucao() {
568         return enviarAltaResolucao;
569     }
570 
571     /**
572      * Retorna o diretório raiz de albúns. TODO: Já existe um método igual a
573      * esse na classe Util.
574      *
575      * @return Retorna um diretório.
576      */
577     public File getAlbunsRoot() {
578         return albunsRoot;
579     }
580 }