package com.openfarmanager.android.core.network.ftp;
import android.database.Cursor;
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.openfarmanager.android.App;
import com.openfarmanager.android.R;
import com.openfarmanager.android.core.DataStorageHelper;
import com.openfarmanager.android.core.dbadapters.NetworkAccountDbAdapter;
import com.openfarmanager.android.core.network.NetworkApi;
import com.openfarmanager.android.filesystem.FileProxy;
import com.openfarmanager.android.filesystem.FileSystemScanner;
import com.openfarmanager.android.filesystem.SftpFile;
import com.openfarmanager.android.model.NetworkAccount;
import com.openfarmanager.android.model.NetworkEnum;
import com.openfarmanager.android.model.exeptions.FtpDirectoryDeleteException;
import com.openfarmanager.android.model.exeptions.InAppAuthException;
import com.openfarmanager.android.model.exeptions.NetworkException;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Vector;
import static com.openfarmanager.android.utils.Extensions.tryParse;
/**
* @author Vlad Namashko
*/
public class SftpAPI implements NetworkApi {
public static final String SFTP_SERVER = "server";
public static final String SFTP_PORT = "port";
public static final String SFTP_USER = "user";
public static final String SFTP_PASSWORD = "password";
public static final String SFTP_LOGIN_BY_PRIVATE_KEY = "login_by_private_key";
public static final String SFTP_PRIVATE_KEY = "private_key";
private ChannelSftp mSftpChannel;
private SftpAccount mCurrentAccount;
public void connectAndSave(String server, int port, String user, String password,
boolean isLoginByPrivateKey, byte[] privateKey) throws InAppAuthException {
connect(server, port, user, password, isLoginByPrivateKey, privateKey);
// save account to db
JSONObject authData = new JSONObject();
try {
authData.put(SFTP_SERVER, server);
authData.put(SFTP_PORT, port);
authData.put(SFTP_USER, user);
authData.put(SFTP_PASSWORD, password);
authData.put(SFTP_LOGIN_BY_PRIVATE_KEY, isLoginByPrivateKey);
//authData.put(SFTP_PRIVATE_KEY, privateKey);
long currentAccountId = NetworkAccountDbAdapter.insert(
server + "(" + user + ")",
NetworkEnum.SFTP.ordinal(), authData.toString());
List<NetworkAccount> accounts = parseAccounts(NetworkAccountDbAdapter.getAccountById(currentAccountId));
if (accounts.size() == 1) {
mCurrentAccount = (SftpAccount) accounts.get(0);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
public void connect(SftpAccount sftpAccount) throws InAppAuthException {
mCurrentAccount = sftpAccount;
connect(sftpAccount.getServer(), sftpAccount.getPort(), sftpAccount.getUser(),
sftpAccount.getPassword(), sftpAccount.isLoginByPrivateKey(), sftpAccount.getPrivateKey());
}
public void connect(String server, int port, String user, String password,
boolean isLoginByPrivateKey, byte[] privateKey) throws InAppAuthException {
JSch jsch = new JSch();
Session session;
try {
session = jsch.getSession(user, server, port);
} catch (JSchException e) {
e.printStackTrace();
throw new InAppAuthException(App.sInstance.getString(R.string.error_sftp_connection_error));
}
session.setPassword(password);
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "no");
session.setConfig(prop);
Channel channel;
try {
session.connect();
channel = session.openChannel("sftp");
channel.connect();
} catch (JSchException e) {
throw new InAppAuthException(App.sInstance.getString(R.string.error_sftp_connection_error));
}
mSftpChannel = (ChannelSftp) channel;
String charset = App.sInstance.getSettings().getSftpCharset(server);
if (charset != null) {
setCharsetEncoding(charset);
}
}
private void setCharsetEncoding(String charset) {
try {
mSftpChannel.setFilenameEncoding(charset);
} catch (Exception e) {
e.printStackTrace();
}
}
private List<NetworkAccount> parseAccounts(Cursor cursor) {
List<NetworkAccount> accounts = new ArrayList<NetworkAccount>();
if (cursor == null) {
return accounts;
}
try {
int idxId = cursor.getColumnIndex(NetworkAccountDbAdapter.Columns.ID);
int idxUserName = cursor.getColumnIndex(NetworkAccountDbAdapter.Columns.USER_NAME);
int idxAuthData = cursor.getColumnIndex(NetworkAccountDbAdapter.Columns.AUTH_DATA);
while(cursor.moveToNext()) {
String authData = cursor.getString(idxAuthData);
try {
JSONObject data = new JSONObject(authData);
SftpAccount account = new SftpAccount(cursor.getLong(idxId), cursor.getString(idxUserName),
data.getString(SFTP_SERVER), tryParse(data.getString(SFTP_PORT), 22),
data.getString(SFTP_USER), data.getString(SFTP_PASSWORD),
data.getBoolean(SFTP_LOGIN_BY_PRIVATE_KEY), null);
accounts.add(account);
} catch (JSONException e) {
e.printStackTrace();
}
}
} finally {
cursor.close();
DataStorageHelper.closeDatabase();
}
return accounts;
}
public List<FileProxy> getDirectoryFiles(String path) throws NetworkException {
List<FileProxy> files = new ArrayList<FileProxy>();
try {
if (!path.equals(mSftpChannel.pwd())) {
mSftpChannel.cd(path);
}
Vector sftpFiles = mSftpChannel.ls(path);
for (Object entry : sftpFiles) {
ChannelSftp.LsEntry lsEntry = (ChannelSftp.LsEntry) entry;
if (!lsEntry.getFilename().equals(".") && !lsEntry.getFilename().equals("..")) {
files.add(new SftpFile(path, lsEntry));
}
}
FileSystemScanner.sInstance.sort(files);
} catch (SftpException e) {
throw NetworkException.handleNetworkException(e);
} catch (Exception e) {
e.printStackTrace();
throw NetworkException.handleNetworkException(e);
}
return files;
}
@Override
public int getAuthorizedAccountsCount() {
return NetworkAccountDbAdapter.count(NetworkEnum.SFTP.ordinal());
}
@Override
public List<NetworkAccount> getAuthorizedAccounts() {
return parseAccounts(NetworkAccountDbAdapter.getAccounts(NetworkEnum.SFTP.ordinal()));
}
@Override
public NetworkAccount newAccount() {
return new SftpAccount(-1, App.sInstance.getResources().getString(com.openfarmanager.android.R.string.btn_new),
null, 0, null, null, false, null);
}
@Override
public NetworkAccount getCurrentNetworkAccount() {
return mCurrentAccount;
}
@Override
public void delete(FileProxy file) throws Exception {
changeDirectory(file.getParentPath());
if (file.isDirectory()) {
try {
mSftpChannel.rmdir(file.getFullPath());
} catch (SftpException e) {
if (App.sInstance.getSettings().isFtpAllowRecursiveDelete()) {
for (FileProxy proxy : getDirectoryFiles(file.getFullPath())) {
delete(proxy);
}
mSftpChannel.rmdir(file.getFullPath());
} else {
throw new FtpDirectoryDeleteException();
}
}
} else {
mSftpChannel.rm(file.getFullPath());
}
}
public void setCharset(Charset charset) {
setCharsetEncoding(charset.name());
App.sInstance.getSettings().saveCharset(mCurrentAccount.getServer(), charset.name());
}
public OutputStream getUploadStream(String path) throws SftpException {
return mSftpChannel.put(path);
}
public void writeFileToStream(String filePath, OutputStream dst) throws SftpException {
mSftpChannel.get(filePath, dst);
}
@Override
public String createDirectory(String baseDirectory, String newDirectoryName) throws Exception {
String path = baseDirectory + "/" + newDirectoryName;
mSftpChannel.mkdir(path);
return path;
}
@Override
public List<FileProxy> search(String path, String query) {
throw new RuntimeException();
}
@Override
public boolean rename(FileProxy srcFile, String s) throws Exception {
String fullPath = srcFile.getFullPath();
mSftpChannel.rename(fullPath, s.substring(s.lastIndexOf('/') + 1));
return true;
}
public void closeChannel() {
if (mSftpChannel != null && mSftpChannel.isConnected()) {
mSftpChannel.exit();
}
}
public void changeDirectory(String destination) throws SftpException {
mSftpChannel.cd(destination);
}
public static class SftpAccount extends NetworkAccount {
private String mServer;
private int mPort;
private String mUser;
private String mPassword;
private boolean mIsLoginByPrivateKey;
private byte[] mPrivateKey;
public SftpAccount(long id, String userName, JSONObject data) throws JSONException {
this(id, userName, data.getString(SFTP_SERVER), tryParse(data.getString(SFTP_PORT), 22),
data.getString(SFTP_USER), data.getString(SFTP_PASSWORD),
data.getBoolean(SFTP_LOGIN_BY_PRIVATE_KEY), null);
}
public SftpAccount(long id, String userName, String server, int port, String user,
String password, boolean isLoginByPrivateKey, byte[] privateKey ) {
mId = id;
mUserName = userName;
mServer = server;
mPort = port;
mUser = user;
mPassword = password;
mIsLoginByPrivateKey = isLoginByPrivateKey;
mPrivateKey = privateKey;
}
public String getServer() {
return mServer;
}
public int getPort() {
return mPort;
}
public String getUser() {
return mUser;
}
public String getPassword() {
return mPassword;
}
public boolean isLoginByPrivateKey() {
return mIsLoginByPrivateKey;
}
public byte[] getPrivateKey() {
return mPrivateKey;
}
@Override
public NetworkEnum getNetworkType() {
return NetworkEnum.SFTP;
}
}
}