• 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
    ");
    			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
    ");
    		}
    		
    		String attemptPassword = getParameter(input, true); // silent
    		String attemptUsername = sessionThread.account.getUsername();
    		if(attemptUsername == null) {
    			sessionThread.writeString("503 Must send USER first
    ");
    			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
    ");
    			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.
    ");
    			sessionThread.authAttempt(false);
    		}
    	}

    注:
    (1)、通过SharedPreferences获取配置信息,判断是否配置为匿名访问,如果是,则不再校验用户名和密码。
    (2)、sessionThread.writeString("230 Access granted ")和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的实现。
    转载请注明出处:


  • 相关阅读:
    一帧
    神经网络动物园
    持续集成:Jenkins插件Blue Ocean介绍
    JZOJ 2022.07.18【提高组A】模拟
    CF1093G Multidimensional Queries
    P5491 【模板】二次剩余
    AC 自动机上 DP
    A*算法小记
    [COCI2015]Divljak
    Trie 的一类应用
  • 原文地址:https://www.cnblogs.com/sparkleDai/p/7605030.html
Copyright © 2020-2023  润新知