2019年7月15日月曜日

android AsyncTaskによるJSchを用いたSSH通信とAsyncTask内イベントの呼び出し元クラスでのキャッチ

2019 Jul. 15.

MyJsch.java
package your.package;

import android.content.Context;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.UserInfo;

import java.io.File;
import java.io.InputStream;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;

class MyJsch {
    private final String ServerIp;
    private final int Port;
    private final String User;
    private final String PassPhraseWord;
    private final String IdentityKeyPath;
    private final Context AppContext;

    /** Channel接続タイプ */
    private static final String CHANNEL_TYPE = "sftp";

    /*
     * コンストラクタ
     */
    MyJsch( final String ServerIp, final int Port, final String User, final String PassPhraseWord,
            final String IdentityKeyPath, final Context AppContext) {
        this.ServerIp = ServerIp;
        this.Port = Port;
        this.User = User;
        this.PassPhraseWord = PassPhraseWord;
        this.IdentityKeyPath = IdentityKeyPath;
        this.AppContext = AppContext;
    }


    /*
     * Sessionを開始
     */
    protected Session connectSession() throws JSchException {
        Session session = null;

        // android端末内の外部ストーリッジ確認
        List sdCardFilesDirPaths =
                SdCardDirPaths.getSdCardFilesDirPathListForLollipop( AppContext );
        Collections.sort(sdCardFilesDirPaths, new CompStringLength());
        String externalPath = sdCardFilesDirPaths.get(0);
        externalPath = externalPath.replaceAll("/Android.*$", "");

        final JSch jsch = new JSch();

        // クライアント側のknownHostsチェックを行わない
        Hashtable config = new Hashtable();
        config.put("StrictHostKeyChecking", "no");
        jsch.setConfig(config);

        // パスフレーズ・秘密鍵方式
        String privKeyFilePath = externalPath + "/" + IdentityKeyPath;
        File privKeyFile = new File(privKeyFilePath);
        if (privKeyFile.exists() ) {
            jsch.addIdentity(privKeyFilePath, PassPhraseWord);
        }

        // Session取得
        session = jsch.getSession(User, ServerIp, Port);

        // パスワード方式でも可とする
        session.setPassword(PassPhraseWord);

        final UserInfo userInfo = new SftpUserInfo();
        session.setUserInfo(userInfo);
        session.connect();
        return session;
    }


    /**
     * SFTPのChannelを開始
     *
     * @param session
     *            開始されたSession情報
     */
    protected ChannelSftp connectChannelSftp(final Session session)
            throws JSchException {
        final ChannelSftp channel = (ChannelSftp) session.openChannel(CHANNEL_TYPE);
        try {
            channel.connect();
        } catch (JSchException e) {
            return null;
        }
        return channel;
    }


    /**
     * Session・Channelの終了
     *
     * @param session
     *            開始されたSession情報
     * @param channels
     *            開始されたChannel情報.複数指定可能
     */
    protected void disconnect(final Session session, final Channel... channels) {
        if (channels != null) {
            for (Channel c: channels ) {
                if (c != null) {
                    c.disconnect();
                }
            }
        }
        if (session != null) {
            session.disconnect();
        }
    }


    /**
     * SFTPに接続するユーザ情報を保持するクラス
     */
    private static class SftpUserInfo implements UserInfo {
        @Override
        public String getPassword() {
            return null;
        }
        @Override
        public boolean promptPassword(String arg0) {
            return true;
        }
        @Override
        public boolean promptPassphrase(String arg0) {
            return true;
        }
        @Override
        public boolean promptYesNo(String arg0) {
            return true;
        }
        @Override
        public void showMessage(String arg0) {
        }
        @Override
        public String getPassphrase() {
            return null;
        }
    }


    /**
     * ファイルアップロード
     *
     * @throws JSchException
     *             Session・Channelの設定/接続エラー時に発生
     */
    public void putFile(ChannelSftp channel, InputStream inStream, String destPath)
            throws SftpException {

        /*
         * sftp通信を実行
         */
        String absoluteDestPath = channel.getHome() + "/" + destPath;
        String destFile = new File(absoluteDestPath).getName();
        int numDestPath = absoluteDestPath.length();
        int numDestFile = destFile.length();
        String destParentPath = absoluteDestPath.substring( 0, numDestPath - numDestFile );
        channel.cd(destParentPath);
        channel.put( inStream, destFile);

        // confirm existance of destFile
        channel.lstat(destFile);
    }

    /*
     * サーバー上のファイルの削除
     *   destFileNameFront文字列から始まるファイルを全て削除する
    */
    public void deleteFiles(ChannelSftp channel, String destFileNameFront) throws SftpException {
        channel.rm(destFileNameFront + "*");
    }
}

SdCardDirPaths.java
package your.package;

