您的位置:首页 > 移动开发 > Swift

Android基于SwiFTP开源库的FTP实现(FTP匿名登录)

2014-03-06 10:39 351 查看
FTP是基于FTP协议来实现文件的管理,理论上只要将协议逐个实现,就可以实现一个FTP的服务端了,但需要一些时间,而且还是个体力活。现在有了SwiFTP的开源库,只要对其稍加改造,就可以将手机快速变成一个FTP的服务器。这里提供一个SwiFTP的下载地址https://github.com/sparkleDai/swiftp

我们先来看看SwiFTP源码中实现的效果图:





前一个图是FTP的配置,后一个图是FTP服务器控制。这两个画面,一看就是一嘛黑的,老外貌似比较喜欢这种风格。下面是修改后的效果图。





修改后的FTP服务端只有一个启动/停止的按钮,其他的都采用默认设置。下面我们来看具体的修改步骤。

1、跳过配置画面

SwiFTP一开启就会跳到配置画面,经查代码,发现是在ServerControlActivity的OnResume中跳转过来的,所以只要想办法跳过去就可以了。要跳过去,有几种方式,最直接的就是屏蔽掉。不过,考虑到原来有配置画面,这里可能需要留一个口来增加设置,所以这里我增加了一个配置函数,将所需要的配置项配置成了默认项,代码如下。(我将ServerControlActivity改成了MainActivity)

MainActivity.java
protected void onResume() {
super.onResume();
// If the required preferences are not present, launch the configuration
// Activity.
configSetting();
if (!requiredSettingsDefined()) {
launchCONFIG_KEYS();
}
UiUpdater.registerClient(handler);
updateUi();
// Register to receive wifi status broadcasts
myLog.l(Log.DEBUG, "Registered for wifi updates");
this.registerReceiver(wifiReceiver, new IntentFilter(
WifiManager.WIFI_STATE_CHANGED_ACTION));
}

private void configSetting() {
// Validation was successful, save the settings object
SharedPreferences settings = getSharedPreferences(
Defaults.getSettingsName(), Defaults.getSettingsMode());
SharedPreferences.Editor editor = settings.edit();

editor.putString(CONFIG_KEYS.USERNAME, Defaults.username);
editor.putString(CONFIG_KEYS.PASSWORD, Defaults.password);
editor.putInt(CONFIG_KEYS.PORTNUM, 2121);
editor.putString(CONFIG_KEYS.CHROOTDIR, Defaults.chrootDir);
editor.putBoolean(CONFIG_KEYS.ACCEPT_WIFI, Defaults.acceptWifi);
editor.putBoolean(CONFIG_KEYS.ACCEPT_NET, Defaults.acceptNet);
editor.putBoolean(CONFIG_KEYS.STAY_AWAKE, Defaults.stayAwake);
editor.putBoolean(CONFIG_KEYS.IS_ANONYMOUS, Defaults.isAnonymous);
editor.commit();

}


CONFIG_KEYS.java
package com.sparkle.ftp;

public class CONFIG_KEYS {
public final static String USERNAME = "username";
public final static String PASSWORD = "password";
public final static String PORTNUM = "portNum";
public final static String CHROOTDIR = "chrootDir";
public final static String ACCEPT_WIFI = "allowWifi";
public final static String ACCEPT_NET = "allowNet";
public final static String STAY_AWAKE = "stayAwake";
public final static String IS_ANONYMOUS="isAnonymous";
}


Defaults.java部分代码
protected static int inputBufferSize = 256;
protected static int dataChunkSize = 65536;  // do file I/O in 64k chunks
protected static int sessionMonitorScrollBack = 10;
protected static int serverLogScrollBack = 10;
protected static int uiLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
protected static int consoleLogLevel = Defaults.release ? Log.INFO : Log.DEBUG;
protected static String settingsName = "FTP";
public static String username = "Anonynous";
public static String password = "";
protected static int portNumber = 2121;
//	protected static int ipRetrievalAttempts = 5;
public static final int tcpConnectionBacklog = 5;
public static final String chrootDir = Environment.getExternalStorageDirectory().getAbsolutePath();
public static final boolean acceptWifi = true;
public static final boolean acceptNet = false; // don't incur bandwidth charges
public static final boolean stayAwake = false;
public static final boolean isAnonymous=true;
public static final int REMOTE_PROXY_PORT = 2222;
public static final String STRING_ENCODING = "UTF-8";
public static final int SO_TIMEOUT_MS = 30000; // socket timeout millis


