トップ地図ユーティリティ > FTPクライアント

FTPクライアント

はじめに

Androidスマホおよび Androidタブレット間でファイル同期を図りたい。

最初のプログラム

記事[1]をベースにした次のプログラムをテストした。

部品化としては、接続(connect)、ディレクトリ移動、ファイルリスト入手、download、 upload、ファイル削除などが考えられる。 ディレクトリの生成や削除は後回しでいいだろう。

ミラーリングアップロード/ダウンロードは定期的(1日または1週間隔)またはメニュー操作で任意時間に実行する。

    public void download(String sHost, int nPort, String sUser, String sPass, String sRemoteDir) {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());

        FileOutputStream outputstream;
        int nTimeout = 60 * 1000;   //60秒
        FTPClient ftpCli = null;
        try {
            ftpCli = new FTPClient();
            //タイムアウト設定
            ftpCli.setDefaultTimeout(nTimeout);
            ftpCli.setConnectTimeout(nTimeout);  //タイムアウトをキャッチするにはこれが必要

            //接続
            ftpCli.connect(sHost, nPort);
            if (!FTPReply.isPositiveCompletion(ftpCli.getReplyCode())) {
                throw new Exception("FTP接続エラー Code=" + ftpCli.getReplyCode());
            }

            //ソケットタイムアウト設定
            ftpCli.setSoTimeout(nTimeout);

            //ログイン
            if (!ftpCli.login(sUser, sPass)) {
                throw new Exception("FTP認証エラー Code=" + ftpCli.getReplyCode());
            }

            //ファイル転送モード設定
            //ftpCli.setFileType(FTP.ASCII_FILE_TYPE);    //テキストファイルなど
            ftpCli.setFileType(FTP.BINARY_FILE_TYPE);   //画像ファイルなど

            //モード設定
            ftpCli.enterLocalPassiveMode(); //パッシブモード
            //ftpCli.enterLocalActiveMode();  //アクティブモード

            //ディレクトリー移動
            if (!ftpCli.changeWorkingDirectory(sRemoteDir)) {
                throw new Exception("ディレクトリー移動エラー " + sRemoteDir + " Code=" + ftpCli.getReplyCode());
            }

            //受信
            outputstream =  new FileOutputStream("/storage/emulated/0/Map/download.txt", false);
            ftpCli.retrieveFile("words_utf16.txt", outputstream);
            outputstream.close();

        } catch (NumberFormatException e) {
            toastMake("Port異常");
        } catch (FileNotFoundException e) {
            //送信ファイルが無い時に発生
            toastMake("送信ファイルがありません");
        } catch (SocketException e) {  // ソケット例外
            //ポートの指定が無かった時などに発生
            toastMake("中断されました " + e.toString());
        } catch (SocketTimeoutException e) {
            //接続先が違うなどの場合発生
            toastMake("タイムアウトが発生しました");
        } catch (Exception e) {
            toastMake("エラー " + e.toString());
        } finally {
            if (ftpCli.isConnected()) {
                try {
                    ftpCli.disconnect();
                } catch (IOException e) {
                    // ignore
                }
            }
        }
    }

    private void toastMake(String message) {
        Toast toast = Toast.makeText(this, message, Toast.LENGTH_LONG);
        toast.show();
    }

部品化

まだ、ゴールに達していないが、少し、部品化を行った。

enum Command { mirroring_download, mirroring_upload }

public class FTPClientThread extends Thread {
    ConstraintLayout layout;
    Command cmd;     // mirroring_download, mirroring_upload
    String dirRemote;
    String dirLocal;

    public FTPClientThread(ConstraintLayout layout, Command cmd, String remote, String local) {
        this.layout = layout;
        this.cmd = cmd;
        this.dirRemote = remote;
        this.dirLocal = local;
    }

    @Override
    public void run() {
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build());
        long start = System.currentTimeMillis();
        FTPClient ftpCli = connectFTP("サーバ", 21, "ユーザID", "パスワード");
        if (ftpCli == null) {
            return;
        }
        try {
            if (!ftpCli.changeWorkingDirectory(dirRemote)) {
                throw new Exception("ディレクトリー移動エラー " + dirRemote +
                                " Code=" + ftpCli.getReplyCode());
            }
            FTPFile[] ftpFiles = ftpCli.listFiles();
            for (FTPFile file : ftpFiles) {
                String name = file.getName();
                if (name.startsWith(".")) continue;
                File fileObj = new File(Map.DIR + dirLocal + "/" + name);
                Files.createFile(fileObj.toPath());
                try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(fileObj))) {
                    // ftpclient.retrieveFile will get the file from Ftp server and write it in outputStream.
                    boolean isFileRetrieve = ftpCli.retrieveFile(file.getName(), outputStream);
                }
            }
            disconnectFTP(ftpCli);
        } catch (Exception e) {
            snackbarMake(e.toString());
        }

        long elapsed = System.currentTimeMillis() - start;
        snackbarMake((elapsed/1000) + "秒");
/*
        Handler mainThreadHandler = new Handler(Looper.getMainLooper());
        mainThreadHandler.post(new Runnable() {
            @Override
            public void run() {
                //メインスレッドで実行する処理
                snackbarMake((elapsed/1000) + "秒");
            }
        });

 */
    }


    FTPClient connectFTP(String sHost, int nPort, String sUser, String sPass) {
        FTPClient ftpCli = null;
        try {
            ftpCli = new FTPClient();
            //タイムアウト設定
            int nTimeout = 60 * 1000;   //60秒
            ftpCli.setDefaultTimeout(nTimeout);
            ftpCli.setConnectTimeout(nTimeout);  //タイムアウトをキャッチするにはこれが必要

            //接続
            ftpCli.connect(sHost, nPort);
            if (!FTPReply.isPositiveCompletion(ftpCli.getReplyCode())) {
                throw new Exception("FTP接続エラー Code=" + ftpCli.getReplyCode());
            }

            //ソケットタイムアウト設定
            ftpCli.setSoTimeout(nTimeout);

            //ログイン
            if (!ftpCli.login(sUser, sPass)) {
                throw new Exception("FTP認証エラー Code=" + ftpCli.getReplyCode());
            }

            //ファイル転送モード設定
            //ftpCli.setFileType(FTP.ASCII_FILE_TYPE);    //テキストファイルなど
            ftpCli.setFileType(FTP.BINARY_FILE_TYPE);   //画像ファイルなど

            //モード設定
            ftpCli.enterLocalPassiveMode(); //パッシブモード
            //ftpCli.enterLocalActiveMode();  //アクティブモード
        } catch (Exception e) {
            snackbarMake(e.toString());
            ftpCli = null;
        }
        return ftpCli;
    }

    void disconnectFTP(FTPClient ftpCli) {
        if (ftpCli != null && ftpCli.isConnected()) {
            try {
                ftpCli.disconnect();
            } catch (IOException e) {
                // ignore
            }
        }
    }


    private void snackbarMake(String message) {
        Snackbar.make(layout, message, 10000).show();
    }

}

次のプログラムでテストした。サブディレクトリには対応していないが、ファイルはダウンロードできた。 また、まだミラーリング処理は行っていない。

    ConstraintLayout layout = findViewById(R.id.constraintLayout);
    FTPClientThread thread = new FTPClientThread(layout, Command.mirroring_download, "html_css", "html_css");
    thread.start();

リファレンス

[1] Android Studio 2021.1.1 スマホでFTP 受信
[2] FTPのアクティブモードとパッシブモードの違いを徹底解説