import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.os.Environment;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class SdCardDirPaths {

    /**
     * SDカードのfilesディレクトリパスのリストを取得する。
     * Android5.0以上対応。
     *
     * @param context
     * @return SDカードのfilesディレクトリパスのリスト
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public static List getSdCardFilesDirPathListForLollipop(Context context) {
        List sdCardFilesDirPathList = new ArrayList<>();

        // getExternalFilesDirsはAndroid4.4から利用できるAPI。
        // filesディレクトリのリストを取得できる。
        File[] dirArr = context.getExternalFilesDirs(null);


        for (File dir : dirArr) {
            if (dir != null) {
                String path = dir.getAbsolutePath();

                // isExternalStorageRemovableはAndroid5.0から利用できるAPI。
                // 取り外し可能かどうか(SDカードかどうか)を判定している。
                if (Environment.isExternalStorageRemovable(dir)) {

                    // 取り外し可能であればSDカード。
                    // このパスをパスリストに加える
                    if (!sdCardFilesDirPathList.contains(path)) {
                        sdCardFilesDirPathList.add(path);
                    }
                }
            }
        }
        return sdCardFilesDirPathList;
    }
}

(利用例)
Send2ServerTask.java
package your.package;

import android.app.Activity;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;

import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;

import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

// 複数のファイルをSSH送信する
public class Send2ServerTask extends AsyncTask {
    final String ServerIp = "SSH.SERVER.IP.ADDRESS";
    final int Port = 22;
    final String User = "SshLogInUser";
    final String IdentityKeyPath = "PRIVATE/KEY/FILE/PATH/IN/CLIENT/DEVICE";
    //     if IdentityKeyPath is illegal, password authentication is used.
    final String DestParentPath = "SERVER/DIR/TO/BE/STORED";

    private WeakReference weakRef;
    private HashMap inStreamMap;
    final private String passPhrase;
    final private String fileNameFront;
      // SSH失敗時のファイル削除のためにファイル名共通文字列を保持
      // 複数の送信ファイルはいずれもfileNameFront文字列で始まるファイル名とすること

    private Listener listener;

    /*
     * constructor
     *   inStreamMap 送信する複数ファイルのファイル名とインプットストリームをHashMapで渡す
     */
    Send2ServerTask(Activity parentActivity, HashMap inStreamMap,
                    String passPhrase, String fileNameFront) {
        weakRef = new WeakReference<>(parentActivity);
        this.inStreamMap = new HashMap<>(inStreamMap);
        this.passPhrase = passPhrase;
        this.fileNameFront = fileNameFront;
    }


    @Override
    protected String doInBackground(Void... params) {
        MyJsch mJsch;
        Session sftpSession = null;
        ChannelSftp sftpChannel = null;

        Activity refActivity = weakRef.get();
        if (refActivity == null || refActivity.isFinishing()) {
            return null;
        }

        Context appContext = weakRef.get().getApplicationContext();

        // initialise Jsch
        mJsch = new MyJsch(ServerIp, Port, User, passPhrase, IdentityKeyPath, appContext);

        // connect session, channel
        try {
            sftpSession = mJsch.connectSession();
            sftpChannel = mJsch.connectChannelSftp( sftpSession );

            // upload
            int count = 0;
            for ( Map.Entry entry : inStreamMap.entrySet()) {
                // entry.getKey()    entry.getValue()
                String destPath = DestParentPath + "/" + entry.getKey();
                try {
                    mJsch.putFile(sftpChannel, entry.getValue(), destPath);
                } catch (SftpException e) {
                    try {
                        // このセッションでアップロードされたファイルをすべて削除する
                        mJsch.deleteFiles(sftpChannel, fileNameFront);
                        mJsch.disconnect( sftpSession, sftpChannel);
                        return "Failed to upload files. count: " + count + ". " + destPath + " "  + e.toString();
                    } catch (SftpException e1) {
                        mJsch.disconnect( sftpSession, sftpChannel);
                        return "Failed to delete remote files. " + e1.toString();
                    }
                }

                try {
                    // take sleep between SFTP#puts
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // このセッションでアップロードされたファイルをすべて削除する
                    mJsch.deleteFiles(sftpChannel, fileNameFront);
                    mJsch.disconnect( sftpSession, sftpChannel);
                    return "Failed! sleep" + e.toString();
                }
                count++;
            }
            if ( count != inStreamMap.size()) {
                // このセッションでアップロードされたファイルをすべて削除する
                mJsch.deleteFiles(sftpChannel, fileNameFront);
                mJsch.disconnect( sftpSession, sftpChannel);
                return "Could not upload " + inStreamMap.size() + " files";
            }
        } catch (JSchException | SftpException e) {
            mJsch.disconnect( sftpSession, sftpChannel);
            return "connection failed. " + e.toString();

        } finally {
            // disconnect channel, session
            mJsch.disconnect( sftpSession, sftpChannel);
        }
        return "Succeeded. " + inStreamMap.size() + " files upload";
    }


    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        Activity refActivity = weakRef.get();
        if (refActivity == null || listener == null)  {
            Toast.makeText(refActivity, "failed sending to server.", Toast.LENGTH_LONG).show();
        } else {
            if ( result.contains("Succeeded") ) {
                Toast.makeText(refActivity, result, Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(refActivity, result, Toast.LENGTH_LONG).show();
            }
            listener.onSuccess(result);
        }
        return;
    }

    void setListener(Listener listener) {
        this.listener = listener;
    }

    interface Listener {
        void onSuccess(String str);
    }
}

MyActivity.java
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_confirm_before_send);
        buttonOK = findViewById(R.id.buttonOK);
        setListeners();
    }

    /*
     * Send2ServerTaskへのリスナ
     *   サーバーへのアップロード終了後に行う処理を記述
     */
    private Send2ServerTask.Listener createListener() {
        return new Send2ServerTask.Listener() {
            @Override
            public void onSuccess(String result) {
                deleteFiles();
            }
        };
    }

    protected void setListeners(){
        buttonOK.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /*
                 * HashMap SSHサーバーに送るデータ
                 *   String 送信先ファイルパス
                 *   InputStream 送信ファイルのインプットストリーム
                 */
                HashMap dataMap = new HashMap<>();

                setData2dataMap(); // dataMapにSSH通信データをセットする
                setPassPhraseWord();
                setFileNameFront();

                /*
                 * dataMapを保管サーバーに送る
                 */
                send2serverTask = new Send2ServerTask(thisActivity, dataMap, passPhraseWord, fileNameFront);

                // Listenerを設定し、send2serverTaskを実行してdataMapを保管サーバーに送る
                send2serverTask.setListener(createListener());
                send2serverTask.execute();
            }
        });
    }

0 件のコメント:

コメントを投稿