注:
(1)、Activity的生命周期是OnCreate->OnStart->OnResume->OnPause->OnStop->OnDestory。在activity1跳转到另一个activity2后,如果跳转时activity1没有finish掉,那么activity2关闭跳后,activity1会从activity堆栈中重新唤醒,也就是会调用OnResume。所以在配置的activity中,如果没有配置,当点cancel,配置的activity虽然被关闭了,但是当回到服务控制的activity后,又激活了OnResume,然后判断配置的情况,如果不符合,又会启动配置的activity。所以会发现,如果没有配置,即使点cancel也没有作用,不知道的还以为中镖了。
(2)、SharedPreference是共享数据的一种方式,可以实现跨activity的数据共享,是一个轻量的存储方式,本质上是一个xml的key-value对。对其修改数据时,需要请求edit,然后修改数据,最后还要commit。这个和提交代码到git/svn等类似。
(3)、CONFIG_KEYS是将原来配置的activity中的一些key写到了这个类中。
(4)、Defaults中增加了username、password、isAnonymous。其中isAnonymous是为了实现FTP的匿名访问而增设的一个配置项。
(5)、由于手机内存中的文件不一定都能访问,所以FTP默认的目录设置到了SD卡中。在不同的设备中,对于SD卡的路径有所不同,所以采用了系统自带的函数来实现,即chrootDir = Environment.getExternalStorageDirectory().getAbsolutePath()。

2、实现匿名访问

现在将服务端跑起来后,已经可以正常访问了,不过每次访问的时候都会弹出用户名和密码的输入框,让人很烦,所以就想着法子的屏蔽掉。既然会弹出这个框,程序中肯定会有对应的判断项,经过查找,发现是在FTP中的PASS命令中实现的。所以对CmdPASS.java作了些修改,修改部分的代码如下。

public void run() {
// User must have already executed a USER command to
// populate the Account object's username
myLog.l(Log.DEBUG, "Executing PASS");
Context ctx = Globals.getContext();
SharedPreferences settings = ctx.getSharedPreferences(
Defaults.getSettingsName(), Defaults.getSettingsMode());
boolean isAnonymous=settings.getBoolean(CONFIG_KEYS.IS_ANONYMOUS,false);
if(isAnonymous)
{
sessionThread.writeString("230 Access granted\r\n");
myLog.l(Log.INFO, "Anonymous visit!");
sessionThread.authAttempt(true);
return;
}

if(ctx == null) {
// This will probably never happen, since the global
// context is configured by the Service
myLog.l(Log.ERROR, "No global context in PASS\r\n");
}

String attemptPassword = getParameter(input, true); // silent
String attemptUsername = sessionThread.account.getUsername();
if(attemptUsername == null) {
sessionThread.writeString("503 Must send USER first\r\n");
return;
}

String username = settings.getString("username", null);
String password = settings.getString("password", null);
if(username == null || password == null) {
myLog.l(Log.ERROR, "Username or password misconfigured");
sessionThread.writeString("500 Internal error during authentication");
} else if(username.equals(attemptUsername) &&
password.equals(attemptPassword)) {
sessionThread.writeString("230 Access granted\r\n");
myLog.l(Log.INFO, "User " + username + " password verified");
sessionThread.authAttempt(true);
} else {
try {
// If the login failed, sleep for one second to foil
// brute force attacks
Thread.sleep(1000);
} catch(InterruptedException e) {}
myLog.l(Log.INFO, "Failed authentication");
sessionThread.writeString("530 Login incorrect.\r\n");
sessionThread.authAttempt(false);
}
}


注:
(1)、通过SharedPreferences获取配置信息,判断是否配置为匿名访问,如果是,则不再校验用户名和密码。
(2)、sessionThread.writeString("230 Access granted\r\n")和sessionThread.authAttempt(true)是授权是否允许的部分,true是允许,false是不允许。

3、界面的实现

