package com.ghostsq.commander;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import com.ghostsq.commander.adapters.CA;
import com.ghostsq.commander.adapters.CommanderAdapter;
import com.ghostsq.commander.adapters.CommanderAdapter.Item;
import com.ghostsq.commander.utils.Credentials;
import com.ghostsq.commander.utils.Utils;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.WifiLock;
import android.os.IBinder;
import android.util.Log;
public class StreamServer extends Service {
private final static String TAG = "StreamServer";
private final static String CRLF = "\r\n";
private Context ctx;
public ListenThread thread = null;
public WifiLock wifiLock = null;
public String last_host = null;
public CommanderAdapter ca = null;
// pass the credentials through static is better then through a foreign application
public static Credentials credentials = null;
@Override
public void onCreate() {
super.onCreate();
ctx = this; //getApplicationContext();
WifiManager manager = (WifiManager) getSystemService( Context.WIFI_SERVICE );
wifiLock = manager.createWifiLock( TAG );
wifiLock.setReferenceCounted( false );
}
@Override
public void onStart( Intent intent, int start_id ) {
super.onStart( intent, start_id );
//Log.d( TAG, "onStart" );
if( thread == null ) {
//Log.d( TAG, "Starting the server thread" );
thread = new ListenThread();
thread.start();
getBaseContext();
}
}
@Override
public void onDestroy() {
super.onDestroy();
//Log.d( TAG, "onDestroy" );
if( thread != null && thread.isAlive() ) {
thread.close();
thread.interrupt();
try {
thread.join( 10000 );
}
catch( InterruptedException e ) {
e.printStackTrace();
}
if( thread.isAlive() )
Log.e( TAG, "Listen tread has ignored the interruption" );
}
}
private class ListenThread extends Thread {
private final static String TAG = "GCSS.ListenThread";
private Thread stream_thread;
public ServerSocket ss = null;
public long lastUsed = System.currentTimeMillis();
public void run() {
try {
//Log.d( TAG, "started" );
setName( TAG );
setPriority( Thread.MIN_PRIORITY );
new Thread( new Runnable() {
@Override
public void run() {
while( true ) {
try {
synchronized( ListenThread.this ) {
final int max_idle = 100000;
ListenThread.this.wait( max_idle );
//Log.d( TAG, "Checking the idle time... last used: " + (System.currentTimeMillis()-lastUsed) + "ms ago " );
if( System.currentTimeMillis() - max_idle > lastUsed ) {
//Log.d( TAG, "Time to closer the listen thread" );
ListenThread.this.close();
break;
}
}
} catch( InterruptedException e ) {
e.printStackTrace();
}
}
//Log.d( TAG, "Closer thread stopped" );
}
}, "Closer" ).start();
StreamServer.this.wifiLock.acquire();
//Log.d( TAG, "WiFi lock" );
synchronized( this ) {
ss = new ServerSocket( 5322 );
}
int count = 0;
while( !isInterrupted() ) {
//Log.d( TAG, "Listening for a new connection..." );
Socket data_socket = ss.accept();
//Log.d( TAG, "Connection accepted" );
if( data_socket != null && data_socket.isConnected() ) {
int tn = -1; //count++
stream_thread = new StreamingThread( data_socket, tn );
stream_thread.start();
}
touch();
}
}
catch( Exception e ) {
Log.w( TAG, "Exception", e );
}
finally {
StreamServer.this.wifiLock.release();
//Log.d( TAG, "WiFi lock release" );
this.close();
}
StreamServer.this.stopSelf();
}
public synchronized void touch() {
lastUsed = System.currentTimeMillis();
}
public synchronized void close() {
try {
if( ss != null ) {
ss.close();
ss = null;
}
if( stream_thread != null && stream_thread.isAlive() ) {
stream_thread.interrupt();
stream_thread = null;
}
if( ca != null ) {
ca.prepareToDestroy();
ca = null;
}
}
catch( IOException e ) {
e.printStackTrace();
}
}
};
private class StreamingThread extends Thread {
private final static String TAG = "GCSS.StreamingThread";
private Socket data_socket;
private int num_id;
private boolean l = false;
public StreamingThread( Socket data_socket_, int num_id_ ) {
data_socket = data_socket_;
num_id = num_id_;
l = num_id >= 0;
}
private void Log( String s ) {
if( l ) Log.d( TAG, "" + num_id + ": " + s );
}
public void run() {
InputStream is = null;
OutputStream os = null;
try {
if( l ) Log( "Thread started" );
setName( TAG );
if( data_socket == null || !data_socket.isConnected() ) {
Log.e( TAG, "Invalid data socked" );
return;
}
os = data_socket.getOutputStream();
if( os == null ) {
Log.e( TAG, "Can't get the output stream" );
return;
}
OutputStreamWriter osw = new OutputStreamWriter( os );
yield();
is = data_socket.getInputStream();
if( is == null ) {
Log.e( TAG, "Can't get the input stream" );
SendStatus( osw, 500 );
return;
}
InputStreamReader isr = new InputStreamReader( is );
BufferedReader br = new BufferedReader( isr );
String cmd = br.readLine();
if( !Utils.str( cmd ) ) {
Log.e( TAG, "Invalid HTTP input" );
SendStatus( osw, 400 );
return;
}
String[] parts = cmd.split( " " );
if( l ) Log( cmd );
if( parts.length <= 1 ) {
Log.e( TAG, "Invalid HTTP input" );
SendStatus( osw, 400 );
return;
}
String passed_uri_s = parts[1].substring( 1 );
if( !Utils.str( passed_uri_s ) ) {
Log.w( TAG, "No URI passed in the request" );
SendStatus( osw, 404 );
return;
}
Uri uri = Uri.parse( Uri.decode( passed_uri_s ) );
if( uri == null || !Utils.str( uri.getPath() ) ) {
Log.w( TAG, "Wrong URI passed in the request" );
SendStatus( osw, 404 );
return;
}
if( l ) Log( "Requested URI: " + uri );
long offset = 0;
while( br.ready() ) {
String hl = br.readLine();
if( hl != null ) {
if( l ) Log( hl );
if( hl.startsWith( "Range: bytes=" ) ) {
int end = hl.indexOf( '-', 13 );
String range_s = hl.substring( 13, end );
try {
offset = Long.parseLong( range_s );
} catch( NumberFormatException nfe ) {}
}
}
}
String scheme = uri.getScheme();
int ca_type = CA.GetAdapterTypeId( scheme );
String host = uri.getHost();
if( ca != null ) {
if( ca.getType() != ca_type )
throw new Exception( "Can't switch to different adapter types: " + ca.getType() + "!=" + ca_type );
}
else {
ca = CA.CreateAdapterInstance( ca_type, ctx );
if( ca == null ) {
Log.e( TAG, "Can't create the adapter for: " + scheme );
SendStatus( osw, 500 );
return;
}
ca.Init( null );
if( l ) Log( "Adapter is created" );
}
last_host = host;
if( credentials != null )
ca.setCredentials( credentials );
Item item = ca.getItem( uri );
if( item == null ) {
Log.e( TAG, "Can't get the item for " + uri );
SendStatus( osw, 404 );
return;
}
InputStream cs = ca.getContent( uri, offset );
if( cs == null ) {
Log.e( TAG, "Can't get the content for " + uri );
SendStatus( osw, 500 );
return;
}
if( offset > 0 && item != null ) {
SendStatus( osw, 206 );
} else {
SendStatus( osw, 200 );
}
String fn = "zip".equals( scheme ) ? uri.getFragment() : uri.getLastPathSegment();
if( fn != null ) {
String ext = Utils.getFileExt( fn );
String mime = Utils.getMimeByExt( ext );
if( l ) Log( "Content-Type: " + mime );
osw.write( "Content-Type: " + mime + CRLF );
}
else
osw.write( "Content-Type: application/octet-stream" + CRLF );
String content_range = "Content-Range: bytes ";
String content_length = "Content-Length: ";
if( offset == 0 ) {
content_length += item.size;
content_range += "0-" + (item.size-1) + "/" + item.size;
}
else {
content_length += (item.size - offset);
content_range += offset + "-" + (item.size-1) + "/" + item.size;
}
osw.write( content_length + CRLF );
osw.write( content_range + CRLF );
if( l ) Log( content_length );
if( l ) Log( content_range );
osw.write( "Connection: close" + CRLF );
osw.write( CRLF );
osw.flush();
ReaderThread rt = new ReaderThread( cs, num_id );
rt.start();
setPriority( Thread.MAX_PRIORITY );
int count = 0;
while( rt.isAlive() ) {
try {
if( br.ready() )
if( l ) Log( "HTTP additional command arrived!!! " + br.readLine() );
thread.touch();
byte[] out_buf = rt.getOutputBuffer();
if( out_buf == null ) break;
int n = rt.GetDataSize();
if( n < 0 )
break;
if( l ) Log( " W..." );
os.write( out_buf, 0, n );
if( l ) Log( " ...W " + n + "/" + ( count += n ) );
rt.doneOutput( false );
}
catch( Exception e ) {
if( l ) Log( "write exception: " + e.getMessage() );
rt.doneOutput( true );
break;
}
}
ca.closeStream( cs );
//rt.interrupt();
if( l ) Log( "----------- done -------------" );
}
catch( Exception e ) {
Log.e( TAG, "Exception", e );
}
finally {
if( l ) Log( "Thread exits" );
try {
if( is != null ) is.close();
if( os != null ) os.close();
}
catch( IOException e ) {
Log.e( TAG, "Exception on Closing", e );
}
}
}
private void SendStatus( OutputStreamWriter osw, int code ) throws IOException {
final String http = "HTTP/1.1 ";
String descr;
switch( code ) {
case 200: descr = "OK"; break;
case 206: descr = "Partial Content"; break;
case 400: descr = "Invalid"; break;
case 404: descr = "Not found"; break;
case 416: descr = "Bad Requested Range"; break;
case 500: descr = "Server error"; break;
default: descr = "";
}
String resp = http + code + " " + descr;
osw.write( resp + CRLF );
if( l ) Log( resp );
Date date = new Date();
osw.write( "Date: " + date + CRLF );
if( l ) Log( "Date: " + date + CRLF );
}
};
class ReaderThread extends Thread {
private final static String TAG = "GCSS.RT";
private InputStream is;
private int roller = 0, chunk = 4096;
private final static int MAX = 32768; //524288
private byte[][] bufs = { new byte[MAX], new byte[MAX] };
private byte[] out_buf = null;
private int data_size = 0;
private int num_id;
private boolean stop = false;
private boolean l = false;
public ReaderThread( InputStream is_, int num_id_ ) {
is = is_;
setName( TAG );
num_id = num_id_;
l = num_id >= 0;
}
private void Log( String s ) {
if( l ) Log.d( TAG, "" + num_id + ": " + s );
}
public void run() {
try {
setPriority( Thread.MAX_PRIORITY );
int count = 0;
while( !stop ) {
byte[] inp_buf = bufs[roller++ % 2];
if( l ) Log( "R..." );
int has_read = 0;
has_read = is.read( inp_buf, 0, chunk );
if( stop || has_read < 0 )
break;
if( has_read == chunk && chunk < MAX )
chunk <<= 1;
if( chunk > MAX )
chunk = MAX;
if( l ) Log( "...R " + has_read + "/" + ( count += has_read ) );
synchronized( this ) {
int wcount = 0;
if( l ) Log( "?.." );
while( out_buf != null ) {
wait( 10 );
wcount += 10;
}
if( l ) Log( "...! (" + wcount + "ms)" );
out_buf = inp_buf;
data_size = has_read;
if( l ) Log( "O=I ->" );
notify();
}
}
} catch( Throwable e ) {
Log.e( TAG, "" + num_id + ": ", e );
}
if( l ) Log( "The read thread is done!" );
}
public synchronized byte[] getOutputBuffer() throws InterruptedException {
int wcount = 0;
if( l ) Log( " ?.." );
while( out_buf == null && this.isAlive() ) {
wait( 10 );
wcount += 10;
}
if( out_buf != null )
if( l ) Log( " ..! (" + wcount + "ms)" );
else
if( l ) Log( "X" );
return out_buf;
}
public int GetDataSize() {
int ds = data_size;
data_size = 0;
return ds;
}
public synchronized void doneOutput( boolean stop_ ) {
stop = stop_;
out_buf = null;
if( l ) Log( " <- O done" );
notify();
}
};
@Override
public IBinder onBind( Intent intent ) {
return null;
}
}