<commit_before>package fm.libre.droid;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class LibreService extends Service implements OnBufferingUpdateListener, OnCompletionListener {
	private LibreServiceBinder serviceBinder = new LibreServiceBinder();
	private Playlist playlist;
	private String sessionKey;
	private String scrobbleKey;
	private String serviceToken; 
	private String stationName;
	private int currentSong;
	private int currentPage = 0;
	private boolean loggedIn = false;
	private boolean playing = false;
	private boolean buffering = false;
	private MediaPlayer mp;
	
	@Override
	public void onCreate() {
		super.onCreate();
		this.mp = new MediaPlayer();
		this.currentSong = 0;
        this.playlist = new Playlist();
        this.sessionKey = "";
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		return serviceBinder;
	}

	@Override
	public void onDestroy() {
		this.mp.release();
	}
	
	public String httpGet(String url) throws URISyntaxException, ClientProtocolException, IOException {
    	DefaultHttpClient client = new DefaultHttpClient();
    	URI uri = new URI(url);
		HttpGet method = new HttpGet(uri);
		HttpResponse res = client.execute(method);
		ByteArrayOutputStream outstream = new ByteArrayOutputStream();  
		res.getEntity().writeTo(outstream);
		return outstream.toString();
    }
	
	public String httpPost(String url, String... params) throws URISyntaxException, ClientProtocolException, IOException {
    	DefaultHttpClient client = new DefaultHttpClient();
    	URI uri = new URI(url);
    	HttpPost method = new HttpPost(uri);
    	List<NameValuePair> paramPairs = new ArrayList<NameValuePair>(2);
    	for (int i = 0; i < params.length; i+=2) {
    		paramPairs.add(new BasicNameValuePair(params[i], params[i+1]));  
    	}  
    	method.setEntity(new UrlEncodedFormEntity(paramPairs));
    	method.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); // Disable expect-continue, caching server doesn't like this
    	HttpResponse res = client.execute(method);
    	ByteArrayOutputStream outstream = new ByteArrayOutputStream();
    	res.getEntity().writeTo(outstream);
    	return outstream.toString();
    }
	
	public boolean login(String username, String password) {
		loggedIn = false;
    	String passMD5 = "";
    	String token = "";
    	String wsToken = "";
    	long timestamp = new Date().getTime() / 1000;
    	try {
    		MessageDigest md = MessageDigest.getInstance("MD5");
    		md.update(password.getBytes(), 0, password.length());
    		passMD5 = new BigInteger(1, md.digest()).toString(16);
    		if (passMD5.length() == 31) {
    			passMD5 = "0" + passMD5; 
    		}
    		token = <PASSWORD> + Long.toString(timestamp);
    		md.update(token.getBytes(), 0, token.length());
    		token = new BigInteger(1, md.digest()).toString(16);
    		if (token.length() == 31) {
    			token = "0" + token;
    		}
    		wsToken = <PASSWORD> + <PASSWORD>;
    		md.update(wsToken.getBytes(), 0, wsToken.length());
    		wsToken = new BigInteger(1, md.digest()).toString(16);
    		if (wsToken.length() == 31) {
    			wsToken = "0" + wsToken;
    		}
    	} catch (NoSuchAlgorithmException ex) {
    		Toast.makeText(this, "MD5 hashing unavailable, unable to login.", Toast.LENGTH_LONG);
    	}
    	
    	try {
    		// Login for streaming
    		String output = this.httpGet("http://alpha.libre.fm/radio/handshake.php?username=" + username + "&passwordmd5=" + <PASSWORD>);
    		if (output.trim().equals("BADAUTH")) {
    			Toast.makeText(this, "Incorrect username or password", Toast.LENGTH_SHORT).show();
    		} else {
    		    String[] result = output.split("[=\n]");
    		    for (int x=0; x<result.length; x++)  {
    		    	if (result[x].trim().equals("session")) {
    		    		this.sessionKey = result[x+1].trim();
    		    	}
    		    }
    		    loggedIn = true;
    		}
    		// Login for scrobbling
    		output = this.httpGet("http://turtle.libre.fm/?hs=true&p=1.2&u=" + username + "&t=" + Long.toString(timestamp) + "&a=" + token + "&c=ldr" );    		
    		if (output.split("\n")[0].equals("OK")) {
    			this.scrobbleKey = output.split("\n")[1].trim();
    		}
    		// Login for web services
    		output = this.httpGet("http://alpha.libre.fm/2.0/?method=auth.getmobilesession&username=" + username + "&authToken=" + wsToken);
    		WSParser wsp = new WSParser();
    		wsp.parse(output);
    		this.serviceToken = wsp.getKey();
    	} catch (Exception ex) {
    		Toast.makeText(this, "Unable to connect to libre.fm server: " + ex.getMessage(), Toast.LENGTH_LONG).show();
    	}
    	return loggedIn;
	}
	
	public boolean isLoggedIn() {
		return loggedIn;
	}
	
	public void onBufferingUpdate(MediaPlayer mp, int percent) {
		if (percent > 2 && !mp.isPlaying() && this.playing) {
			this.mp.start();
		}
		if (percent > 99) {
			this.buffering = false;
		}
	}
	
	public void onCompletion(MediaPlayer mp) {
		if(!this.buffering) { // We get spurious complete messages if we're still buffering
			// Scrobble
			Song song = this.playlist.getSong(this.currentSong);
			try { 
				String time = Long.toString(new Date().getTime() / 1000);
				this.httpPost("http://turtle.libre.fm/submissions/1.2/", "s", this.scrobbleKey, "a[0]", song.artist, "t[0]", song.title, "b[0]", song.album, "i[0]", time);
			} catch (Exception ex) {
				Log.d("libredroid", "Couldn't scrobble: " + ex.getMessage());
			}
			
			this.next();			
		}
	}
	
	public void play() {
    	if (this.currentSong >= this.playlist.size()) {
    		this.getPlaylist();
    	}
    	this.playing = true;
    	this.buffering = true;
    	Song song = this.playlist.getSong(currentSong);
    	Log.d("libredroid", "Song: " + this.playlist);
    	try {
    		this.mp.reset();
    		// Hack to get Jamendo MP3 stream instead of OGG because MediaPlayer
    		// doesn't support streaming OGG at the moment
    		this.mp.setDataSource(song.location.replace("ogg2", "mp31"));
    		this.mp.setOnBufferingUpdateListener(this);
            this.mp.setOnCompletionListener(this);
    		this.mp.prepareAsync();
    		// Send now playing data
    		this.httpPost("http://turtle.libre.fm/nowplaying/1.2/", "s", this.scrobbleKey, "a", song.artist, "t", song.title);
    	} catch (Exception ex) {
    		Log.d("libredroid", "Couldn't play " + song.title + ": " + ex.getMessage());
    		this.next();
    	}
    	this.sendBroadcast(new Intent("LibreDroidNewSong"));
    }
	
	public void next() {
    	mp.stop();
    	this.currentSong++;
    	this.play();
    }
    
    public void prev() {
    	if (this.currentSong > 0) {
    		mp.stop();
    		this.currentSong--;
    		this.play();<commit_msg>Work around Froyo's incomplete HTTP streaming implementation for the android client<commit_after>package fm.libre.droid;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.URL;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreProtocolPNames;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;

public class LibreService extends Service implements OnBufferingUpdateListener, OnCompletionListener {
	private LibreServiceBinder serviceBinder = new LibreServiceBinder();
	private Playlist playlist;
	private String sessionKey;
	private String scrobbleKey;
	private String serviceToken; 
	private String stationName;
	private int currentSong;
	private int currentPage = 0;
	private boolean loggedIn = false;
	private boolean playing = false;
	private boolean buffering = false;
	private MediaPlayer mp;
	
	@Override
	public void onCreate() {
		super.onCreate();
		this.mp = new MediaPlayer();
		this.currentSong = 0;
        this.playlist = new Playlist();
        this.sessionKey = "";
	}
	
	@Override
	public IBinder onBind(Intent intent) {
		return serviceBinder;
	}

	@Override 
	public void onDestroy() {
		this.mp.release();
	}
	
	public String httpGet(String url) throws URISyntaxException, ClientProtocolException, IOException {
    	DefaultHttpClient client = new DefaultHttpClient();
    	URI uri = new URI(url);
		HttpGet method = new HttpGet(uri);
		HttpResponse res = client.execute(method);
		ByteArrayOutputStream outstream = new ByteArrayOutputStream();  
		res.getEntity().writeTo(outstream);
		return outstream.toString();
    }
	
	public String httpPost(String url, String... params) throws URISyntaxException, ClientProtocolException, IOException {
    	DefaultHttpClient client = new DefaultHttpClient();
    	URI uri = new URI(url);
    	HttpPost method = new HttpPost(uri);
    	List<NameValuePair> paramPairs = new ArrayList<NameValuePair>(2);
    	for (int i = 0; i < params.length; i+=2) {
    		paramPairs.add(new BasicNameValuePair(params[i], params[i+1]));  
    	}  
    	method.setEntity(new UrlEncodedFormEntity(paramPairs));
    	method.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false); // Disable expect-continue, caching server doesn't like this
    	HttpResponse res = client.execute(method);
    	ByteArrayOutputStream outstream = new ByteArrayOutputStream();
    	res.getEntity().writeTo(outstream);
    	return outstream.toString();
    }
	
	public boolean login(String username, String password) {
		loggedIn = false;
    	String passMD5 = "";
    	String token = "";
    	String wsToken = "";
    	long timestamp = new Date().getTime() / 1000;
    	try {
    		MessageDigest md = MessageDigest.getInstance("MD5");
    		md.update(password.getBytes(), 0, password.length());
    		passMD5 = new BigInteger(1, md.digest()).toString(16);
    		if (passMD5.length() == 31) {
    			passMD5 = "0" + <PASSWORD>; 
    		}
    		token = <PASSWORD>(timestamp);
    		md.update(token.getBytes(), 0, token.length());
    		token = new BigInteger(1, md.digest()).toString(16);
    		if (token.length() == 31) {
    			token = "0" + token;
    		}
    		wsToken = <PASSWORD> + pass<PASSWORD>;
    		md.update(wsToken.getBytes(), 0, wsToken.length());
    		wsToken = new BigInteger(1, md.digest()).toString(16);
    		if (wsToken.length() == 31) {
    			wsToken = "0" + wsToken;
    		}
    	} catch (NoSuchAlgorithmException ex) {
    		Toast.makeText(this, "MD5 hashing unavailable, unable to login.", Toast.LENGTH_LONG);
    	}
    	
    	try {
    		// Login for streaming
    		String output = this.httpGet("http://alpha.libre.fm/radio/handshake.php?username=" + username + "&passwordmd5=" + passMD5);
    		if (output.trim().equals("BADAUTH")) {
    			Toast.makeText(this, "Incorrect username or password", Toast.LENGTH_SHORT).show();
    		} else {
    		    String[] result = output.split("[=\n]");
    		    for (int x=0; x<result.length; x++)  {
    		    	if (result[x].trim().equals("session")) {
    		    		this.sessionKey = result[x+1].trim();
    		    	}
    		    }
    		    loggedIn = true;
    		}
    		// Login for scrobbling
    		output = this.httpGet("http://turtle.libre.fm/?hs=true&p=1.2&u=" + username + "&t=" + Long.toString(timestamp) + "&a=" + token + "&c=ldr" );    		
    		if (output.split("\n")[0].equals("OK")) {
    			this.scrobbleKey = output.split("\n")[1].trim();
    		}
    		// Login for web services
    		output = this.httpGet("http://alpha.libre.fm/2.0/?method=auth.getmobilesession&username=" + username + "&authToken=" + wsToken);
    		WSParser wsp = new WSParser();
    		wsp.parse(output);
    		this.serviceToken = wsp.getKey();
    	} catch (Exception ex) {
    		Toast.makeText(this, "Unable to connect to libre.fm server: " + ex.getMessage(), Toast.LENGTH_LONG).show();
    	}
    	return loggedIn;
	}
	
	public boolean isLoggedIn() {
		return loggedIn;
	}
	
	public void onBufferingUpdate(MediaPlayer mp, int percent) {
		if (percent > 2 && !mp.isPlaying() && this.playing) {
			this.mp.start();
		}
		if (percent > 99) {
			this.buffering = false;
		}
	}
	
	public void onCompletion(MediaPlayer mp) {
		if(!this.buffering) { // We get spurious complete messages if we're still buffering
			// Scrobble
			Song song = this.playlist.getSong(this.currentSong);
			try { 
				String time = Long.toString(new Date().getTime() / 1000);
				this.httpPost("http://turtle.libre.fm/submissions/1.2/", "s", this.scrobbleKey, "a[0]", song.artist, "t[0]", song.title, "b[0]", song.album, "i[0]", time);
			} catch (Exception ex) {
				Log.d("libredroid", "Couldn't scrobble: " + ex.getMessage());
			}
			
			this.next();			
		}
	}
	
	public void play() {
    	if (this.currentSong >= this.playlist.size()) {
    		this.getPlaylist();
    	}
    	this.playing = true;
    	this.buffering = true;
    	Song song = this.playlist.getSong(currentSong);
    	Log.d("libredroid", "Song: " + this.playlist);
    	try {
    		this.mp.reset();
    		// Hack to get Jamendo MP3 stream instead of OGG because MediaPlayer
    		// doesn't support streaming OGG at the moment.
    		URL url = new URL(song.location.replace("ogg2", "mp31"));
    		// Hack2: Froyo's new streaming mechanism is massively broken and fails
    		// when presented with a HTTP redirection message so we have to resolve
    		// it first.
    		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    		conn.setInstanceFollowRedirects(false);
    		conn.connect();
    		if(conn.getResponseCode() == 307) {
    			Log.d("libredroid", "Got a HTTP redirection for " + url.toString() + ". Resolves to: " + conn.getHeaderField("Location") + " (resolving it ourselves to avoid Froyo's incomplete HTTP streaming implementation)");
    			this.mp.setDataSource(conn.getHeaderField("Location"));
    		} else  {
    			this.mp.setDataSource(url.toString());
    		}
    		conn.disconnect();
    		this.mp.setOnBufferingUpdateListener(this);
            this.mp.setOnCompletionListener(this);
    		this.mp.prepareAsync();
    		// Send now playing data
    		this.httpPost("http://turtle.libre.fm/nowplaying/1.2/", "s", this.scrobbleKey, "a", song.artist, "t", song.title);
    	} catch (Exception ex) {
    		Log.d("libredroid", "Couldn't play " + song.title + ": " + ex.getMessage());
    		this.next();
    	}
    	this.sendBroadcast(new Intent("LibreDroidNewSong"));
    }
	
	public void next() {
    	mp.stop();
    	this.currentSong++;
    	this.play();
    }
    
    public void prev() {
    	if (this.currentSong > 0) {
    		mp.stop();
    		this.currentSong--;
    		this.play();
{'fraction_non_alphanumeric': 0.13648681941364868, 'fraction_numerical': 0.008499630450849963, 'mean_word_length': 3.3565870673463913, 'pattern_counts': {'":': 2, '<': 19, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '369392', 'n_tokens_mistral': 5632, 'n_tokens_neox': 5307, 'n_words': 1119}