对于界面,重新写了一个,下面是界面的代码。
MainActivity.java
package com.sparkle.ftp;

import java.net.InetAddress;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

private Button _startStop_Button=null;
private TextView _ip_TextView=null;
private ImageView _ftpStatus_imageView=null;

protected MyLog myLog = new MyLog(this.getClass().getName());

protected Context activityContext = this;

@SuppressLint("HandlerLeak")
public Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0: // We are being told to do a UI update
// If more than one UI update is queued up, we only need to do
// one.
removeMessages(0);
updateUi();
break;
case 1: // We are being told to display an error message
removeMessages(1);
}
}
};

public MainActivity() {

}

/** Called with the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// Request no title bar on our window
//requestWindowFeature(Window.FEATURE_NO_TITLE);

// Set the application-wide context global, if not already set
Context myContext = Globals.getContext();
if (myContext == null) {
myContext = getApplicationContext();
if (myContext == null) {
throw new NullPointerException("Null context!?!?!?");
}
Globals.setContext(myContext);
}
// Inflate our UI from its XML layout description.
setContentView(R.layout.main_activity);

_ip_TextView = (TextView) findViewById(R.id.ip_address);
_ftpStatus_imageView=(ImageView)findViewById(R.id.ftp_status);
_startStop_Button = (Button) findViewById(R.id.start_stop_button);

_startStop_Button.setOnClickListener(startStopListener);

}

/**
* Whenever we regain focus, we should update the button text depending on
* the state of the server service.
*/
protected void onStart() {
super.onStart();
UiUpdater.registerClient(handler);
updateUi();
}

protected void onResume() {
super.onResume();
// If the required preferences are not present, launch the configuration
// Activity.
configSetting();
if (!requiredSettingsDefined()) {
launchCONFIG_KEYS();
}
UiUpdater.registerClient(handler);
updateUi();
// Register to receive wifi status broadcasts
myLog.l(Log.DEBUG, "Registered for wifi updates");
this.registerReceiver(wifiReceiver, new IntentFilter(
WifiManager.WIFI_STATE_CHANGED_ACTION));
}

/*
* Whenever we lose focus, we must unregister from UI update messages from
* the FTPServerService, because we may be deallocated.
*/
protected void onPause() {
super.onPause();
UiUpdater.unregisterClient(handler);
myLog.l(Log.DEBUG, "Unregistered for wifi updates");
this.unregisterReceiver(wifiReceiver);
}

protected void onStop() {
super.onStop();
UiUpdater.unregisterClient(handler);
}

protected void onDestroy() {
super.onDestroy();
UiUpdater.unregisterClient(handler);
}

private void configSetting() {
// Validation was successful, save the settings object
SharedPreferences settings = getSharedPreferences(
Defaults.getSettingsName(), Defaults.getSettingsMode());
SharedPreferences.Editor editor = settings.edit();

editor.putString(CONFIG_KEYS.USERNAME, Defaults.username);
editor.putString(CONFIG_KEYS.PASSWORD, Defaults.password);
editor.putInt(CONFIG_KEYS.PORTNUM, 2121);
editor.putString(CONFIG_KEYS.CHROOTDIR, Defaults.chrootDir);
editor.putBoolean(CONFIG_KEYS.ACCEPT_WIFI, Defaults.acceptWifi);
editor.putBoolean(CONFIG_KEYS.ACCEPT_NET, Defaults.acceptNet);
editor.putBoolean(CONFIG_KEYS.STAY_AWAKE, Defaults.stayAwake);
editor.putBoolean(CONFIG_KEYS.IS_ANONYMOUS, Defaults.isAnonymous);
editor.commit();

}

