package com.ghostsq.commander.adapters;
import java.lang.System;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import com.ghostsq.commander.Commander;
import com.ghostsq.commander.R;
import com.ghostsq.commander.adapters.CommanderAdapter;
import com.ghostsq.commander.adapters.CommanderAdapterBase;
import com.ghostsq.commander.adapters.Engines.IReciever;
import com.ghostsq.commander.adapters.FTPEngines.CopyFromEngine;
import com.ghostsq.commander.favorites.Favorite;
import com.ghostsq.commander.utils.Credentials;
import com.ghostsq.commander.utils.LsItem.LsItemPropComparator;
import com.ghostsq.commander.utils.FTP;
import com.ghostsq.commander.utils.LsItem;
import com.ghostsq.commander.utils.Utils;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.ContextMenu;
import android.widget.AdapterView;
public class FTPAdapter extends CommanderAdapterBase implements Engines.IReciever {
private final static String TAG = "FTPAdapter";
// Java compiler creates a thunk function to access to the private owner class member from a subclass
// to avoid that all the member accessible from the subclasses are public
public FTP ftp;
public Uri uri = null;
public LsItem[] items = null;
private Timer heartBeat;
public boolean noHeartBeats = false;
public FTPCredentials theUserPass = null;
private final static int CHMOD_CMD = 36793;
public FTPAdapter( Context ctx_ ) {
super( ctx_ );
ftp = new FTP();
}
@Override
public void Init( Commander c ) {
super.Init( c );
}
@Override
public int getType() {
return CA.FTP;
}
@Override
protected int getPredictedAttributesLength() {
return 25;
}
class Noop extends TimerTask {
@Override
public void run() {
if( !noHeartBeats && reader == null && ftp.isLoggedIn() )
try {
//Log.v( TAG, "FTP NOOP" );
ftp.heartBeat();
} catch( InterruptedException e ) {
e.printStackTrace();
}
}
}
@Override
public void setIdentities( String name, String pass ) {
theUserPass = new FTPCredentials( name, pass );
}
@Override
public void setCredentials( Credentials crd ) {
theUserPass = crd != null ? new FTPCredentials( crd ) : null;
}
@Override
public Credentials getCredentials() {
if( theUserPass == null || theUserPass.isNotSet() )
return null;
return theUserPass;
}
@Override
public boolean readSource( Uri tmp_uri, String pass_back_on_done ) {
try {
boolean need_reconnect = false;
if( tmp_uri != null ) {
String new_user_info = tmp_uri.getUserInfo();
if( uri == null )
need_reconnect = true;
else if( !tmp_uri.getHost().equalsIgnoreCase( uri.getHost() ) ) {
need_reconnect = true;
theUserPass = null;
}
else if( new_user_info != null ) {
if( theUserPass == null )
need_reconnect = true;
else if( theUserPass != null && !theUserPass.equals( new FTPCredentials( new_user_info ) ) )
need_reconnect = true;
}
else
if( theUserPass != null )
need_reconnect = theUserPass.dirty;
if( uri != null )
synchronized( uri ) {
setUri( tmp_uri );
}
else
setUri( tmp_uri );
}
else
if( uri == null )
return false;
if( reader != null ) { // that's not good.
Log.w( TAG, "reader's existed!" );
if( reader.isAlive() ) {
Log.e( TAG, "reader's busy!" );
return false;
}
}
if( items == null )
numItems = 1;
notify( Commander.OPERATION_STARTED );
Log.v( TAG, "Creating and starting the reader..." );
reader = new ListEngine( readerHandler, need_reconnect, pass_back_on_done );
reader.start();
if( heartBeat == null ) {
heartBeat = new Timer( "FTP Heartbeat", true );
heartBeat.schedule( new Noop(), 120000, 40000 );
}
return true;
}
catch( Exception e ) {
commander.showError( e.getLocalizedMessage() );
e.printStackTrace();
}
notify( ftp.getLog(), Commander.OPERATION_FAILED );
return false;
}
class ListEngine extends Engine {
private boolean needReconnect;
private LsItem[] items_tmp;
public String pass_back_on_done;
ListEngine( Handler h, boolean need_reconnect_, String pass_back_on_done_ ) {
setHandler( h );
needReconnect = need_reconnect_;
pass_back_on_done = pass_back_on_done_;
}
public LsItem[] getItems() {
return items_tmp;
}
@Override
public void run() {
try {
if( uri == null ) {
sendProgress( "Wrong URI", Commander.OPERATION_FAILED );
return;
}
Log.i( TAG, "ListEngine started" );
threadStartedAt = System.currentTimeMillis();
ftp.clearLog();
if( needReconnect && ftp.isLoggedIn() ) {
ftp.disconnect( false );
}
if( theUserPass == null || theUserPass.isNotSet() )
theUserPass = new FTPCredentials( uri.getUserInfo() );
int cl_res = ftp.connectAndLogin( uri, theUserPass.getUserName(), theUserPass.getPassword(), true );
if( cl_res < 0 ) {
if( cl_res == FTP.NO_LOGIN )
sendLoginReq( uri.toString(), theUserPass, pass_back_on_done );
return;
}
if( cl_res == FTP.LOGGED_IN )
sendProgress( ctx.getString( R.string.ftp_connected,
uri.getHost(), theUserPass.getUserName() ), Commander.OPERATION_STARTED );
if( ftp.isLoggedIn() ) {
//Log.v( TAG, "ftp is logged in" );
items_tmp = ftp.getDirList( null, ( mode & MODE_HIDDEN ) == SHOW_MODE );
String path = ftp.getCurrentDir();
if( path != null )
synchronized( uri ) {
uri = uri.buildUpon().encodedPath( path ).build();
}
if( items_tmp != null ) {
//Log.v( TAG, "Got the items list" );
if( items_tmp.length > 0 ) {
LsItem.LsItemPropComparator comp =
items_tmp[0].new LsItemPropComparator( mode & MODE_SORTING, (mode & MODE_CASE) != 0, ascending );
Arrays.sort( items_tmp, comp );
}
parentLink = path == null || path.length() == 0 || path.equals( SLS ) ? SLS : PLS;
//Log.v( TAG, "items list sorted" );
sendProgress( tooLong( 8 ) ? ftp.getLog() : null, Commander.OPERATION_COMPLETED, pass_back_on_done );
return;
}
else
Log.e( TAG, "Can't get the items list" );
}
else
Log.e( TAG, "Did not log in." );
}
catch( UnknownHostException e ) {
ftp.debugPrint( "Unknown host:\n" + e.getLocalizedMessage() );
}
catch( IOException e ) {
ftp.debugPrint( "IO exception:\n" + e.getLocalizedMessage() );
Log.e( TAG, "", e );
}
catch( Exception e ) {
ftp.debugPrint( e.getLocalizedMessage() );
Log.e( TAG, "", e );
}
finally {
super.run();
}
ftp.disconnect( true );
sendProgress( ftp.getLog(), Commander.OPERATION_FAILED, pass_back_on_done );
}
}
@Override
protected void onReadComplete() {
Log.v( TAG, "UI thread finishes the items obtaining. reader=" + reader );
if( reader instanceof ListEngine ) {
ListEngine list_engine = (ListEngine)reader;
items = null;
if( ( mode & MODE_HIDDEN ) == HIDE_MODE ) {
LsItem[] tmp_items = list_engine.getItems();
if( tmp_items != null ) {
int cnt = 0;
for( int i = 0; i < tmp_items.length; i++ )
if( tmp_items[i].getName().charAt( 0 ) != '.' )
cnt++;
items = new LsItem[cnt];
int j = 0;
for( int i = 0; i < tmp_items.length; i++ )
if( tmp_items[i].getName().charAt( 0 ) != '.' )
items[j++] = tmp_items[i];
}
}
else
items = list_engine.getItems();
numItems = items != null ? items.length + 1 : 1;
notifyDataSetChanged();
if( theUserPass != null )
theUserPass.dirty = false;
}
}
@Override
public String toString() {
if( uri == null )
return "";
String ui = uri.getUserInfo();
if( ui != null && theUserPass == null )
return Favorite.screenPwd( uri );
if( theUserPass == null || theUserPass.isNotSet() )
return uri.toString();
return Favorite.screenPwd( Utils.getUriWithAuth( uri, theUserPass ) );
}
/*
* CommanderAdapter implementation
*/
@Override
public Uri getUri() {
return Utils.updateUserInfo( uri, null );
}
private final void setFTPMode( Uri uri_ ) {
String active_s = uri_.getQueryParameter( "a" );
boolean a_set = Utils.str( active_s );
if( ( mode & MODE_CLONE ) == NORMAL_MODE || a_set )
ftp.setActiveMode( a_set && ( "1".equals( active_s ) || "true".equals( active_s ) || "yes".equals( active_s ) ) );
}
@Override
public void setUri( Uri uri_ ) {
uri = uri_;
try {
setFTPMode( uri );
String charset = uri.getQueryParameter( "e" );
boolean e_set = Utils.str( charset );
if( ( mode & MODE_CLONE ) == NORMAL_MODE || e_set )
ftp.setCharset( charset );
} catch( Exception e ) {
Log.e( TAG, "Uri: " + uri_, e );
}
}
@Override
public void populateContextMenu( ContextMenu menu, AdapterView.AdapterContextMenuInfo acmi, int num ) {
try {
super.populateContextMenu( menu, acmi, num );
if( acmi.position > 0 )
menu.add( 0, CHMOD_CMD, 0, R.string.permissions );
} catch( Exception e ) {
Log.e( TAG, null, e );
}
}
@Override
public void doIt( int command_id, SparseBooleanArray cis ) {
try {
if( CHMOD_CMD == command_id ) {
LsItem[] items_todo = bitsToItems( cis );
boolean selected_one = items_todo != null && items_todo.length > 0 && items_todo[0] != null;
if( selected_one ) {
Intent i = new Intent( ctx, EditFTPPermissions.class );
i.putExtra( "perm", items_todo[0].getAttr() );
i.putExtra( "path", Utils.mbAddSl( uri.getPath() ) + items_todo[0].getName() );
i.putExtra( "uri", Utils.getUriWithAuth( uri, theUserPass ) );
commander.issue( i, Commander.ACTIVITY_REQUEST_FOR_NOTIFY_RESULT );
}
else
commander.showError( commander.getContext().getString( R.string.select_some ) );
}
} catch( Exception e ) {
Log.e( TAG, "Can't do the command " + command_id, e );
}
}
@Override
public void reqItemsSize( SparseBooleanArray cis ) {
notify( "Not supported.", Commander.OPERATION_FAILED );
}
@Override
public boolean copyItems( SparseBooleanArray cis, CommanderAdapter to, boolean move ) {
String err_msg = null;
try {
LsItem[] subItems = bitsToItems( cis );
if( subItems == null ) {
notify( s( R.string.copy_err ), Commander.OPERATION_FAILED );
return false;
}
if( !checkReadyness() ) return false;
Engines.IReciever recipient = null;
File dest = null;
if( to instanceof FSAdapter ) {
dest = new File( to.toString() );
if( !dest.exists() ) dest.mkdirs();
if( !dest.isDirectory() )
throw new RuntimeException( s( R.string.dest_exist ) );
} else {
dest = new File( createTempDir() );
recipient = to.getReceiver();
}
notify( Commander.OPERATION_STARTED );
CopyFromEngine cfe = new FTPEngines.CopyFromEngine( commander, theUserPass, uri, subItems, dest, move, recipient, ftp.getActiveMode() );
commander.startEngine( cfe );
return true;
}
catch( Exception e ) {
err_msg = e.getLocalizedMessage();
}
notify( err_msg, Commander.OPERATION_FAILED );
return false;
}
@Override
public boolean createFile( String fileURI ) {
notify( "Operation not supported on a FTP folder.", Commander.OPERATION_FAILED );
return false;
}
@Override
public void createFolder( String name ) {
notify( Commander.OPERATION_STARTED );
commander.startEngine( new MkDirEngine( name ) );
}
class MkDirEngine extends Engine {
private String name;
MkDirEngine( String name_ ) {
name = name_;
}
@Override
public void run() {
ftp.clearLog();
try {
ftp.makeDir( name );
sendResult( "" );
return;
} catch( Exception e ) {
}
error( ctx.getString( R.string.ftp_mkdir_failed, name, ftp.getLog() ) );
if( !noErrors() )
sendResult( "" );
else
sendRefrReq( name );
}
}
@Override
public boolean deleteItems( SparseBooleanArray cis ) {
try {
if( !checkReadyness() ) return false;
LsItem[] subItems = bitsToItems( cis );
if( subItems != null ) {
notify( Commander.OPERATION_STARTED );
commander.startEngine( new FTPEngines.DelEngine( ctx, theUserPass, uri, subItems, ftp.getActiveMode() ) );
return true;
}
}
catch( Exception e ) {
commander.showError( e.getLocalizedMessage() );
}
return false;
}
@Override
public Uri getItemUri( int position ) {
Uri u = getUri();
if( u == null ) return null;
return u.buildUpon().appendEncodedPath( getItemName( position, false ) ).build();
}
@Override
public String getItemName( int position, boolean full ) {
if( items != null && position > 0 && position <= items.length ) {
if( full ) {
String path = toString();
if( path != null && path.length() > 0 ) {
if( path.charAt( path.length() - 1 ) != SLC )
path += SLS;
return path + items[position-1].getName();
}
}
return items[position-1].getName();
}
return null;
}
@Override
public void openItem( int position ) {
if( position == 0 ) { // ..
if( uri != null && parentLink != SLS ) {
String path = uri.getPath();
int len_ = path.length()-1;
if( len_ > 0 ) {
if( path.charAt( len_ ) == SLC )
path = path.substring( 0, len_ );
path = path.substring( 0, path.lastIndexOf( SLC ) );
if( path.length() == 0 )
path = SLS;
// passing null instead of credentials keeps the current authentication session
commander.Navigate( uri.buildUpon().path( path ).build(), null, uri.getLastPathSegment() );
}
}
return;
}
if( items == null || position < 0 || position > items.length )
return;
LsItem item = items[position - 1];
if( item.isDirectory() ) {
String cur = uri.getPath();
if( cur == null || cur.length() == 0 )
cur = SLS;
else
if( cur.charAt( cur.length()-1 ) != SLC )
cur += SLS;
Uri item_uri = uri.buildUpon().appendEncodedPath( item.getName() ).build();
commander.Navigate( item_uri, null, null );
}
else {
Uri auth_item_uri = getUri().buildUpon().appendEncodedPath( item.getName() ).build();
commander.Open( auth_item_uri, theUserPass );
}
}
@Override
public boolean receiveItems( String[] uris, int move_mode ) {
try {
if( uris == null || uris.length == 0 ) {
notify( s( R.string.copy_err ), Commander.OPERATION_FAILED );
return false;
}
File[] list = Utils.getListOfFiles( uris );
if( list == null ) {
notify( "Something wrong with the files", Commander.OPERATION_FAILED );
return false;
}
notify( Commander.OPERATION_STARTED );
boolean move = ( move_mode & MODE_MOVE ) != 0;
boolean del_src_dir = ( move_mode & CommanderAdapter.MODE_DEL_SRC_DIR ) != 0;
commander.startEngine( new FTPEngines.CopyToEngine( ctx, theUserPass, uri, list, move, del_src_dir, ftp.getActiveMode() ) );
return true;
} catch( Exception e ) {
notify( e.getLocalizedMessage(), Commander.OPERATION_FAILED );
}
return false;
}
@Override
public boolean renameItem( int position, String new_name, boolean copy ) {
try {
if( copy ) {
notify( s( R.string.not_supported ), Commander.OPERATION_FAILED );
}
if( items == null || position <= 0 || position > items.length )
return false;
String old_name = getItemName( position, false );
if( old_name != null ) {
notify( Commander.OPERATION_STARTED );
commander.startEngine( new FTPEngines.RenEngine( ctx, theUserPass, uri, old_name, new_name, ftp.getActiveMode() ) );
}
} catch( Exception e ) {
e.printStackTrace();
}
return false;
}
@Override
public void prepareToDestroy() {
if( heartBeat != null ) {
heartBeat.cancel();
heartBeat.purge();
heartBeat = null;
}
super.prepareToDestroy();
new Thread( new Runnable() {
@Override
public void run() {
ftp.disconnect( false );
}
}, "FTP disconnect" ).start();
items = null;
}
/*
* BaseAdapter implementation
*/
@Override
public Object getItem( int position ) {
Item item = new Item();
item.name = "???";
{
if( position == 0 ) {
item.name = parentLink;
}
else {
if( items != null && position > 0 && position <= items.length ) {
LsItem ls_item = items[position - 1];
item.dir = ls_item.isDirectory();
item.name = item.dir ? SLS + ls_item.getName() : ls_item.getName();
item.size = !item.dir || ls_item.length() > 0 ? ls_item.length() : -1;
item.date = ls_item.getDate();
item.attr = ls_item.getAttr();
}
}
}
return item;
}
private final LsItem[] bitsToItems( SparseBooleanArray cis ) {
try {
int counter = 0;
for( int i = 0; i < cis.size(); i++ )
if( cis.valueAt( i ) )
counter++;
LsItem[] subItems = new LsItem[counter];
int j = 0;
for( int i = 0; i < cis.size(); i++ )
if( cis.valueAt( i ) )
subItems[j++] = items[ cis.keyAt( i ) - 1 ];
return subItems;
} catch( Exception e ) {
Log.e( TAG, "", e );
}
return null;
}
private final boolean checkReadyness()
{
if( !ftp.isLoggedIn() ) {
notify( s( R.string.ftp_nologin ), Commander.OPERATION_FAILED );
return false;
}
return true;
}
public static class FTPCredentials extends Credentials {
public boolean dirty = true;
public FTPCredentials( String userName, String password ) {
super( userName, password );
}
public FTPCredentials( String newUserInfo ) {
super( newUserInfo == null ? ":" : newUserInfo );
}
public FTPCredentials( Credentials c ) {
super( c );
}
public String getUserName() {
String u = super.getUserName();
return u == null || u.length() == 0 ? "anonymous" : u;
}
public String getPassword() {
String u = super.getUserName();
String p = u == null || u.length() == 0 ? "user@host.com" : super.getPassword();
return p != null ? p : "";
}
public final boolean isNotSet() {
String u = super.getUserName();
if( u == null || u.length() == 0 ) return true;
String p = super.getPassword();
if( p == null ) return true;
return false;
}
}
@Override
protected void reSort() {
if( items == null || items.length < 1 ) return;
LsItemPropComparator comp = items[0].new LsItemPropComparator( mode & MODE_SORTING, (mode & MODE_CASE) != 0, ascending );
Arrays.sort( items, comp );
}
@Override
public Item getItem( Uri u ) {
try {
if( theUserPass == null || theUserPass.isNotSet() )
theUserPass = new FTPCredentials( u.getUserInfo() );
if( ftp.connectAndLogin( u, theUserPass.getUserName(), theUserPass.getPassword(), false ) > 0 ) {
List<String> segs = u.getPathSegments();
if( segs.size() == 0 ) {
Item item = new Item( "/" );
item.dir = true;
return item;
}
String prt_path = "";
for( int i = 0; i < segs.size()-1; i++ ) {
prt_path += "/" + segs.get( i );
}
LsItem[] subItems = ftp.getDirList( prt_path, true );
if( subItems != null ) {
String fn = segs.get( segs.size() - 1 );
for( int i = 0; i < subItems.length; i++ ) {
LsItem ls_item = subItems[i];
String ifn = ls_item.getName();
if( fn.equals( ifn ) ) {
Item item = new Item( ifn );
item.size = ls_item.length();
item.date = ls_item.getDate();
item.dir = ls_item.isDirectory();
return item;
}
}
}
}
} catch( Throwable e ) {
e.printStackTrace();
}
return null;
}
@Override
public InputStream getContent( Uri u, long skip ) {
try {
if( uri != null && !uri.getHost().equals( u.getHost() ) )
return null;
if( theUserPass == null || theUserPass.isNotSet() )
theUserPass = new FTPCredentials( u.getUserInfo() );
setFTPMode( u );
if( ftp.connectAndLogin( u, theUserPass.getUserName(), theUserPass.getPassword(), false ) > 0 ) {
noHeartBeats = true;
return ftp.prepRetr( u.getPath(), skip );
}
} catch( Exception e ) {
Log.e( TAG, u.getPath(), e );
}
return null;
}
@Override
public OutputStream saveContent( Uri u ) {
try {
if( uri != null && !uri.getHost().equals( u.getHost() ) )
return null;
if( theUserPass == null || theUserPass.isNotSet() )
theUserPass = new FTPCredentials( u.getUserInfo() );
setFTPMode( u );
if( ftp.connectAndLogin( u, theUserPass.getUserName(), theUserPass.getPassword(), false ) > 0 ) {
noHeartBeats = true;
return ftp.prepStore( u.getPath() );
}
} catch( Exception e ) {
Log.e( TAG, u.getPath(), e );
}
return null;
}
@Override
public void closeStream( Closeable s ) {
try {
noHeartBeats = false;
if( s != null )
s.close();
} catch( IOException e ) {
e.printStackTrace();
}
}
@Override
public IReciever getReceiver() {
return this;
}
}