/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package org.pieShare.pieShareApp.task.localTasks; import com.turn.ttorrent.client.Client; import com.turn.ttorrent.client.SharedTorrent; import com.turn.ttorrent.common.Torrent; import com.turn.ttorrent.tracker.TrackedTorrent; import com.turn.ttorrent.tracker.Tracker; import java.io.File; import java.net.InetSocketAddress; import java.net.URI; import java.util.List; import org.pieShare.pieShareApp.model.message.api.IFileTransferCompleteMessage; import org.pieShare.pieShareApp.service.networkService.INetworkService; import org.pieShare.pieShareApp.model.pieFile.FileMeta; import org.pieShare.pieShareApp.service.fileService.api.IFileService; import org.pieShare.pieShareApp.service.requestService.api.IRequestService; import org.pieShare.pieShareApp.service.shareService.IBitTorrentService; import org.pieShare.pieShareApp.service.shareService.IShareService; import org.pieShare.pieShareApp.task.AMessageSendingTask; import org.pieShare.pieTools.pieUtilities.service.pieLogger.PieLogger; import org.pieShare.pieTools.pieUtilities.service.shutDownService.api.IShutdownableService; /** * * @author Svetoslav */ public class TorrentTask extends AMessageSendingTask implements IShutdownableService { //services private IShareService shareService; private INetworkService networkService; private IBitTorrentService bitTorrentService; private IFileService fileService; private IRequestService requestService; //injected fields private FileMeta fileMeta; private Torrent torrent; //local fields private Client client; private boolean shutdown = false; private Tracker tracker; public void setBitTorrentService(IBitTorrentService bitTorrentService) { this.bitTorrentService = bitTorrentService; } public void setShareService(IShareService shareService) { this.shareService = shareService; } public void setNetworkService(INetworkService networkService) { this.networkService = networkService; } public void setFileService(IFileService fileService) { this.fileService = fileService; } public void setFile(FileMeta file) { this.fileMeta = file; } public void setTorrent(Torrent torrent) { this.torrent = torrent; } public void setRequestService(IRequestService requestService) { this.requestService = requestService; } @Override public void run() { PieLogger.trace(this.getClass(), "Starting torrent task for {}.", this.fileMeta.getFile()); try { boolean seeder = this.torrent.isSeeder(); File destDir = this.fileService.getAbsoluteTmpPath(this.fileMeta.getFile()).toFile().getParentFile(); //todo: ther is a bug when triing to share 0 byte files //start a tracker for each file seperately //this is a workaround for the time being due to ttorrent bugs. if (seeder) { for (List<URI> list : torrent.getAnnounceList()) { for (URI uri : list) { if (uri.getHost().equals(this.networkService.getLocalHost().getHostAddress())) { this.networkService.freeReservedPort(uri.getPort()); this.tracker = new Tracker(new InetSocketAddress(this.networkService.getLocalHost(), uri.getPort())); //todo: security issues? this.tracker.announce(new TrackedTorrent(this.torrent)); this.tracker.start(); } } } } //this.fileWatcherService.addPieFileToModifiedList(pieFile); //todo: handle ports out problem!!! //todo: this should run somehow over the beans SharedTorrent sharedTorrent = new SharedTorrent(this.torrent, destDir); this.client = new Client(networkService.getLocalHost(), sharedTorrent); //todo: this time has to move into the properties //todo: won't work for server and client the same way: problem with this timeout the server //shuts down after 30 seconds... implement other timeout strategy or rerequest messages //reregquest is maybe the better way //todo: the problem is like follows: what happens if we never receive a fileShareCompleteMessage //we have to recover somehow client.share(); //todo: errorState discovery is just a dirty fix for bug in ttorrent library!! boolean loopDone = false; long lastAmount = 0; int errorSeconds = 0; int sleepTime = 1000; int errorThreshold = 30000 / sleepTime; boolean errorState = false; while (!Client.ClientState.DONE.equals(client.getState()) && !loopDone) { // Wait one second Thread.sleep(sleepTime); // Display statistics PieLogger.debug(this.getClass(), "{} %% - state {} - {} bytes downloaded - {} bytes uploaded - peers {} - {}", sharedTorrent.getCompletion(), client.getState(), sharedTorrent.getDownloaded(), sharedTorrent.getUploaded(), client.getPeers().size(), fileMeta.getFile().getFileName()); if (this.shutdown) { PieLogger.info(this.getClass(), String.format("Shuting down torrent task for %s", fileMeta.getFile().getFileName())); return; } // Check if there's an error if (Client.ClientState.ERROR.equals(client.getState())) { this.shutdown(); //todo: ports release when exception throw new Exception("ttorrent client Error State"); } if (!seeder && Client.ClientState.SEEDING.equals(client.getState())) { //the local client is done this.bitTorrentService.clientDone(fileMeta); //it is important that this message gets send as soon as possible so the different clients don't block each other //for example on server instance and two clients //each client will keep sharing until the error timeout if this message doesn't get send here IFileTransferCompleteMessage msgComplete = this.messageFactoryService.getFileTransferCompleteMessage(); msgComplete.setFileMeta(fileMeta); this.setDefaultAdresse(msgComplete); this.clusterManagementService.sendMessage(msgComplete); } if ((seeder || Client.ClientState.SEEDING.equals(client.getState())) && !this.bitTorrentService.isShareActive(this.fileMeta)) { PieLogger.debug(this.getClass(), String.format("Stoping client for %s by check done loop!", fileMeta.getFile().getFileName())); loopDone = true; } if (!Client.ClientState.VALIDATING.equals(client.getState())) { long currentState = sharedTorrent.getDownloaded(); if (seeder) { currentState = sharedTorrent.getUploaded(); } if (currentState == lastAmount) { errorSeconds++; } else { errorSeconds = 0; } if (errorSeconds > errorThreshold) { loopDone = true; errorState = true; } lastAmount = currentState; } } this.shutdown(); this.bitTorrentService.torrentClientDone(seeder, this.fileMeta); this.requestService.deleteRequestedFile(this.fileMeta.getFile()); if (!seeder && (!errorState || sharedTorrent.isComplete())) { //todo: improve this to work in parallel so we can copy file while still sharing this.shareService.localFileTransferComplete(fileMeta.getFile(), seeder); /*IFileTransferCompleteMessage msgComplete = this.messageFactoryService.getFileTransferCompleteMessage(); msgComplete.setPieFile(fileMeta.getFile()); this.setDefaultAdresse(msgComplete); this.clusterManagementService.sendMessage(msgComplete);*/ } if (errorState && !sharedTorrent.isComplete() && !seeder) { this.requestService.requestFile(this.fileMeta.getFile()); } } catch (Exception ex) { PieLogger.error(this.getClass(), "Exception in torrent task.", ex); } } @Override public void shutdown() { this.shutdown = true; PieLogger.info(this.getClass(), "Shuting down torrent task!"); if (this.client != null) { this.client.stop(false); } if (this.tracker != null) { this.tracker.stop(); } } }