/**
* This will be called by the static UiUpdater whenever the service has
* changed state in a way that requires us to update our UI.
*
* We can't use any myLog.l() calls in this function, because that will
* trigger an endless loop of UI updates.
*/
public void updateUi() {
WifiManager wifiMgr = (WifiManager) getSystemService(Context.WIFI_SERVICE);
int wifiState = wifiMgr.getWifiState();
myLog.l(Log.DEBUG, "Updating UI", true);
if (FTPServerService.isRunning()) {
myLog.l(Log.DEBUG, "updateUi: server is running", true);
// Put correct text in start/stop button
_startStop_Button.setText(R.string.stop_server);

// Fill in wifi status and address
InetAddress address = FTPServerService.getWifiIp();
if (address != null) {
_ip_TextView.setText("ftp://" + address.getHostAddress() + ":"
+ FTPServerService.getPort() + "/");
_ftpStatus_imageView.setBackgroundDrawable(getResources().getDrawable(R.drawable.ftp_on));
} else {
myLog.l(Log.VERBOSE, "Null address from getServerAddress()",
true);
_ip_TextView.setText(R.string.cant_get_url);
_ftpStatus_imageView.setBackgroundDrawable(getResources().getDrawable(R.drawable.ftp_off));
}
} else {
myLog.l(Log.DEBUG, "updateUi: server is not running", true);
// Update the start/stop button to show the correct text
_startStop_Button.setText(R.string.start_server);
_ip_TextView.setText(R.string.no_url_yet);
_startStop_Button.setText(R.string.start_server);
_ftpStatus_imageView.setBackgroundDrawable(getResources().getDrawable(R.drawable.ftp_off));
}
}

/**
* Called when your activity's options menu needs to be created.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
return true;
}

/**
* Called right before your activity's option menu is displayed.
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
return true;
}

/**
* Called when a menu item is selected.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
/*
* switch (item.getItemId()) { case BACK_ID: finish(); return true; case
* CLEAR_ID: mEditor.setText(""); return true; }
*/

return super.onOptionsItemSelected(item);
}

OnClickListener startStopListener = new OnClickListener() {
public void onClick(View v) {
Context context = getApplicationContext();
Intent intent = new Intent(context, FTPServerService.class);
/*
* In order to choose whether to stop or start the server, we check
* the text on the button to see which action the user was
* expecting.
*/
String startString = getString(R.string.start_server);
String stopString = getString(R.string.stop_server);
String buttonText = _startStop_Button.getText().toString();
if (buttonText.equals(startString)) {
/* The button had the "start server" text */
if (!FTPServerService.isRunning()) {
warnIfNoExternalStorage();
context.startService(intent);
}
} else if (buttonText.equals(stopString)) {
/*
* The button had the "stop server" text. We stop the server
* now.
*/
context.stopService(intent);
} else {
// Do nothing
myLog.l(Log.ERROR, "Unrecognized start/stop text");
}
}
};

private void warnIfNoExternalStorage() {
String storageState = Environment.getExternalStorageState();
if (!storageState.equals(Environment.MEDIA_MOUNTED)) {
myLog.i("Warning due to storage state " + storageState);
Toast toast = Toast.makeText(this, R.string.storage_warning,
Toast.LENGTH_LONG);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
}
}

OnClickListener addUserListener = new OnClickListener() {
public void onClick(View v) {
myLog.l(Log.INFO, "Add user stub");
}
};

OnClickListener manageUsersListener = new OnClickListener() {
public void onClick(View v) {
myLog.l(Log.INFO, "Manage users stub");
}
};

OnClickListener serverOptionsListener = new OnClickListener() {
public void onClick(View v) {
myLog.l(Log.INFO, "Server options stub");
}
};

DialogInterface.OnClickListener ignoreDialogListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
};

/**
* A call-back for when the user presses the "setup" button.
*/
OnClickListener setupListener = new OnClickListener() {
public void onClick(View v) {
launchCONFIG_KEYS();
}
};

void launchCONFIG_KEYS() {
if (!requiredSettingsDefined()) {
Toast toast = Toast.makeText(this, R.string.must_config,
Toast.LENGTH_SHORT);
toast.setGravity(Gravity.CENTER, 0, 0);
toast.show();
}
Intent intent = new Intent(activityContext, CONFIG_KEYS.class);
startActivity(intent);
}

/**
* A callback for when the user toggles the session monitor on or off
*/
OnClickListener sessionMonitorCheckBoxListener = new OnClickListener() {
public void onClick(View v) {
// Trigger a UI update message to our Activity
UiUpdater.updateClients();
// updateUi();
}
};

