diff --git a/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy b/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy index db84f7c5..5106e536 100644 --- a/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy +++ b/core/src/main/groovy/com/muwire/core/download/DownloadManager.groovy @@ -58,6 +58,8 @@ public class DownloadManager { e.result.each { destinations.add(it.sender.destination) } + destinations.addAll(e.sources) + destinations.remove(me.destination) def downloader = new Downloader(eventBus, this, me, e.target, size, infohash, pieceSize, connector, destinations, diff --git a/core/src/main/groovy/com/muwire/core/download/DownloadSession.groovy b/core/src/main/groovy/com/muwire/core/download/DownloadSession.groovy index 00874c54..a6a5172d 100644 --- a/core/src/main/groovy/com/muwire/core/download/DownloadSession.groovy +++ b/core/src/main/groovy/com/muwire/core/download/DownloadSession.groovy @@ -85,7 +85,7 @@ class DownloadSession { if (code.startsWith("404 ")) { log.warning("file not found") endpoint.close() - return + return false } if (code.startsWith("416 ")) { diff --git a/core/src/main/groovy/com/muwire/core/download/Downloader.groovy b/core/src/main/groovy/com/muwire/core/download/Downloader.groovy index 6d772c5d..4458b67b 100644 --- a/core/src/main/groovy/com/muwire/core/download/Downloader.groovy +++ b/core/src/main/groovy/com/muwire/core/download/Downloader.groovy @@ -21,6 +21,7 @@ import com.muwire.core.files.FileDownloadedEvent import groovy.util.logging.Log import net.i2p.data.Destination +import net.i2p.util.ConcurrentHashSet @Log public class Downloader { @@ -49,6 +50,7 @@ public class Downloader { private final File incompleteFile final int pieceSizePow2 private final Map activeWorkers = new ConcurrentHashMap<>() + private final Set successfulDestinations = new ConcurrentHashSet<>() private volatile boolean cancelled @@ -249,6 +251,7 @@ public class Downloader { requestPerformed = currentSession.request() if (!requestPerformed) break + successfulDestinations.add(endpoint.destination) writePieces() } } catch (Exception bad) { @@ -268,7 +271,7 @@ public class Downloader { } eventBus.publish( new FileDownloadedEvent( - downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, Collections.emptySet()), + downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations), downloader : Downloader.this)) } diff --git a/core/src/main/groovy/com/muwire/core/download/UIDownloadEvent.groovy b/core/src/main/groovy/com/muwire/core/download/UIDownloadEvent.groovy index 0dab3d18..6e70dd88 100644 --- a/core/src/main/groovy/com/muwire/core/download/UIDownloadEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/download/UIDownloadEvent.groovy @@ -3,8 +3,11 @@ package com.muwire.core.download import com.muwire.core.Event import com.muwire.core.search.UIResultEvent +import net.i2p.data.Destination + class UIDownloadEvent extends Event { UIResultEvent[] result + Set sources File target } diff --git a/core/src/main/groovy/com/muwire/core/search/ResultsParser.groovy b/core/src/main/groovy/com/muwire/core/search/ResultsParser.groovy index 91c4a986..4b541971 100644 --- a/core/src/main/groovy/com/muwire/core/search/ResultsParser.groovy +++ b/core/src/main/groovy/com/muwire/core/search/ResultsParser.groovy @@ -1,5 +1,7 @@ package com.muwire.core.search +import java.util.stream.Collectors + import javax.naming.directory.InvalidSearchControlsException import com.muwire.core.InfoHash @@ -7,6 +9,7 @@ import com.muwire.core.Persona import com.muwire.core.util.DataUtil import net.i2p.data.Base64 +import net.i2p.data.Destination class ResultsParser { public static UIResultEvent parse(Persona p, UUID uuid, def json) throws InvalidSearchResultException { @@ -58,6 +61,7 @@ class ResultsParser { size : size, infohash : parsedIH, pieceSize : pieceSize, + sources : Collections.emptySet(), uuid : uuid) } catch (Exception e) { throw new InvalidSearchResultException("parsing search result failed",e) @@ -82,11 +86,17 @@ class ResultsParser { if (infoHash.length != InfoHash.SIZE) throw new InvalidSearchResultException("invalid infohash size $infoHash.length") int pieceSize = json.pieceSize + + Set sources = Collections.emptySet() + if (json.sources != null) + sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet()) + return new UIResultEvent( sender : p, name : name, size : size, infohash : new InfoHash(infoHash), pieceSize : pieceSize, + sources : sources, uuid: uuid) } catch (Exception e) { throw new InvalidSearchResultException("parsing search result failed",e) diff --git a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy index 342043ff..56a331f8 100644 --- a/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy +++ b/core/src/main/groovy/com/muwire/core/search/ResultsSender.groovy @@ -11,7 +11,9 @@ import java.util.concurrent.Executor import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicInteger +import java.util.stream.Collectors +import com.muwire.core.DownloadedFile import com.muwire.core.EventBus import com.muwire.core.InfoHash @@ -54,12 +56,16 @@ class ResultsSender { int pieceSize = it.getPieceSize() if (pieceSize == 0) pieceSize = FileHasher.getPieceSize(length) + Set suggested = Collections.emptySet() + if (it instanceof DownloadedFile) + suggested = it.sources def uiResultEvent = new UIResultEvent( sender : me, name : it.getFile().getName(), size : length, infohash : it.getInfoHash(), pieceSize : pieceSize, - uuid : uuid + uuid : uuid, + sources : suggested ) eventBus.publish(uiResultEvent) } @@ -110,6 +116,10 @@ class ResultsSender { } obj.hashList = hashListB64 } + + if (it instanceof DownloadedFile) + obj.sources = it.sources.stream().map({dest -> dest.toBase64()}).collect(Collectors.toSet()) + def json = jsonOutput.toJson(obj) os.writeShort((short)json.length()) os.write(json.getBytes(StandardCharsets.US_ASCII)) diff --git a/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy b/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy index db3b12e0..c8d5be69 100644 --- a/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy +++ b/core/src/main/groovy/com/muwire/core/search/UIResultEvent.groovy @@ -4,8 +4,11 @@ import com.muwire.core.Event import com.muwire.core.InfoHash import com.muwire.core.Persona +import net.i2p.data.Destination + class UIResultEvent extends Event { Persona sender + Set sources UUID uuid String name long size diff --git a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy index 609e7480..f491703a 100644 --- a/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy +++ b/gui/griffon-app/controllers/com/muwire/gui/MainFrameController.groovy @@ -129,8 +129,9 @@ class MainFrameController { def group = selected.getClientProperty("mvc-group") def resultsBucket = group.model.hashBucket[result.infohash] + def sources = group.model.sourcesBucket[result.infohash] - core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, target : file)) + core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file)) } @ControllerAction diff --git a/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy b/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy index 476f7819..85aeb9b0 100644 --- a/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy +++ b/gui/griffon-app/models/com/muwire/gui/SearchTabModel.groovy @@ -23,6 +23,7 @@ class SearchTabModel { String uuid def results = [] def hashBucket = [:] + def sourcesBucket = [:] void mvcGroupInit(Map args) { @@ -46,6 +47,13 @@ class SearchTabModel { hashBucket[e.infohash] = bucket } bucket << e + + Set sourceBucket = sourcesBucket.get(e.infohash) + if (sourceBucket == null) { + sourceBucket = new HashSet() + sourcesBucket.put(e.infohash, sourceBucket) + } + sourceBucket.addAll(e.sources) results << e JTable table = builder.getVariable("results-table") @@ -63,6 +71,14 @@ class SearchTabModel { bucket = [] hashBucket[it.infohash] = bucket } + + Set sourceBucket = sourcesBucket.get(it.infohash) + if (sourceBucket == null) { + sourceBucket = new HashSet() + sourcesBucket.put(it.infohash, sourceBucket) + } + sourceBucket.addAll(it.sources) + bucket << it results << it } diff --git a/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy index a80607f4..deb94685 100644 --- a/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy +++ b/gui/griffon-app/views/com/muwire/gui/SearchTabView.groovy @@ -47,8 +47,9 @@ class SearchTabView { resultsTable = table(id : "results-table", autoCreateRowSorter : true) { tableModel(list: model.results) { closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')}) - closureColumn(header: "Size", preferredWidth: 50, type: Long, read : {row -> row.size}) - closureColumn(header: "Sources", preferredWidth: 10, type : Integer, read : { row -> model.hashBucket[row.infohash].size()}) + closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size}) + closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()}) + closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()}) closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()}) closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row -> model.core.trustService.getLevel(row.sender.destination).toString()