diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 91dd332..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "ServerProxy"] - path = ServerProxy - url = https://github.com/daneren2005/ServerProxy.git diff --git a/ServerProxy b/ServerProxy deleted file mode 160000 index a4d9573..0000000 --- a/ServerProxy +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a4d957353db2634906e0d5099d7a078a111bfab9 diff --git a/app/build.gradle b/app/build.gradle index 05e590b..2e15b66 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,7 +46,6 @@ android { } dependencies { - compile project(':Server Proxy') compile fileTree(include: ['*.jar'], dir: 'libs') compile 'com.android.support:support-v4:23.4.+' compile 'com.android.support:appcompat-v7:23.4.+' diff --git a/app/src/main/java/github/nvllsvm/audinaut/service/DownloadFile.java b/app/src/main/java/github/nvllsvm/audinaut/service/DownloadFile.java index c5305d3..3ef02b4 100644 --- a/app/src/main/java/github/nvllsvm/audinaut/service/DownloadFile.java +++ b/app/src/main/java/github/nvllsvm/audinaut/service/DownloadFile.java @@ -31,12 +31,12 @@ import android.os.PowerManager; import android.util.Log; import net.nullsum.audinaut.domain.MusicDirectory; -import net.nullsum.audinaut.util.Constants; -import net.nullsum.audinaut.util.SilentBackgroundTask; -import net.nullsum.audinaut.util.FileUtil; -import net.nullsum.audinaut.util.Util; +import net.nullsum.audinaut.util.BufferFile; import net.nullsum.audinaut.util.CacheCleaner; -import github.daneren2005.serverproxy.BufferFile; +import net.nullsum.audinaut.util.Constants; +import net.nullsum.audinaut.util.FileUtil; +import net.nullsum.audinaut.util.SilentBackgroundTask; +import net.nullsum.audinaut.util.Util; import org.apache.http.Header; diff --git a/app/src/main/java/github/nvllsvm/audinaut/service/DownloadService.java b/app/src/main/java/github/nvllsvm/audinaut/service/DownloadService.java index 9be5a1c..9d338d4 100644 --- a/app/src/main/java/github/nvllsvm/audinaut/service/DownloadService.java +++ b/app/src/main/java/github/nvllsvm/audinaut/service/DownloadService.java @@ -36,17 +36,17 @@ import net.nullsum.audinaut.domain.MusicDirectory; import net.nullsum.audinaut.domain.PlayerState; import net.nullsum.audinaut.domain.RepeatMode; import net.nullsum.audinaut.receiver.MediaButtonIntentReceiver; +import net.nullsum.audinaut.util.BufferProxy; +import net.nullsum.audinaut.util.Constants; import net.nullsum.audinaut.util.ImageLoader; import net.nullsum.audinaut.util.Notifications; -import net.nullsum.audinaut.util.SilentBackgroundTask; -import net.nullsum.audinaut.util.Constants; import net.nullsum.audinaut.util.ShufflePlayBuffer; +import net.nullsum.audinaut.util.SilentBackgroundTask; import net.nullsum.audinaut.util.SimpleServiceBinder; import net.nullsum.audinaut.util.UpdateHelper; import net.nullsum.audinaut.util.Util; import net.nullsum.audinaut.util.tags.BastpUtil; import net.nullsum.audinaut.view.UpdateView; -import github.daneren2005.serverproxy.BufferProxy; import java.io.File; import java.io.IOException; diff --git a/app/src/main/java/github/nvllsvm/audinaut/util/BufferFile.java b/app/src/main/java/github/nvllsvm/audinaut/util/BufferFile.java new file mode 100644 index 0000000..4b0f93f --- /dev/null +++ b/app/src/main/java/github/nvllsvm/audinaut/util/BufferFile.java @@ -0,0 +1,28 @@ +/* + This file is part of ServerProxy. + SocketProxy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + Copyright 2014 (C) Scott Jackson +*/ + +package net.nullsum.audinaut.util; + +import java.io.File; + +public interface BufferFile { + File getFile(); + Long getContentLength(); + long getEstimatedSize(); + boolean isWorkDone(); + void onStart(); + void onStop(); + void onResume(); +} diff --git a/app/src/main/java/github/nvllsvm/audinaut/util/BufferProxy.java b/app/src/main/java/github/nvllsvm/audinaut/util/BufferProxy.java new file mode 100644 index 0000000..b4e947c --- /dev/null +++ b/app/src/main/java/github/nvllsvm/audinaut/util/BufferProxy.java @@ -0,0 +1,82 @@ +/* +This file is part of ServerProxy. +SocketProxy is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. +Subsonic is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +You should have received a copy of the GNU General Public License +along with Subsonic. If not, see . +Copyright 2014 (C) Scott Jackson +*/ + +package net.nullsum.audinaut.util; + +import java.io.File; +import java.net.Socket; + +import android.content.Context; + +import net.nullsum.audinaut.util.FileProxy; + +public class BufferProxy extends FileProxy { + private static final String TAG = BufferProxy.class.getSimpleName(); + protected BufferFile progress; + + public BufferProxy(Context context) { + super(context); + } + + protected ProxyTask getTask(Socket client) { + return new BufferFileTask(client); + } + + public void setBufferFile(BufferFile progress) { + this.progress = progress; + } + + protected class BufferFileTask extends StreamFileTask { + public BufferFileTask(Socket client) { + super(client); + } + + @Override + File getFile(String path) { + return progress.getFile(); + } + + @Override + Long getContentLength() { + Long contentLength = progress.getContentLength(); + if(contentLength == null && progress.isWorkDone()) { + contentLength = file.length(); + } + return contentLength; + } + @Override + long getFileSize() { + return progress.getEstimatedSize(); + } + + @Override + public void onStart() { + progress.onStart(); + } + @Override + public void onStop() { + progress.onStop(); + } + @Override + public void onResume() { + progress.onResume(); + } + + @Override + public boolean isWorkDone() { + return progress.isWorkDone() && cbSkip >= file.length(); + } + } +} diff --git a/app/src/main/java/github/nvllsvm/audinaut/util/FileProxy.java b/app/src/main/java/github/nvllsvm/audinaut/util/FileProxy.java new file mode 100644 index 0000000..388e488 --- /dev/null +++ b/app/src/main/java/github/nvllsvm/audinaut/util/FileProxy.java @@ -0,0 +1,219 @@ +/* + This file is part of ServerProxy. + SocketProxy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + Copyright 2014 (C) Scott Jackson +*/ + +package net.nullsum.audinaut.util; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; + +import android.content.Context; +import android.util.Log; + +import net.nullsum.audinaut.util.ServerProxy; + +public class FileProxy extends ServerProxy { + private static final String TAG = FileProxy.class.getSimpleName(); + + public FileProxy(Context context) { + super(context); + } + + protected ProxyTask getTask(Socket client) { + return new StreamFileTask(client); + } + + protected class StreamFileTask extends ProxyTask { + File file; + + public StreamFileTask(Socket client) { + super(client); + } + + @Override + public boolean processRequest() { + if(!super.processRequest()) { + return false; + } + + Log.i(TAG, "Processing request for file " + path); + file = getFile(path); + if (!file.exists()) { + Log.e(TAG, "File " + path + " does not exist"); + return false; + } + + // Make sure to not try to read past where the file is downloaded + if(cbSkip != 0 && cbSkip >= file.length()) { + return false; + } + + return true; + } + + File getFile(String path) { + return new File(path); + } + + Long getContentLength() { + return file.length(); + } + long getFileSize() { + return file.length(); + } + + @Override + public void run() { + Long contentLength = getContentLength(); + + // Create HTTP header + String headers; + if(cbSkip == 0) { + headers = "HTTP/1.0 200 OK\r\n"; + } else { + headers = "HTTP/1.0 206 OK\r\n"; + headers += "Content-Range: bytes " + cbSkip + "-" + (file.length() - 1) + "/"; + if(contentLength == null) { + headers += "*"; + } else { + headers += contentLength; + } + headers += "\r\n"; + + Log.i(TAG, "Streaming starts from: " + cbSkip); + } + + String name = file.getPath(); + int index = name.lastIndexOf('.'); + String ext = ""; + if(index != -1) { + ext = name.substring(index + 1).toLowerCase(); + } + if("mp3".equals(ext)) { + headers += "Content-Type: audio/mpeg\r\n"; + } else { + headers += "Content-Type: " + "application/octet-stream" + "\r\n"; + } + + long fileSize; + if(contentLength == null) { + fileSize = getFileSize(); + } else { + fileSize = contentLength; + if(cbSkip > 0) { + headers += "Content-Length: " + (fileSize - cbSkip) + "\r\n"; + } else { + headers += "Content-Length: " + fileSize + "\r\n"; + } + headers += "Accept-Ranges: bytes \r\n"; + } + Log.i(TAG, "Streaming fileSize: " + fileSize); + + headers += "Connection: close\r\n"; + headers += "\r\n"; + + long cbToSend = fileSize - cbSkip; + OutputStream output = null; + byte[] buff = new byte[64 * 1024]; + try { + output = new BufferedOutputStream(client.getOutputStream(), 32*1024); + output.write(headers.getBytes()); + + // Make sure to have file lock + onStart(); + + // Loop as long as there's stuff to send + while (isRunning && !client.isClosed()) { + onResume(); + + // See if there's more to send + int cbSentThisBatch = 0; + if (file.exists()) { + FileInputStream input = new FileInputStream(file); + input.skip(cbSkip); + int cbToSendThisBatch = input.available(); + while (cbToSendThisBatch > 0) { + int cbToRead = Math.min(cbToSendThisBatch, buff.length); + int cbRead = input.read(buff, 0, cbToRead); + if (cbRead == -1) { + break; + } + cbToSendThisBatch -= cbRead; + cbToSend -= cbRead; + output.write(buff, 0, cbRead); + output.flush(); + cbSkip += cbRead; + cbSentThisBatch += cbRead; + } + input.close(); + } + + // Done regardless of whether or not it thinks it is + if(isWorkDone()) { + break; + } + + // If we did nothing this batch, block for a second + if (cbSentThisBatch == 0) { + Log.d(TAG, "Blocking until more data appears (" + cbToSend + ")"); + Thread.sleep(1000); + } + } + + // Release file lock, use of stream proxy means nothing else is using it + onStop(); + } + catch (SocketException socketException) { + Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly"); + + // Release file lock, use of stream proxy means nothing else is using it + onStop(); + } + catch (Exception e) { + Log.e(TAG, "Exception thrown from streaming task:"); + Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); + } + + // Cleanup + try { + if (output != null) { + output.close(); + } + client.close(); + } + catch (IOException e) { + Log.e(TAG, "IOException while cleaning up streaming task:"); + Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage()); + } + } + + public void onStart() { + + } + public void onStop() { + + } + public void onResume() { + + } + public boolean isWorkDone() { + return cbSkip >= file.length(); + } + } +} diff --git a/app/src/main/java/github/nvllsvm/audinaut/util/ServerProxy.java b/app/src/main/java/github/nvllsvm/audinaut/util/ServerProxy.java new file mode 100644 index 0000000..86bdba6 --- /dev/null +++ b/app/src/main/java/github/nvllsvm/audinaut/util/ServerProxy.java @@ -0,0 +1,231 @@ +/* + This file is part of ServerProxy. + SocketProxy is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see . + Copyright 2014 (C) Scott Jackson +*/ + +package net.nullsum.audinaut.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.net.UnknownHostException; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.util.Log; + +public abstract class ServerProxy implements Runnable { + private static final String TAG = ServerProxy.class.getSimpleName(); + + private Thread thread; + protected boolean isRunning; + private ServerSocket socket; + private int port; + private Context context; + + public ServerProxy(Context context) { + // Create listening socket + try { + socket = new ServerSocket(0); + socket.setSoTimeout(5000); + port = socket.getLocalPort(); + this.context = context; + } catch (UnknownHostException e) { // impossible + } catch (IOException e) { + Log.e(TAG, "IOException initializing server", e); + } + } + + public void start() { + if(socket.isBound()) { + thread = new Thread(this, "Socket Proxy"); + thread.start(); + } else { + Log.e(TAG, "Attempting to start a non-initialized proxy"); + } + } + + public void stop() { + isRunning = false; + if(thread != null) { + thread.interrupt(); + } + } + + public String getPrivateAddress(String request) { + return getAddress("127.0.0.1", request); + } + public String getPublicAddress(String request) { + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + int ipAddress = wifiManager.getConnectionInfo().getIpAddress(); + + if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) { + ipAddress = Integer.reverseBytes(ipAddress); + } + + byte[] ipByteArray = BigInteger.valueOf(ipAddress).toByteArray(); + String ipAddressString = null; + try { + ipAddressString = InetAddress.getByAddress(ipByteArray).getHostAddress(); + } catch(UnknownHostException ex) { + Log.e(TAG, "Unable to get host address."); + } + + return getAddress(ipAddressString, request); + } + private String getAddress(String host, String request) { + try { + return String.format("http://%s:%d/%s", host, port, URLEncoder.encode(request, "UTF-8")); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + @Override + public void run() { + isRunning = true; + while (isRunning) { + try { + Socket client = socket.accept(); + if (client == null) { + continue; + } + Log.i(TAG, "client connected"); + + ProxyTask task = getTask(client); + if (task.processRequest()) { + new Thread(task, "ProxyTask").start(); + } + + } catch (SocketTimeoutException e) { + // Do nothing + } catch (IOException e) { + Log.e(TAG, "Error connecting to client", e); + } + } + Log.i(TAG, "Proxy interrupted. Shutting down."); + } + + abstract ProxyTask getTask(Socket client); + + protected abstract class ProxyTask implements Runnable { + protected Socket client; + protected String path; + protected int cbSkip = 0; + protected Map requestHeaders = new HashMap<>(); + + public ProxyTask(Socket client) { + this.client = client; + } + + protected boolean readRequest() { + InputStream is; + String firstLine; + BufferedReader reader; + try { + is = client.getInputStream(); + reader = new BufferedReader(new InputStreamReader(is), 8192); + firstLine = reader.readLine(); + } catch (IOException e) { + Log.e(TAG, "Error parsing request", e); + return false; + } + + if (firstLine == null) { + Log.i(TAG, "Proxy client closed connection without a request."); + return false; + } + + StringTokenizer st = new StringTokenizer(firstLine); + if(!st.hasMoreTokens()) { + Log.w(TAG, "Unknown request with no tokens"); + return false; + } else if(st.countTokens() < 2) { + Log.w(TAG, "Unknown request with no uri: \"" + firstLine + '"'); + return false; + } + String method = st.nextToken(); + String uri = st.nextToken(); + String realUri = uri.substring(1); + + // Process path + try { + path = URLDecoder.decode(realUri, "UTF-8"); + } catch (UnsupportedEncodingException e) { + Log.e(TAG, "Unsupported encoding", e); + return false; + } + + // Get all of the headers + try { + String line; + while((line = reader.readLine()) != null && !"".equals(line)) { + int index = line.indexOf(':'); + // Ignore headers without ':' or where ':' is the last thing in the string + if(index != -1 && (index + 2) < line.length()) { + String headerName = line.substring(0, index); + String headerValue = line.substring(index + 2); + + requestHeaders.put(headerName, headerValue); + } + } + } catch(IOException e) { + // Don't really care once past first line + } catch(Exception e) { + Log.w(TAG, "Exception reading request", e); + } + + return true; + } + + public boolean processRequest() { + if (!readRequest()) { + return false; + } + Log.i(TAG, "Processing request for " + path); + + // Try to get range requested + String range = requestHeaders.get("Range"); + if(range != null) { + int index = range.indexOf("="); + if(index >= 0) { + range = range.substring(index + 1); + + index = range.indexOf("-"); + if(index > 0) { + range = range.substring(0, index); + } + + cbSkip = Integer.parseInt(range); + } + } + + return true; + } + } +} diff --git a/settings.gradle b/settings.gradle index 430c775..e7b4def 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1 @@ -include ':app', ':Server Proxy' -project(':Server Proxy').projectDir = new File('ServerProxy') +include ':app'