/**
* A callback for when the user toggles the server log on or off
*/
OnClickListener serverLogCheckBoxListener = new OnClickListener() {
public void onClick(View v) {
// Trigger a UI update message to our Activity
UiUpdater.updateClients();
// updateUi();
}
};

BroadcastReceiver wifiReceiver = new BroadcastReceiver() {
public void onReceive(Context ctx, Intent intent) {
myLog.l(Log.DEBUG, "Wifi status broadcast received");
updateUi();
}
};

boolean requiredSettingsDefined() {
SharedPreferences settings = getSharedPreferences(
Defaults.getSettingsName(), Defaults.getSettingsMode());
String username = settings.getString("username", null);
String password = settings.getString("password", null);
if (username == null || password == null) {
return false;
} else {
return true;
}
}

/**
* Get the settings from the FTPServerService if it's running, otherwise
* load the settings directly from persistent storage.
*/
SharedPreferences getSettings() {
SharedPreferences settings = FTPServerService.getSettings();
if (settings != null) {
return settings;
} else {
return this.getPreferences(MODE_PRIVATE);
}
}

}


main_activity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity" >

<LinearLayout
android:id="@+id/content"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:gravity="center"
android:orientation="vertical" >

<ImageView
android:id="@+id/ftp_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ftp_off" />

<TextView
android:id="@+id/ip_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_url_yet"
android:textColor="#000000"
android:textSize="25sp" />
</LinearLayout>

<LinearLayout
android:id="@+id/bottom_panel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="30dp"
android:gravity="center"
android:orientation="horizontal" >

<Button
android:id="@+id/start_stop_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start_server"
android:background="@drawable/button_bg"
android:textSize="40sp" />
</LinearLayout>

</RelativeLayout>


styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="ActionButton">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textAppearance">@style/TextAppearance.ActionButton</item>
</style>

<style name="TextAppearance" parent="android:TextAppearance"></style>

<style name="AppBaseTheme" parent="android:Theme.Light"></style>

<style name="TextAppearance.ActionButton">
<item name="android:textStyle">italic</item>
</style>

<style name="AppTheme" parent="AppBaseTheme">
<item name="android:windowTitleBackgroundStyle">@style/CustomWindowTitleBackground</item>
<item name="android:windowTitleStyle">@style/CustomWindowTitle</item>
</style>

<style name="CustomWindowTitleBackground">
<item name="android:background">@drawable/title_bg_blue</item>
</style>

<style name="CustomWindowTitle" parent="AppBaseTheme">
<item name="android:textAppearance">@style/CustomWindowTitleText</item>
<item name="android:layout_gravity">center</item>
</style>

<style name="CustomWindowTitleText" parent="android:TextAppearance.WindowTitle">
<item name="android:textColor">#ffffff</item>
<item name="android:textSize">14sp</item>
<item name="android:textStyle">bold</item>
</style>

</resources>


strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

<string name="name_version">电脑管理手机文件 1.1.1</string>
<string name="app_name">电脑管理手机文件</string>
<string name="start_server"><b>启动</b></string>
<string name="stop_server"><b>停止</b></string>
<string name="no_url_yet"></string>
<string name="my_url_is">电脑上输入:</string>
<string name="cant_get_url">无法获取IP</string>
<string name="storage_warning">没有SD卡</string>
<string name="must_config">FTP未配置</string>

</resources>


FTPServerService.java

package com.sparkle.ftp;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;

