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

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的实现。
转载请注明出处:


转载于:https://www.cnblogs.com/sparkleDai/p/7605030.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值