package com.torrenttunes.client.db;
import static com.torrenttunes.client.db.Tables.LIBRARY;
import static com.torrenttunes.client.db.Tables.SETTINGS;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringEscapeUtils;
import org.codehaus.jackson.JsonNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.frostwire.jlibtorrent.TorrentAlertAdapter;
import com.frostwire.jlibtorrent.TorrentHandle;
import com.frostwire.jlibtorrent.alerts.TorrentFinishedAlert;
import com.torrenttunes.client.LibtorrentEngine;
import com.torrenttunes.client.ScanDirectory.ScanInfo;
import com.torrenttunes.client.ScanDirectory.ScanStatus;
import com.torrenttunes.client.db.Tables.Library;
import com.torrenttunes.client.db.Tables.Settings;
import com.torrenttunes.client.extra.TorrentStats;
import com.torrenttunes.client.tools.DataSources;
import com.torrenttunes.client.tools.Tools;
public class Actions {
static final Logger log = LoggerFactory.getLogger(Actions.class);
// 15 minute download timeout
public static final Long DOWNLOAD_TIMEOUT = TimeUnit.MILLISECONDS.convert(15, TimeUnit.MINUTES);
public static Library saveSongToLibrary(String mbid, String torrentPath, String infoHash,
String filePath, String artist, String artistMbid,
String title, Long durationMS) {
log.info("Saving song " + infoHash + " to library");
Library library = LIBRARY.createIt("mbid", mbid,
"torrent_path", torrentPath,
"info_hash", infoHash,
"file_path", filePath,
"artist", StringEscapeUtils.escapeHtml4(artist),
"artist_mbid", artistMbid,
"title", StringEscapeUtils.escapeHtml4(title),
"duration_ms", durationMS);
return library;
}
public static String saveSettings(String storagePath, Integer maxDownloadSpeed,
Integer maxUploadSpeed, Integer maxCacheSize) {
Settings s = SETTINGS.findFirst("id = ?", 1);
StringBuilder message = new StringBuilder();
// If storage path has changed, you need to move all the torrents in that cache directory,
// to wherever they want, and change their
String currentStoragePath = s.getString("storage_path");
if (!storagePath.equals(currentStoragePath)) {
message.append("Moved all music files from " + currentStoragePath + " to " + storagePath);
s.set("storage_path", storagePath);
for (Library track : getCachedTracks()) {
File trackFile = new File(track.getString("file_path"));
// moves the file to the new dir
trackFile.renameTo(new File(storagePath + "/" + trackFile.getName()));
// Save its new directory
track.set("file_path", storagePath).saveIt();
}
}
s.set("max_download_speed", maxDownloadSpeed,
"max_upload_speed", maxUploadSpeed,
"max_cache_size_mb", maxCacheSize);
s.saveIt();
updateLibtorrentSettings(s);
message.append("Settings Saved");
return message.toString();
}
public static void updateLibtorrentSettings(Settings s) {
log.info("Applying libtorrent Settings...");
LibtorrentEngine lte = LibtorrentEngine.INSTANCE;
Integer maxDownloadSpeed = s.getInteger("max_download_speed");
Integer maxUploadSpeed = s.getInteger("max_upload_speed");
maxDownloadSpeed = (maxDownloadSpeed != -1) ? maxDownloadSpeed : 0;
maxUploadSpeed = (maxUploadSpeed != -1) ? maxUploadSpeed : 0;
lte.getSettings().setUploadRateLimit(maxUploadSpeed*1000);
lte.getSettings().setDownloadRateLimit(maxDownloadSpeed*1000);
lte.getSession().applySettings(lte.getSettings());
}
public static void setupMusicStoragePath(Settings s) {
String storagePath = s.getString("storage_path");
DataSources.MUSIC_STORAGE_PATH = storagePath;
log.info("Storage path = " + DataSources.MUSIC_STORAGE_PATH);
}
public static String downloadTorrent(String infoHash) throws IOException, InterruptedException {
log.info("Downloading infohash: " + infoHash);
String json = null;
LibtorrentEngine lte = LibtorrentEngine.INSTANCE;
Library track;
String torrentPath = DataSources.TORRENTS_DIR() + "/" + infoHash + ".torrent";
// Fetch the .torrent file it to a file, save it to the torrents dir
Tools.httpSaveFile(DataSources.TORRENT_DOWNLOAD_URL(infoHash), torrentPath);
// Fetch the .torrent file json info
String trackJson = Tools.httpGetString(DataSources.TORRENT_INFO_DOWNLOAD_URL(infoHash));
JsonNode jsonNode = Tools.jsonToNode(trackJson);
// Set up all the necessary vars from the jsonInfo
String songMbid = jsonNode.get("song_mbid").asText();
String songTitle = jsonNode.get("title").asText();
Long duration = jsonNode.get("duration_ms").asLong();
Integer trackNumber = jsonNode.get("track_number").asInt();
String album = jsonNode.get("album").asText();
String albumMbid = jsonNode.get("release_group_mbid").asText();
String artist = jsonNode.get("artist").asText();
String artistMbid = jsonNode.get("artist_mbid").asText();
String year = jsonNode.get("year").asText();
// add the torrent file(saving to the storage dir), scan info, and start seeding it
TorrentHandle torrent = lte.addTorrent(
new File(DataSources.MUSIC_STORAGE_PATH), new File(torrentPath), false, true);
String audioFilePath = DataSources.AUDIO_FILE(torrent.getName());
// Set up the scanInfo
ScanInfo si = ScanInfo.create(new File(audioFilePath));
si.setStatus(ScanStatus.Seeding);
si.setMbid(songMbid);
lte.getScanInfos().add(si);
// Need to add the # of peers, and block IO until download is done, or times out
final CountDownLatch signal = new CountDownLatch(1);
// If it takes more than 30 seconds to download a file, then set no peers,
// and throw an error
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
signal.countDown();
}
}, DOWNLOAD_TIMEOUT);
lte.getSession().addListener(new TorrentAlertAdapter(torrent) {
private Timer timer;
@Override
public void torrentFinished(TorrentFinishedAlert alert) {
log.info("torrent finished: " + infoHash);
// Save the track to your DB
try {
Tools.dbInit();
Actions.saveSongToLibrary(songMbid,
torrentPath,
infoHash,
audioFilePath,
artist,
artistMbid,
songTitle,
duration);
} catch(Exception e) {
e.printStackTrace();
} finally {
Tools.dbClose();
}
TorrentStats ts = TorrentStats.create(torrent);
log.info(ts.toString());
// Once the torrent's finished, save the number of peers:
String resp = Tools.httpGetString(DataSources.SEEDER_INFO_UPLOAD(
infoHash, ts.getPeers()));
log.info("Seeder count :" + ts.getPeers());
log.info("Seeder post response: " + resp);
signal.countDown();
timer.cancel();
}
private TorrentAlertAdapter init(Timer t) {
timer = t;
return this;
}
}.init(timer));
signal.await();
// Get the json for the saved track
Tools.dbInit();
track = LIBRARY.findFirst("info_hash = ?", infoHash);
Tools.dbClose();
// if it wasn't successful(IE no peers found or > 40 seconds)
if (track == null) {
// Set the # of seeders to 0
String resp = Tools.httpGetString(DataSources.SEEDER_INFO_UPLOAD(
infoHash, "0"));
log.info("No peers found, Setting seeders to :" + 0);
log.info("Seeder post response: " + resp);
throw new NoSuchElementException("No peers found for " +
artist + " - " + songTitle + ", or download took too long. Check to make sure your firewall"
+ " is turned off.");
} else {
json = track.toJson(false);
}
return json;
}
public static Boolean spaceFreeInStoragePath() {
// Check to make sure you have space in the cache
Tools.dbInit();
Settings settings = SETTINGS.findFirst("id = ?", 1);
Tools.dbClose();
Integer settingsFreeSpaceMB = settings.getInteger("max_cache_size_mb");
settingsFreeSpaceMB = (settingsFreeSpaceMB != -1) ? settingsFreeSpaceMB : Integer.MAX_VALUE;
Long temp = Math.round(Tools.folderSize(new File(DataSources.MUSIC_STORAGE_PATH)) * 0.000001);
Integer storageFolderSizeMB = temp.intValue();
// Make sure there is at least 100 MB free
Boolean spaceFree = (storageFolderSizeMB < settingsFreeSpaceMB) &&
(new File("/").getUsableSpace() > 1E8);
return spaceFree;
}
public static String deleteSong(String infoHash) {
String message = null;
try {
// Stop the torrent, remove it from the session
TorrentHandle torrent = LibtorrentEngine.INSTANCE.getInfoHashToTorrentMap().get(infoHash);
LibtorrentEngine.INSTANCE.getSession().removeTorrent(torrent);
// Remove it from the DB
Library song = LIBRARY.findFirst("info_hash = ?", infoHash);
String torrentPath = song.getString("torrent_path");
String filePath = song.getString("file_path");
song.delete();
// delete the .torrent file and the file
new File(torrentPath).delete();
new File(filePath).delete();
message = filePath + " has been deleted";
} catch(NullPointerException e) {
e.printStackTrace();
throw new NoSuchElementException("Song has already been deleted");
}
return message;
}
public static String removeArtist(String artistMBID) {
List<Library> songs = LIBRARY.find("artist_mbid = ?", artistMBID);
String cacheDir = SETTINGS.findFirst("id = ?", 1).getString("storage_path");
for (Library song : songs) {
removeSong(song, cacheDir);
}
return "Artist : " + artistMBID + " deleted from library";
}
public static String removeSong(String songMBID) {
Library song = LIBRARY.findFirst("mbid = ?", songMBID);
if (song != null) {
removeSong(song);
return "Song : " + songMBID + " deleted from library";
} else {
return "Song mbid = " + songMBID + " wasn't found";
}
}
public static String removeSong(Library song) {
String cacheDir = SETTINGS.findFirst("id = ?", 1).getString("storage_path");
return removeSong(song, cacheDir);
}
public static String removeSong(Library song, String cacheDir) {
LibtorrentEngine lt = LibtorrentEngine.INSTANCE;
String songMBID = song.getString("mbid");
// remove the infohash from the session
String infoHash = song.getString("info_hash");
try {
lt.getSession().removeTorrent(lt.getInfoHashToTorrentMap().get(infoHash));
} catch(NullPointerException e) {
log.error("Torrent infohash " + infoHash + " was not in Libtorrent Session");
}
// delete the torrent file
new File(song.getString("torrent_path")).delete();
// delete the save resume data file
String srDataPath = DataSources.TORRENTS_DIR() + "/srdata_" +
new File(song.getString("file_path")).getName();
new File(srDataPath).delete();
// delete the song(only if its a cached one)
File songFile = new File(song.getString("file_path"));
String parent = songFile.getParentFile().getAbsolutePath();
if (parent.equals(cacheDir)) {
log.info("file: " + songFile.getAbsolutePath() + " deleted");
songFile.delete();
}
// delete the row
song.delete();
return "Track : " + songMBID + " deleted from library.";
}
public static String clearCache() {
List<Library> cachedTracks = getCachedTracks();
String cacheDir = SETTINGS.findFirst("id = ?", 1).getString("storage_path");
for (Library song : cachedTracks) {
log.info("Song removed from cache: " + song.getString("file_path"));
removeSong(song, cacheDir);
}
String msg = "Songs removed from cache: " + cachedTracks.size();
log.info(msg);
return msg;
}
public static String clearDatabase() {
LIBRARY.deleteAll();
return "Database cleared";
}
public static List<Library> getCachedTracks() {
String cacheDir = SETTINGS.findFirst("id = ?", 1).getString("storage_path");
log.info("cache dir = " + cacheDir);
// remove the tracks that aren't in the cache directory from the list
List<Library> tracks = LIBRARY.findAll();
List<Library> cachedTracks = new ArrayList<>();
for (int i = 0; i < tracks.size(); i++) {
Library song = tracks.get(i);
File filePath = new File(song.getString("file_path"));
String parent = filePath.getParentFile().getAbsolutePath();
// IE, if this is in the cache dir, this gets added
if (parent.equals(cacheDir)) {
cachedTracks.add(song);
}
}
return cachedTracks;
}
}