public class FTPServerService extends Service implements Runnable {
protected static Thread serverThread = null;
protected boolean shouldExit = false;
protected MyLog myLog = new MyLog(getClass().getName());
protected static MyLog staticLog =
new MyLog(FTPServerService.class.getName());

public static final int BACKLOG = 21;
public static final int MAX_SESSIONS = 5;
public static final String WAKE_LOCK_TAG = "SwiFTP";

//protected ServerSocketChannel wifiSocket;
protected ServerSocket listenSocket;
protected static WifiLock wifiLock = null;

//	protected static InetAddress serverAddress = null;

protected static List<String> sessionMonitor = new ArrayList<String>();
protected static List<String> serverLog = new ArrayList<String>();
protected static int uiLogLevel = Defaults.getUiLogLevel();

// The server thread will check this often to look for incoming
// connections. We are forced to use non-blocking accept() and polling
// because we cannot wait forever in accept() if we want to be able
// to receive an exit signal and cleanly exit.
public static final int WAKE_INTERVAL_MS = 1000; // milliseconds

protected static int port;
protected static boolean acceptWifi;
protected static boolean acceptNet;
protected static boolean fullWake;

private TcpListener wifiListener = null;
private List<SessionThread> sessionThreads = new ArrayList<SessionThread>();

private static SharedPreferences settings = null;

NotificationManager notificationMgr = null;
PowerManager.WakeLock wakeLock;

public FTPServerService() {
}

public IBinder onBind(Intent intent) {
// We don't implement this functionality, so ignore it
return null;
}

@Override
public void onCreate() {
myLog.l(Log.DEBUG, "SwiFTP server created");
// Set the application-wide context global, if not already set
Context myContext = Globals.getContext();
if(myContext == null) {
myContext = getApplicationContext();
if(myContext != null) {
Globals.setContext(myContext);
}
}
return;
}

public void onStart(Intent intent, int startId ){
super.onStart(intent, startId);

shouldExit = false;
int attempts = 10;
// The previous server thread may still be cleaning up, wait for it
// to finish.
while(serverThread != null) {
myLog.l(Log.WARN, "Won't start, server thread exists");
if(attempts > 0) {
attempts--;
Util.sleepIgnoreInterupt(1000);
} else {
myLog.l(Log.ERROR, "Server thread already exists");
return;
}
}
myLog.l(Log.DEBUG, "Creating server thread");
serverThread = new Thread(this);
serverThread.start();

// todo: we should broadcast an intent to inform anyone who cares
}

public static boolean isRunning() {
// return true if and only if a server Thread is running
if(serverThread == null) {
staticLog.l(Log.DEBUG, "Server is not running (null serverThread)");
return false;
}
if(!serverThread.isAlive()) {
staticLog.l(Log.DEBUG, "serverThread non-null but !isAlive()");
} else {
staticLog.l(Log.DEBUG, "Server is alive");
}
return true;
}

public void onDestroy() {
myLog.l(Log.INFO, "onDestroy() Stopping server");
shouldExit = true;
if(serverThread == null) {
myLog.l(Log.WARN, "Stopping with null serverThread");
return;
} else {
serverThread.interrupt();
try {
serverThread.join(10000);  // wait 10 sec for server thread to finish
} catch (InterruptedException e) {}
if(serverThread.isAlive()) {
myLog.l(Log.WARN, "Server thread failed to exit");
// it may still exit eventually if we just leave the
// shouldExit flag set
} else {
myLog.d("serverThread join()ed ok");
serverThread = null;
}
}
try {
if(listenSocket != null) {
myLog.l(Log.INFO, "Closing listenSocket");
listenSocket.close();
}
} catch (IOException e) {}

UiUpdater.updateClients();
if(wifiLock != null) {
wifiLock.release();
wifiLock = null;
}
clearNotification();
myLog.d("FTPServerService.onDestroy() finished");
}

private boolean loadSettings() {
myLog.l(Log.DEBUG, "Loading settings");
settings = getSharedPreferences(
Defaults.getSettingsName(), Defaults.getSettingsMode());
port = settings.getInt("portNum", Defaults.portNumber);
if(port == 0) {
// If port number from settings is invalid, use the default
port = Defaults.portNumber;
}
myLog.l(Log.DEBUG, "Using port " + port);

acceptNet = settings.getBoolean(CONFIG_KEYS.ACCEPT_NET,
Defaults.acceptNet);
acceptWifi = settings.getBoolean(CONFIG_KEYS.ACCEPT_WIFI,
Defaults.acceptWifi);
fullWake = settings.getBoolean(CONFIG_KEYS.STAY_AWAKE,
Defaults.stayAwake);

// The username, password, and chrootDir are just checked for sanity

String username = settings.getString(CONFIG_KEYS.USERNAME, Defaults.username);
String password = settings.getString(CONFIG_KEYS.PASSWORD, Defaults.password);
String chrootDir = settings.getString(CONFIG_KEYS.CHROOTDIR,
Defaults.chrootDir);

validateBlock: {
if(username == null || password == null) {
myLog.l(Log.ERROR, "Username or password is invalid");
break validateBlock;
}
File chrootDirAsFile = new File(chrootDir);
if(!chrootDirAsFile.isDirectory()) {
myLog.l(Log.ERROR, "Chroot dir is invalid");
break validateBlock;
}
Globals.setChrootDir(chrootDirAsFile);
Globals.setUsername(username);
return true;
}
// We reach here if the settings were not sane
return false;
}

// This opens a listening socket on all interfaces.
void setupListener() throws IOException {
listenSocket = new ServerSocket();
listenSocket.setReuseAddress(true);
listenSocket.bind(new InetSocketAddress(port));
}

private void setupNotification() {
// http://developer.android.com/guide/topics/ui/notifiers/notifications.html 
// Get NotificationManager reference
String ns = Context.NOTIFICATION_SERVICE;
notificationMgr = (NotificationManager) getSystemService(ns);

// Instantiate a Notification
int icon = R.drawable.notification;

long when = System.currentTimeMillis();

}

private void clearNotification() {
if(notificationMgr == null) {
// Get NotificationManager reference
String ns = Context.NOTIFICATION_SERVICE;
notificationMgr = (NotificationManager) getSystemService(ns);
}
notificationMgr.cancelAll();
myLog.d("Cleared notification");
}

public void run() {
// The UI will want to check the server status to update its
// start/stop server button
int consecutiveProxyStartFailures = 0;
long proxyStartMillis = 0;

UiUpdater.updateClients();

myLog.l(Log.DEBUG, "Server thread running");

// set our members according to user preferences
if(!loadSettings()) {
// loadSettings returns false if settings are not sane
cleanupAndStopService();
return;
}

// Initialization of wifi
if(acceptWifi) {
// If configured to accept connections via wifi, then set up the socket
try {
setupListener();
} catch (IOException e) {
myLog.l(Log.WARN, "Error opening port, check your network connection.");
//				serverAddress = null;
cleanupAndStopService();
return;
}
takeWifiLock();
}
takeWakeLock();

myLog.l(Log.INFO, "SwiFTP server ready");
setupNotification();

// We should update the UI now that we have a socket open, so the UI
// can present the URL
UiUpdater.updateClients();

while(!shouldExit) {
if(acceptWifi) {
if(wifiListener != null) {
if(!wifiListener.isAlive()) {
myLog.l(Log.DEBUG, "Joining crashed wifiListener thread");
try {
wifiListener.join();
} catch (InterruptedException e) {}
wifiListener = null;
}
}
if(wifiListener == null) {
// Either our wifi listener hasn't been created yet, or has crashed,
// so spawn it
wifiListener = new TcpListener(listenSocket, this);
wifiListener.start();
}
}

try {
// todo: think about using ServerSocket, and just closing
// the main socket to send an exit signal
Thread.sleep(WAKE_INTERVAL_MS);
} catch(InterruptedException e) {
myLog.l(Log.DEBUG, "Thread interrupted");
}
}

terminateAllSessions();

if(wifiListener != null) {
wifiListener.quit();
wifiListener = null;
}
shouldExit = false; // we handled the exit flag, so reset it to acknowledge
myLog.l(Log.DEBUG, "Exiting cleanly, returning from run()");
clearNotification();
releaseWakeLock();
releaseWifiLock();
}

private void terminateAllSessions() {
myLog.i("Terminating " + sessionThreads.size() + " session thread(s)");
synchronized(this) {
for(SessionThread sessionThread : sessionThreads) {
if(sessionThread != null) {
sessionThread.closeDataSocket();
sessionThread.closeSocket();
}
}
}
}

public void cleanupAndStopService() {
// Call the Android Service shutdown function
Context context = getApplicationContext();
Intent intent = new Intent(context,	FTPServerService.class);
context.stopService(intent);
releaseWifiLock();
releaseWakeLock();
clearNotification();
}

private void takeWakeLock() {
if(wakeLock == null) {
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);

// Many (all?) devices seem to not properly honor a PARTIAL_WAKE_LOCK,
// which should prevent CPU throttling. This has been
// well-complained-about on android-developers.
// For these devices, we have a config option to force the phone into a
// full wake lock.
if(fullWake) {
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK,
WAKE_LOCK_TAG);
} else {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WAKE_LOCK_TAG);
}
wakeLock.setReferenceCounted(false);
}
myLog.d("Acquiring wake lock");
wakeLock.acquire();
}

