TubeLab-App-Android/torrentStream/src/main/java/com/github/se_bastiaan/torrentstream/TorrentStream.java

543 lines
19 KiB
Java

/*
* Copyright (C) 2015-2018 Sébastiaan (github.com/se-bastiaan)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.se_bastiaan.torrentstream;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import com.frostwire.jlibtorrent.Priority;
import com.frostwire.jlibtorrent.SessionManager;
import com.frostwire.jlibtorrent.SessionParams;
import com.frostwire.jlibtorrent.SettingsPack;
import com.frostwire.jlibtorrent.TorrentHandle;
import com.frostwire.jlibtorrent.TorrentInfo;
import com.frostwire.jlibtorrent.alerts.AddTorrentAlert;
import com.frostwire.jlibtorrent.swig.settings_pack;
import com.github.se_bastiaan.torrentstream.exceptions.DirectoryModifyException;
import com.github.se_bastiaan.torrentstream.exceptions.NotInitializedException;
import com.github.se_bastiaan.torrentstream.exceptions.TorrentInfoException;
import com.github.se_bastiaan.torrentstream.listeners.DHTStatsAlertListener;
import com.github.se_bastiaan.torrentstream.listeners.TorrentAddedAlertListener;
import com.github.se_bastiaan.torrentstream.listeners.TorrentListener;
import com.github.se_bastiaan.torrentstream.utils.FileUtils;
import com.github.se_bastiaan.torrentstream.utils.ThreadUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
public final class TorrentStream {
private static final String LIBTORRENT_THREAD_NAME = "TORRENTSTREAM_LIBTORRENT", STREAMING_THREAD_NAME = "TORRENTSTREAMER_STREAMING";
private static TorrentStream sThis;
private final List<TorrentListener> listeners = new ArrayList<>();
private CountDownLatch initialisingLatch;
private SessionManager torrentSession;
private Boolean initialising = false, initialised = false, isStreaming = false, isCanceled = false;
private TorrentOptions torrentOptions;
private Torrent currentTorrent;
private final TorrentAddedAlertListener torrentAddedAlertListener = new TorrentAddedAlertListener() {
@Override
public void torrentAdded(AddTorrentAlert alert) {
InternalTorrentListener listener = new InternalTorrentListener();
TorrentHandle th = torrentSession.find(alert.handle().infoHash());
currentTorrent = new Torrent(th, listener, torrentOptions.prepareSize);
torrentSession.addListener(currentTorrent);
}
};
private String currentTorrentUrl;
private Integer dhtNodes = 0;
private final DHTStatsAlertListener dhtStatsAlertListener = new DHTStatsAlertListener() {
@Override
public void stats(int totalDhtNodes) {
dhtNodes = totalDhtNodes;
}
};
private HandlerThread libTorrentThread, streamingThread;
private Handler libTorrentHandler, streamingHandler;
private TorrentStream(TorrentOptions options) {
torrentOptions = options;
initialise();
}
public static TorrentStream init(TorrentOptions options) {
sThis = new TorrentStream(options);
return sThis;
}
public static TorrentStream getInstance() throws NotInitializedException {
if (sThis == null)
throw new NotInitializedException();
return sThis;
}
/**
* Obtain internal session manager
*
* @return {@link SessionManager}
*/
public SessionManager getSessionManager() {
return torrentSession;
}
private void initialise() {
if (libTorrentThread != null && torrentSession != null) {
resumeSession();
} else {
if ((initialising || initialised) && libTorrentThread != null) {
libTorrentThread.interrupt();
}
initialising = true;
initialised = false;
initialisingLatch = new CountDownLatch(1);
libTorrentThread = new HandlerThread(LIBTORRENT_THREAD_NAME);
libTorrentThread.start();
libTorrentHandler = new Handler(libTorrentThread.getLooper());
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession = new SessionManager();
setOptions(torrentOptions);
torrentSession.addListener(dhtStatsAlertListener);
torrentSession.startDht();
initialising = false;
initialised = true;
initialisingLatch.countDown();
}
});
}
}
/**
* Resume TorrentSession
*/
public void resumeSession() {
if (libTorrentThread != null && torrentSession != null) {
libTorrentHandler.removeCallbacksAndMessages(null);
//resume torrent session if needed
if (torrentSession.isPaused()) {
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession.resume();
}
});
}
if (!torrentSession.isDhtRunning()) {
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession.startDht();
}
});
}
}
}
/**
* Pause TorrentSession
*/
public void pauseSession() {
if (!isStreaming)
libTorrentHandler.post(new Runnable() {
@Override
public void run() {
torrentSession.pause();
}
});
}
/**
* Get torrent metadata, either by downloading the .torrent or fetching the magnet
*
* @param torrentUrl {@link String} URL to .torrent or magnet link
* @return {@link TorrentInfo}
*/
private TorrentInfo getTorrentInfo(String torrentUrl) throws TorrentInfoException {
if (torrentUrl.startsWith("magnet")) {
byte[] data = torrentSession.fetchMagnet(torrentUrl, 30);
if (data != null)
try {
return TorrentInfo.bdecode(data);
} catch (IllegalArgumentException e) {
throw new TorrentInfoException(e);
}
} else if (torrentUrl.startsWith("http") || torrentUrl.startsWith("https")) {
try {
URL url = new URL(torrentUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setInstanceFollowRedirects(true);
connection.connect();
InputStream inputStream = connection.getInputStream();
byte[] responseByteArray = new byte[0];
if (connection.getResponseCode() == 200) {
responseByteArray = getBytesFromInputStream(inputStream);
}
inputStream.close();
connection.disconnect();
if (responseByteArray.length > 0) {
return TorrentInfo.bdecode(responseByteArray);
}
} catch (IOException | IllegalArgumentException e) {
throw new TorrentInfoException(e);
}
} else if (torrentUrl.startsWith("file")) {
Uri path = Uri.parse(torrentUrl);
File file = new File(path.getPath());
try {
FileInputStream fileInputStream = new FileInputStream(file);
byte[] responseByteArray = getBytesFromInputStream(fileInputStream);
fileInputStream.close();
if (responseByteArray.length > 0) {
return TorrentInfo.bdecode(responseByteArray);
}
} catch (IOException | IllegalArgumentException e) {
throw new TorrentInfoException(e);
}
}
return null;
}
private byte[] getBytesFromInputStream(InputStream inputStream) throws IOException {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int len = 0;
while ((len = inputStream.read(buffer)) != -1) {
byteBuffer.write(buffer, 0, len);
}
return byteBuffer.toByteArray();
}
/**
* Start stream download for specified torrent
*
* @param torrentUrl {@link String} .torrent or magnet link
*/
public void startStream(final String torrentUrl) {
if (!initialising && !initialised)
initialise();
if (libTorrentHandler == null || isStreaming) return;
isCanceled = false;
streamingThread = new HandlerThread(STREAMING_THREAD_NAME);
streamingThread.start();
streamingHandler = new Handler(streamingThread.getLooper());
streamingHandler.post(new Runnable() {
@Override
public void run() {
isStreaming = true;
if (initialisingLatch != null) {
try {
initialisingLatch.await();
initialisingLatch = null;
} catch (InterruptedException e) {
isStreaming = false;
return;
}
}
currentTorrentUrl = torrentUrl;
File saveDirectory = new File(torrentOptions.saveLocation);
if (!saveDirectory.isDirectory() && !saveDirectory.mkdirs()) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(null, new DirectoryModifyException());
}
});
}
isStreaming = false;
return;
}
torrentSession.removeListener(torrentAddedAlertListener);
TorrentInfo torrentInfo = null;
try {
torrentInfo = getTorrentInfo(torrentUrl);
} catch (final TorrentInfoException e) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(null, e);
}
});
}
}
torrentSession.addListener(torrentAddedAlertListener);
if (torrentInfo == null) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(null, new TorrentInfoException(null));
}
});
}
isStreaming = false;
return;
}
Priority[] priorities = new Priority[torrentInfo.numFiles()];
for (int i = 0; i < priorities.length; i++) {
priorities[i] = Priority.IGNORE;
}
if (!currentTorrentUrl.equals(torrentUrl) || isCanceled) {
return;
}
torrentSession.download(torrentInfo, saveDirectory, null, priorities, null);
}
});
}
/**
* Stop current torrent stream
*/
public void stopStream() {
//remove all callbacks from handler
if (libTorrentHandler != null)
libTorrentHandler.removeCallbacksAndMessages(null);
if (streamingHandler != null)
streamingHandler.removeCallbacksAndMessages(null);
isCanceled = true;
isStreaming = false;
if (currentTorrent != null) {
final File saveLocation = currentTorrent.getSaveLocation();
currentTorrent.pause();
torrentSession.removeListener(currentTorrent);
torrentSession.remove(currentTorrent.getTorrentHandle());
currentTorrent = null;
if (torrentOptions.removeFiles) {
new Thread(new Runnable() {
@Override
public void run() {
int tries = 0;
while (!FileUtils.recursiveDelete(saveLocation) && tries < 5) {
tries++;
try {
Thread.sleep(1000); // If deleted failed then something is still using the file, wait and then retry
} catch (InterruptedException e) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(currentTorrent, new DirectoryModifyException());
}
});
}
}
}
}
}).start();
}
}
if (streamingThread != null)
streamingThread.interrupt();
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamStopped();
}
});
}
}
public TorrentOptions getOptions() {
return torrentOptions;
}
public void setOptions(TorrentOptions options) {
torrentOptions = options;
SettingsPack settingsPack = new SettingsPack()
.anonymousMode(torrentOptions.anonymousMode)
.connectionsLimit(torrentOptions.maxConnections)
.downloadRateLimit(torrentOptions.maxDownloadSpeed)
.uploadRateLimit(torrentOptions.maxUploadSpeed)
.activeDhtLimit(torrentOptions.maxDht);
if (torrentOptions.listeningPort != -1) {
String ifStr = String.format(Locale.ENGLISH, "%s:%d", "0.0.0.0", torrentOptions.listeningPort);
settingsPack.setString(settings_pack.string_types.listen_interfaces.swigValue(), ifStr);
}
if (torrentOptions.proxyHost != null) {
settingsPack.setString(settings_pack.string_types.proxy_hostname.swigValue(), torrentOptions.proxyHost);
if (torrentOptions.proxyUsername != null) {
settingsPack.setString(settings_pack.string_types.proxy_username.swigValue(), torrentOptions.proxyUsername);
if (torrentOptions.proxyPassword != null) {
settingsPack.setString(settings_pack.string_types.proxy_password.swigValue(), torrentOptions.proxyPassword);
}
}
}
if (torrentOptions.peerFingerprint != null) {
settingsPack.setString(settings_pack.string_types.peer_fingerprint.swigValue(), torrentOptions.peerFingerprint);
}
if (!torrentSession.isRunning()) {
SessionParams sessionParams = new SessionParams(settingsPack);
torrentSession.start(sessionParams);
} else {
torrentSession.applySettings(settingsPack);
}
}
public boolean isStreaming() {
return isStreaming;
}
public String getCurrentTorrentUrl() {
return currentTorrentUrl;
}
public Integer getTotalDhtNodes() {
return dhtNodes;
}
public Torrent getCurrentTorrent() {
return currentTorrent;
}
public void addListener(TorrentListener listener) {
if (listener != null)
listeners.add(listener);
}
public void removeListener(TorrentListener listener) {
if (listener != null)
listeners.remove(listener);
}
protected class InternalTorrentListener implements TorrentListener {
public void onStreamStarted(final Torrent torrent) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamStarted(torrent);
}
});
}
}
public void onStreamError(final Torrent torrent, final Exception e) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamError(torrent, e);
}
});
}
}
public void onStreamReady(final Torrent torrent) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamReady(torrent);
}
});
}
}
public void onStreamProgress(final Torrent torrent, final StreamStatus status) {
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamProgress(torrent, status);
}
});
}
}
@Override
public void onStreamStopped() {
// Not used
}
@Override
public void onStreamPrepared(final Torrent torrent) {
if (torrentOptions.autoDownload) {
torrent.startDownload();
}
for (final TorrentListener listener : listeners) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
listener.onStreamPrepared(torrent);
}
});
}
}
}
}