private void releaseWakeLock() {
myLog.d("Releasing wake lock");
if(wakeLock != null) {
wakeLock.release();
wakeLock = null;
myLog.d("Finished releasing wake lock");
} else {
myLog.i("Couldn't release null wake lock");
}
}

private void takeWifiLock() {
myLog.d("Taking wifi lock");
if(wifiLock == null) {
WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
wifiLock = manager.createWifiLock("SwiFTP");
wifiLock.setReferenceCounted(false);
}
wifiLock.acquire();
}

private void releaseWifiLock() {
myLog.d("Releasing wifi lock");
if(wifiLock != null) {
wifiLock.release();
wifiLock = null;
}
}

public void errorShutdown() {
myLog.l(Log.ERROR, "Service errorShutdown() called");
cleanupAndStopService();
}

/**
* Gets the IP address of the wifi connection.
* @return The integer IP address if wifi enabled, or null if not.
*/
public static InetAddress getWifiIp() {
Context myContext = Globals.getContext();
if(myContext == null) {
throw new NullPointerException("Global context is null");
}
WifiManager wifiMgr = (WifiManager)myContext
.getSystemService(Context.WIFI_SERVICE);
if(isWifiEnabled()) {
int ipAsInt = wifiMgr.getConnectionInfo().getIpAddress();
if(ipAsInt == 0) {
return null;
} else {
return Util.intToInet(ipAsInt);
}
} else {
return null;
}
}

public static boolean isWifiEnabled() {
Context myContext = Globals.getContext();
if(myContext == null) {
throw new NullPointerException("Global context is null");
}
WifiManager wifiMgr = (WifiManager)myContext
.getSystemService(Context.WIFI_SERVICE);
if(wifiMgr.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
return true;
} else {
return false;
}
}

public static List<String> getSessionMonitorContents() {
return new ArrayList<String>(sessionMonitor);
}

public static List<String> getServerLogContents() {
return new ArrayList<String>(serverLog);
}

public static void log(int msgLevel, String s) {
serverLog.add(s);
int maxSize = Defaults.getServerLogScrollBack();
while(serverLog.size() > maxSize) {
serverLog.remove(0);
}
//updateClients();
}

public static void updateClients() {
UiUpdater.updateClients();
}

public static void writeMonitor(boolean incoming, String s) {}

public static int getPort() {
return port;
}

public static void setPort(int port) {
FTPServerService.port = port;
}

/**
* The FTPServerService must know about all running session threads so they
* can be terminated on exit. Called when a new session is created.
*/
public void registerSessionThread(SessionThread newSession) {
// Before adding the new session thread, clean up any finished session
// threads that are present in the list.

// Since we're not allowed to modify the list while iterating over
// it, we construct a list in toBeRemoved of threads to remove
// later from the sessionThreads list.
synchronized(this) {
List <SessionThread> toBeRemoved = new ArrayList<SessionThread>();
for(SessionThread sessionThread : sessionThreads) {
if(!sessionThread.isAlive()) {
myLog.l(Log.DEBUG, "Cleaning up finished session...");
try {
sessionThread.join();
myLog.l(Log.DEBUG, "Thread joined");
toBeRemoved.add(sessionThread);
sessionThread.closeSocket(); // make sure socket closed
} catch (InterruptedException e) {
myLog.l(Log.DEBUG, "Interrupted while joining");
// We will try again in the next loop iteration
}
}
}
for(SessionThread removeThread : toBeRemoved) {
sessionThreads.remove(removeThread);
}

// Cleanup is complete. Now actually add the new thread to the list.
sessionThreads.add(newSession);
}
myLog.d("Registered session thread");
}

static public SharedPreferences getSettings() {
return settings;
}
}


注:
(1)、以上作修改部分的代码。
(2)、代码中使用到的图片没有贴上,有需要的可以自行补上。
(3)、去除了Proxy部分。

以上就是基于SwiFTP的实现。
转载请注明出处:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: