remove trailing spaces
This commit is contained in:
@ -16,24 +16,24 @@ import com.muwire.core.upload.UploadEvent
|
||||
import com.muwire.core.upload.UploadFinishedEvent
|
||||
|
||||
class Cli {
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
home = new File(home)
|
||||
if (!home.exists())
|
||||
home.mkdirs()
|
||||
|
||||
|
||||
def propsFile = new File(home,"MuWire.properties")
|
||||
if (!propsFile.exists()) {
|
||||
println "create props file ${propsFile.getAbsoluteFile()} before launching MuWire"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
|
||||
def props = new Properties()
|
||||
propsFile.withInputStream { props.load(it) }
|
||||
props = new MuWireSettings(props)
|
||||
|
||||
Core core
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.7")
|
||||
} catch (Exception bad) {
|
||||
@ -42,40 +42,40 @@ class Cli {
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def filesList
|
||||
if (args.length == 0) {
|
||||
println "Enter a file containing list of files to share"
|
||||
def reader = new BufferedReader(new InputStreamReader(System.in))
|
||||
filesList = reader.readLine()
|
||||
} else
|
||||
} else
|
||||
filesList = args[0]
|
||||
|
||||
|
||||
Thread.sleep(1000)
|
||||
println "loading shared files from $filesList"
|
||||
|
||||
|
||||
// listener for shared files
|
||||
def sharedListener = new SharedListener()
|
||||
core.eventBus.register(FileHashedEvent.class, sharedListener)
|
||||
core.eventBus.register(FileLoadedEvent.class, sharedListener)
|
||||
|
||||
|
||||
// for connections
|
||||
def connectionsListener = new ConnectionListener()
|
||||
core.eventBus.register(ConnectionEvent.class, connectionsListener)
|
||||
core.eventBus.register(DisconnectionEvent.class, connectionsListener)
|
||||
|
||||
|
||||
// for uploads
|
||||
def uploadsListener = new UploadsListener()
|
||||
core.eventBus.register(UploadEvent.class, uploadsListener)
|
||||
core.eventBus.register(UploadFinishedEvent.class, uploadsListener)
|
||||
|
||||
|
||||
Timer timer = new Timer("status-printer", true)
|
||||
timer.schedule({
|
||||
println String.valueOf(new Date()) + " Connections $connectionsListener.connections Uploads $uploadsListener.uploads Shared $sharedListener.shared"
|
||||
} as TimerTask, 60000, 60000)
|
||||
|
||||
|
||||
def latch = new CountDownLatch(1)
|
||||
def fileLoader = new Object() {
|
||||
public void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
@ -85,14 +85,14 @@ class Cli {
|
||||
core.eventBus.register(AllFilesLoadedEvent.class, fileLoader)
|
||||
core.startServices()
|
||||
|
||||
core.eventBus.publish(new UILoadedEvent())
|
||||
core.eventBus.publish(new UILoadedEvent())
|
||||
println "waiting for files to load"
|
||||
latch.await()
|
||||
// now we begin
|
||||
println "MuWire is ready"
|
||||
|
||||
|
||||
filesList = new File(filesList)
|
||||
filesList.withReader {
|
||||
filesList.withReader {
|
||||
def toShare = it.readLine()
|
||||
core.eventBus.publish(new FileSharedEvent(file : new File(toShare)))
|
||||
}
|
||||
@ -103,7 +103,7 @@ class Cli {
|
||||
})
|
||||
Thread.sleep(Integer.MAX_VALUE)
|
||||
}
|
||||
|
||||
|
||||
static class ConnectionListener {
|
||||
volatile int connections
|
||||
public void onConnectionEvent(ConnectionEvent e) {
|
||||
@ -114,7 +114,7 @@ class Cli {
|
||||
connections--
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class UploadsListener {
|
||||
volatile int uploads
|
||||
public void onUploadEvent(UploadEvent e) {
|
||||
@ -126,7 +126,7 @@ class Cli {
|
||||
println String.valueOf(new Date()) + " Finished upload of ${e.uploader.file.getName()} to ${e.uploader.request.downloader.getHumanReadableName()}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class SharedListener {
|
||||
volatile int shared
|
||||
void onFileHashedEvent(FileHashedEvent e) {
|
||||
|
@ -17,31 +17,31 @@ import com.muwire.core.search.UIResultEvent
|
||||
import net.i2p.data.Base64
|
||||
|
||||
class CliDownloader {
|
||||
|
||||
|
||||
private static final List<Downloader> downloaders = Collections.synchronizedList(new ArrayList<>())
|
||||
private static final Map<UUID,ResultsHolder> resultsListeners = new ConcurrentHashMap<>()
|
||||
|
||||
|
||||
public static void main(String []args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
home = new File(home)
|
||||
if (!home.exists())
|
||||
home.mkdirs()
|
||||
|
||||
|
||||
def propsFile = new File(home,"MuWire.properties")
|
||||
if (!propsFile.exists()) {
|
||||
println "create props file ${propsFile.getAbsoluteFile()} before launching MuWire"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
|
||||
def props = new Properties()
|
||||
propsFile.withInputStream { props.load(it) }
|
||||
props = new MuWireSettings(props)
|
||||
|
||||
|
||||
def filesList
|
||||
int connections
|
||||
int resultWait
|
||||
if (args.length != 3) {
|
||||
println "Enter a file containing list of hashes of files to download, " +
|
||||
println "Enter a file containing list of hashes of files to download, " +
|
||||
"how many connections you want before searching" +
|
||||
"and how long to wait for results to arrive"
|
||||
System.exit(1)
|
||||
@ -59,18 +59,18 @@ class CliDownloader {
|
||||
println "Failed to initialize core, exiting"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
def latch = new CountDownLatch(connections)
|
||||
def connectionListener = new ConnectionWaiter(latch : latch)
|
||||
core.eventBus.register(ConnectionEvent.class, connectionListener)
|
||||
|
||||
|
||||
core.startServices()
|
||||
println "starting to wait until there are $connections connections"
|
||||
latch.await()
|
||||
|
||||
|
||||
println "connected, searching for files"
|
||||
|
||||
|
||||
def file = new File(filesList)
|
||||
file.eachLine {
|
||||
String[] split = it.split(",")
|
||||
@ -79,22 +79,22 @@ class CliDownloader {
|
||||
def hash = Base64.decode(split[0])
|
||||
def searchEvent = new SearchEvent(searchHash : hash, uuid : uuid)
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop:true,
|
||||
replyTo: core.me.destination, receivedOn : core.me.destination, originator: core.me))
|
||||
replyTo: core.me.destination, receivedOn : core.me.destination, originator: core.me))
|
||||
}
|
||||
|
||||
|
||||
println "waiting for results to arrive"
|
||||
Thread.sleep(resultWait * 1000)
|
||||
|
||||
|
||||
core.eventBus.register(DownloadStartedEvent.class, new DownloadListener())
|
||||
resultsListeners.each { uuid, resultsListener ->
|
||||
println "starting download of $resultsListener.fileName from ${resultsListener.getResults().size()} hosts"
|
||||
File target = new File(resultsListener.fileName)
|
||||
|
||||
|
||||
core.eventBus.publish(new UIDownloadEvent(target : target, result : resultsListener.getResults()))
|
||||
}
|
||||
|
||||
|
||||
Thread.sleep(1000)
|
||||
|
||||
|
||||
Timer timer = new Timer("stats-printer")
|
||||
timer.schedule({
|
||||
println "==== STATUS UPDATE ==="
|
||||
@ -109,7 +109,7 @@ class CliDownloader {
|
||||
}
|
||||
println "==== END ==="
|
||||
} as TimerTask, 60000, 60000)
|
||||
|
||||
|
||||
println "waiting for downloads to finish"
|
||||
while(true) {
|
||||
boolean allFinished = true
|
||||
@ -120,10 +120,10 @@ class CliDownloader {
|
||||
break
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
|
||||
|
||||
println "all downloads finished"
|
||||
}
|
||||
|
||||
|
||||
static class ResultsHolder {
|
||||
final List<UIResultEvent> results = Collections.synchronizedList(new ArrayList<>())
|
||||
String fileName
|
||||
@ -134,7 +134,7 @@ class CliDownloader {
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ResultsListener {
|
||||
UUID uuid
|
||||
String fileName
|
||||
@ -148,7 +148,7 @@ class CliDownloader {
|
||||
listener.add(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class ConnectionWaiter {
|
||||
CountDownLatch latch
|
||||
public void onConnectionEvent(ConnectionEvent e) {
|
||||
@ -156,7 +156,7 @@ class CliDownloader {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static class DownloadListener {
|
||||
public void onDownloadStartedEvent(DownloadStartedEvent e) {
|
||||
|
@ -11,10 +11,10 @@ class FileList {
|
||||
println "pass files.json as argument"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
|
||||
def slurper = new JsonSlurper()
|
||||
File filesJson = new File(args[0])
|
||||
filesJson.eachLine {
|
||||
filesJson.eachLine {
|
||||
def json = slurper.parseText(it)
|
||||
String name = DataUtil.readi18nString(Base64.decode(json.file))
|
||||
println "$name,$json.length,$json.pieceSize,$json.infoHash"
|
||||
|
@ -4,10 +4,10 @@ import net.i2p.crypto.SigType
|
||||
|
||||
class Constants {
|
||||
public static final byte PERSONA_VERSION = (byte)1
|
||||
public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519
|
||||
|
||||
public static final SigType SIG_TYPE = SigType.EdDSA_SHA512_Ed25519
|
||||
|
||||
public static final int MAX_HEADER_SIZE = 0x1 << 14
|
||||
public static final int MAX_HEADERS = 16
|
||||
|
||||
|
||||
public static final String SPLIT_PATTERN = "[\\*\\+\\-,\\.:;\\(\\)=_/\\\\\\!\\\"\\\'\\\$%\\|\\[\\]\\{\\}\\?]"
|
||||
}
|
||||
|
@ -69,13 +69,13 @@ import net.i2p.router.RouterContext
|
||||
|
||||
@Log
|
||||
public class Core {
|
||||
|
||||
|
||||
final EventBus eventBus
|
||||
final Persona me
|
||||
final File home
|
||||
final Properties i2pOptions
|
||||
final MuWireSettings muOptions
|
||||
|
||||
|
||||
private final TrustService trustService
|
||||
private final TrustSubscriber trustSubscriber
|
||||
private final PersisterService persisterService
|
||||
@ -90,20 +90,20 @@ public class Core {
|
||||
private final DirectoryWatcher directoryWatcher
|
||||
final FileManager fileManager
|
||||
final UploadManager uploadManager
|
||||
|
||||
|
||||
private final Router router
|
||||
|
||||
|
||||
final AtomicBoolean shutdown = new AtomicBoolean()
|
||||
|
||||
|
||||
public Core(MuWireSettings props, File home, String myVersion) {
|
||||
this.home = home
|
||||
this.home = home
|
||||
this.muOptions = props
|
||||
|
||||
|
||||
i2pOptions = new Properties()
|
||||
def i2pOptionsFile = new File(home,"i2p.properties")
|
||||
if (i2pOptionsFile.exists()) {
|
||||
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
|
||||
|
||||
|
||||
if (!i2pOptions.containsKey("inbound.nickname"))
|
||||
i2pOptions["inbound.nickname"] = "MuWire"
|
||||
if (!i2pOptions.containsKey("outbound.nickname"))
|
||||
@ -123,7 +123,7 @@ public class Core {
|
||||
i2pOptions["i2np.udp.port"] = String.valueOf(port)
|
||||
i2pOptionsFile.withOutputStream { i2pOptions.store(it, "") }
|
||||
}
|
||||
|
||||
|
||||
if (!props.embeddedRouter) {
|
||||
log.info "Initializing I2P context"
|
||||
I2PAppContext.getGlobalContext().logManager()
|
||||
@ -146,18 +146,18 @@ public class Core {
|
||||
while(!router.isRunning())
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
|
||||
log.info("initializing I2P socket manager")
|
||||
def i2pClient = new I2PClientFactory().createClient()
|
||||
File keyDat = new File(home, "key.dat")
|
||||
if (!keyDat.exists()) {
|
||||
log.info("Creating new key.dat")
|
||||
keyDat.withOutputStream {
|
||||
keyDat.withOutputStream {
|
||||
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// options like tunnel length and quantity
|
||||
I2PSession i2pSession
|
||||
I2PSocketManager socketManager
|
||||
@ -168,7 +168,7 @@ public class Core {
|
||||
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
||||
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
|
||||
i2pSession = socketManager.getSession()
|
||||
|
||||
|
||||
def destination = new Destination()
|
||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||
keyDat.withInputStream {
|
||||
@ -177,7 +177,7 @@ public class Core {
|
||||
privateKey.readBytes(it)
|
||||
spk.readBytes(it)
|
||||
}
|
||||
|
||||
|
||||
def baos = new ByteArrayOutputStream()
|
||||
def daos = new DataOutputStream(baos)
|
||||
daos.write(Constants.PERSONA_VERSION)
|
||||
@ -195,14 +195,14 @@ public class Core {
|
||||
log.info("Loaded myself as "+me.getHumanReadableName())
|
||||
|
||||
eventBus = new EventBus()
|
||||
|
||||
|
||||
log.info("initializing trust service")
|
||||
File goodTrust = new File(home, "trusted")
|
||||
File badTrust = new File(home, "distrusted")
|
||||
trustService = new TrustService(goodTrust, badTrust, 5000)
|
||||
eventBus.register(TrustEvent.class, trustService)
|
||||
|
||||
|
||||
|
||||
|
||||
log.info "initializing file manager"
|
||||
fileManager = new FileManager(eventBus, props)
|
||||
eventBus.register(FileHashedEvent.class, fileManager)
|
||||
@ -211,49 +211,49 @@ public class Core {
|
||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||
eventBus.register(SearchEvent.class, fileManager)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||
|
||||
|
||||
log.info("initializing mesh manager")
|
||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
||||
|
||||
|
||||
log.info "initializing persistence service"
|
||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
||||
eventBus.register(UILoadedEvent.class, persisterService)
|
||||
|
||||
|
||||
log.info("initializing host cache")
|
||||
File hostStorage = new File(home, "hosts.json")
|
||||
hostCache = new HostCache(trustService,hostStorage, 30000, props, i2pSession.getMyDestination())
|
||||
eventBus.register(HostDiscoveredEvent.class, hostCache)
|
||||
eventBus.register(ConnectionEvent.class, hostCache)
|
||||
|
||||
|
||||
log.info("initializing connection manager")
|
||||
connectionManager = props.isLeaf() ?
|
||||
new LeafConnectionManager(eventBus, me, 3, hostCache, props) :
|
||||
connectionManager = props.isLeaf() ?
|
||||
new LeafConnectionManager(eventBus, me, 3, hostCache, props) :
|
||||
new UltrapeerConnectionManager(eventBus, me, 512, 512, hostCache, trustService, props)
|
||||
eventBus.register(TrustEvent.class, connectionManager)
|
||||
eventBus.register(ConnectionEvent.class, connectionManager)
|
||||
eventBus.register(DisconnectionEvent.class, connectionManager)
|
||||
eventBus.register(QueryEvent.class, connectionManager)
|
||||
|
||||
|
||||
log.info("initializing cache client")
|
||||
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
||||
|
||||
|
||||
log.info("initializing update client")
|
||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
||||
eventBus.register(FileDownloadedEvent.class, updateClient)
|
||||
eventBus.register(UIResultBatchEvent.class, updateClient)
|
||||
|
||||
|
||||
log.info("initializing connector")
|
||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||
|
||||
|
||||
log.info "initializing results sender"
|
||||
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
||||
|
||||
|
||||
log.info "initializing search manager"
|
||||
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
||||
eventBus.register(QueryEvent.class, searchManager)
|
||||
eventBus.register(ResultsEvent.class, searchManager)
|
||||
|
||||
|
||||
log.info("initializing download manager")
|
||||
downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me)
|
||||
eventBus.register(UIDownloadEvent.class, downloadManager)
|
||||
@ -263,34 +263,34 @@ public class Core {
|
||||
eventBus.register(SourceDiscoveredEvent.class, downloadManager)
|
||||
eventBus.register(UIDownloadPausedEvent.class, downloadManager)
|
||||
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
||||
|
||||
|
||||
log.info("initializing upload manager")
|
||||
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
||||
|
||||
|
||||
log.info("initializing connection establisher")
|
||||
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
||||
|
||||
|
||||
log.info("initializing acceptor")
|
||||
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||
|
||||
|
||||
log.info("initializing directory watcher")
|
||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
||||
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||
|
||||
|
||||
log.info("initializing hasher service")
|
||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
||||
eventBus.register(FileSharedEvent.class, hasherService)
|
||||
|
||||
|
||||
log.info("initializing trust subscriber")
|
||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
||||
}
|
||||
|
||||
|
||||
public void startServices() {
|
||||
hasherService.start()
|
||||
trustService.start()
|
||||
@ -303,7 +303,7 @@ public class Core {
|
||||
hostCache.waitForLoad()
|
||||
updateClient.start()
|
||||
}
|
||||
|
||||
|
||||
public void shutdown() {
|
||||
if (!shutdown.compareAndSet(false, true)) {
|
||||
log.info("already shutting down")
|
||||
@ -336,7 +336,7 @@ public class Core {
|
||||
log.info("creating home dir")
|
||||
home.mkdir()
|
||||
}
|
||||
|
||||
|
||||
def props = new Properties()
|
||||
def propsFile = new File(home, "MuWire.properties")
|
||||
if (propsFile.exists()) {
|
||||
@ -352,10 +352,10 @@ public class Core {
|
||||
props.write(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Core core = new Core(props, home, "0.4.7")
|
||||
core.startServices()
|
||||
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
if (args.length == 0) {
|
||||
log.info("initialized everything, sleeping")
|
||||
|
380
core/src/main/groovy/com/muwire/core/Core.groovy.orig
Normal file
380
core/src/main/groovy/com/muwire/core/Core.groovy.orig
Normal file
@ -0,0 +1,380 @@
|
||||
package com.muwire.core
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
import com.muwire.core.connection.ConnectionAcceptor
|
||||
import com.muwire.core.connection.ConnectionEstablisher
|
||||
import com.muwire.core.connection.ConnectionEvent
|
||||
import com.muwire.core.connection.ConnectionManager
|
||||
import com.muwire.core.connection.DisconnectionEvent
|
||||
import com.muwire.core.connection.I2PAcceptor
|
||||
import com.muwire.core.connection.I2PConnector
|
||||
import com.muwire.core.connection.LeafConnectionManager
|
||||
import com.muwire.core.connection.UltrapeerConnectionManager
|
||||
import com.muwire.core.download.DownloadManager
|
||||
import com.muwire.core.download.SourceDiscoveredEvent
|
||||
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||
import com.muwire.core.download.UIDownloadEvent
|
||||
import com.muwire.core.download.UIDownloadPausedEvent
|
||||
import com.muwire.core.download.UIDownloadResumedEvent
|
||||
import com.muwire.core.files.FileDownloadedEvent
|
||||
import com.muwire.core.files.FileHashedEvent
|
||||
import com.muwire.core.files.FileHashingEvent
|
||||
import com.muwire.core.files.FileHasher
|
||||
import com.muwire.core.files.FileLoadedEvent
|
||||
import com.muwire.core.files.FileManager
|
||||
import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
import com.muwire.core.files.HasherService
|
||||
import com.muwire.core.files.PersisterService
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
import com.muwire.core.files.DirectoryUnsharedEvent
|
||||
import com.muwire.core.files.DirectoryWatcher
|
||||
import com.muwire.core.hostcache.CacheClient
|
||||
import com.muwire.core.hostcache.HostCache
|
||||
import com.muwire.core.hostcache.HostDiscoveredEvent
|
||||
import com.muwire.core.mesh.MeshManager
|
||||
import com.muwire.core.search.QueryEvent
|
||||
import com.muwire.core.search.ResultsEvent
|
||||
import com.muwire.core.search.ResultsSender
|
||||
import com.muwire.core.search.SearchEvent
|
||||
import com.muwire.core.search.SearchManager
|
||||
import com.muwire.core.search.UIResultBatchEvent
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
import com.muwire.core.trust.TrustService
|
||||
import com.muwire.core.trust.TrustSubscriber
|
||||
import com.muwire.core.trust.TrustSubscriptionEvent
|
||||
import com.muwire.core.update.UpdateClient
|
||||
import com.muwire.core.upload.UploadManager
|
||||
import com.muwire.core.util.MuWireLogManager
|
||||
|
||||
import groovy.util.logging.Log
|
||||
import net.i2p.I2PAppContext
|
||||
import net.i2p.client.I2PClientFactory
|
||||
import net.i2p.client.I2PSession
|
||||
import net.i2p.client.streaming.I2PSocketManager
|
||||
import net.i2p.client.streaming.I2PSocketManagerFactory
|
||||
import net.i2p.client.streaming.I2PSocketOptions
|
||||
import net.i2p.client.streaming.I2PSocketManager.DisconnectListener
|
||||
import net.i2p.crypto.DSAEngine
|
||||
import net.i2p.crypto.SigType
|
||||
import net.i2p.data.Destination
|
||||
import net.i2p.data.PrivateKey
|
||||
import net.i2p.data.Signature
|
||||
import net.i2p.data.SigningPrivateKey
|
||||
|
||||
import net.i2p.router.Router
|
||||
import net.i2p.router.RouterContext
|
||||
|
||||
@Log
|
||||
public class Core {
|
||||
|
||||
final EventBus eventBus
|
||||
final Persona me
|
||||
final File home
|
||||
final Properties i2pOptions
|
||||
final MuWireSettings muOptions
|
||||
|
||||
private final TrustService trustService
|
||||
private final TrustSubscriber trustSubscriber
|
||||
private final PersisterService persisterService
|
||||
private final HostCache hostCache
|
||||
private final ConnectionManager connectionManager
|
||||
private final CacheClient cacheClient
|
||||
private final UpdateClient updateClient
|
||||
private final ConnectionAcceptor connectionAcceptor
|
||||
private final ConnectionEstablisher connectionEstablisher
|
||||
private final HasherService hasherService
|
||||
private final DownloadManager downloadManager
|
||||
private final DirectoryWatcher directoryWatcher
|
||||
final FileManager fileManager
|
||||
final UploadManager uploadManager
|
||||
|
||||
private final Router router
|
||||
|
||||
final AtomicBoolean shutdown = new AtomicBoolean()
|
||||
|
||||
public Core(MuWireSettings props, File home, String myVersion) {
|
||||
this.home = home
|
||||
this.muOptions = props
|
||||
|
||||
i2pOptions = new Properties()
|
||||
def i2pOptionsFile = new File(home,"i2p.properties")
|
||||
if (i2pOptionsFile.exists()) {
|
||||
i2pOptionsFile.withInputStream { i2pOptions.load(it) }
|
||||
|
||||
if (!i2pOptions.containsKey("inbound.nickname"))
|
||||
i2pOptions["inbound.nickname"] = "MuWire"
|
||||
if (!i2pOptions.containsKey("outbound.nickname"))
|
||||
i2pOptions["outbound.nickname"] = "MuWire"
|
||||
} else {
|
||||
i2pOptions["inbound.nickname"] = "MuWire"
|
||||
i2pOptions["outbound.nickname"] = "MuWire"
|
||||
i2pOptions["inbound.length"] = "3"
|
||||
i2pOptions["inbound.quantity"] = "4"
|
||||
i2pOptions["outbound.length"] = "3"
|
||||
i2pOptions["outbound.quantity"] = "4"
|
||||
i2pOptions["i2cp.tcp.host"] = "127.0.0.1"
|
||||
i2pOptions["i2cp.tcp.port"] = "7654"
|
||||
Random r = new Random()
|
||||
int port = r.nextInt(60000) + 4000
|
||||
i2pOptions["i2np.ntcp.port"] = String.valueOf(port)
|
||||
i2pOptions["i2np.udp.port"] = String.valueOf(port)
|
||||
i2pOptionsFile.withOutputStream { i2pOptions.store(it, "") }
|
||||
}
|
||||
|
||||
if (!props.embeddedRouter) {
|
||||
log.info "Initializing I2P context"
|
||||
I2PAppContext.getGlobalContext().logManager()
|
||||
I2PAppContext.getGlobalContext()._logManager = new MuWireLogManager()
|
||||
router = null
|
||||
} else {
|
||||
log.info("launching embedded router")
|
||||
Properties routerProps = new Properties()
|
||||
routerProps.setProperty("i2p.dir.config", home.getAbsolutePath())
|
||||
routerProps.setProperty("router.excludePeerCaps", "KLM")
|
||||
routerProps.setProperty("i2np.inboundKBytesPerSecond", String.valueOf(props.inBw))
|
||||
routerProps.setProperty("i2np.outboundKBytesPerSecond", String.valueOf(props.outBw))
|
||||
routerProps.setProperty("i2cp.disableInterface", "true")
|
||||
routerProps.setProperty("i2np.ntcp.port", i2pOptions["i2np.ntcp.port"])
|
||||
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
||||
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
||||
router = new Router(routerProps)
|
||||
router.getContext().setLogManager(new MuWireLogManager())
|
||||
router.runRouter()
|
||||
while(!router.isRunning())
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
log.info("initializing I2P socket manager")
|
||||
def i2pClient = new I2PClientFactory().createClient()
|
||||
File keyDat = new File(home, "key.dat")
|
||||
if (!keyDat.exists()) {
|
||||
log.info("Creating new key.dat")
|
||||
keyDat.withOutputStream {
|
||||
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// options like tunnel length and quantity
|
||||
I2PSession i2pSession
|
||||
I2PSocketManager socketManager
|
||||
keyDat.withInputStream {
|
||||
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
||||
}
|
||||
socketManager.getDefaultOptions().setReadTimeout(60000)
|
||||
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
||||
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
|
||||
i2pSession = socketManager.getSession()
|
||||
|
||||
def destination = new Destination()
|
||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||
keyDat.withInputStream {
|
||||
destination.readBytes(it)
|
||||
def privateKey = new PrivateKey()
|
||||
privateKey.readBytes(it)
|
||||
spk.readBytes(it)
|
||||
}
|
||||
|
||||
def baos = new ByteArrayOutputStream()
|
||||
def daos = new DataOutputStream(baos)
|
||||
daos.write(Constants.PERSONA_VERSION)
|
||||
daos.writeShort((short)props.getNickname().length())
|
||||
daos.write(props.getNickname().getBytes(StandardCharsets.UTF_8))
|
||||
destination.writeBytes(daos)
|
||||
daos.flush()
|
||||
byte [] payload = baos.toByteArray()
|
||||
Signature sig = DSAEngine.getInstance().sign(payload, spk)
|
||||
|
||||
baos = new ByteArrayOutputStream()
|
||||
baos.write(payload)
|
||||
sig.writeBytes(baos)
|
||||
me = new Persona(new ByteArrayInputStream(baos.toByteArray()))
|
||||
log.info("Loaded myself as "+me.getHumanReadableName())
|
||||
|
||||
eventBus = new EventBus()
|
||||
|
||||
log.info("initializing trust service")
|
||||
File goodTrust = new File(home, "trusted")
|
||||
File badTrust = new File(home, "distrusted")
|
||||
trustService = new TrustService(goodTrust, badTrust, 5000)
|
||||
eventBus.register(TrustEvent.class, trustService)
|
||||
|
||||
|
||||
log.info "initializing file manager"
|
||||
fileManager = new FileManager(eventBus, props)
|
||||
eventBus.register(FileHashedEvent.class, fileManager)
|
||||
eventBus.register(FileLoadedEvent.class, fileManager)
|
||||
eventBus.register(FileDownloadedEvent.class, fileManager)
|
||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||
eventBus.register(SearchEvent.class, fileManager)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||
|
||||
log.info("initializing mesh manager")
|
||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
||||
|
||||
log.info "initializing persistence service"
|
||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
||||
eventBus.register(UILoadedEvent.class, persisterService)
|
||||
|
||||
log.info("initializing host cache")
|
||||
File hostStorage = new File(home, "hosts.json")
|
||||
hostCache = new HostCache(trustService,hostStorage, 30000, props, i2pSession.getMyDestination())
|
||||
eventBus.register(HostDiscoveredEvent.class, hostCache)
|
||||
eventBus.register(ConnectionEvent.class, hostCache)
|
||||
|
||||
log.info("initializing connection manager")
|
||||
connectionManager = props.isLeaf() ?
|
||||
new LeafConnectionManager(eventBus, me, 3, hostCache, props) :
|
||||
new UltrapeerConnectionManager(eventBus, me, 512, 512, hostCache, trustService, props)
|
||||
eventBus.register(TrustEvent.class, connectionManager)
|
||||
eventBus.register(ConnectionEvent.class, connectionManager)
|
||||
eventBus.register(DisconnectionEvent.class, connectionManager)
|
||||
eventBus.register(QueryEvent.class, connectionManager)
|
||||
|
||||
log.info("initializing cache client")
|
||||
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
||||
|
||||
log.info("initializing update client")
|
||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
||||
eventBus.register(FileDownloadedEvent.class, updateClient)
|
||||
eventBus.register(UIResultBatchEvent.class, updateClient)
|
||||
|
||||
log.info("initializing connector")
|
||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||
|
||||
log.info "initializing results sender"
|
||||
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
||||
|
||||
log.info "initializing search manager"
|
||||
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
||||
eventBus.register(QueryEvent.class, searchManager)
|
||||
eventBus.register(ResultsEvent.class, searchManager)
|
||||
|
||||
log.info("initializing download manager")
|
||||
downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me)
|
||||
eventBus.register(UIDownloadEvent.class, downloadManager)
|
||||
eventBus.register(UILoadedEvent.class, downloadManager)
|
||||
eventBus.register(FileDownloadedEvent.class, downloadManager)
|
||||
eventBus.register(UIDownloadCancelledEvent.class, downloadManager)
|
||||
eventBus.register(SourceDiscoveredEvent.class, downloadManager)
|
||||
eventBus.register(UIDownloadPausedEvent.class, downloadManager)
|
||||
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
|
||||
|
||||
log.info("initializing upload manager")
|
||||
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
|
||||
|
||||
log.info("initializing connection establisher")
|
||||
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
||||
|
||||
log.info("initializing acceptor")
|
||||
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||
|
||||
log.info("initializing directory watcher")
|
||||
directoryWatcher = new DirectoryWatcher(eventBus, fileManager)
|
||||
eventBus.register(FileSharedEvent.class, directoryWatcher)
|
||||
eventBus.register(AllFilesLoadedEvent.class, directoryWatcher)
|
||||
eventBus.register(DirectoryUnsharedEvent.class, directoryWatcher)
|
||||
|
||||
log.info("initializing hasher service")
|
||||
hasherService = new HasherService(new FileHasher(), eventBus, fileManager)
|
||||
eventBus.register(FileSharedEvent.class, hasherService)
|
||||
|
||||
log.info("initializing trust subscriber")
|
||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
||||
}
|
||||
|
||||
public void startServices() {
|
||||
hasherService.start()
|
||||
trustService.start()
|
||||
trustService.waitForLoad()
|
||||
hostCache.start()
|
||||
connectionManager.start()
|
||||
cacheClient.start()
|
||||
connectionAcceptor.start()
|
||||
connectionEstablisher.start()
|
||||
hostCache.waitForLoad()
|
||||
updateClient.start()
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
if (!shutdown.compareAndSet(false, true)) {
|
||||
log.info("already shutting down")
|
||||
return
|
||||
}
|
||||
log.info("shutting down trust subscriber")
|
||||
trustSubscriber.stop()
|
||||
log.info("shutting down download manageer")
|
||||
downloadManager.shutdown()
|
||||
log.info("shutting down connection acceeptor")
|
||||
connectionAcceptor.stop()
|
||||
log.info("shutting down connection establisher")
|
||||
connectionEstablisher.stop()
|
||||
log.info("shutting down directory watcher")
|
||||
directoryWatcher.stop()
|
||||
log.info("shutting down cache client")
|
||||
cacheClient.stop()
|
||||
log.info("shutting down connection manager")
|
||||
connectionManager.shutdown()
|
||||
if (router != null) {
|
||||
log.info("shutting down embedded router")
|
||||
router.shutdown(0)
|
||||
}
|
||||
}
|
||||
|
||||
static main(args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
home = new File(home)
|
||||
if (!home.exists()) {
|
||||
log.info("creating home dir")
|
||||
home.mkdir()
|
||||
}
|
||||
|
||||
def props = new Properties()
|
||||
def propsFile = new File(home, "MuWire.properties")
|
||||
if (propsFile.exists()) {
|
||||
log.info("loading existing props file")
|
||||
propsFile.withInputStream {
|
||||
props.load(it)
|
||||
}
|
||||
props = new MuWireSettings(props)
|
||||
} else {
|
||||
log.info("creating default properties")
|
||||
props = new MuWireSettings()
|
||||
propsFile.withOutputStream {
|
||||
props.write(it)
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.4.7")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
if (args.length == 0) {
|
||||
log.info("initialized everything, sleeping")
|
||||
Thread.sleep(Integer.MAX_VALUE)
|
||||
} else {
|
||||
log.info("executing script ${args[0]}")
|
||||
File f = new File(args[0])
|
||||
if (!f.exists()) {
|
||||
log.warning("Script file doesn't exist")
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
def binding = new Binding()
|
||||
def shell = new GroovyShell(binding)
|
||||
binding.setProperty('eventBus', core.eventBus)
|
||||
binding.setProperty('me', core.me)
|
||||
// TOOD: other bindings?
|
||||
def script = shell.parse(f)
|
||||
script.run()
|
||||
}
|
||||
}
|
||||
}
|
31
core/src/main/groovy/com/muwire/core/Core.groovy.rej
Normal file
31
core/src/main/groovy/com/muwire/core/Core.groovy.rej
Normal file
@ -0,0 +1,31 @@
|
||||
***************
|
||||
*** 334,347 ****
|
||||
RouterContextMetaClass() {
|
||||
super(RouterContext.class)
|
||||
}
|
||||
-
|
||||
Object invokeMethod(Object object, String name, Object[] args) {
|
||||
if (name == "logManager")
|
||||
return logManager
|
||||
super.invokeMethod(object, name, args)
|
||||
}
|
||||
}
|
||||
-
|
||||
static main(args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
home = new File(home)
|
||||
--- 334,347 ----
|
||||
RouterContextMetaClass() {
|
||||
super(RouterContext.class)
|
||||
}
|
||||
+
|
||||
Object invokeMethod(Object object, String name, Object[] args) {
|
||||
if (name == "logManager")
|
||||
return logManager
|
||||
super.invokeMethod(object, name, args)
|
||||
}
|
||||
}
|
||||
+
|
||||
static main(args) {
|
||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||
home = new File(home)
|
@ -7,12 +7,12 @@ class Event {
|
||||
private static final AtomicLong SEQ_NO = new AtomicLong();
|
||||
final long seqNo
|
||||
final long timestamp
|
||||
|
||||
|
||||
Event() {
|
||||
seqNo = SEQ_NO.getAndIncrement()
|
||||
timestamp = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"seqNo $seqNo timestamp $timestamp"
|
||||
|
@ -10,7 +10,7 @@ import com.muwire.core.files.FileSharedEvent
|
||||
import groovy.util.logging.Log
|
||||
@Log
|
||||
class EventBus {
|
||||
|
||||
|
||||
private Map handlers = new HashMap()
|
||||
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
||||
def rv = new Thread(r)
|
||||
@ -22,7 +22,7 @@ class EventBus {
|
||||
void publish(Event e) {
|
||||
executor.execute({publishInternal(e)} as Runnable)
|
||||
}
|
||||
|
||||
|
||||
private void publishInternal(Event e) {
|
||||
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
||||
def currentHandlers
|
||||
@ -38,7 +38,7 @@ class EventBus {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
synchronized void register(Class<? extends Event> eventType, def handler) {
|
||||
log.info "Registering $handler for type $eventType"
|
||||
def currentHandlers = handlers.get(eventType)
|
||||
|
@ -13,5 +13,5 @@ class InvalidSignatureException extends Exception {
|
||||
public InvalidSignatureException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import com.muwire.core.util.DataUtil
|
||||
import net.i2p.data.Base64
|
||||
|
||||
class MuWireSettings {
|
||||
|
||||
|
||||
final boolean isLeaf
|
||||
boolean allowUntrusted
|
||||
boolean allowTrustLists
|
||||
@ -28,11 +28,11 @@ class MuWireSettings {
|
||||
int meshExpiration
|
||||
boolean embeddedRouter
|
||||
int inBw, outBw
|
||||
|
||||
|
||||
MuWireSettings() {
|
||||
this(new Properties())
|
||||
}
|
||||
|
||||
|
||||
MuWireSettings(Properties props) {
|
||||
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
||||
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
||||
@ -40,7 +40,7 @@ class MuWireSettings {
|
||||
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
||||
nickname = props.getProperty("nickname","MuWireUser")
|
||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||
System.getProperty("user.home")))
|
||||
downloadRetryInterval = Integer.parseInt(props.getProperty("downloadRetryInterval","1"))
|
||||
updateCheckInterval = Integer.parseInt(props.getProperty("updateCheckInterval","24"))
|
||||
@ -53,21 +53,21 @@ class MuWireSettings {
|
||||
embeddedRouter = Boolean.valueOf(props.getProperty("embeddedRouter","false"))
|
||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||
|
||||
|
||||
watchedDirectories = new HashSet<>()
|
||||
if (props.containsKey("watchedDirectories")) {
|
||||
String[] encoded = props.getProperty("watchedDirectories").split(",")
|
||||
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
|
||||
}
|
||||
|
||||
|
||||
trustSubscriptions = new HashSet<>()
|
||||
if (props.containsKey("trustSubscriptions")) {
|
||||
props.getProperty("trustSubscriptions").split(",").each {
|
||||
props.getProperty("trustSubscriptions").split(",").each {
|
||||
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void write(OutputStream out) throws IOException {
|
||||
Properties props = new Properties()
|
||||
props.setProperty("leaf", isLeaf.toString())
|
||||
@ -88,44 +88,44 @@ class MuWireSettings {
|
||||
props.setProperty("embeddedRouter", String.valueOf(embeddedRouter))
|
||||
props.setProperty("inBw", String.valueOf(inBw))
|
||||
props.setProperty("outBw", String.valueOf(outBw))
|
||||
|
||||
|
||||
if (!watchedDirectories.isEmpty()) {
|
||||
String encoded = watchedDirectories.stream().
|
||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
||||
collect(Collectors.joining(","))
|
||||
props.setProperty("watchedDirectories", encoded)
|
||||
}
|
||||
|
||||
|
||||
if (!trustSubscriptions.isEmpty()) {
|
||||
String encoded = trustSubscriptions.stream().
|
||||
map({it.toBase64()}).
|
||||
collect(Collectors.joining(","))
|
||||
props.setProperty("trustSubscriptions", encoded)
|
||||
}
|
||||
|
||||
|
||||
props.store(out, "")
|
||||
}
|
||||
|
||||
boolean isLeaf() {
|
||||
isLeaf
|
||||
}
|
||||
|
||||
|
||||
boolean allowUntrusted() {
|
||||
allowUntrusted
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void setAllowUntrusted(boolean allowUntrusted) {
|
||||
this.allowUntrusted = allowUntrusted
|
||||
}
|
||||
|
||||
|
||||
CrawlerResponse getCrawlerResponse() {
|
||||
crawlerResponse
|
||||
}
|
||||
|
||||
|
||||
void setCrawlerResponse(CrawlerResponse crawlerResponse) {
|
||||
this.crawlerResponse = crawlerResponse
|
||||
}
|
||||
|
||||
|
||||
String getNickname() {
|
||||
nickname
|
||||
}
|
||||
|
@ -7,11 +7,11 @@ import java.nio.charset.StandardCharsets
|
||||
*/
|
||||
public class Name {
|
||||
final String name
|
||||
|
||||
|
||||
Name(String name) {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
|
||||
Name(InputStream nameStream) throws IOException {
|
||||
DataInputStream dis = new DataInputStream(nameStream)
|
||||
int length = dis.readUnsignedShort()
|
||||
@ -19,22 +19,22 @@ public class Name {
|
||||
dis.readFully(nameBytes)
|
||||
this.name = new String(nameBytes, StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
|
||||
public void write(OutputStream out) throws IOException {
|
||||
DataOutputStream dos = new DataOutputStream(out)
|
||||
dos.writeShort(name.length())
|
||||
dos.write(name.getBytes(StandardCharsets.UTF_8))
|
||||
}
|
||||
|
||||
|
||||
public getName() {
|
||||
name
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
name.hashCode()
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Name))
|
||||
|
@ -9,7 +9,7 @@ import net.i2p.data.SigningPublicKey
|
||||
|
||||
public class Persona {
|
||||
private static final int SIG_LEN = Constants.SIG_TYPE.getSigLen()
|
||||
|
||||
|
||||
private final byte version
|
||||
private final Name name
|
||||
private final Destination destination
|
||||
@ -17,12 +17,12 @@ public class Persona {
|
||||
private volatile String humanReadableName
|
||||
private volatile String base64
|
||||
private volatile byte[] payload
|
||||
|
||||
|
||||
public Persona(InputStream personaStream) throws IOException, InvalidSignatureException {
|
||||
version = (byte) (personaStream.read() & 0xFF)
|
||||
if (version != Constants.PERSONA_VERSION)
|
||||
throw new IOException("Unknown version "+version)
|
||||
|
||||
|
||||
name = new Name(personaStream)
|
||||
destination = Destination.create(personaStream)
|
||||
sig = new byte[SIG_LEN]
|
||||
@ -31,7 +31,7 @@ public class Persona {
|
||||
if (!verify(version, name, destination, sig))
|
||||
throw new InvalidSignatureException(getHumanReadableName() + " didn't verify")
|
||||
}
|
||||
|
||||
|
||||
private static boolean verify(byte version, Name name, Destination destination, byte [] sig) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
baos.write(version)
|
||||
@ -42,7 +42,7 @@ public class Persona {
|
||||
Signature signature = new Signature(Constants.SIG_TYPE, sig)
|
||||
DSAEngine.getInstance().verifySignature(signature, payload, spk)
|
||||
}
|
||||
|
||||
|
||||
public void write(OutputStream out) throws IOException {
|
||||
if (payload == null) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
@ -54,13 +54,13 @@ public class Persona {
|
||||
}
|
||||
out.write(payload)
|
||||
}
|
||||
|
||||
|
||||
public String getHumanReadableName() {
|
||||
if (humanReadableName == null)
|
||||
if (humanReadableName == null)
|
||||
humanReadableName = name.getName() + "@" + destination.toBase32().substring(0,32)
|
||||
humanReadableName
|
||||
}
|
||||
|
||||
|
||||
public String toBase64() {
|
||||
if (base64 == null) {
|
||||
def baos = new ByteArrayOutputStream()
|
||||
@ -69,12 +69,12 @@ public class Persona {
|
||||
}
|
||||
base64
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
name.hashCode() ^ destination.hashCode()
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Persona))
|
||||
@ -82,7 +82,7 @@ public class Persona {
|
||||
Persona other = (Persona)o
|
||||
name.equals(other.name) && destination.equals(other.destination)
|
||||
}
|
||||
|
||||
|
||||
public static void main(String []args) {
|
||||
if (args.length != 1) {
|
||||
println "This utility decodes a bas64-encoded persona"
|
||||
|
@ -3,9 +3,9 @@ package com.muwire.core
|
||||
abstract class Service {
|
||||
|
||||
volatile boolean loaded
|
||||
|
||||
|
||||
abstract void load()
|
||||
|
||||
|
||||
void waitForLoad() {
|
||||
while (!loaded)
|
||||
Thread.sleep(10)
|
||||
|
@ -21,7 +21,7 @@ import net.i2p.data.Destination
|
||||
|
||||
@Log
|
||||
abstract class Connection implements Closeable {
|
||||
|
||||
|
||||
private static final int SEARCHES = 10
|
||||
private static final long INTERVAL = 1000
|
||||
|
||||
@ -31,17 +31,17 @@ abstract class Connection implements Closeable {
|
||||
final HostCache hostCache
|
||||
final TrustService trustService
|
||||
final MuWireSettings settings
|
||||
|
||||
|
||||
private final AtomicBoolean running = new AtomicBoolean()
|
||||
private final BlockingQueue messages = new LinkedBlockingQueue()
|
||||
private final Thread reader, writer
|
||||
private final LinkedList<Long> searchTimestamps = new LinkedList<>()
|
||||
|
||||
|
||||
protected final String name
|
||||
|
||||
|
||||
long lastPingSentTime, lastPongReceivedTime
|
||||
|
||||
Connection(EventBus eventBus, Endpoint endpoint, boolean incoming,
|
||||
|
||||
Connection(EventBus eventBus, Endpoint endpoint, boolean incoming,
|
||||
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
||||
this.eventBus = eventBus
|
||||
this.incoming = incoming
|
||||
@ -49,18 +49,18 @@ abstract class Connection implements Closeable {
|
||||
this.hostCache = hostCache
|
||||
this.trustService = trustService
|
||||
this.settings = settings
|
||||
|
||||
|
||||
this.name = endpoint.destination.toBase32().substring(0,8)
|
||||
|
||||
|
||||
this.reader = new Thread({readLoop()} as Runnable)
|
||||
this.reader.setName("reader-$name")
|
||||
this.reader.setDaemon(true)
|
||||
|
||||
|
||||
this.writer = new Thread({writeLoop()} as Runnable)
|
||||
this.writer.setName("writer-$name")
|
||||
this.writer.setDaemon(true)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* starts the connection threads
|
||||
*/
|
||||
@ -72,7 +72,7 @@ abstract class Connection implements Closeable {
|
||||
reader.start()
|
||||
writer.start()
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!running.compareAndSet(true, false)) {
|
||||
@ -85,7 +85,7 @@ abstract class Connection implements Closeable {
|
||||
endpoint.close()
|
||||
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
||||
}
|
||||
|
||||
|
||||
protected void readLoop() {
|
||||
try {
|
||||
while(running.get()) {
|
||||
@ -98,9 +98,9 @@ abstract class Connection implements Closeable {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected abstract void read()
|
||||
|
||||
|
||||
protected void writeLoop() {
|
||||
try {
|
||||
while(running.get()) {
|
||||
@ -113,9 +113,9 @@ abstract class Connection implements Closeable {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected abstract void write(def message);
|
||||
|
||||
|
||||
void sendPing() {
|
||||
def ping = [:]
|
||||
ping.type = "Ping"
|
||||
@ -123,7 +123,7 @@ abstract class Connection implements Closeable {
|
||||
messages.put(ping)
|
||||
lastPingSentTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
|
||||
void sendQuery(QueryEvent e) {
|
||||
def query = [:]
|
||||
query.type = "Search"
|
||||
@ -139,7 +139,7 @@ abstract class Connection implements Closeable {
|
||||
query.originator = e.originator.toBase64()
|
||||
messages.put(query)
|
||||
}
|
||||
|
||||
|
||||
protected void handlePing() {
|
||||
log.fine("$name received ping")
|
||||
def pong = [:]
|
||||
@ -148,18 +148,18 @@ abstract class Connection implements Closeable {
|
||||
pong.pongs = hostCache.getGoodHosts(10).collect { d -> d.toBase64() }
|
||||
messages.put(pong)
|
||||
}
|
||||
|
||||
|
||||
protected void handlePong(def pong) {
|
||||
log.fine("$name received pong")
|
||||
lastPongReceivedTime = System.currentTimeMillis()
|
||||
if (pong.pongs == null)
|
||||
throw new Exception("Pong doesn't have pongs")
|
||||
pong.pongs.each {
|
||||
pong.pongs.each {
|
||||
def dest = new Destination(it)
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: dest))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean throttleSearch() {
|
||||
final long now = System.currentTimeMillis()
|
||||
if (searchTimestamps.size() < SEARCHES) {
|
||||
@ -173,19 +173,19 @@ abstract class Connection implements Closeable {
|
||||
searchTimestamps.removeFirst()
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
protected void handleSearch(def search) {
|
||||
if (throttleSearch()) {
|
||||
log.info("dropping excessive search")
|
||||
return
|
||||
}
|
||||
}
|
||||
UUID uuid = UUID.fromString(search.uuid)
|
||||
byte [] infohash = null
|
||||
if (search.infohash != null) {
|
||||
search.keywords = null
|
||||
infohash = Base64.decode(search.infohash)
|
||||
}
|
||||
|
||||
|
||||
Destination replyTo = new Destination(search.replyTo)
|
||||
TrustLevel trustLevel = trustService.getLevel(replyTo)
|
||||
if (trustLevel == TrustLevel.DISTRUSTED) {
|
||||
@ -196,7 +196,7 @@ abstract class Connection implements Closeable {
|
||||
log.info("dropping search from neutral peer")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Persona originator = null
|
||||
if (search.originator != null) {
|
||||
originator = new Persona(new ByteArrayInputStream(Base64.decode(search.originator)))
|
||||
@ -205,11 +205,11 @@ abstract class Connection implements Closeable {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean oob = false
|
||||
if (search.oobInfohash != null)
|
||||
oob = search.oobInfohash
|
||||
|
||||
|
||||
SearchEvent searchEvent = new SearchEvent(searchTerms : search.keywords,
|
||||
searchHash : infohash,
|
||||
uuid : uuid,
|
||||
@ -220,6 +220,6 @@ abstract class Connection implements Closeable {
|
||||
receivedOn : endpoint.destination,
|
||||
firstHop : search.firstHop )
|
||||
eventBus.publish(event)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -35,15 +35,15 @@ class ConnectionAcceptor {
|
||||
final I2PAcceptor acceptor
|
||||
final HostCache hostCache
|
||||
final TrustService trustService
|
||||
final SearchManager searchManager
|
||||
final SearchManager searchManager
|
||||
final UploadManager uploadManager
|
||||
final ConnectionEstablisher establisher
|
||||
|
||||
|
||||
final ExecutorService acceptorThread
|
||||
final ExecutorService handshakerThreads
|
||||
|
||||
|
||||
private volatile shutdown
|
||||
|
||||
|
||||
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
||||
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
||||
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
||||
@ -57,14 +57,14 @@ class ConnectionAcceptor {
|
||||
this.searchManager = searchManager
|
||||
this.uploadManager = uploadManager
|
||||
this.establisher = establisher
|
||||
|
||||
acceptorThread = Executors.newSingleThreadExecutor { r ->
|
||||
|
||||
acceptorThread = Executors.newSingleThreadExecutor { r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("acceptor")
|
||||
rv
|
||||
}
|
||||
|
||||
|
||||
handshakerThreads = Executors.newCachedThreadPool { r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
@ -72,17 +72,17 @@ class ConnectionAcceptor {
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
acceptorThread.execute({acceptLoop()} as Runnable)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
shutdown = true
|
||||
acceptorThread.shutdownNow()
|
||||
handshakerThreads.shutdownNow()
|
||||
}
|
||||
|
||||
|
||||
private void acceptLoop() {
|
||||
try {
|
||||
while(true) {
|
||||
@ -106,7 +106,7 @@ class ConnectionAcceptor {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processIncoming(Endpoint e) {
|
||||
InputStream is = e.inputStream
|
||||
try {
|
||||
@ -138,7 +138,7 @@ class ConnectionAcceptor {
|
||||
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processMuWire(Endpoint e) {
|
||||
byte[] uWire = "uWire ".bytes
|
||||
for (int i = 0; i < uWire.length; i++) {
|
||||
@ -147,21 +147,21 @@ class ConnectionAcceptor {
|
||||
throw new IOException("unexpected value $read at position $i")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
byte[] type = new byte[4]
|
||||
DataInputStream dis = new DataInputStream(e.inputStream)
|
||||
dis.readFully(type)
|
||||
|
||||
|
||||
if (type == "leaf".bytes)
|
||||
handleIncoming(e, true)
|
||||
else if (type == "peer".bytes)
|
||||
handleIncoming(e, false)
|
||||
else
|
||||
else
|
||||
throw new IOException("unknown connection type $type")
|
||||
}
|
||||
|
||||
private void handleIncoming(Endpoint e, boolean leaf) {
|
||||
boolean accept = !manager.isConnected(e.destination) &&
|
||||
boolean accept = !manager.isConnected(e.destination) &&
|
||||
!establisher.isInProgress(e.destination) &&
|
||||
(leaf ? manager.hasLeafSlots() : manager.hasPeerSlots())
|
||||
if (accept) {
|
||||
@ -187,9 +187,9 @@ class ConnectionAcceptor {
|
||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.REJECTED))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void processGET(Endpoint e) {
|
||||
byte[] et = new byte[3]
|
||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
@ -198,7 +198,7 @@ class ConnectionAcceptor {
|
||||
throw new IOException("Invalid GET connection")
|
||||
uploadManager.processGET(e)
|
||||
}
|
||||
|
||||
|
||||
private void processHashList(Endpoint e) {
|
||||
byte[] ashList = new byte[8]
|
||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
@ -207,7 +207,7 @@ class ConnectionAcceptor {
|
||||
throw new IOException("Invalid HASHLIST connection")
|
||||
uploadManager.processHashList(e)
|
||||
}
|
||||
|
||||
|
||||
private void processPOST(final Endpoint e) throws IOException {
|
||||
byte [] ost = new byte[4]
|
||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
@ -246,7 +246,7 @@ class ConnectionAcceptor {
|
||||
e.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processTRUST(Endpoint e) {
|
||||
byte[] RUST = new byte[6]
|
||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
@ -255,7 +255,7 @@ class ConnectionAcceptor {
|
||||
throw new IOException("Invalid TRUST connection")
|
||||
String header
|
||||
while ((header = DataUtil.readTillRN(dis)) != ""); // ignore headers for now
|
||||
|
||||
|
||||
OutputStream os = e.getOutputStream()
|
||||
if (!settings.allowTrustLists) {
|
||||
os.write("403 Not Allowed\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
@ -263,7 +263,7 @@ class ConnectionAcceptor {
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
os.write("200 OK\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
List<Persona> good = new ArrayList<>(trustService.good.values())
|
||||
int size = Math.min(Short.MAX_VALUE * 2, good.size())
|
||||
@ -273,7 +273,7 @@ class ConnectionAcceptor {
|
||||
good.each {
|
||||
it.write(dos)
|
||||
}
|
||||
|
||||
|
||||
List<Persona> bad = new ArrayList<>(trustService.bad.values())
|
||||
size = Math.min(Short.MAX_VALUE * 2, bad.size())
|
||||
bad = bad.subList(0, size)
|
||||
@ -281,9 +281,9 @@ class ConnectionAcceptor {
|
||||
bad.each {
|
||||
it.write(dos)
|
||||
}
|
||||
|
||||
|
||||
dos.flush()
|
||||
e.close()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
@Log
|
||||
class ConnectionEstablisher {
|
||||
|
||||
|
||||
private static final int CONCURRENT = 4
|
||||
|
||||
final EventBus eventBus
|
||||
@ -29,14 +29,14 @@ class ConnectionEstablisher {
|
||||
final MuWireSettings settings
|
||||
final ConnectionManager connectionManager
|
||||
final HostCache hostCache
|
||||
|
||||
|
||||
final Timer timer
|
||||
final ExecutorService executor
|
||||
|
||||
|
||||
final Set inProgress = new ConcurrentHashSet()
|
||||
|
||||
|
||||
ConnectionEstablisher(){}
|
||||
|
||||
|
||||
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
||||
ConnectionManager connectionManager, HostCache hostCache) {
|
||||
this.eventBus = eventBus
|
||||
@ -45,23 +45,23 @@ class ConnectionEstablisher {
|
||||
this.connectionManager = connectionManager
|
||||
this.hostCache = hostCache
|
||||
timer = new Timer("connection-timer",true)
|
||||
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
||||
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("connector-${System.currentTimeMillis()}")
|
||||
rv
|
||||
rv
|
||||
} as ThreadFactory)
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
executor.shutdownNow()
|
||||
}
|
||||
|
||||
|
||||
private void connectIfNeeded() {
|
||||
if (!connectionManager.needsConnections())
|
||||
return
|
||||
@ -84,19 +84,19 @@ class ConnectionEstablisher {
|
||||
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
||||
executor.execute({connect(toTry)} as Runnable)
|
||||
}
|
||||
|
||||
|
||||
private void connect(Destination toTry) {
|
||||
log.info("starting connect to ${toTry.toBase32()}")
|
||||
try {
|
||||
def endpoint = i2pConnector.connect(toTry)
|
||||
log.info("successful transport connect to ${toTry.toBase32()}")
|
||||
|
||||
|
||||
// outgoing handshake
|
||||
endpoint.outputStream.write("MuWire ".bytes)
|
||||
def type = settings.isLeaf() ? "leaf" : "peer"
|
||||
endpoint.outputStream.write(type.bytes)
|
||||
endpoint.outputStream.flush()
|
||||
|
||||
|
||||
InputStream is = endpoint.inputStream
|
||||
int read = is.read()
|
||||
if (read == -1) {
|
||||
@ -118,12 +118,12 @@ class ConnectionEstablisher {
|
||||
inProgress.remove(toTry)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void fail(Endpoint endpoint) {
|
||||
endpoint.close()
|
||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
||||
}
|
||||
|
||||
|
||||
private void readK(Endpoint e) {
|
||||
int read = e.inputStream.read()
|
||||
if (read != 'K') {
|
||||
@ -131,14 +131,14 @@ class ConnectionEstablisher {
|
||||
fail e
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
log.info("connection to ${e.destination.toBase32()} established")
|
||||
|
||||
|
||||
// wrap into deflater / inflater streams and publish
|
||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
||||
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: false, leaf: false, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||
}
|
||||
|
||||
|
||||
private void readEJECT(Endpoint e) {
|
||||
byte[] eject = "EJECT".bytes
|
||||
for (int i = 0; i < eject.length; i++) {
|
||||
@ -150,8 +150,8 @@ class ConnectionEstablisher {
|
||||
}
|
||||
}
|
||||
log.info("connection to ${e.destination.toBase32()} rejected")
|
||||
|
||||
|
||||
|
||||
|
||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
||||
try {
|
||||
DataInputStream dais = new DataInputStream(e.inputStream)
|
||||
@ -178,7 +178,7 @@ class ConnectionEstablisher {
|
||||
e.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isInProgress(Destination d) {
|
||||
inProgress.contains(d)
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ class ConnectionEvent extends Event {
|
||||
boolean incoming
|
||||
Boolean leaf // can be null if uknown
|
||||
ConnectionAttemptStatus status
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
||||
|
@ -11,19 +11,19 @@ import com.muwire.core.trust.TrustLevel
|
||||
import net.i2p.data.Destination
|
||||
|
||||
abstract class ConnectionManager {
|
||||
|
||||
|
||||
private static final int PING_TIME = 20000
|
||||
|
||||
final EventBus eventBus
|
||||
|
||||
|
||||
private final Timer timer
|
||||
|
||||
|
||||
protected final HostCache hostCache
|
||||
protected final Persona me
|
||||
protected final MuWireSettings settings
|
||||
|
||||
|
||||
ConnectionManager() {}
|
||||
|
||||
|
||||
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
||||
this.eventBus = eventBus
|
||||
this.me = me
|
||||
@ -31,42 +31,42 @@ abstract class ConnectionManager {
|
||||
this.settings = settings
|
||||
this.timer = new Timer("connections-pinger",true)
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
getConnections().each { it.close() }
|
||||
}
|
||||
|
||||
|
||||
void onTrustEvent(TrustEvent e) {
|
||||
if (e.level == TrustLevel.DISTRUSTED)
|
||||
drop(e.persona.destination)
|
||||
}
|
||||
|
||||
|
||||
abstract void drop(Destination d)
|
||||
|
||||
|
||||
abstract Collection<Connection> getConnections()
|
||||
|
||||
|
||||
protected abstract int getDesiredConnections()
|
||||
|
||||
|
||||
boolean needsConnections() {
|
||||
return getConnections().size() < getDesiredConnections()
|
||||
}
|
||||
|
||||
|
||||
abstract boolean isConnected(Destination d)
|
||||
|
||||
|
||||
abstract void onConnectionEvent(ConnectionEvent e)
|
||||
|
||||
|
||||
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
||||
|
||||
|
||||
abstract void shutdown()
|
||||
|
||||
|
||||
protected void sendPings() {
|
||||
final long now = System.currentTimeMillis()
|
||||
getConnections().each {
|
||||
getConnections().each {
|
||||
if (now - it.lastPingSentTime > PING_TIME)
|
||||
it.sendPing()
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import com.muwire.core.Event
|
||||
import net.i2p.data.Destination
|
||||
|
||||
class DisconnectionEvent extends Event {
|
||||
|
||||
|
||||
Destination destination
|
||||
|
||||
@Override
|
||||
|
@ -12,16 +12,16 @@ class Endpoint implements Closeable {
|
||||
final InputStream inputStream
|
||||
final OutputStream outputStream
|
||||
final def toClose
|
||||
|
||||
|
||||
private final AtomicBoolean closed = new AtomicBoolean()
|
||||
|
||||
|
||||
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
||||
this.destination = destination
|
||||
this.inputStream = inputStream
|
||||
this.outputStream = outputStream
|
||||
this.toClose = toClose
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed.compareAndSet(false, true)) {
|
||||
@ -38,9 +38,9 @@ class Endpoint implements Closeable {
|
||||
try {toClose.reset()} catch (Exception ignore) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"destination: ${destination.toBase32()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,14 +7,14 @@ class I2PAcceptor {
|
||||
|
||||
final I2PSocketManager socketManager
|
||||
final I2PServerSocket serverSocket
|
||||
|
||||
|
||||
I2PAcceptor() {}
|
||||
|
||||
|
||||
I2PAcceptor(I2PSocketManager socketManager) {
|
||||
this.socketManager = socketManager
|
||||
this.serverSocket = socketManager.getServerSocket()
|
||||
}
|
||||
|
||||
|
||||
Endpoint accept() {
|
||||
def socket = serverSocket.accept()
|
||||
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
||||
|
@ -4,15 +4,15 @@ import net.i2p.client.streaming.I2PSocketManager
|
||||
import net.i2p.data.Destination
|
||||
|
||||
class I2PConnector {
|
||||
|
||||
|
||||
final I2PSocketManager socketManager
|
||||
|
||||
|
||||
I2PConnector() {}
|
||||
|
||||
|
||||
I2PConnector(I2PSocketManager socketManager) {
|
||||
this.socketManager = socketManager
|
||||
}
|
||||
|
||||
|
||||
Endpoint connect(Destination dest) {
|
||||
def socket = socketManager.connect(dest)
|
||||
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
||||
|
@ -11,13 +11,13 @@ import com.muwire.core.trust.TrustService
|
||||
import net.i2p.data.Destination
|
||||
|
||||
/**
|
||||
* Connection where the other side is a leaf.
|
||||
* Connection where the other side is a leaf.
|
||||
* Such connections can only be incoming.
|
||||
* @author zab
|
||||
*/
|
||||
class LeafConnection extends Connection {
|
||||
|
||||
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
||||
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
||||
TrustService trustService, MuWireSettings settings) {
|
||||
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
||||
}
|
||||
@ -25,13 +25,13 @@ class LeafConnection extends Connection {
|
||||
@Override
|
||||
protected void read() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(Object message) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -13,28 +13,28 @@ import net.i2p.data.Destination
|
||||
|
||||
@Log
|
||||
class LeafConnectionManager extends ConnectionManager {
|
||||
|
||||
|
||||
final int maxConnections
|
||||
|
||||
|
||||
final Map<Destination, UltrapeerConnection> connections = new ConcurrentHashMap()
|
||||
|
||||
public LeafConnectionManager(EventBus eventBus, Persona me, int maxConnections,
|
||||
|
||||
public LeafConnectionManager(EventBus eventBus, Persona me, int maxConnections,
|
||||
HostCache hostCache, MuWireSettings settings) {
|
||||
super(eventBus, me, hostCache, settings)
|
||||
this.maxConnections = maxConnections
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void drop(Destination d) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
if (me.destination == e.receivedOn) {
|
||||
connections.values().each { it.sendQuery(e) }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -60,21 +60,21 @@ class LeafConnectionManager extends ConnectionManager {
|
||||
}
|
||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||
return
|
||||
|
||||
|
||||
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
||||
connections.put(e.endpoint.destination, c)
|
||||
c.start()
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||
def removed = connections.remove(e.destination)
|
||||
if (removed == null)
|
||||
log.severe("removed destination not present in connection manager ${e.destination.toBase32()}")
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void shutdown() {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,13 @@ import net.i2p.data.Destination
|
||||
*/
|
||||
@Log
|
||||
class PeerConnection extends Connection {
|
||||
|
||||
|
||||
private final DataInputStream dis
|
||||
private final DataOutputStream dos
|
||||
|
||||
|
||||
private final byte[] readHeader = new byte[3]
|
||||
private final byte[] writeHeader = new byte[3]
|
||||
|
||||
|
||||
private final JsonSlurper slurper = new JsonSlurper()
|
||||
|
||||
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
||||
@ -42,10 +42,10 @@ class PeerConnection extends Connection {
|
||||
dis.readFully(readHeader)
|
||||
int length = DataUtil.readLength(readHeader)
|
||||
log.fine("$name read length $length")
|
||||
|
||||
|
||||
byte[] payload = new byte[length]
|
||||
dis.readFully(payload)
|
||||
|
||||
|
||||
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
||||
// TODO process binary
|
||||
} else {
|
||||
@ -73,7 +73,7 @@ class PeerConnection extends Connection {
|
||||
} else {
|
||||
// TODO: write binary
|
||||
}
|
||||
|
||||
|
||||
dos.write(writeHeader)
|
||||
dos.write(payload)
|
||||
dos.flush()
|
||||
|
@ -24,7 +24,7 @@ class UltrapeerConnection extends Connection {
|
||||
@Override
|
||||
protected void read() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -37,10 +37,10 @@ class UltrapeerConnection extends Connection {
|
||||
}
|
||||
|
||||
private void writeJsonMessage(def message) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void writeBinaryMessage(def message) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -15,16 +15,16 @@ import net.i2p.data.Destination
|
||||
|
||||
@Log
|
||||
class UltrapeerConnectionManager extends ConnectionManager {
|
||||
|
||||
|
||||
final int maxPeers, maxLeafs
|
||||
final TrustService trustService
|
||||
|
||||
|
||||
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
||||
final Map<Destination, LeafConnection> leafConnections = new ConcurrentHashMap()
|
||||
|
||||
|
||||
UltrapeerConnectionManager() {}
|
||||
|
||||
public UltrapeerConnectionManager(EventBus eventBus, Persona me, int maxPeers, int maxLeafs,
|
||||
public UltrapeerConnectionManager(EventBus eventBus, Persona me, int maxPeers, int maxLeafs,
|
||||
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
||||
super(eventBus, me, hostCache, settings)
|
||||
this.maxPeers = maxPeers
|
||||
@ -36,7 +36,7 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
||||
peerConnections.get(d)?.close()
|
||||
leafConnections.get(d)?.close()
|
||||
}
|
||||
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
forwardQueryToLeafs(e)
|
||||
if (!e.firstHop)
|
||||
@ -57,15 +57,15 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
||||
rv.addAll(leafConnections.values())
|
||||
rv
|
||||
}
|
||||
|
||||
|
||||
boolean hasLeafSlots() {
|
||||
leafConnections.size() < maxLeafs
|
||||
}
|
||||
|
||||
|
||||
boolean hasPeerSlots() {
|
||||
peerConnections.size() < maxPeers
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected int getDesiredConnections() {
|
||||
return maxPeers / 2;
|
||||
@ -81,18 +81,18 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
||||
log.severe("Inconsistent event $e")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||
return
|
||||
|
||||
Connection c = e.leaf ?
|
||||
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
||||
|
||||
Connection c = e.leaf ?
|
||||
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
||||
new PeerConnection(eventBus, e.endpoint, e.incoming, hostCache, trustService, settings)
|
||||
def map = e.leaf ? leafConnections : peerConnections
|
||||
map.put(e.endpoint.destination, c)
|
||||
c.start()
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||
def removed = peerConnections.remove(e.destination)
|
||||
@ -101,7 +101,7 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
||||
if (removed == null)
|
||||
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void shutdown() {
|
||||
peerConnections.values().stream().parallel().forEach({v -> v.close()})
|
||||
@ -109,8 +109,8 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
||||
peerConnections.clear()
|
||||
leafConnections.clear()
|
||||
}
|
||||
|
||||
|
||||
void forwardQueryToLeafs(QueryEvent e) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -21,5 +21,5 @@ class BadHashException extends Exception {
|
||||
public BadHashException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
public class DownloadManager {
|
||||
|
||||
|
||||
private final EventBus eventBus
|
||||
private final TrustService trustService
|
||||
private final MeshManager meshManager
|
||||
@ -36,9 +36,9 @@ public class DownloadManager {
|
||||
private final Executor executor
|
||||
private final File incompletes, home
|
||||
private final Persona me
|
||||
|
||||
|
||||
private final Map<InfoHash, Downloader> downloaders = new ConcurrentHashMap<>()
|
||||
|
||||
|
||||
public DownloadManager(EventBus eventBus, TrustService trustService, MeshManager meshManager, MuWireSettings muSettings,
|
||||
I2PConnector connector, File home, Persona me) {
|
||||
this.eventBus = eventBus
|
||||
@ -49,9 +49,9 @@ public class DownloadManager {
|
||||
this.incompletes = new File(home,"incompletes")
|
||||
this.home = home
|
||||
this.me = me
|
||||
|
||||
|
||||
incompletes.mkdir()
|
||||
|
||||
|
||||
this.executor = Executors.newCachedThreadPool({ r ->
|
||||
Thread rv = new Thread(r)
|
||||
rv.setName("download-worker")
|
||||
@ -59,23 +59,23 @@ public class DownloadManager {
|
||||
rv
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void onUIDownloadEvent(UIDownloadEvent e) {
|
||||
|
||||
|
||||
def size = e.result[0].size
|
||||
def infohash = e.result[0].infohash
|
||||
def pieceSize = e.result[0].pieceSize
|
||||
|
||||
|
||||
Set<Destination> destinations = new HashSet<>()
|
||||
e.result.each {
|
||||
e.result.each {
|
||||
destinations.add(it.sender.destination)
|
||||
}
|
||||
destinations.addAll(e.sources)
|
||||
destinations.remove(me.destination)
|
||||
|
||||
|
||||
Pieces pieces = getPieces(infohash, size, pieceSize)
|
||||
|
||||
|
||||
def downloader = new Downloader(eventBus, this, me, e.target, size,
|
||||
infohash, pieceSize, connector, destinations,
|
||||
incompletes, pieces)
|
||||
@ -84,24 +84,24 @@ public class DownloadManager {
|
||||
executor.execute({downloader.download()} as Runnable)
|
||||
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
||||
}
|
||||
|
||||
|
||||
public void onUIDownloadCancelledEvent(UIDownloadCancelledEvent e) {
|
||||
downloaders.remove(e.downloader.infoHash)
|
||||
persistDownloaders()
|
||||
}
|
||||
|
||||
|
||||
public void onUIDownloadPausedEvent(UIDownloadPausedEvent e) {
|
||||
persistDownloaders()
|
||||
}
|
||||
|
||||
|
||||
public void onUIDownloadResumedEvent(UIDownloadResumedEvent e) {
|
||||
persistDownloaders()
|
||||
}
|
||||
|
||||
|
||||
void resume(Downloader downloader) {
|
||||
executor.execute({downloader.download() as Runnable})
|
||||
}
|
||||
|
||||
|
||||
void onUILoadedEvent(UILoadedEvent e) {
|
||||
File downloadsFile = new File(home, "downloads.json")
|
||||
if (!downloadsFile.exists())
|
||||
@ -111,7 +111,7 @@ public class DownloadManager {
|
||||
def json = slurper.parseText(it)
|
||||
File file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||
def destinations = new HashSet<>()
|
||||
json.destinations.each { destination ->
|
||||
json.destinations.each { destination ->
|
||||
destinations.add new Destination(destination)
|
||||
}
|
||||
InfoHash infoHash
|
||||
@ -122,9 +122,9 @@ public class DownloadManager {
|
||||
byte [] root = Base64.decode(json.hashRoot)
|
||||
infoHash = new InfoHash(root)
|
||||
}
|
||||
|
||||
|
||||
Pieces pieces = getPieces(infoHash, (long)json.length, json.pieceSizePow2)
|
||||
|
||||
|
||||
def downloader = new Downloader(eventBus, this, me, file, (long)json.length,
|
||||
infoHash, json.pieceSizePow2, connector, destinations, incompletes, pieces)
|
||||
if (json.paused != null)
|
||||
@ -136,7 +136,7 @@ public class DownloadManager {
|
||||
eventBus.publish(new DownloadStartedEvent(downloader : downloader))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Pieces getPieces(InfoHash infoHash, long length, int pieceSizePow2) {
|
||||
int pieceSize = 0x1 << pieceSizePow2
|
||||
int nPieces = (int)(length / pieceSize)
|
||||
@ -145,7 +145,7 @@ public class DownloadManager {
|
||||
Mesh mesh = meshManager.getOrCreate(infoHash, nPieces)
|
||||
mesh.pieces
|
||||
}
|
||||
|
||||
|
||||
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
||||
Downloader downloader = downloaders.get(e.infoHash)
|
||||
if (downloader == null)
|
||||
@ -156,19 +156,19 @@ public class DownloadManager {
|
||||
case TrustLevel.NEUTRAL: ok = muSettings.allowUntrusted; break
|
||||
case TrustLevel.DISTRUSTED: ok = false; break
|
||||
}
|
||||
|
||||
|
||||
if (ok)
|
||||
downloader.addSource(e.source.destination)
|
||||
}
|
||||
|
||||
|
||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||
downloaders.remove(e.downloader.infoHash)
|
||||
persistDownloaders()
|
||||
}
|
||||
|
||||
|
||||
private void persistDownloaders() {
|
||||
File downloadsFile = new File(home,"downloads.json")
|
||||
downloadsFile.withPrintWriter { writer ->
|
||||
downloadsFile.withPrintWriter { writer ->
|
||||
downloaders.values().each { downloader ->
|
||||
if (!downloader.cancelled) {
|
||||
def json = [:]
|
||||
@ -180,20 +180,20 @@ public class DownloadManager {
|
||||
destinations << it.toBase64()
|
||||
}
|
||||
json.destinations = destinations
|
||||
|
||||
|
||||
InfoHash infoHash = downloader.getInfoHash()
|
||||
if (infoHash.hashList != null)
|
||||
json.hashList = Base64.encode(infoHash.hashList)
|
||||
else
|
||||
json.hashRoot = Base64.encode(infoHash.getRoot())
|
||||
|
||||
|
||||
json.paused = downloader.paused
|
||||
writer.println(JsonOutput.toJson(json))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void shutdown() {
|
||||
downloaders.values().each { it.stop() }
|
||||
Downloader.executorService.shutdownNow()
|
||||
|
@ -24,7 +24,7 @@ import java.util.logging.Level
|
||||
|
||||
@Log
|
||||
class DownloadSession {
|
||||
|
||||
|
||||
private final EventBus eventBus
|
||||
private final String meB64
|
||||
private final Pieces pieces
|
||||
@ -37,11 +37,11 @@ class DownloadSession {
|
||||
private final MessageDigest digest
|
||||
|
||||
private long lastSpeedRead = System.currentTimeMillis()
|
||||
private long dataSinceLastRead
|
||||
|
||||
private long dataSinceLastRead
|
||||
|
||||
private ByteBuffer mapped
|
||||
|
||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||
|
||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||
int pieceSize, long fileLength, Set<Integer> available) {
|
||||
this.eventBus = eventBus
|
||||
this.meB64 = meB64
|
||||
@ -59,7 +59,7 @@ class DownloadSession {
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return if the request will proceed. The only time it may not
|
||||
* is if all the pieces have been claimed by other sessions.
|
||||
@ -68,7 +68,7 @@ class DownloadSession {
|
||||
public boolean request() throws IOException {
|
||||
OutputStream os = endpoint.getOutputStream()
|
||||
InputStream is = endpoint.getInputStream()
|
||||
|
||||
|
||||
int piece
|
||||
if (available.isEmpty())
|
||||
piece = pieces.claim()
|
||||
@ -77,35 +77,35 @@ class DownloadSession {
|
||||
if (piece == -1)
|
||||
return false
|
||||
boolean unclaim = true
|
||||
|
||||
|
||||
log.info("will download piece $piece")
|
||||
|
||||
|
||||
long start = piece * pieceSize
|
||||
long end = Math.min(fileLength, start + pieceSize) - 1
|
||||
long length = end - start + 1
|
||||
|
||||
|
||||
String root = Base64.encode(infoHash.getRoot())
|
||||
|
||||
|
||||
try {
|
||||
os.write("GET $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.write("Range: $start-$end\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.write("X-Persona: $meB64\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
String xHave = DataUtil.encodeXHave(pieces.getDownloaded(), pieces.nPieces)
|
||||
os.write("X-Have: $xHave\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.write("X-Have: $xHave\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.flush()
|
||||
String codeString = readTillRN(is)
|
||||
int space = codeString.indexOf(' ')
|
||||
if (space > 0)
|
||||
codeString = codeString.substring(0, space)
|
||||
|
||||
|
||||
int code = Integer.parseInt(codeString.trim())
|
||||
|
||||
|
||||
if (code == 404) {
|
||||
log.warning("file not found")
|
||||
endpoint.close()
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
if (!(code == 200 || code == 416)) {
|
||||
log.warning("unknown code $code")
|
||||
endpoint.close()
|
||||
@ -120,10 +120,10 @@ class DownloadSession {
|
||||
if (colon == -1 || colon == header.length() - 1)
|
||||
throw new IOException("invalid header $header")
|
||||
String key = header.substring(0, colon)
|
||||
String value = header.substring(colon + 1)
|
||||
String value = header.substring(colon + 1)
|
||||
headers[key] = value.trim()
|
||||
}
|
||||
|
||||
|
||||
// prase X-Alt if present
|
||||
if (headers.containsKey("X-Alt")) {
|
||||
headers["X-Alt"].split(",").each {
|
||||
@ -136,7 +136,7 @@ class DownloadSession {
|
||||
}
|
||||
|
||||
// parse X-Have if present
|
||||
if (headers.containsKey("X-Have")) {
|
||||
if (headers.containsKey("X-Have")) {
|
||||
DataUtil.decodeXHave(headers["X-Have"]).each {
|
||||
available.add(it)
|
||||
}
|
||||
@ -147,16 +147,16 @@ class DownloadSession {
|
||||
throw new IOException("Code $code but no X-Have")
|
||||
available.clear()
|
||||
}
|
||||
|
||||
|
||||
if (code != 200)
|
||||
return true
|
||||
|
||||
|
||||
String range = headers["Content-Range"]
|
||||
if (range == null)
|
||||
if (range == null)
|
||||
throw new IOException("Code 200 but no Content-Range")
|
||||
|
||||
|
||||
def group = (range =~ /^(\d+)-(\d+)$/)
|
||||
if (group.size() != 1)
|
||||
if (group.size() != 1)
|
||||
throw new IOException("invalid Content-Range header $range")
|
||||
|
||||
long receivedStart = Long.parseLong(group[0][1])
|
||||
@ -167,7 +167,7 @@ class DownloadSession {
|
||||
endpoint.close()
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// start the download
|
||||
FileChannel channel
|
||||
try {
|
||||
@ -207,13 +207,13 @@ class DownloadSession {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
synchronized int positionInPiece() {
|
||||
if (mapped == null)
|
||||
return 0
|
||||
mapped.position()
|
||||
}
|
||||
|
||||
|
||||
synchronized int speed() {
|
||||
final long now = System.currentTimeMillis()
|
||||
long interval = Math.max(1000, now - lastSpeedRead)
|
||||
|
@ -29,7 +29,7 @@ import net.i2p.util.ConcurrentHashSet
|
||||
public class Downloader {
|
||||
public enum DownloadState { CONNECTING, HASHLIST, DOWNLOADING, FAILED, CANCELLED, PAUSED, FINISHED }
|
||||
private enum WorkerState { CONNECTING, HASHLIST, DOWNLOADING, FINISHED}
|
||||
|
||||
|
||||
private static final ExecutorService executorService = Executors.newCachedThreadPool({r ->
|
||||
Thread rv = new Thread(r)
|
||||
rv.setName("download worker")
|
||||
@ -38,8 +38,8 @@ public class Downloader {
|
||||
})
|
||||
|
||||
private final EventBus eventBus
|
||||
private final DownloadManager downloadManager
|
||||
private final Persona me
|
||||
private final DownloadManager downloadManager
|
||||
private final Persona me
|
||||
private final File file
|
||||
private final Pieces pieces
|
||||
private final long length
|
||||
@ -53,8 +53,8 @@ public class Downloader {
|
||||
final int pieceSizePow2
|
||||
private final Map<Destination, DownloadWorker> activeWorkers = new ConcurrentHashMap<>()
|
||||
private final Set<Destination> successfulDestinations = new ConcurrentHashSet<>()
|
||||
|
||||
|
||||
|
||||
|
||||
private volatile boolean cancelled, paused
|
||||
private final AtomicBoolean eventFired = new AtomicBoolean()
|
||||
private boolean piecesFileClosed
|
||||
@ -64,8 +64,8 @@ public class Downloader {
|
||||
private int speedAvg = 0
|
||||
private long timestamp = Instant.now().toEpochMilli()
|
||||
|
||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||
Persona me, File file, long length, InfoHash infoHash,
|
||||
public Downloader(EventBus eventBus, DownloadManager downloadManager,
|
||||
Persona me, File file, long length, InfoHash infoHash,
|
||||
int pieceSizePow2, I2PConnector connector, Set<Destination> destinations,
|
||||
File incompletes, Pieces pieces) {
|
||||
this.eventBus = eventBus
|
||||
@ -87,15 +87,15 @@ public class Downloader {
|
||||
// it's easily adjustable by resizing the size of speedArr
|
||||
this.speedArr = [ 0, 0, 0, 0, 0 ]
|
||||
}
|
||||
|
||||
|
||||
public synchronized InfoHash getInfoHash() {
|
||||
infoHash
|
||||
}
|
||||
|
||||
|
||||
private synchronized void setInfoHash(InfoHash infoHash) {
|
||||
this.infoHash = infoHash
|
||||
}
|
||||
|
||||
|
||||
void download() {
|
||||
readPieces()
|
||||
destinations.each {
|
||||
@ -106,16 +106,16 @@ public class Downloader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void readPieces() {
|
||||
if (!piecesFile.exists())
|
||||
return
|
||||
piecesFile.eachLine {
|
||||
piecesFile.eachLine {
|
||||
int piece = Integer.parseInt(it)
|
||||
pieces.markDownloaded(piece)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void writePieces() {
|
||||
synchronized(piecesFile) {
|
||||
if (piecesFileClosed)
|
||||
@ -127,12 +127,12 @@ public class Downloader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public long donePieces() {
|
||||
pieces.donePieces()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public int speed() {
|
||||
int currSpeed = 0
|
||||
if (getCurrentState() == DownloadState.DOWNLOADING) {
|
||||
@ -164,15 +164,15 @@ public class Downloader {
|
||||
|
||||
speedAvg
|
||||
}
|
||||
|
||||
|
||||
public DownloadState getCurrentState() {
|
||||
if (cancelled)
|
||||
return DownloadState.CANCELLED
|
||||
if (paused)
|
||||
return DownloadState.PAUSED
|
||||
|
||||
|
||||
boolean allFinished = true
|
||||
activeWorkers.values().each {
|
||||
activeWorkers.values().each {
|
||||
allFinished &= it.currentState == WorkerState.FINISHED
|
||||
}
|
||||
if (allFinished) {
|
||||
@ -180,22 +180,22 @@ public class Downloader {
|
||||
return DownloadState.FINISHED
|
||||
return DownloadState.FAILED
|
||||
}
|
||||
|
||||
|
||||
// if at least one is downloading...
|
||||
boolean oneDownloading = false
|
||||
activeWorkers.values().each {
|
||||
activeWorkers.values().each {
|
||||
if (it.currentState == WorkerState.DOWNLOADING) {
|
||||
oneDownloading = true
|
||||
return
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (oneDownloading)
|
||||
return DownloadState.DOWNLOADING
|
||||
|
||||
|
||||
// at least one is requesting hashlist
|
||||
boolean oneHashlist = false
|
||||
activeWorkers.values().each {
|
||||
activeWorkers.values().each {
|
||||
if (it.currentState == WorkerState.HASHLIST) {
|
||||
oneHashlist = true
|
||||
return
|
||||
@ -203,10 +203,10 @@ public class Downloader {
|
||||
}
|
||||
if (oneHashlist)
|
||||
return DownloadState.HASHLIST
|
||||
|
||||
|
||||
return DownloadState.CONNECTING
|
||||
}
|
||||
|
||||
|
||||
public void cancel() {
|
||||
cancelled = true
|
||||
stop()
|
||||
@ -217,27 +217,27 @@ public class Downloader {
|
||||
incompleteFile.delete()
|
||||
pieces.clearAll()
|
||||
}
|
||||
|
||||
|
||||
public void pause() {
|
||||
paused = true
|
||||
stop()
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
activeWorkers.values().each {
|
||||
activeWorkers.values().each {
|
||||
it.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int activeWorkers() {
|
||||
int active = 0
|
||||
activeWorkers.values().each {
|
||||
activeWorkers.values().each {
|
||||
if (it.currentState != WorkerState.FINISHED)
|
||||
active++
|
||||
}
|
||||
active
|
||||
}
|
||||
|
||||
|
||||
public void resume() {
|
||||
paused = false
|
||||
readPieces()
|
||||
@ -256,7 +256,7 @@ public class Downloader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void addSource(Destination d) {
|
||||
if (activeWorkers.containsKey(d))
|
||||
return
|
||||
@ -264,7 +264,7 @@ public class Downloader {
|
||||
activeWorkers.put(d, newWorker)
|
||||
executorService.submit(newWorker)
|
||||
}
|
||||
|
||||
|
||||
class DownloadWorker implements Runnable {
|
||||
private final Destination destination
|
||||
private volatile WorkerState currentState
|
||||
@ -272,11 +272,11 @@ public class Downloader {
|
||||
private Endpoint endpoint
|
||||
private volatile DownloadSession currentSession
|
||||
private final Set<Integer> available = new HashSet<>()
|
||||
|
||||
|
||||
DownloadWorker(Destination destination) {
|
||||
this.destination = destination
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
downloadThread = Thread.currentThread()
|
||||
currentState = WorkerState.CONNECTING
|
||||
@ -292,7 +292,7 @@ public class Downloader {
|
||||
currentState = WorkerState.DOWNLOADING
|
||||
boolean requestPerformed
|
||||
while(!pieces.isComplete()) {
|
||||
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
||||
currentSession = new DownloadSession(eventBus, me.toBase64(), pieces, getInfoHash(),
|
||||
endpoint, incompleteFile, pieceSize, length, available)
|
||||
requestPerformed = currentSession.request()
|
||||
if (!requestPerformed)
|
||||
@ -319,18 +319,18 @@ public class Downloader {
|
||||
new FileDownloadedEvent(
|
||||
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations),
|
||||
downloader : Downloader.this))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
endpoint?.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int speed() {
|
||||
if (currentSession == null)
|
||||
return 0
|
||||
currentSession.speed()
|
||||
}
|
||||
|
||||
|
||||
void cancel() {
|
||||
downloadThread?.interrupt()
|
||||
}
|
||||
|
@ -20,32 +20,32 @@ class HashListSession {
|
||||
private final String meB64
|
||||
private final InfoHash infoHash
|
||||
private final Endpoint endpoint
|
||||
|
||||
|
||||
HashListSession(String meB64, InfoHash infoHash, Endpoint endpoint) {
|
||||
this.meB64 = meB64
|
||||
this.infoHash = infoHash
|
||||
this.endpoint = endpoint
|
||||
}
|
||||
|
||||
|
||||
InfoHash request() throws IOException {
|
||||
InputStream is = endpoint.getInputStream()
|
||||
OutputStream os = endpoint.getOutputStream()
|
||||
|
||||
|
||||
String root = Base64.encode(infoHash.getRoot())
|
||||
os.write("HASHLIST $root\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.write("X-Persona: $meB64\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.flush()
|
||||
|
||||
|
||||
String code = readTillRN(is)
|
||||
if (!code.startsWith("200"))
|
||||
throw new IOException("unknown code $code")
|
||||
|
||||
|
||||
// parse all headers
|
||||
Set<String> headers = new HashSet<>()
|
||||
String header
|
||||
while((header = readTillRN(is)) != "" && headers.size() < Constants.MAX_HEADERS)
|
||||
headers.add(header)
|
||||
|
||||
|
||||
long receivedStart = -1
|
||||
long receivedEnd = -1
|
||||
for (String receivedHeader : headers) {
|
||||
@ -58,10 +58,10 @@ class HashListSession {
|
||||
receivedStart = Long.parseLong(group[0][1])
|
||||
receivedEnd = Long.parseLong(group[0][2])
|
||||
}
|
||||
|
||||
|
||||
if (receivedStart != 0)
|
||||
throw new IOException("hashlist started at $receivedStart")
|
||||
|
||||
|
||||
byte[] hashList = new byte[receivedEnd]
|
||||
ByteBuffer hashListBuf = ByteBuffer.wrap(hashList)
|
||||
byte[] tmp = new byte[0x1 << 13]
|
||||
@ -73,7 +73,7 @@ class HashListSession {
|
||||
throw new IOException()
|
||||
hashListBuf.put(tmp, 0, read)
|
||||
}
|
||||
|
||||
|
||||
InfoHash received = InfoHash.fromHashList(hashList)
|
||||
if (received.getRoot() != infoHash.getRoot())
|
||||
throw new IOException("fetched list doesn't match root")
|
||||
|
@ -5,30 +5,30 @@ class Pieces {
|
||||
private final int nPieces
|
||||
private final float ratio
|
||||
private final Random random = new Random()
|
||||
|
||||
|
||||
Pieces(int nPieces) {
|
||||
this(nPieces, 1.0f)
|
||||
}
|
||||
|
||||
|
||||
Pieces(int nPieces, float ratio) {
|
||||
this.nPieces = nPieces
|
||||
this.ratio = ratio
|
||||
done = new BitSet(nPieces)
|
||||
claimed = new BitSet(nPieces)
|
||||
}
|
||||
|
||||
|
||||
synchronized int claim() {
|
||||
int claimedCardinality = claimed.cardinality()
|
||||
if (claimedCardinality == nPieces)
|
||||
return -1
|
||||
|
||||
|
||||
// if fuller than ratio just do sequential
|
||||
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
||||
int rv = claimed.nextClearBit(0)
|
||||
claimed.set(rv)
|
||||
return rv
|
||||
}
|
||||
|
||||
|
||||
while(true) {
|
||||
int start = random.nextInt(nPieces)
|
||||
if (claimed.get(start))
|
||||
@ -37,7 +37,7 @@ class Pieces {
|
||||
return start
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
synchronized int claim(Set<Integer> available) {
|
||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
||||
available.remove(i)
|
||||
@ -49,7 +49,7 @@ class Pieces {
|
||||
claimed.set(rv)
|
||||
rv
|
||||
}
|
||||
|
||||
|
||||
synchronized def getDownloaded() {
|
||||
def rv = []
|
||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
||||
@ -57,28 +57,28 @@ class Pieces {
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
|
||||
synchronized void markDownloaded(int piece) {
|
||||
done.set(piece)
|
||||
claimed.set(piece)
|
||||
}
|
||||
|
||||
|
||||
synchronized void unclaim(int piece) {
|
||||
claimed.clear(piece)
|
||||
}
|
||||
|
||||
|
||||
synchronized boolean isComplete() {
|
||||
done.cardinality() == nPieces
|
||||
}
|
||||
|
||||
|
||||
synchronized int donePieces() {
|
||||
done.cardinality()
|
||||
}
|
||||
|
||||
|
||||
synchronized boolean isDownloaded(int piece) {
|
||||
done.get(piece)
|
||||
}
|
||||
|
||||
|
||||
synchronized void clearAll() {
|
||||
done.clear()
|
||||
claimed.clear()
|
||||
|
@ -6,7 +6,7 @@ import com.muwire.core.search.UIResultEvent
|
||||
import net.i2p.data.Destination
|
||||
|
||||
class UIDownloadEvent extends Event {
|
||||
|
||||
|
||||
UIResultEvent[] result
|
||||
Set<Destination> sources
|
||||
File target
|
||||
|
@ -20,9 +20,9 @@ import net.i2p.util.SystemVersion
|
||||
|
||||
@Log
|
||||
class DirectoryWatcher {
|
||||
|
||||
|
||||
private static final long WAIT_TIME = 1000
|
||||
|
||||
|
||||
private static final WatchEvent.Kind[] kinds
|
||||
static {
|
||||
if (SystemVersion.isMac())
|
||||
@ -30,7 +30,7 @@ class DirectoryWatcher {
|
||||
else
|
||||
kinds = [ENTRY_CREATE, ENTRY_MODIFY, ENTRY_DELETE]
|
||||
}
|
||||
|
||||
|
||||
private final EventBus eventBus
|
||||
private final FileManager fileManager
|
||||
private final Thread watcherThread, publisherThread
|
||||
@ -38,7 +38,7 @@ class DirectoryWatcher {
|
||||
private final Map<File, WatchKey> watchedDirectories = new ConcurrentHashMap<>()
|
||||
private WatchService watchService
|
||||
private volatile boolean shutdown
|
||||
|
||||
|
||||
DirectoryWatcher(EventBus eventBus, FileManager fileManager) {
|
||||
this.eventBus = eventBus
|
||||
this.fileManager = fileManager
|
||||
@ -47,29 +47,29 @@ class DirectoryWatcher {
|
||||
this.publisherThread = new Thread({publish()} as Runnable, "watched-files-publisher")
|
||||
publisherThread.setDaemon(true)
|
||||
}
|
||||
|
||||
|
||||
void onAllFilesLoadedEvent(AllFilesLoadedEvent e) {
|
||||
watchService = FileSystems.getDefault().newWatchService()
|
||||
watcherThread.start()
|
||||
publisherThread.start()
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
shutdown = true
|
||||
watcherThread?.interrupt()
|
||||
publisherThread?.interrupt()
|
||||
watchService?.close()
|
||||
}
|
||||
|
||||
|
||||
void onFileSharedEvent(FileSharedEvent e) {
|
||||
if (!e.file.isDirectory())
|
||||
return
|
||||
Path path = e.file.getCanonicalFile().toPath()
|
||||
WatchKey wk = path.register(watchService, kinds)
|
||||
watchedDirectories.put(e.file, wk)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||
WatchKey wk = watchedDirectories.remove(e.directory)
|
||||
wk?.cancel()
|
||||
@ -93,7 +93,7 @@ class DirectoryWatcher {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void processCreated(Path parent, Path path) {
|
||||
File f= join(parent, path)
|
||||
@ -103,13 +103,13 @@ class DirectoryWatcher {
|
||||
else
|
||||
waitingFiles.put(f, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
|
||||
private void processModified(Path parent, Path path) {
|
||||
File f = join(parent, path)
|
||||
log.fine("modified entry $f")
|
||||
waitingFiles.put(f, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
|
||||
private void processDeleted(Path parent, Path path) {
|
||||
File f = join(parent, path)
|
||||
log.fine("deleted entry $f")
|
||||
@ -117,12 +117,12 @@ class DirectoryWatcher {
|
||||
if (sf != null)
|
||||
eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||
}
|
||||
|
||||
|
||||
private static File join(Path parent, Path path) {
|
||||
File parentFile = parent.toFile().getCanonicalFile()
|
||||
new File(parentFile, path.toFile().getName()).getCanonicalFile()
|
||||
}
|
||||
|
||||
|
||||
private void publish() {
|
||||
try {
|
||||
while(!shutdown) {
|
||||
|
@ -7,10 +7,10 @@ class FileHashedEvent extends Event {
|
||||
|
||||
SharedFile sharedFile
|
||||
String error
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
super.toString() + " sharedFile " + sharedFile?.file.getAbsolutePath() + " error: $error"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ class FileHasher {
|
||||
|
||||
/** max size of shared file is 128 GB */
|
||||
public static final long MAX_SIZE = 0x1L << 37
|
||||
|
||||
|
||||
/**
|
||||
* @param size of the file to be shared
|
||||
* @return the size of each piece in power of 2
|
||||
@ -25,18 +25,18 @@ class FileHasher {
|
||||
static int getPieceSize(long size) {
|
||||
if (size <= 0x1 << 30)
|
||||
return 17
|
||||
|
||||
|
||||
for (int i = 31; i <= 37; i++) {
|
||||
if (size <= 0x1L << i) {
|
||||
return i-13
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
throw new IllegalArgumentException("File too large $size")
|
||||
}
|
||||
|
||||
|
||||
final MessageDigest digest
|
||||
|
||||
|
||||
FileHasher() {
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256")
|
||||
@ -45,14 +45,14 @@ class FileHasher {
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InfoHash hashFile(File file) {
|
||||
final long length = file.length()
|
||||
final int size = 0x1 << getPieceSize(length)
|
||||
int numPieces = (int) (length / size)
|
||||
if (numPieces * size < length)
|
||||
numPieces++
|
||||
|
||||
|
||||
def output = new ByteArrayOutputStream()
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
||||
try {
|
||||
@ -70,18 +70,18 @@ class FileHasher {
|
||||
} finally {
|
||||
raf.close()
|
||||
}
|
||||
|
||||
|
||||
byte [] hashList = output.toByteArray()
|
||||
InfoHash.fromHashList(hashList)
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 1) {
|
||||
println "This utility computes an infohash of a file"
|
||||
println "Pass absolute path to a file as an argument"
|
||||
System.exit(1)
|
||||
}
|
||||
|
||||
|
||||
def file = new File(args[0])
|
||||
file = file.getAbsoluteFile()
|
||||
def hasher = new FileHasher()
|
||||
|
@ -21,27 +21,27 @@ class FileManager {
|
||||
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||
final SearchIndex index = new SearchIndex()
|
||||
|
||||
|
||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||
this.settings = settings
|
||||
this.eventBus = eventBus
|
||||
}
|
||||
|
||||
|
||||
void onFileHashedEvent(FileHashedEvent e) {
|
||||
if (e.sharedFile != null)
|
||||
addToIndex(e.sharedFile)
|
||||
}
|
||||
|
||||
|
||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||
addToIndex(e.loadedFile)
|
||||
}
|
||||
|
||||
|
||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||
if (settings.shareDownloadedFiles) {
|
||||
addToIndex(e.downloadedFile)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void addToIndex(SharedFile sf) {
|
||||
log.info("Adding shared file " + sf.getFile())
|
||||
InfoHash infoHash = sf.getInfoHash()
|
||||
@ -53,7 +53,7 @@ class FileManager {
|
||||
}
|
||||
existing.add(sf)
|
||||
fileToSharedFile.put(sf.file, sf)
|
||||
|
||||
|
||||
String name = sf.getFile().getName()
|
||||
Set<File> existingFiles = nameToFiles.get(name)
|
||||
if (existingFiles == null) {
|
||||
@ -61,10 +61,10 @@ class FileManager {
|
||||
nameToFiles.put(name, existingFiles)
|
||||
}
|
||||
existingFiles.add(sf.getFile())
|
||||
|
||||
|
||||
index.add(name)
|
||||
}
|
||||
|
||||
|
||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||
SharedFile sf = e.unsharedFile
|
||||
InfoHash infoHash = sf.getInfoHash()
|
||||
@ -75,9 +75,9 @@ class FileManager {
|
||||
rootToFiles.remove(infoHash)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileToSharedFile.remove(sf.file)
|
||||
|
||||
|
||||
String name = sf.getFile().getName()
|
||||
Set<File> existingFiles = nameToFiles.get(name)
|
||||
if (existingFiles != null) {
|
||||
@ -86,20 +86,20 @@ class FileManager {
|
||||
nameToFiles.remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
index.remove(name)
|
||||
}
|
||||
|
||||
|
||||
Map<File, SharedFile> getSharedFiles() {
|
||||
synchronized(fileToSharedFile) {
|
||||
return new HashMap<>(fileToSharedFile)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Set<SharedFile> getSharedFiles(byte []root) {
|
||||
return rootToFiles.get(new InfoHash(root))
|
||||
}
|
||||
|
||||
|
||||
void onSearchEvent(SearchEvent e) {
|
||||
// hash takes precedence
|
||||
ResultsEvent re = null
|
||||
@ -118,26 +118,26 @@ class FileManager {
|
||||
files = filter(sharedFiles, e.oobInfohash)
|
||||
if (!sharedFiles.isEmpty())
|
||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (re != null)
|
||||
eventBus.publish(re)
|
||||
}
|
||||
|
||||
|
||||
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
||||
if (!oob)
|
||||
return files
|
||||
Set<SharedFile> rv = new HashSet<>()
|
||||
files.each {
|
||||
files.each {
|
||||
if (it.getPieceSize() != 0)
|
||||
rv.add(it)
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
|
||||
void onDirectoryUnsharedEvent(DirectoryUnsharedEvent e) {
|
||||
e.directory.listFiles().each {
|
||||
e.directory.listFiles().each {
|
||||
if (it.isDirectory())
|
||||
eventBus.publish(new DirectoryUnsharedEvent(directory : it))
|
||||
else {
|
||||
|
@ -5,7 +5,7 @@ import com.muwire.core.Event
|
||||
class FileSharedEvent extends Event {
|
||||
|
||||
File file
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + " file: "+file.getAbsolutePath()
|
||||
|
@ -12,23 +12,23 @@ class HasherService {
|
||||
final EventBus eventBus
|
||||
final FileManager fileManager
|
||||
Executor executor
|
||||
|
||||
|
||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||
this.hasher = hasher
|
||||
this.eventBus = eventBus
|
||||
this.fileManager = fileManager
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
executor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
|
||||
void onFileSharedEvent(FileSharedEvent evt) {
|
||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||
return
|
||||
executor.execute( { -> process(evt.file) } as Runnable)
|
||||
}
|
||||
|
||||
|
||||
private void process(File f) {
|
||||
f = f.getCanonicalFile()
|
||||
if (f.isDirectory()) {
|
||||
|
@ -28,7 +28,7 @@ class PersisterService extends Service {
|
||||
final int interval
|
||||
final Timer timer
|
||||
final FileManager fileManager
|
||||
|
||||
|
||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||
this.location = location
|
||||
this.listener = listener
|
||||
@ -36,7 +36,7 @@ class PersisterService extends Service {
|
||||
this.fileManager = fileManager
|
||||
timer = new Timer("file persister", true)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
@ -44,7 +44,7 @@ class PersisterService extends Service {
|
||||
void onUILoadedEvent(UILoadedEvent e) {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
|
||||
void load() {
|
||||
if (location.exists() && location.isFile()) {
|
||||
def slurper = new JsonSlurper()
|
||||
@ -69,13 +69,13 @@ class PersisterService extends Service {
|
||||
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
||||
loaded = true
|
||||
}
|
||||
|
||||
|
||||
private static FileLoadedEvent fromJson(def json) {
|
||||
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
||||
throw new IllegalArgumentException()
|
||||
if (!(json.hashList instanceof List))
|
||||
throw new IllegalArgumentException()
|
||||
|
||||
|
||||
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||
file = file.getCanonicalFile()
|
||||
if (!file.exists() || file.isDirectory())
|
||||
@ -83,7 +83,7 @@ class PersisterService extends Service {
|
||||
long length = Long.valueOf(json.length)
|
||||
if (length != file.length())
|
||||
return null
|
||||
|
||||
|
||||
List hashList = (List) json.hashList
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
hashList.each {
|
||||
@ -93,34 +93,34 @@ class PersisterService extends Service {
|
||||
baos.write hash
|
||||
}
|
||||
byte[] hashListBytes = baos.toByteArray()
|
||||
|
||||
|
||||
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
||||
byte [] root = Base64.decode(json.infoHash.toString())
|
||||
if (root == null)
|
||||
throw new IllegalArgumentException()
|
||||
if (!Arrays.equals(root, ih.getRoot()))
|
||||
return null
|
||||
|
||||
|
||||
int pieceSize = 0
|
||||
if (json.pieceSize != null)
|
||||
pieceSize = json.pieceSize
|
||||
|
||||
|
||||
if (json.sources != null) {
|
||||
List sources = (List)json.sources
|
||||
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
||||
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
||||
return new FileLoadedEvent(loadedFile : df)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||
return new FileLoadedEvent(loadedFile: sf)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void persistFiles() {
|
||||
def sharedFiles = fileManager.getSharedFiles()
|
||||
|
||||
|
||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||
tmp.deleteOnExit()
|
||||
tmp.withPrintWriter { writer ->
|
||||
@ -133,7 +133,7 @@ class PersisterService extends Service {
|
||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
tmp.delete()
|
||||
}
|
||||
|
||||
|
||||
private def toJson(File f, SharedFile sf) {
|
||||
def json = [:]
|
||||
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
||||
@ -147,11 +147,11 @@ class PersisterService extends Service {
|
||||
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
||||
json.hashList.add Base64.encode(tmp)
|
||||
}
|
||||
|
||||
|
||||
if (sf instanceof DownloadedFile) {
|
||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||
}
|
||||
|
||||
|
||||
json
|
||||
}
|
||||
}
|
||||
|
@ -17,9 +17,9 @@ import net.i2p.data.Destination
|
||||
|
||||
@Log
|
||||
class CacheClient {
|
||||
|
||||
|
||||
private static final int CRAWLER_RETURN = 10
|
||||
|
||||
|
||||
final EventBus eventBus
|
||||
final HostCache cache
|
||||
final ConnectionManager manager
|
||||
@ -28,7 +28,7 @@ class CacheClient {
|
||||
final MuWireSettings settings
|
||||
final Timer timer
|
||||
|
||||
public CacheClient(EventBus eventBus, HostCache cache,
|
||||
public CacheClient(EventBus eventBus, HostCache cache,
|
||||
ConnectionManager manager, I2PSession session,
|
||||
MuWireSettings settings, long interval) {
|
||||
this.eventBus = eventBus
|
||||
@ -39,24 +39,24 @@ class CacheClient {
|
||||
this.interval = interval
|
||||
this.timer = new Timer("hostcache-client",true)
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
||||
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
|
||||
private void queryIfNeeded() {
|
||||
if (!manager.getConnections().isEmpty())
|
||||
return
|
||||
if (!cache.getHosts(1).isEmpty())
|
||||
return
|
||||
|
||||
log.info "Will query hostcaches"
|
||||
|
||||
|
||||
log.info "Will query hostcaches"
|
||||
|
||||
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
||||
ping = JsonOutput.toJson(ping)
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
@ -68,9 +68,9 @@ class CacheClient {
|
||||
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Listener implements I2PSessionMuxedListener {
|
||||
|
||||
|
||||
private final JsonSlurper slurper = new JsonSlurper()
|
||||
|
||||
@Override
|
||||
@ -79,27 +79,27 @@ class CacheClient {
|
||||
|
||||
@Override
|
||||
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
||||
|
||||
|
||||
if (proto != I2PSession.PROTO_DATAGRAM) {
|
||||
log.warning "Received unexpected protocol $proto"
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
def payload = session.receiveMessage(msgId)
|
||||
def dissector = new I2PDatagramDissector()
|
||||
try {
|
||||
dissector.loadI2PDatagram(payload)
|
||||
def sender = dissector.getSender()
|
||||
log.info("Received something from ${sender.toBase32()}")
|
||||
|
||||
|
||||
payload = dissector.getPayload()
|
||||
payload = slurper.parse(payload)
|
||||
|
||||
|
||||
if (payload.type == null) {
|
||||
log.warning("type missing")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
switch(payload.type) {
|
||||
case "Pong" : handlePong(sender, payload); break
|
||||
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
||||
@ -123,34 +123,34 @@ class CacheClient {
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
log.severe "I2P error occured $message $error"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void handlePong(Destination from, def pong) {
|
||||
if (!CacheServers.isRegistered(from)) {
|
||||
log.warning("received pong from non-registered destination")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (pong.pongs == null) {
|
||||
log.warning("malformed pong - no pongs")
|
||||
return
|
||||
}
|
||||
|
||||
pong.pongs.asList().each {
|
||||
|
||||
pong.pongs.asList().each {
|
||||
Destination dest = new Destination(it)
|
||||
if (!session.getMyDestination().equals(dest))
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void handleCrawlerPing(I2PSession session, Destination from, def ping) {
|
||||
if (settings.isLeaf()) {
|
||||
log.warning("Received crawler ping but I'm a leaf")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
switch(settings.getCrawlerResponse()) {
|
||||
case CrawlerResponse.NONE:
|
||||
log.info("Responding to crawlers is disabled by user")
|
||||
@ -166,15 +166,15 @@ class CacheClient {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
||||
log.info "responding to crawler ping"
|
||||
|
||||
|
||||
def neighbors = manager.getConnections().collect { c -> c.endpoint.destination.toBase64() }
|
||||
Collections.shuffle(neighbors)
|
||||
if (neighbors.size() > CRAWLER_RETURN)
|
||||
neighbors = neighbors[0..CRAWLER_RETURN - 1]
|
||||
|
||||
|
||||
def upManager = (UltrapeerConnectionManager) manager;
|
||||
def pong = [:]
|
||||
pong.peers = neighbors
|
||||
@ -184,7 +184,7 @@ class CacheClient {
|
||||
pong.leafSlots = upManager.hasLeafSlots()
|
||||
pong.peerSlots = upManager.hasPeerSlots()
|
||||
pong = JsonOutput.toJson(pong)
|
||||
|
||||
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
pong = maker.makeI2PDatagram(pong.bytes)
|
||||
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
||||
|
@ -17,7 +17,7 @@ class CacheServers {
|
||||
return allCaches
|
||||
allCaches[0..TO_GIVE-1]
|
||||
}
|
||||
|
||||
|
||||
static boolean isRegistered(Destination d) {
|
||||
return CACHES.contains(d)
|
||||
}
|
||||
|
@ -5,12 +5,12 @@ import net.i2p.data.Destination
|
||||
class Host {
|
||||
|
||||
private static final int MAX_FAILURES = 3
|
||||
|
||||
|
||||
final Destination destination
|
||||
private final int clearInterval
|
||||
int failures,successes
|
||||
long lastAttempt
|
||||
|
||||
|
||||
public Host(Destination destination, int clearInterval) {
|
||||
this.destination = destination
|
||||
this.clearInterval = clearInterval
|
||||
@ -21,25 +21,25 @@ class Host {
|
||||
successes++
|
||||
lastAttempt = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
|
||||
synchronized void onFailure() {
|
||||
failures++
|
||||
successes = 0
|
||||
lastAttempt = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
|
||||
synchronized boolean isFailed() {
|
||||
failures >= MAX_FAILURES
|
||||
}
|
||||
|
||||
|
||||
synchronized boolean hasSucceeded() {
|
||||
successes > 0
|
||||
}
|
||||
|
||||
|
||||
synchronized void clearFailures() {
|
||||
failures = 0
|
||||
}
|
||||
|
||||
|
||||
synchronized void canTryAgain() {
|
||||
System.currentTimeMillis() - lastAttempt > (clearInterval * 60 * 1000)
|
||||
}
|
||||
|
@ -22,10 +22,10 @@ class HostCache extends Service {
|
||||
final MuWireSettings settings
|
||||
final Destination myself
|
||||
final Map<Destination, Host> hosts = new ConcurrentHashMap<>()
|
||||
|
||||
|
||||
HostCache(){}
|
||||
|
||||
public HostCache(TrustService trustService, File storage, int interval,
|
||||
|
||||
public HostCache(TrustService trustService, File storage, int interval,
|
||||
MuWireSettings settings, Destination myself) {
|
||||
this.trustService = trustService
|
||||
this.storage = storage
|
||||
@ -38,11 +38,11 @@ class HostCache extends Service {
|
||||
void start() {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
|
||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||
if (myself == e.destination)
|
||||
return
|
||||
@ -57,7 +57,7 @@ class HostCache extends Service {
|
||||
hosts.put(e.destination, host)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void onConnectionEvent(ConnectionEvent e) {
|
||||
if (e.leaf)
|
||||
return
|
||||
@ -78,7 +78,7 @@ class HostCache extends Service {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<Destination> getHosts(int n) {
|
||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||
rv.retainAll {allowHost(hosts[it])}
|
||||
@ -87,7 +87,7 @@ class HostCache extends Service {
|
||||
Collections.shuffle(rv)
|
||||
rv[0..n-1]
|
||||
}
|
||||
|
||||
|
||||
List<Destination> getGoodHosts(int n) {
|
||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||
rv.retainAll {
|
||||
@ -99,7 +99,7 @@ class HostCache extends Service {
|
||||
Collections.shuffle(rv)
|
||||
rv[0..n-1]
|
||||
}
|
||||
|
||||
|
||||
void load() {
|
||||
if (storage.exists()) {
|
||||
JsonSlurper slurper = new JsonSlurper()
|
||||
@ -118,7 +118,7 @@ class HostCache extends Service {
|
||||
timer.schedule({save()} as TimerTask, interval, interval)
|
||||
loaded = true
|
||||
}
|
||||
|
||||
|
||||
private boolean allowHost(Host host) {
|
||||
if (host.isFailed() && !host.canTryAgain())
|
||||
return false
|
||||
@ -135,7 +135,7 @@ class HostCache extends Service {
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
|
||||
private void save() {
|
||||
storage.delete()
|
||||
storage.withPrintWriter { writer ->
|
||||
|
@ -8,7 +8,7 @@ class HostDiscoveredEvent extends Event {
|
||||
|
||||
Destination destination
|
||||
boolean fromHostcache
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
||||
|
@ -11,12 +11,12 @@ class Mesh {
|
||||
private final InfoHash infoHash
|
||||
private final Set<Persona> sources = new ConcurrentHashSet<>()
|
||||
private final Pieces pieces
|
||||
|
||||
|
||||
Mesh(InfoHash infoHash, Pieces pieces) {
|
||||
this.infoHash = infoHash
|
||||
this.pieces = pieces
|
||||
}
|
||||
|
||||
|
||||
Set<Persona> getRandom(int n, Persona exclude) {
|
||||
List<Persona> tmp = new ArrayList<>(sources)
|
||||
tmp.remove(exclude)
|
||||
|
@ -16,23 +16,23 @@ import groovy.json.JsonSlurper
|
||||
import net.i2p.data.Base64
|
||||
|
||||
class MeshManager {
|
||||
|
||||
|
||||
private final Map<InfoHash, Mesh> meshes = Collections.synchronizedMap(new HashMap<>())
|
||||
private final FileManager fileManager
|
||||
private final File home
|
||||
private final MuWireSettings settings
|
||||
|
||||
|
||||
MeshManager(FileManager fileManager, File home, MuWireSettings settings) {
|
||||
this.fileManager = fileManager
|
||||
this.home = home
|
||||
this.settings = settings
|
||||
load()
|
||||
}
|
||||
|
||||
|
||||
Mesh get(InfoHash infoHash) {
|
||||
meshes.get(infoHash)
|
||||
}
|
||||
|
||||
|
||||
Mesh getOrCreate(InfoHash infoHash, int nPieces) {
|
||||
synchronized(meshes) {
|
||||
if (meshes.containsKey(infoHash))
|
||||
@ -47,7 +47,7 @@ class MeshManager {
|
||||
return rv
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void onSourceDiscoveredEvent(SourceDiscoveredEvent e) {
|
||||
Mesh mesh = meshes.get(e.infoHash)
|
||||
if (mesh == null)
|
||||
@ -55,7 +55,7 @@ class MeshManager {
|
||||
mesh.sources.add(e.source)
|
||||
save()
|
||||
}
|
||||
|
||||
|
||||
private void save() {
|
||||
File meshFile = new File(home, "mesh.json")
|
||||
synchronized(meshes) {
|
||||
@ -72,29 +72,29 @@ class MeshManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void load() {
|
||||
File meshFile = new File(home, "mesh.json")
|
||||
if (!meshFile.exists())
|
||||
return
|
||||
long now = System.currentTimeMillis()
|
||||
JsonSlurper slurper = new JsonSlurper()
|
||||
meshFile.eachLine {
|
||||
meshFile.eachLine {
|
||||
def json = slurper.parseText(it)
|
||||
if (now - json.timestamp > settings.meshExpiration * 60 * 1000)
|
||||
return
|
||||
InfoHash infoHash = new InfoHash(Base64.decode(json.infoHash))
|
||||
Pieces pieces = new Pieces(json.nPieces, settings.downloadSequentialRatio)
|
||||
|
||||
|
||||
Mesh mesh = new Mesh(infoHash, pieces)
|
||||
json.sources.each { source ->
|
||||
json.sources.each { source ->
|
||||
Persona persona = new Persona(new ByteArrayInputStream(Base64.decode(source)))
|
||||
mesh.sources.add(persona)
|
||||
}
|
||||
|
||||
if (json.xHave != null)
|
||||
|
||||
if (json.xHave != null)
|
||||
DataUtil.decodeXHave(json.xHave).each { pieces.markDownloaded(it) }
|
||||
|
||||
|
||||
if (!mesh.sources.isEmpty())
|
||||
meshes.put(infoHash, mesh)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import net.i2p.data.Destination
|
||||
class DeleteEvent extends Event {
|
||||
byte [] infoHash
|
||||
Destination leaf
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||
|
@ -21,5 +21,5 @@ class InvalidSearchResultException extends Exception {
|
||||
super(cause);
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -6,33 +6,33 @@ import com.muwire.core.connection.UltrapeerConnectionManager
|
||||
import net.i2p.data.Destination
|
||||
|
||||
class LeafSearcher {
|
||||
|
||||
|
||||
final UltrapeerConnectionManager connectionManager
|
||||
final SearchIndex searchIndex = new SearchIndex()
|
||||
|
||||
|
||||
final Map<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
||||
final Map<byte[], Set<Destination>> hashToLeafs = new HashMap<>()
|
||||
|
||||
|
||||
final Map<Destination, Map<byte[], Set<String>>> leafToFiles = new HashMap<>()
|
||||
|
||||
|
||||
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager
|
||||
}
|
||||
|
||||
|
||||
void onUpsertEvent(UpsertEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
|
||||
void onDeleteEvent(DeleteEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
|
||||
void onDisconnectionEvent(DisconnectionEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import com.muwire.core.Persona
|
||||
import net.i2p.data.Destination
|
||||
|
||||
class QueryEvent extends Event {
|
||||
|
||||
|
||||
SearchEvent searchEvent
|
||||
boolean firstHop
|
||||
Destination replyTo
|
||||
|
@ -26,7 +26,7 @@ class ResultsParser {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static parseV1(Persona p, UUID uuid, def json) {
|
||||
if (json.name == null)
|
||||
throw new InvalidSearchResultException("name missing")
|
||||
@ -55,7 +55,7 @@ class ResultsParser {
|
||||
InfoHash parsedIH = InfoHash.fromHashList(hashList)
|
||||
if (parsedIH.getRoot() != infoHash)
|
||||
throw new InvalidSearchControlsException("infohash root doesn't match")
|
||||
|
||||
|
||||
return new UIResultEvent( sender : p,
|
||||
name : name,
|
||||
size : size,
|
||||
@ -67,7 +67,7 @@ class ResultsParser {
|
||||
throw new InvalidSearchResultException("parsing search result failed",e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static UIResultEvent parseV2(Persona p, UUID uuid, def json) {
|
||||
if (json.name == null)
|
||||
throw new InvalidSearchResultException("name missing")
|
||||
@ -86,11 +86,11 @@ class ResultsParser {
|
||||
if (infoHash.length != InfoHash.SIZE)
|
||||
throw new InvalidSearchResultException("invalid infohash size $infoHash.length")
|
||||
int pieceSize = json.pieceSize
|
||||
|
||||
|
||||
Set<Destination> sources = Collections.emptySet()
|
||||
if (json.sources != null)
|
||||
if (json.sources != null)
|
||||
sources = json.sources.stream().map({new Destination(it)}).collect(Collectors.toSet())
|
||||
|
||||
|
||||
return new UIResultEvent( sender : p,
|
||||
name : name,
|
||||
size : size,
|
||||
|
@ -25,9 +25,9 @@ import net.i2p.data.Destination
|
||||
|
||||
@Log
|
||||
class ResultsSender {
|
||||
|
||||
|
||||
private static final AtomicInteger THREAD_NO = new AtomicInteger()
|
||||
|
||||
|
||||
private final Executor executor = Executors.newCachedThreadPool(
|
||||
new ThreadFactory() {
|
||||
@Override
|
||||
@ -38,27 +38,27 @@ class ResultsSender {
|
||||
rv
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
private final I2PConnector connector
|
||||
private final Persona me
|
||||
private final EventBus eventBus
|
||||
|
||||
|
||||
ResultsSender(EventBus eventBus, I2PConnector connector, Persona me) {
|
||||
this.connector = connector;
|
||||
this.eventBus = eventBus
|
||||
this.me = me
|
||||
}
|
||||
|
||||
|
||||
void sendResults(UUID uuid, SharedFile[] results, Destination target, boolean oobInfohash) {
|
||||
log.info("Sending $results.length results for uuid $uuid to ${target.toBase32()} oobInfohash : $oobInfohash")
|
||||
if (target.equals(me.destination)) {
|
||||
results.each {
|
||||
results.each {
|
||||
long length = it.getFile().length()
|
||||
int pieceSize = it.getPieceSize()
|
||||
if (pieceSize == 0)
|
||||
pieceSize = FileHasher.getPieceSize(length)
|
||||
Set<Destination> suggested = Collections.emptySet()
|
||||
if (it instanceof DownloadedFile)
|
||||
if (it instanceof DownloadedFile)
|
||||
suggested = it.sources
|
||||
def uiResultEvent = new UIResultEvent( sender : me,
|
||||
name : it.getFile().getName(),
|
||||
@ -71,17 +71,17 @@ class ResultsSender {
|
||||
eventBus.publish(uiResultEvent)
|
||||
}
|
||||
} else {
|
||||
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
||||
executor.execute(new ResultSendJob(uuid : uuid, results : results,
|
||||
target: target, oobInfohash : oobInfohash))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class ResultSendJob implements Runnable {
|
||||
UUID uuid
|
||||
SharedFile [] results
|
||||
Destination target
|
||||
boolean oobInfohash
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
|
@ -9,7 +9,7 @@ class SearchEvent extends Event {
|
||||
byte [] searchHash
|
||||
UUID uuid
|
||||
boolean oobInfohash
|
||||
|
||||
|
||||
String toString() {
|
||||
def infoHash = null
|
||||
if (searchHash != null)
|
||||
|
@ -5,7 +5,7 @@ import com.muwire.core.Constants
|
||||
class SearchIndex {
|
||||
|
||||
final Map<String, Set<String>> keywords = new HashMap<>()
|
||||
|
||||
|
||||
void add(String string) {
|
||||
String [] split = split(string)
|
||||
split.each {
|
||||
@ -17,7 +17,7 @@ class SearchIndex {
|
||||
existing.add(string)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void remove(String string) {
|
||||
String [] split = split(string)
|
||||
split.each {
|
||||
@ -30,7 +30,7 @@ class SearchIndex {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static String[] split(String source) {
|
||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||
String [] split = source.split(" ")
|
||||
@ -38,10 +38,10 @@ class SearchIndex {
|
||||
split.each { if (it.length() > 0) rv << it }
|
||||
rv.toArray(new String[0])
|
||||
}
|
||||
|
||||
|
||||
String[] search(List<String> terms) {
|
||||
Set<String> rv = null;
|
||||
|
||||
|
||||
terms.each {
|
||||
Set<String> forWord = keywords.getOrDefault(it,[])
|
||||
if (rv == null) {
|
||||
@ -49,9 +49,9 @@ class SearchIndex {
|
||||
} else {
|
||||
rv.retainAll(forWord)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (rv != null)
|
||||
return rv.asList()
|
||||
[]
|
||||
|
@ -8,17 +8,17 @@ import net.i2p.data.Destination
|
||||
|
||||
@Log
|
||||
public class SearchManager {
|
||||
|
||||
|
||||
private static final int EXPIRE_TIME = 60 * 1000 * 1000
|
||||
private static final int CHECK_INTERVAL = 60 * 1000
|
||||
|
||||
|
||||
private final EventBus eventBus
|
||||
private final Persona me
|
||||
private final ResultsSender resultsSender
|
||||
private final Map<UUID, QueryEvent> responderAddress = Collections.synchronizedMap(new HashMap<>())
|
||||
|
||||
|
||||
SearchManager(){}
|
||||
|
||||
|
||||
SearchManager(EventBus eventBus, Persona me, ResultsSender resultsSender) {
|
||||
this.eventBus = eventBus
|
||||
this.me = me
|
||||
@ -26,7 +26,7 @@ public class SearchManager {
|
||||
Timer timer = new Timer("query-expirer", true)
|
||||
timer.schedule({cleanup()} as TimerTask, CHECK_INTERVAL, CHECK_INTERVAL)
|
||||
}
|
||||
|
||||
|
||||
void onQueryEvent(QueryEvent event) {
|
||||
if (responderAddress.containsKey(event.searchEvent.uuid)) {
|
||||
log.info("Dropping duplicate search uuid $event.searchEvent.uuid")
|
||||
@ -35,7 +35,7 @@ public class SearchManager {
|
||||
responderAddress.put(event.searchEvent.uuid, event)
|
||||
eventBus.publish(event.searchEvent)
|
||||
}
|
||||
|
||||
|
||||
void onResultsEvent(ResultsEvent event) {
|
||||
Destination target = responderAddress.get(event.uuid)?.replyTo
|
||||
if (target == null)
|
||||
@ -46,11 +46,11 @@ public class SearchManager {
|
||||
}
|
||||
resultsSender.sendResults(event.uuid, event.results, target, event.searchEvent.oobInfohash)
|
||||
}
|
||||
|
||||
|
||||
boolean hasLocalSearch(UUID uuid) {
|
||||
me.destination.equals(responderAddress.get(uuid)?.replyTo)
|
||||
}
|
||||
|
||||
|
||||
private void cleanup() {
|
||||
final long now = System.currentTimeMillis()
|
||||
synchronized(responderAddress) {
|
||||
|
@ -14,7 +14,7 @@ class UIResultEvent extends Event {
|
||||
long size
|
||||
InfoHash infohash
|
||||
int pieceSize
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
super.toString() + "name:$name size:$size sender:${sender.getHumanReadableName()} pieceSize $pieceSize"
|
||||
|
@ -18,5 +18,5 @@ class UnexpectedResultsException extends Exception {
|
||||
public UnexpectedResultsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ class UpsertEvent extends Event {
|
||||
Set<String> names
|
||||
byte [] infoHash
|
||||
Destination leaf
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||
|
@ -8,19 +8,19 @@ import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class RemoteTrustList {
|
||||
public enum Status { NEW, UPDATING, UPDATED, UPDATE_FAILED }
|
||||
|
||||
|
||||
private final Persona persona
|
||||
private final Set<Persona> good, bad
|
||||
volatile long timestamp
|
||||
volatile boolean forceUpdate
|
||||
Status status = Status.NEW
|
||||
|
||||
|
||||
RemoteTrustList(Persona persona) {
|
||||
this.persona = persona
|
||||
good = new ConcurrentHashSet<>()
|
||||
bad = new ConcurrentHashSet<>()
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof RemoteTrustList))
|
||||
|
@ -13,29 +13,29 @@ class TrustService extends Service {
|
||||
|
||||
final File persistGood, persistBad
|
||||
final long persistInterval
|
||||
|
||||
|
||||
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
||||
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
||||
|
||||
|
||||
final Timer timer
|
||||
|
||||
|
||||
TrustService() {}
|
||||
|
||||
|
||||
TrustService(File persistGood, File persistBad, long persistInterval) {
|
||||
this.persistBad = persistBad
|
||||
this.persistGood = persistGood
|
||||
this.persistInterval = persistInterval
|
||||
this.timer = new Timer("trust-persister",true)
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
|
||||
void load() {
|
||||
if (persistGood.exists()) {
|
||||
persistGood.eachLine {
|
||||
@ -54,7 +54,7 @@ class TrustService extends Service {
|
||||
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
||||
loaded = true
|
||||
}
|
||||
|
||||
|
||||
private void persist() {
|
||||
persistGood.delete()
|
||||
persistGood.withPrintWriter { writer ->
|
||||
@ -69,7 +69,7 @@ class TrustService extends Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TrustLevel getLevel(Destination dest) {
|
||||
if (good.containsKey(dest))
|
||||
return TrustLevel.TRUSTED
|
||||
@ -77,7 +77,7 @@ class TrustService extends Service {
|
||||
return TrustLevel.DISTRUSTED
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
|
||||
|
||||
void onTrustEvent(TrustEvent e) {
|
||||
switch(e.level) {
|
||||
case TrustLevel.TRUSTED:
|
||||
|
@ -22,32 +22,32 @@ class TrustSubscriber {
|
||||
private final EventBus eventBus
|
||||
private final I2PConnector i2pConnector
|
||||
private final MuWireSettings settings
|
||||
|
||||
|
||||
private final Map<Destination, RemoteTrustList> remoteTrustLists = new ConcurrentHashMap<>()
|
||||
|
||||
private final Object waitLock = new Object()
|
||||
private volatile boolean shutdown
|
||||
private volatile Thread thread
|
||||
private volatile Thread thread
|
||||
private final ExecutorService updateThreads = Executors.newCachedThreadPool()
|
||||
|
||||
|
||||
TrustSubscriber(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings) {
|
||||
this.eventBus = eventBus
|
||||
this.i2pConnector = i2pConnector
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
|
||||
void onUILoadedEvent(UILoadedEvent e) {
|
||||
thread = new Thread({checkLoop()} as Runnable, "trust-subscriber")
|
||||
thread.setDaemon(true)
|
||||
thread.start()
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
shutdown = true
|
||||
thread?.interrupt()
|
||||
updateThreads.shutdownNow()
|
||||
}
|
||||
|
||||
|
||||
void onTrustSubscriptionEvent(TrustSubscriptionEvent e) {
|
||||
if (!e.subscribe) {
|
||||
remoteTrustLists.remove(e.persona.destination)
|
||||
@ -59,7 +59,7 @@ class TrustSubscriber {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkLoop() {
|
||||
try {
|
||||
while(!shutdown) {
|
||||
@ -82,15 +82,15 @@ class TrustSubscriber {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private class UpdateJob implements Runnable {
|
||||
|
||||
|
||||
private final RemoteTrustList trustList
|
||||
|
||||
|
||||
UpdateJob(RemoteTrustList trustList) {
|
||||
this.trustList = trustList
|
||||
}
|
||||
|
||||
|
||||
public void run() {
|
||||
trustList.status = RemoteTrustList.Status.UPDATING
|
||||
eventBus.publish(new TrustSubscriptionUpdatedEvent(trustList : trustList))
|
||||
@ -111,44 +111,44 @@ class TrustSubscriber {
|
||||
InputStream is = endpoint.getInputStream()
|
||||
os.write("TRUST\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.flush()
|
||||
|
||||
|
||||
String codeString = DataUtil.readTillRN(is)
|
||||
int space = codeString.indexOf(' ')
|
||||
if (space > 0)
|
||||
codeString = codeString.substring(0,space)
|
||||
int code = Integer.parseInt(codeString.trim())
|
||||
|
||||
|
||||
if (code != 200) {
|
||||
log.info("couldn't fetch trust list, code $code")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// swallow any headers
|
||||
String header
|
||||
while (( header = DataUtil.readTillRN(is)) != "");
|
||||
|
||||
|
||||
DataInputStream dis = new DataInputStream(is)
|
||||
|
||||
|
||||
Set<Persona> good = new HashSet<>()
|
||||
int nGood = dis.readUnsignedShort()
|
||||
for (int i = 0; i < nGood; i++) {
|
||||
Persona p = new Persona(dis)
|
||||
good.add(p)
|
||||
}
|
||||
|
||||
|
||||
Set<Persona> bad = new HashSet<>()
|
||||
int nBad = dis.readUnsignedShort()
|
||||
for (int i = 0; i < nBad; i++) {
|
||||
Persona p = new Persona(dis)
|
||||
bad.add(p)
|
||||
}
|
||||
|
||||
|
||||
trustList.timestamp = now
|
||||
trustList.good.clear()
|
||||
trustList.good.addAll(good)
|
||||
trustList.bad.clear()
|
||||
trustList.bad.addAll(bad)
|
||||
|
||||
|
||||
return true
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING,"exception fetching trust list from ${trustList.persona.getHumanReadableName()}",e)
|
||||
@ -156,6 +156,6 @@ class TrustSubscriber {
|
||||
} finally {
|
||||
endpoint?.close()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -32,15 +32,15 @@ class UpdateClient {
|
||||
final MuWireSettings settings
|
||||
final FileManager fileManager
|
||||
final Persona me
|
||||
|
||||
|
||||
private final Timer timer
|
||||
|
||||
|
||||
private long lastUpdateCheckTime
|
||||
|
||||
|
||||
private volatile InfoHash updateInfoHash
|
||||
private volatile String version, signer
|
||||
private volatile boolean updateDownloading
|
||||
|
||||
|
||||
UpdateClient(EventBus eventBus, I2PSession session, String myVersion, MuWireSettings settings, FileManager fileManager, Persona me) {
|
||||
this.eventBus = eventBus
|
||||
this.session = session
|
||||
@ -50,16 +50,16 @@ class UpdateClient {
|
||||
this.me = me
|
||||
timer = new Timer("update-client",true)
|
||||
}
|
||||
|
||||
|
||||
void start() {
|
||||
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 2)
|
||||
timer.schedule({checkUpdate()} as TimerTask, 60000, 60 * 60 * 1000)
|
||||
}
|
||||
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
|
||||
void onUIResultBatchEvent(UIResultBatchEvent results) {
|
||||
if (results.results[0].infohash != updateInfoHash)
|
||||
return
|
||||
@ -70,14 +70,14 @@ class UpdateClient {
|
||||
def downloadEvent = new UIDownloadEvent(result: results.results[0], sources : results.results[0].sources, target : file)
|
||||
eventBus.publish(downloadEvent)
|
||||
}
|
||||
|
||||
|
||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||
if (e.downloadedFile.infoHash != updateInfoHash)
|
||||
return
|
||||
updateDownloading = false
|
||||
eventBus.publish(new UpdateDownloadedEvent(version : version, signer : signer))
|
||||
}
|
||||
|
||||
|
||||
private void checkUpdate() {
|
||||
final long now = System.currentTimeMillis()
|
||||
if (lastUpdateCheckTime > 0) {
|
||||
@ -85,20 +85,20 @@ class UpdateClient {
|
||||
return
|
||||
}
|
||||
lastUpdateCheckTime = now
|
||||
|
||||
|
||||
log.info("checking for update")
|
||||
|
||||
|
||||
def ping = [version : 1, myVersion : myVersion]
|
||||
ping = JsonOutput.toJson(ping)
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
ping = maker.makeI2PDatagram(ping.bytes)
|
||||
def options = new SendMessageOptions()
|
||||
def options = new SendMessageOptions()
|
||||
options.setSendLeaseSet(true)
|
||||
session.sendMessage(UpdateServers.UPDATE_SERVER, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 2, 0, options)
|
||||
session.sendMessage(UpdateServers.UPDATE_SERVER, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 2, 0, options)
|
||||
}
|
||||
|
||||
|
||||
class Listener implements I2PSessionMuxedListener {
|
||||
|
||||
|
||||
final JsonSlurper slurper = new JsonSlurper()
|
||||
|
||||
@Override
|
||||
@ -111,7 +111,7 @@ class UpdateClient {
|
||||
log.warning "Received unexpected protocol $proto"
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
def payload = session.receiveMessage(msgId)
|
||||
def dissector = new I2PDatagramDissector()
|
||||
try {
|
||||
@ -121,33 +121,33 @@ class UpdateClient {
|
||||
log.warning("received something not from update server " + sender.toBase32())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
log.info("Received something from update server")
|
||||
|
||||
|
||||
payload = dissector.getPayload()
|
||||
payload = slurper.parse(payload)
|
||||
|
||||
|
||||
if (payload.version == null) {
|
||||
log.warning("version missing")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (payload.signer == null) {
|
||||
log.warning("signer missing")
|
||||
}
|
||||
|
||||
|
||||
if (VersionComparator.comp(myVersion, payload.version) >= 0) {
|
||||
log.info("no new version available")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
String infoHash
|
||||
if (settings.updateType == "jar") {
|
||||
infoHash = payload.infoHash
|
||||
} else
|
||||
infoHash = payload[settings.updateType]
|
||||
|
||||
|
||||
|
||||
|
||||
if (!settings.autoDownloadUpdate) {
|
||||
log.info("new version $payload.version available, publishing event")
|
||||
eventBus.publish(new UpdateAvailableEvent(version : payload.version, signer : payload.signer, infoHash : infoHash))
|
||||
@ -167,7 +167,7 @@ class UpdateClient {
|
||||
eventBus.publish(queryEvent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING,"Invalid datagram",e)
|
||||
}
|
||||
@ -186,6 +186,6 @@ class UpdateClient {
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
log.log(Level.SEVERE, message, error)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -15,12 +15,12 @@ import com.muwire.core.util.DataUtil
|
||||
import net.i2p.data.Destination
|
||||
|
||||
class ContentUploader extends Uploader {
|
||||
|
||||
|
||||
private final File file
|
||||
private final ContentRequest request
|
||||
private final Mesh mesh
|
||||
private final int pieceSize
|
||||
|
||||
|
||||
ContentUploader(File file, ContentRequest request, Endpoint endpoint, Mesh mesh, int pieceSize) {
|
||||
super(endpoint)
|
||||
this.file = file
|
||||
@ -28,7 +28,7 @@ class ContentUploader extends Uploader {
|
||||
this.mesh = mesh
|
||||
this.pieceSize = pieceSize
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void respond() {
|
||||
OutputStream os = endpoint.getOutputStream()
|
||||
@ -55,7 +55,7 @@ class ContentUploader extends Uploader {
|
||||
os.write("Content-Range: $range.start-$range.end\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
writeMesh(request.downloader)
|
||||
os.write("\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
|
||||
|
||||
FileChannel channel = null
|
||||
try {
|
||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ))
|
||||
@ -78,11 +78,11 @@ class ContentUploader extends Uploader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void writeMesh(Persona toExclude) {
|
||||
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
||||
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
|
||||
|
||||
Set<Persona> sources = mesh.getRandom(3, toExclude)
|
||||
if (!sources.isEmpty()) {
|
||||
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
||||
|
@ -11,19 +11,19 @@ import net.i2p.data.Base64
|
||||
class HashListUploader extends Uploader {
|
||||
private final InfoHash infoHash
|
||||
private final HashListRequest request
|
||||
|
||||
|
||||
HashListUploader(Endpoint endpoint, InfoHash infoHash, HashListRequest request) {
|
||||
super(endpoint)
|
||||
this.infoHash = infoHash
|
||||
mapped = ByteBuffer.wrap(infoHash.getHashList())
|
||||
this.request = request
|
||||
}
|
||||
|
||||
|
||||
void respond() {
|
||||
OutputStream os = endpoint.getOutputStream()
|
||||
os.write("200 OK\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
os.write("Content-Range: 0-${mapped.remaining()}\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
|
||||
|
||||
byte[]tmp = new byte[0x1 << 13]
|
||||
while(mapped.hasRemaining()) {
|
||||
int start = mapped.position()
|
||||
@ -60,7 +60,7 @@ class HashListUploader extends Uploader {
|
||||
public int getTotalPieces() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getTotalSize() {
|
||||
return -1;
|
||||
|
@ -2,7 +2,7 @@ package com.muwire.core.upload
|
||||
|
||||
class Range {
|
||||
final long start, end
|
||||
|
||||
|
||||
Range(long start, long end) {
|
||||
this.start = start
|
||||
this.end = end
|
||||
|
@ -12,21 +12,21 @@ import net.i2p.data.Base64
|
||||
|
||||
@Log
|
||||
class Request {
|
||||
|
||||
|
||||
private static final byte R = "\r".getBytes(StandardCharsets.US_ASCII)[0]
|
||||
private static final byte N = "\n".getBytes(StandardCharsets.US_ASCII)[0]
|
||||
|
||||
|
||||
InfoHash infoHash
|
||||
Persona downloader
|
||||
Map<String, String> headers
|
||||
|
||||
|
||||
static Request parseContentRequest(InfoHash infoHash, InputStream is) throws IOException {
|
||||
|
||||
|
||||
Map<String, String> headers = parseHeaders(is)
|
||||
|
||||
|
||||
if (!headers.containsKey("Range"))
|
||||
throw new IOException("Range header not found")
|
||||
|
||||
|
||||
String range = headers.get("Range").trim()
|
||||
String[] split = range.split("-")
|
||||
if (split.length != 2)
|
||||
@ -39,26 +39,26 @@ class Request {
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException(nfe)
|
||||
}
|
||||
|
||||
|
||||
if (start < 0 || end < start)
|
||||
throw new IOException("Invalid range $start - $end")
|
||||
|
||||
|
||||
Persona downloader = null
|
||||
if (headers.containsKey("X-Persona")) {
|
||||
def encoded = headers["X-Persona"].trim()
|
||||
def decoded = Base64.decode(encoded)
|
||||
downloader = new Persona(new ByteArrayInputStream(decoded))
|
||||
}
|
||||
|
||||
|
||||
int have = 0
|
||||
if (headers.containsKey("X-Have")) {
|
||||
def encoded = headers["X-Have"].trim()
|
||||
have = DataUtil.decodeXHave(encoded).size()
|
||||
}
|
||||
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
||||
new ContentRequest( infoHash : infoHash, range : new Range(start, end),
|
||||
headers : headers, downloader : downloader, have : have)
|
||||
}
|
||||
|
||||
|
||||
static Request parseHashListRequest(InfoHash infoHash, InputStream is) throws IOException {
|
||||
Map<String,String> headers = parseHeaders(is)
|
||||
Persona downloader = null
|
||||
@ -69,7 +69,7 @@ class Request {
|
||||
}
|
||||
new HashListRequest(infoHash : infoHash, headers : headers, downloader : downloader)
|
||||
}
|
||||
|
||||
|
||||
private static Map<String, String> parseHeaders(InputStream is) {
|
||||
Map<String,String> headers = new HashMap<>()
|
||||
byte [] tmp = new byte[Constants.MAX_HEADER_SIZE]
|
||||
@ -81,7 +81,7 @@ class Request {
|
||||
byte read = is.read()
|
||||
if (read == -1)
|
||||
throw new IOException("Stream closed")
|
||||
|
||||
|
||||
if (!r && read == N)
|
||||
throw new IOException("Received N before R")
|
||||
if (read == R) {
|
||||
@ -90,7 +90,7 @@ class Request {
|
||||
r = true
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if (r && !n) {
|
||||
if (read != N)
|
||||
throw new IOException("R not followed by N")
|
||||
@ -101,13 +101,13 @@ class Request {
|
||||
throw new IOException("Header too long")
|
||||
tmp[idx++] = read
|
||||
}
|
||||
|
||||
|
||||
if (idx == 0)
|
||||
break
|
||||
|
||||
|
||||
String header = new String(tmp, 0, idx, StandardCharsets.US_ASCII)
|
||||
log.fine("Read header $header")
|
||||
|
||||
|
||||
int keyIdx = header.indexOf(":")
|
||||
if (keyIdx < 1)
|
||||
throw new IOException("Header key not found")
|
||||
@ -119,5 +119,5 @@ class Request {
|
||||
}
|
||||
headers
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -22,17 +22,17 @@ public class UploadManager {
|
||||
private final FileManager fileManager
|
||||
private final MeshManager meshManager
|
||||
private final DownloadManager downloadManager
|
||||
|
||||
|
||||
public UploadManager() {}
|
||||
|
||||
public UploadManager(EventBus eventBus, FileManager fileManager,
|
||||
|
||||
public UploadManager(EventBus eventBus, FileManager fileManager,
|
||||
MeshManager meshManager, DownloadManager downloadManager) {
|
||||
this.eventBus = eventBus
|
||||
this.fileManager = fileManager
|
||||
this.meshManager = meshManager
|
||||
this.downloadManager = downloadManager
|
||||
}
|
||||
|
||||
|
||||
public void processGET(Endpoint e) throws IOException {
|
||||
byte [] infoHashStringBytes = new byte[44]
|
||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
@ -79,10 +79,10 @@ public class UploadManager {
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
if (request.have > 0)
|
||||
|
||||
if (request.have > 0)
|
||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||
|
||||
|
||||
Mesh mesh
|
||||
File file
|
||||
int pieceSize
|
||||
@ -96,7 +96,7 @@ public class UploadManager {
|
||||
file = sharedFile.file
|
||||
pieceSize = sharedFile.pieceSize
|
||||
}
|
||||
|
||||
|
||||
Uploader uploader = new ContentUploader(file, request, e, mesh, pieceSize)
|
||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||
try {
|
||||
@ -106,14 +106,14 @@ public class UploadManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void processHashList(Endpoint e) {
|
||||
byte [] infoHashStringBytes = new byte[44]
|
||||
DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
dis.readFully(infoHashStringBytes)
|
||||
String infoHashString = new String(infoHashStringBytes, StandardCharsets.US_ASCII)
|
||||
log.info("Responding to hashlist request for root $infoHashString")
|
||||
|
||||
|
||||
byte [] infoHashRoot = Base64.decode(infoHashString)
|
||||
InfoHash infoHash = new InfoHash(infoHashRoot)
|
||||
Downloader downloader = downloadManager.downloaders.get(infoHash)
|
||||
@ -125,7 +125,7 @@ public class UploadManager {
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
byte [] rn = new byte[2]
|
||||
dis.readFully(rn)
|
||||
if (rn != "\r\n".getBytes(StandardCharsets.US_ASCII)) {
|
||||
@ -133,14 +133,14 @@ public class UploadManager {
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Request request = Request.parseHashListRequest(infoHash, e.getInputStream())
|
||||
if (request.downloader != null && request.downloader.destination != e.destination) {
|
||||
log.info("Downloader persona doesn't match their destination")
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
InfoHash fullInfoHash
|
||||
if (downloader == null) {
|
||||
fullInfoHash = sharedFiles.iterator().next().infoHash
|
||||
@ -156,7 +156,7 @@ public class UploadManager {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
|
||||
eventBus.publish(new UploadEvent(uploader : uploader))
|
||||
try {
|
||||
@ -164,7 +164,7 @@ public class UploadManager {
|
||||
} finally {
|
||||
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
|
||||
}
|
||||
|
||||
|
||||
// proceed with content
|
||||
while(true) {
|
||||
byte[] get = new byte[4]
|
||||
@ -204,10 +204,10 @@ public class UploadManager {
|
||||
e.close()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (request.have > 0)
|
||||
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
|
||||
|
||||
|
||||
Mesh mesh
|
||||
File file
|
||||
int pieceSize
|
||||
|
@ -11,31 +11,31 @@ import com.muwire.core.connection.Endpoint
|
||||
abstract class Uploader {
|
||||
protected final Endpoint endpoint
|
||||
protected ByteBuffer mapped
|
||||
|
||||
|
||||
Uploader(Endpoint endpoint) {
|
||||
this.endpoint = endpoint
|
||||
}
|
||||
|
||||
|
||||
abstract void respond()
|
||||
|
||||
|
||||
public synchronized int getPosition() {
|
||||
if (mapped == null)
|
||||
return -1
|
||||
mapped.position()
|
||||
}
|
||||
|
||||
|
||||
abstract String getName();
|
||||
|
||||
|
||||
/**
|
||||
* @return an integer between 0 and 100
|
||||
*/
|
||||
abstract int getProgress();
|
||||
|
||||
|
||||
abstract String getDownloader();
|
||||
|
||||
|
||||
abstract int getDonePieces();
|
||||
|
||||
|
||||
abstract int getTotalPieces();
|
||||
|
||||
|
||||
abstract long getTotalSize();
|
||||
}
|
||||
|
@ -10,42 +10,42 @@ import com.muwire.core.Constants
|
||||
import net.i2p.data.Base64
|
||||
|
||||
class DataUtil {
|
||||
|
||||
|
||||
private final static int MAX_SHORT = (0x1 << 16) - 1
|
||||
|
||||
static void writeUnsignedShort(int value, OutputStream os) {
|
||||
if (value > MAX_SHORT || value < 0)
|
||||
throw new IllegalArgumentException("$value invalid")
|
||||
|
||||
|
||||
byte lsb = (byte) (value & 0xFF)
|
||||
byte msb = (byte) (value >> 8)
|
||||
|
||||
|
||||
os.write(msb)
|
||||
os.write(lsb)
|
||||
os.write(lsb)
|
||||
}
|
||||
|
||||
|
||||
private final static int MAX_HEADER = 0x7FFFFF
|
||||
|
||||
|
||||
static void packHeader(int length, byte [] header) {
|
||||
if (header.length != 3)
|
||||
throw new IllegalArgumentException("header length $header.length")
|
||||
if (length < 0 || length > MAX_HEADER)
|
||||
throw new IllegalArgumentException("length $length")
|
||||
|
||||
|
||||
header[2] = (byte) (length & 0xFF)
|
||||
header[1] = (byte) ((length >> 8) & 0xFF)
|
||||
header[0] = (byte) ((length >> 16) & 0x7F)
|
||||
}
|
||||
|
||||
|
||||
static int readLength(byte [] header) {
|
||||
if (header.length != 3)
|
||||
throw new IllegalArgumentException("header length $header.length")
|
||||
|
||||
|
||||
return (((int)(header[0] & 0x7F)) << 16) |
|
||||
(((int)(header[1] & 0xFF) << 8)) |
|
||||
((int)header[2] & 0xFF)
|
||||
}
|
||||
|
||||
|
||||
static String readi18nString(byte [] encoded) {
|
||||
if (encoded.length < 2)
|
||||
throw new IllegalArgumentException("encoding too short $encoded.length")
|
||||
@ -54,9 +54,9 @@ class DataUtil {
|
||||
throw new IllegalArgumentException("encoding doesn't match length, expected $length found $encoded.length")
|
||||
byte [] string = new byte[length]
|
||||
System.arraycopy(encoded, 2, string, 0, length)
|
||||
new String(string, StandardCharsets.UTF_8)
|
||||
new String(string, StandardCharsets.UTF_8)
|
||||
}
|
||||
|
||||
|
||||
static byte[] encodei18nString(String string) {
|
||||
byte [] utf8 = string.getBytes(StandardCharsets.UTF_8)
|
||||
if (utf8.length > Short.MAX_VALUE)
|
||||
@ -64,11 +64,11 @@ class DataUtil {
|
||||
def baos = new ByteArrayOutputStream()
|
||||
def daos = new DataOutputStream(baos)
|
||||
daos.writeShort((short) utf8.length)
|
||||
daos.write(utf8)
|
||||
daos.write(utf8)
|
||||
daos.close()
|
||||
baos.toByteArray()
|
||||
}
|
||||
|
||||
|
||||
public static String readTillRN(InputStream is) {
|
||||
def baos = new ByteArrayOutputStream()
|
||||
while(baos.size() < (Constants.MAX_HEADER_SIZE)) {
|
||||
@ -84,7 +84,7 @@ class DataUtil {
|
||||
}
|
||||
new String(baos.toByteArray(), StandardCharsets.US_ASCII)
|
||||
}
|
||||
|
||||
|
||||
public static String encodeXHave(List<Integer> pieces, int totalPieces) {
|
||||
int bytes = totalPieces / 8
|
||||
if (totalPieces % 8 != 0)
|
||||
@ -98,7 +98,7 @@ class DataUtil {
|
||||
}
|
||||
Base64.encode(raw)
|
||||
}
|
||||
|
||||
|
||||
public static List<Integer> decodeXHave(String xHave) {
|
||||
byte [] availablePieces = Base64.decode(xHave)
|
||||
List<Integer> available = new ArrayList<>()
|
||||
@ -112,19 +112,19 @@ class DataUtil {
|
||||
}
|
||||
available
|
||||
}
|
||||
|
||||
|
||||
public static Exception findRoot(Exception e) {
|
||||
while(e.getCause() != null)
|
||||
e = e.getCause()
|
||||
e
|
||||
}
|
||||
|
||||
|
||||
public static void tryUnmap(ByteBuffer cb) {
|
||||
if (cb==null || !cb.isDirect()) return;
|
||||
// we could use this type cast and call functions without reflection code,
|
||||
// but static import from sun.* package is risky for non-SUN virtual machine.
|
||||
//try { ((sun.nio.ch.DirectBuffer)cb).cleaner().clean(); } catch (Exception ex) { }
|
||||
|
||||
|
||||
// JavaSpecVer: 1.6, 1.7, 1.8, 9, 10
|
||||
boolean isOldJDK = System.getProperty("java.specification.version","99").startsWith("1.");
|
||||
try {
|
||||
|
@ -16,10 +16,10 @@ class JULLog extends Log {
|
||||
I2P_TO_JUL.put(Log.ERROR, Level.SEVERE)
|
||||
I2P_TO_JUL.put(Log.CRIT, Level.SEVERE)
|
||||
}
|
||||
|
||||
|
||||
private final Logger delegate
|
||||
private final Level level
|
||||
|
||||
|
||||
public JULLog(Class<?> cls) {
|
||||
super(cls)
|
||||
delegate = Logger.getLogger(cls.getName())
|
||||
@ -31,7 +31,7 @@ class JULLog extends Log {
|
||||
delegate = Logger.getLogger(name)
|
||||
level = findLevel(delegate)
|
||||
}
|
||||
|
||||
|
||||
private static Level findLevel(Logger log) {
|
||||
while (log.getLevel() == null)
|
||||
log = log.getParent()
|
||||
|
@ -5,14 +5,14 @@ import net.i2p.util.Log
|
||||
import net.i2p.util.LogManager
|
||||
|
||||
class MuWireLogManager extends LogManager {
|
||||
|
||||
|
||||
private static final Map<Class<?>, Log> classLogs = new HashMap<>()
|
||||
private static final Map<String, Log> stringLogs = new HashMap<>()
|
||||
|
||||
|
||||
MuWireLogManager() {
|
||||
super(I2PAppContext.getGlobalContext())
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized Log getLog(Class<?> cls, String name) {
|
||||
@ -24,7 +24,7 @@ class MuWireLogManager extends LogManager {
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
|
||||
Log rv = stringLogs.get(name)
|
||||
if (rv == null) {
|
||||
rv = new JULLog(name)
|
||||
@ -32,5 +32,5 @@ class MuWireLogManager extends LogManager {
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -7,15 +7,15 @@ import java.util.Set;
|
||||
import net.i2p.data.Destination;
|
||||
|
||||
public class DownloadedFile extends SharedFile {
|
||||
|
||||
|
||||
private final Set<Destination> sources;
|
||||
|
||||
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
||||
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
||||
throws IOException {
|
||||
super(file, infoHash, pieceSize);
|
||||
this.sources = sources;
|
||||
}
|
||||
|
||||
|
||||
public Set<Destination> getSources() {
|
||||
return sources;
|
||||
}
|
||||
|
@ -12,16 +12,16 @@ import net.i2p.data.Base64;
|
||||
public class InfoHash {
|
||||
|
||||
public static final int SIZE = 0x1 << 5;
|
||||
|
||||
|
||||
private final byte[] root;
|
||||
private final byte[] hashList;
|
||||
|
||||
|
||||
private final int hashCode;
|
||||
|
||||
|
||||
public InfoHash(byte[] root, byte[] hashList) {
|
||||
if (root.length != SIZE)
|
||||
throw new IllegalArgumentException("invalid root size "+root.length);
|
||||
if (hashList != null && hashList.length % SIZE != 0)
|
||||
if (hashList != null && hashList.length % SIZE != 0)
|
||||
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
||||
this.root = root;
|
||||
this.hashList = hashList;
|
||||
@ -30,15 +30,15 @@ public class InfoHash {
|
||||
root[2] << 8 |
|
||||
root[3];
|
||||
}
|
||||
|
||||
|
||||
public InfoHash(byte[] root) {
|
||||
this(root, null);
|
||||
}
|
||||
|
||||
|
||||
public InfoHash(String base32) {
|
||||
this(Base32.decode(base32));
|
||||
}
|
||||
|
||||
|
||||
public static InfoHash fromHashList(byte []hashList) {
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
@ -50,20 +50,20 @@ public class InfoHash {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
public byte[] getHashList() {
|
||||
return hashList;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
@ -75,11 +75,11 @@ public class InfoHash {
|
||||
InfoHash other = (InfoHash) o;
|
||||
return Arrays.equals(root, other.root);
|
||||
}
|
||||
|
||||
|
||||
public String toString() {
|
||||
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
||||
List<String> b64HashList = new ArrayList<>();
|
||||
if (hashList != null) {
|
||||
if (hashList != null) {
|
||||
byte [] tmp = new byte[SIZE];
|
||||
for (int i = 0; i < hashList.length / SIZE; i++) {
|
||||
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
||||
|
@ -8,10 +8,10 @@ public class SharedFile {
|
||||
private final File file;
|
||||
private final InfoHash infoHash;
|
||||
private final int pieceSize;
|
||||
|
||||
|
||||
private final String cachedPath;
|
||||
private final long cachedLength;
|
||||
|
||||
|
||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||
this.file = file;
|
||||
this.infoHash = infoHash;
|
||||
@ -27,11 +27,11 @@ public class SharedFile {
|
||||
public InfoHash getInfoHash() {
|
||||
return infoHash;
|
||||
}
|
||||
|
||||
|
||||
public int getPieceSize() {
|
||||
return pieceSize;
|
||||
}
|
||||
|
||||
|
||||
public int getNPieces() {
|
||||
long length = file.length();
|
||||
int rawPieceSize = 0x1 << pieceSize;
|
||||
@ -40,20 +40,20 @@ public class SharedFile {
|
||||
rv++;
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
public String getCachedPath() {
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
|
||||
public long getCachedLength() {
|
||||
return cachedLength;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return file.hashCode() ^ infoHash.hashCode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SharedFile))
|
||||
|
@ -5,18 +5,18 @@ import org.junit.Test
|
||||
class EventBusTest {
|
||||
|
||||
class FakeEvent extends Event {}
|
||||
|
||||
|
||||
class FakeEventHandler {
|
||||
def onFakeEvent(FakeEvent e) {
|
||||
assert e == fakeEvent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FakeEvent fakeEvent = new FakeEvent()
|
||||
|
||||
|
||||
EventBus bus = new EventBus()
|
||||
def handler = new FakeEventHandler()
|
||||
|
||||
|
||||
@Test
|
||||
void testDynamicEvent() {
|
||||
bus.register(FakeEvent.class, handler)
|
||||
|
@ -5,7 +5,7 @@ import net.i2p.data.Base64
|
||||
class Personas {
|
||||
private final String encoded1 = "AQADemFiO~pgSoEo8wQfwncYMvBQWkvPY9I7DYUllHp289UE~zBaLdbl~wbliktAUsW-S70f3UeYgHq34~c7zVuUQjgHZ506iG9hX8B9S3a9gQ3CSG0GuDpeNyiXmZkpHp5m8vT9PZ1zMWzxvzZY~fP9yKFKgO4yrso5I9~DGOPeyJZJ4BFsTJDERv41aZqjFLYUBDmeHGgg9RjYy~93h-nQMVYj9JSO3AgowW-ix49rtiKYIXHMa2PxWHUXkUHWJZtIZntNIDEFeMnPdzLxjAl8so2G6pDcTMZPLLwyb73Ee5ZVfxUynPqyp~fIGVP8Rl4rlaGFli2~ATGBz3XY54aObC~0p7us2JnWaTC~oQT5DVDM7gaOO885o-m8BB8b0duzMBelbdnMZFQJ5jIHVKxkC6Niw4fxTOoXTyOqQmVhtK-9xcwxMuN5DF9IewkR5bhpq5rgnfBP5zvyBaAHMq-d3TCOjTsZ-d3liB98xX5p8G5zmS7gfKArQtM5~CcK~AlX-lGLBQAEAAcAAN5MW1Tq983szfZgY1l8tQFqy8I9tdMf7vc1Ktj~TCIvXYw6AYMbMGy3S67FSPLZVmfHEMQKj2KLAdaRKQkHPAY"
|
||||
private final String encoded2 = "AQAHemxhdGluYiN~3G-hPoBfJ04mhcC52lC6TYSwWxH-WNWno9Y35JS-WrXlnPsodZtwy96ttEaiKTg-hkRqMsaYKpWar1FwayR6qlo0pZCo5pQOLfR7GIM3~wde0JIBEp8BUpgzF1-QXLhuRG1t7tBbenW2tSgp5jQH61RI-c9flyUlOvf6nrhQMZ3aoviZ4aZW23Fx-ajYQBDk7PIxuyn8qYNwWy3kWOhGan05c54NnumS3XCzQWFDDPlADmco1WROeY9qrwwtmLM8lzDCEtJQXJlk~K5yLbyB63hmAeTK7J4iS6f9nnWv7TbB5r-Z3kC6D9TLYrQbu3h4AAxrqso45P8yHQtKUA4QJicS-6NJoBOnlCCU887wx2k9YSxxwNydlIxb1mZsX65Ke4uY0HDFokZHTzUcxvfLB6G~5JkSPDCyZz~2fREgW2-VXu7gokEdEugkuZRrsiQzyfAOOkv53ti5MzTbMOXinBskSb1vZyN2-XcZNaDJvEqUNj~qpfhe-ov2F7FuwQUABAAHAAAfqq-MneIqWBQY92-sy9Z0s~iQsq6lUFa~sYMdY-5o-94fF8a140dm-emF3rO8vuidUIPNaS-37Rl05mAKUCcB"
|
||||
|
||||
|
||||
Persona persona1 = new Persona(new ByteArrayInputStream(Base64.decode(encoded1)))
|
||||
Persona persona2 = new Persona(new ByteArrayInputStream(Base64.decode(encoded2)))
|
||||
}
|
||||
|
@ -25,33 +25,33 @@ class ConnectionAcceptorTest {
|
||||
EventBus eventBus
|
||||
final Destinations destinations = new Destinations()
|
||||
def settings
|
||||
|
||||
|
||||
def connectionManagerMock
|
||||
UltrapeerConnectionManager connectionManager
|
||||
|
||||
|
||||
def i2pAcceptorMock
|
||||
I2PAcceptor i2pAcceptor
|
||||
|
||||
|
||||
def hostCacheMock
|
||||
HostCache hostCache
|
||||
|
||||
|
||||
def trustServiceMock
|
||||
TrustService trustService
|
||||
|
||||
|
||||
def searchManagerMock
|
||||
SearchManager searchManager
|
||||
|
||||
|
||||
def uploadManagerMock
|
||||
UploadManager uploadManager
|
||||
|
||||
|
||||
def connectionEstablisherMock
|
||||
ConnectionEstablisher connectionEstablisher
|
||||
|
||||
|
||||
ConnectionAcceptor acceptor
|
||||
List<ConnectionEvent> connectionEvents
|
||||
InputStream inputStream
|
||||
OutputStream outputStream
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
||||
@ -62,7 +62,7 @@ class ConnectionAcceptorTest {
|
||||
uploadManagerMock = new MockFor(UploadManager.class)
|
||||
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
void after() {
|
||||
acceptor?.stop()
|
||||
@ -75,7 +75,7 @@ class ConnectionAcceptorTest {
|
||||
connectionEstablisherMock.verify connectionEstablisher
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
|
||||
private void initMocks() {
|
||||
connectionEvents = new CopyOnWriteArrayList()
|
||||
eventBus = new EventBus()
|
||||
@ -85,7 +85,7 @@ class ConnectionAcceptorTest {
|
||||
}
|
||||
}
|
||||
eventBus.register(ConnectionEvent.class, listener)
|
||||
|
||||
|
||||
connectionManager = connectionManagerMock.proxyInstance()
|
||||
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
||||
hostCache = hostCacheMock.proxyInstance()
|
||||
@ -93,13 +93,13 @@ class ConnectionAcceptorTest {
|
||||
searchManager = searchManagerMock.proxyInstance()
|
||||
uploadManager = uploadManagerMock.proxyInstance()
|
||||
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
||||
|
||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||
|
||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||
acceptor.start()
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSuccessfulLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -125,15 +125,15 @@ class ConnectionAcceptorTest {
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
outputStream.write("MuWire leaf".bytes)
|
||||
byte [] OK = new byte[2]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "OK".bytes
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
@ -142,7 +142,7 @@ class ConnectionAcceptorTest {
|
||||
assert event.incoming == true
|
||||
assert event.leaf == true
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSuccessfulPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -168,15 +168,15 @@ class ConnectionAcceptorTest {
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
byte [] OK = new byte[2]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "OK".bytes
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
@ -185,7 +185,7 @@ class ConnectionAcceptorTest {
|
||||
assert event.incoming == true
|
||||
assert event.leaf == false
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testLeafRejectsLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -205,14 +205,14 @@ class ConnectionAcceptorTest {
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
outputStream.write("MuWire leaf".bytes)
|
||||
outputStream.flush()
|
||||
Thread.sleep(50)
|
||||
assert inputStream.read() == -1
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
@ -221,7 +221,7 @@ class ConnectionAcceptorTest {
|
||||
assert event.incoming == true
|
||||
assert event.leaf == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testLeafRejectsPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -241,14 +241,14 @@ class ConnectionAcceptorTest {
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
outputStream.flush()
|
||||
Thread.sleep(50)
|
||||
assert inputStream.read() == -1
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
@ -257,7 +257,7 @@ class ConnectionAcceptorTest {
|
||||
assert event.incoming == true
|
||||
assert event.leaf == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPeerRejectsPeerSlots() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -284,18 +284,18 @@ class ConnectionAcceptorTest {
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
byte [] OK = new byte[6]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "REJECT".bytes
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert dis.read() == -1
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
@ -304,7 +304,7 @@ class ConnectionAcceptorTest {
|
||||
assert event.incoming == true
|
||||
assert event.leaf == false
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPeerRejectsLeafSlots() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -331,18 +331,18 @@ class ConnectionAcceptorTest {
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
outputStream.write("MuWire leaf".bytes)
|
||||
byte [] OK = new byte[6]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "REJECT".bytes
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert dis.read() == -1
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
@ -351,7 +351,7 @@ class ConnectionAcceptorTest {
|
||||
assert event.incoming == true
|
||||
assert event.leaf == true
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPeerRejectsPeerSuggests() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -378,26 +378,26 @@ class ConnectionAcceptorTest {
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
byte [] OK = new byte[6]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "REJECT".bytes
|
||||
|
||||
|
||||
short payloadSize = dis.readUnsignedShort()
|
||||
byte[] payload = new byte[payloadSize]
|
||||
dis.readFully(payload)
|
||||
assert dis.read() == -1
|
||||
|
||||
|
||||
def json = new JsonSlurper()
|
||||
json = json.parse(payload)
|
||||
assert json.tryHosts != null
|
||||
assert json.tryHosts.size() == 1
|
||||
assert json.tryHosts.contains(destinations.dest2.toBase64())
|
||||
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
|
@ -23,21 +23,21 @@ class ConnectionEstablisherTest {
|
||||
List<HostDiscoveredEvent> discoveredEvents
|
||||
DataInputStream inputStream
|
||||
DataOutputStream outputStream
|
||||
|
||||
|
||||
def i2pConnectorMock
|
||||
I2PConnector i2pConnector
|
||||
|
||||
|
||||
MuWireSettings settings
|
||||
|
||||
|
||||
def connectionManagerMock
|
||||
ConnectionManager connectionManager
|
||||
|
||||
|
||||
def hostCacheMock
|
||||
HostCache hostCache
|
||||
|
||||
|
||||
ConnectionEstablisher establisher
|
||||
|
||||
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
connectionEvents = new CopyOnWriteArrayList()
|
||||
@ -57,7 +57,7 @@ class ConnectionEstablisherTest {
|
||||
connectionManagerMock = new MockFor(ConnectionManager.class)
|
||||
hostCacheMock = new MockFor(HostCache.class)
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
void after() {
|
||||
establisher?.stop()
|
||||
@ -66,7 +66,7 @@ class ConnectionEstablisherTest {
|
||||
hostCacheMock.verify hostCache
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
|
||||
private void initMocks() {
|
||||
i2pConnector = i2pConnectorMock.proxyInstance()
|
||||
connectionManager = connectionManagerMock.proxyInstance()
|
||||
@ -75,13 +75,13 @@ class ConnectionEstablisherTest {
|
||||
establisher.start()
|
||||
Thread.sleep(250)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
void testConnectFails() {
|
||||
settings = new MuWireSettings()
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
@ -95,16 +95,16 @@ class ConnectionEstablisherTest {
|
||||
assert dest == destinations.dest1
|
||||
throw new IOException()
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.incoming == false
|
||||
assert event.status == ConnectionAttemptStatus.FAILED
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConnectionSucceedsPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -128,26 +128,26 @@ class ConnectionEstablisherTest {
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
|
||||
|
||||
outputStream.write("OK".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
|
||||
Thread.sleep(100)
|
||||
|
||||
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.incoming == false
|
||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConnectionSucceedsLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -171,25 +171,25 @@ class ConnectionEstablisherTest {
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire leaf".bytes
|
||||
|
||||
|
||||
outputStream.write("OK".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
|
||||
Thread.sleep(100)
|
||||
|
||||
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.incoming == false
|
||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConnectionRejected() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -213,18 +213,18 @@ class ConnectionEstablisherTest {
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
|
||||
|
||||
outputStream.write("REJECT".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
|
||||
Thread.sleep(100)
|
||||
|
||||
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
@ -232,7 +232,7 @@ class ConnectionEstablisherTest {
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
assert discoveredEvents.isEmpty()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConnectionRejectedSuggestions() {
|
||||
settings = new MuWireSettings() {
|
||||
@ -256,16 +256,16 @@ class ConnectionEstablisherTest {
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
|
||||
|
||||
outputStream.write("REJECT".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
|
||||
def json = [:]
|
||||
json.tryHosts = [destinations.dest2.toBase64()]
|
||||
json = JsonOutput.toJson(json)
|
||||
@ -273,13 +273,13 @@ class ConnectionEstablisherTest {
|
||||
outputStream.write(json.bytes)
|
||||
outputStream.flush()
|
||||
Thread.sleep(100)
|
||||
|
||||
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.incoming == false
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
|
||||
|
||||
assert discoveredEvents.size() == 1
|
||||
event = discoveredEvents[0]
|
||||
assert event.destination == destinations.dest2
|
||||
|
@ -19,48 +19,48 @@ import net.i2p.data.Base64
|
||||
import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class DownloadSessionTest {
|
||||
|
||||
|
||||
private EventBus eventBus
|
||||
private File source, target
|
||||
private InfoHash infoHash
|
||||
private Endpoint endpoint
|
||||
private Pieces pieces
|
||||
private String rootBase64
|
||||
|
||||
|
||||
private DownloadSession session
|
||||
private Thread downloadThread
|
||||
|
||||
|
||||
private InputStream fromDownloader, fromUploader
|
||||
private OutputStream toDownloader, toUploader
|
||||
|
||||
|
||||
private volatile boolean performed
|
||||
private Set<Integer> available = new ConcurrentHashSet<>()
|
||||
private volatile IOException thrown
|
||||
|
||||
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
eventBus = new EventBus()
|
||||
}
|
||||
|
||||
|
||||
private void initSession(int size, def claimedPieces = []) {
|
||||
Random r = new Random()
|
||||
byte [] content = new byte[size]
|
||||
r.nextBytes(content)
|
||||
|
||||
|
||||
source = File.createTempFile("source", "tmp")
|
||||
source.deleteOnExit()
|
||||
def fos = new FileOutputStream(source)
|
||||
fos.write(content)
|
||||
fos.close()
|
||||
|
||||
|
||||
def hasher = new FileHasher()
|
||||
infoHash = hasher.hashFile(source)
|
||||
rootBase64 = Base64.encode(infoHash.getRoot())
|
||||
|
||||
|
||||
target = File.createTempFile("target", "tmp")
|
||||
int pieceSize = 1 << FileHasher.getPieceSize(size)
|
||||
|
||||
|
||||
int nPieces
|
||||
if (size % pieceSize == 0)
|
||||
nPieces = size / pieceSize
|
||||
@ -68,27 +68,27 @@ class DownloadSessionTest {
|
||||
nPieces = size / pieceSize + 1
|
||||
pieces = new Pieces(nPieces)
|
||||
claimedPieces.each {pieces.claimed.set(it)}
|
||||
|
||||
|
||||
fromDownloader = new PipedInputStream()
|
||||
fromUploader = new PipedInputStream()
|
||||
toDownloader = new PipedOutputStream(fromUploader)
|
||||
toUploader = new PipedOutputStream(fromDownloader)
|
||||
endpoint = new Endpoint(null, fromUploader, toUploader, null)
|
||||
|
||||
|
||||
session = new DownloadSession(eventBus, "",pieces, infoHash, endpoint, target, pieceSize, size, available)
|
||||
downloadThread = new Thread( { perform() } as Runnable)
|
||||
downloadThread.setDaemon(true)
|
||||
downloadThread.start()
|
||||
}
|
||||
|
||||
|
||||
private void perform() {
|
||||
try {
|
||||
try {
|
||||
performed = session.request()
|
||||
} catch (IOException e) {
|
||||
thrown = e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
source?.delete()
|
||||
@ -96,7 +96,7 @@ class DownloadSessionTest {
|
||||
downloadThread?.interrupt()
|
||||
Thread.sleep(50)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSmallFile() {
|
||||
initSession(20)
|
||||
@ -105,38 +105,38 @@ class DownloadSessionTest {
|
||||
readTillRN(fromDownloader)
|
||||
readTillRN(fromDownloader)
|
||||
assert "" == readTillRN(fromDownloader)
|
||||
|
||||
|
||||
toDownloader.write("200 OK\r\n".bytes)
|
||||
toDownloader.write("Content-Range: 0-19\r\n\r\n".bytes)
|
||||
toDownloader.write(source.bytes)
|
||||
toDownloader.flush()
|
||||
|
||||
|
||||
Thread.sleep(150)
|
||||
|
||||
|
||||
assert pieces.isComplete()
|
||||
assert target.bytes == source.bytes
|
||||
assert performed
|
||||
assert available.isEmpty()
|
||||
assert thrown == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPieceSizeFile() {
|
||||
int size = FileHasher.getPieceSize(1)
|
||||
size = 1 << size
|
||||
initSession(size)
|
||||
|
||||
|
||||
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
||||
readTillRN(fromDownloader)
|
||||
readTillRN(fromDownloader)
|
||||
readTillRN(fromDownloader)
|
||||
assert "" == readTillRN(fromDownloader)
|
||||
|
||||
|
||||
toDownloader.write("200 OK\r\n".bytes)
|
||||
toDownloader.write(("Content-Range: 0-"+(size - 1)+"\r\n\r\n").bytes)
|
||||
toDownloader.write(source.bytes)
|
||||
toDownloader.flush()
|
||||
|
||||
|
||||
Thread.sleep(150)
|
||||
assert pieces.isComplete()
|
||||
assert target.bytes == source.bytes
|
||||
@ -144,33 +144,33 @@ class DownloadSessionTest {
|
||||
assert available.isEmpty()
|
||||
assert thrown == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPieceSizePlusOne() {
|
||||
int pieceSize = FileHasher.getPieceSize(1)
|
||||
int size = (1 << pieceSize) + 1
|
||||
initSession(size)
|
||||
|
||||
|
||||
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
||||
String range = readTillRN(fromDownloader)
|
||||
def matcher = (range =~ /^Range: (\d+)-(\d+)$/)
|
||||
int start = Integer.parseInt(matcher[0][1])
|
||||
int end = Integer.parseInt(matcher[0][2])
|
||||
|
||||
assert (start == 0 && end == ((1 << pieceSize) - 1)) ||
|
||||
|
||||
assert (start == 0 && end == ((1 << pieceSize) - 1)) ||
|
||||
(start == (1 << pieceSize) && end == (1 << pieceSize))
|
||||
|
||||
|
||||
readTillRN(fromDownloader)
|
||||
readTillRN(fromDownloader)
|
||||
assert "" == readTillRN(fromDownloader)
|
||||
|
||||
|
||||
toDownloader.write("200 OK\r\n".bytes)
|
||||
toDownloader.write(("Content-Range: $start-$end\r\n\r\n").bytes)
|
||||
byte [] piece = new byte[end - start + 1]
|
||||
System.arraycopy(source.bytes, start, piece, 0, piece.length)
|
||||
toDownloader.write(piece)
|
||||
toDownloader.flush()
|
||||
|
||||
|
||||
Thread.sleep(150)
|
||||
assert !pieces.isComplete()
|
||||
assert 1 == pieces.donePieces()
|
||||
@ -178,7 +178,7 @@ class DownloadSessionTest {
|
||||
assert available.isEmpty()
|
||||
assert thrown == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSmallFileClaimed() {
|
||||
initSession(20, [0])
|
||||
@ -189,29 +189,29 @@ class DownloadSessionTest {
|
||||
assert available.isEmpty()
|
||||
assert thrown == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClaimedPiecesAvoided() {
|
||||
int pieceSize = FileHasher.getPieceSize(1)
|
||||
int size = (1 << pieceSize) * 10
|
||||
initSession(size, [1,2,3,4,5,6,7,8,9])
|
||||
assert !pieces.claimed.get(0)
|
||||
|
||||
|
||||
assert "GET $rootBase64" == readTillRN(fromDownloader)
|
||||
String range = readTillRN(fromDownloader)
|
||||
def matcher = (range =~ /^Range: (\d+)-(\d+)$/)
|
||||
int start = Integer.parseInt(matcher[0][1])
|
||||
int end = Integer.parseInt(matcher[0][2])
|
||||
|
||||
|
||||
assert pieces.claimed.get(0)
|
||||
assert start == 0 && end == (1 << pieceSize) - 1
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test416NoHave() {
|
||||
initSession(20)
|
||||
readAllHeaders(fromDownloader)
|
||||
|
||||
|
||||
toDownloader.write("416 don't have it\r\n\r\n".bytes)
|
||||
toDownloader.flush()
|
||||
Thread.sleep(150)
|
||||
@ -219,45 +219,45 @@ class DownloadSessionTest {
|
||||
assert available.isEmpty()
|
||||
assert thrown != null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test416Have() {
|
||||
initSession(20)
|
||||
readAllHeaders(fromDownloader)
|
||||
|
||||
|
||||
toDownloader.write("416 don't have it\r\n".bytes)
|
||||
toDownloader.write("X-Have: ${encodeXHave([0], 1)}\r\n\r\n".bytes)
|
||||
toDownloader.flush()
|
||||
|
||||
|
||||
Thread.sleep(150)
|
||||
assert performed
|
||||
assert available.contains(0)
|
||||
assert thrown == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test416Have2Pieces() {
|
||||
int pieceSize = FileHasher.getPieceSize(1)
|
||||
int size = (1 << pieceSize) + 1
|
||||
initSession(size)
|
||||
readAllHeaders(fromDownloader)
|
||||
|
||||
|
||||
toDownloader.write("416 don't have it\r\n".bytes)
|
||||
toDownloader.write("X-Have: ${encodeXHave([1], 2)}\r\n\r\n".bytes)
|
||||
toDownloader.flush()
|
||||
|
||||
|
||||
Thread.sleep(150)
|
||||
assert performed
|
||||
assert available.contains(1)
|
||||
assert thrown == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test200TwoPieces1Available() {
|
||||
int pieceSize = FileHasher.getPieceSize(1)
|
||||
int size = (1 << pieceSize) * 9 + 1
|
||||
initSession(size)
|
||||
|
||||
|
||||
Set<String> headers = readAllHeaders(fromDownloader)
|
||||
def matcher = null
|
||||
headers.each {
|
||||
@ -267,27 +267,27 @@ class DownloadSessionTest {
|
||||
assert matcher.groupCount() > 0
|
||||
int start = Integer.parseInt(matcher[0][1])
|
||||
int end = Integer.parseInt(matcher[0][2])
|
||||
|
||||
if (start == 0)
|
||||
|
||||
if (start == 0)
|
||||
fail("inconlcusive")
|
||||
|
||||
|
||||
toDownloader.write("416 don't have it \r\n".bytes)
|
||||
toDownloader.write("X-Have: ${encodeXHave([0],2)}\r\n\r\n".bytes)
|
||||
toDownloader.flush()
|
||||
downloadThread.join()
|
||||
|
||||
|
||||
assert performed
|
||||
performed = false
|
||||
assert available.contains(0)
|
||||
assert thrown == null
|
||||
|
||||
|
||||
// request same session
|
||||
downloadThread = new Thread( { perform() } as Runnable)
|
||||
downloadThread.setDaemon(true)
|
||||
downloadThread.start()
|
||||
|
||||
|
||||
Thread.sleep(150)
|
||||
|
||||
|
||||
headers = readAllHeaders(fromDownloader)
|
||||
matcher = null
|
||||
headers.each {
|
||||
@ -299,7 +299,7 @@ class DownloadSessionTest {
|
||||
end = Integer.parseInt(matcher[0][2])
|
||||
assert start == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testXAlt() throws Exception {
|
||||
Personas personas = new Personas()
|
||||
@ -310,19 +310,19 @@ class DownloadSessionTest {
|
||||
}
|
||||
}
|
||||
eventBus.register(SourceDiscoveredEvent.class, listener)
|
||||
|
||||
|
||||
initSession(20)
|
||||
readAllHeaders(fromDownloader)
|
||||
toDownloader.write("416 don't have it\r\n".bytes)
|
||||
toDownloader.write("X-Alt: ${personas.persona1.toBase64()},${personas.persona2.toBase64()}\r\n\r\n".bytes)
|
||||
toDownloader.flush()
|
||||
|
||||
|
||||
Thread.sleep(150)
|
||||
assert sources.contains(personas.persona1)
|
||||
assert sources.contains(personas.persona2)
|
||||
assert 2 == sources.size()
|
||||
}
|
||||
|
||||
|
||||
private static Set<String> readAllHeaders(InputStream is) {
|
||||
Set<String> rv = new HashSet<>()
|
||||
String header
|
||||
|
@ -3,15 +3,15 @@ package com.muwire.core.download
|
||||
import org.junit.Test
|
||||
|
||||
class PiecesTest {
|
||||
|
||||
|
||||
Pieces pieces
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmpty() {
|
||||
pieces = new Pieces(20)
|
||||
assert !pieces.isComplete()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSinglePiece() {
|
||||
pieces = new Pieces(1)
|
||||
@ -20,7 +20,7 @@ class PiecesTest {
|
||||
pieces.markDownloaded(0)
|
||||
assert pieces.isComplete()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTwoPieces() {
|
||||
pieces = new Pieces(2)
|
||||
@ -34,7 +34,7 @@ class PiecesTest {
|
||||
pieces.markDownloaded(piece2)
|
||||
assert pieces.isComplete()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClaimAvailable() {
|
||||
pieces = new Pieces(2)
|
||||
@ -42,7 +42,7 @@ class PiecesTest {
|
||||
assert claimed == 0
|
||||
assert -1 == pieces.claim([0].toSet())
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClaimNoneAvailable() {
|
||||
pieces = new Pieces(20)
|
||||
|
@ -10,18 +10,18 @@ class FileHasherTest extends GroovyTestCase {
|
||||
|
||||
def hasher = new FileHasher()
|
||||
File tmp
|
||||
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
tmp = File.createTempFile("testFile", "test")
|
||||
tmp.deleteOnExit()
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
void tearDown() {
|
||||
tmp?.delete()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testPieceSize() {
|
||||
assert 17 == FileHasher.getPieceSize(1000000)
|
||||
@ -31,7 +31,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
FileHasher.getPieceSize(Long.MAX_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash1Byte() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
@ -40,7 +40,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
def ih = hasher.hashFile(tmp)
|
||||
assert ih.getHashList().length == 32
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash1PieceExact() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
@ -50,7 +50,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 64
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash1Piece1Byte() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
@ -60,7 +60,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 96
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash2Pieces() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
@ -70,7 +70,7 @@ class FileHasherTest extends GroovyTestCase {
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 128
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash2Pieces2Bytes() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
|
@ -13,16 +13,16 @@ import com.muwire.core.search.SearchEvent
|
||||
class FileManagerTest {
|
||||
|
||||
EventBus eventBus
|
||||
|
||||
|
||||
FileManager manager
|
||||
volatile ResultsEvent results
|
||||
|
||||
|
||||
def listener = new Object() {
|
||||
void onResultsEvent(ResultsEvent e) {
|
||||
results = e
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
eventBus = new EventBus()
|
||||
@ -30,7 +30,7 @@ class FileManagerTest {
|
||||
manager = new FileManager(eventBus, new MuWireSettings())
|
||||
results = null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash1Result() {
|
||||
File f = new File("a b.c")
|
||||
@ -38,19 +38,19 @@ class FileManagerTest {
|
||||
SharedFile sf = new SharedFile(f,ih, 0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
|
||||
|
||||
UUID uuid = UUID.randomUUID()
|
||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||
|
||||
|
||||
manager.onSearchEvent(se)
|
||||
Thread.sleep(20)
|
||||
|
||||
|
||||
assert results != null
|
||||
assert results.results.size() == 1
|
||||
assert results.results.contains(sf)
|
||||
assert results.uuid == uuid
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash2Results() {
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
@ -61,17 +61,17 @@ class FileManagerTest {
|
||||
|
||||
UUID uuid = UUID.randomUUID()
|
||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||
|
||||
|
||||
manager.onSearchEvent(se)
|
||||
Thread.sleep(20)
|
||||
|
||||
|
||||
assert results != null
|
||||
assert results.results.size() == 2
|
||||
assert results.results.contains(sf1)
|
||||
assert results.results.contains(sf2)
|
||||
assert results.uuid == uuid
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHash0Results() {
|
||||
File f = new File("a b.c")
|
||||
@ -79,13 +79,13 @@ class FileManagerTest {
|
||||
SharedFile sf = new SharedFile(f,ih, 0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
|
||||
|
||||
manager.onSearchEvent new SearchEvent(searchHash: new byte[32], uuid: UUID.randomUUID())
|
||||
Thread.sleep(20)
|
||||
|
||||
|
||||
assert results == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testKeyword1Result() {
|
||||
File f = new File("a b.c")
|
||||
@ -93,40 +93,40 @@ class FileManagerTest {
|
||||
SharedFile sf = new SharedFile(f,ih,0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
|
||||
|
||||
UUID uuid = UUID.randomUUID()
|
||||
manager.onSearchEvent new SearchEvent(searchTerms: ["a"], uuid:uuid)
|
||||
Thread.sleep(20)
|
||||
|
||||
|
||||
assert results != null
|
||||
assert results.results.size() == 1
|
||||
assert results.results.contains(sf)
|
||||
assert results.uuid == uuid
|
||||
assert results.uuid == uuid
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testKeyword2Results() {
|
||||
File f1 = new File("a b.c")
|
||||
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
||||
|
||||
|
||||
File f2 = new File("c d.e")
|
||||
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
||||
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
||||
|
||||
|
||||
UUID uuid = UUID.randomUUID()
|
||||
manager.onSearchEvent new SearchEvent(searchTerms: ["c"], uuid:uuid)
|
||||
Thread.sleep(20)
|
||||
|
||||
|
||||
assert results != null
|
||||
assert results.results.size() == 2
|
||||
assert results.results.contains(sf1)
|
||||
assert results.results.contains(sf2)
|
||||
assert results.uuid == uuid
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testKeyword0Results() {
|
||||
File f = new File("a b.c")
|
||||
@ -134,13 +134,13 @@ class FileManagerTest {
|
||||
SharedFile sf = new SharedFile(f,ih,0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
|
||||
|
||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"], uuid: UUID.randomUUID())
|
||||
Thread.sleep(20)
|
||||
|
||||
|
||||
assert results == null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testRemoveFileExistingHash() {
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
@ -148,41 +148,41 @@ class FileManagerTest {
|
||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
||||
|
||||
|
||||
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
||||
|
||||
|
||||
manager.onSearchEvent new SearchEvent(searchHash : ih.getRoot())
|
||||
Thread.sleep(20)
|
||||
assert results != null
|
||||
assert results.results.size() == 1
|
||||
assert results.results.contains(sf1)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testRemoveFile() {
|
||||
File f1 = new File("a b.c")
|
||||
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
||||
|
||||
|
||||
File f2 = new File("c d.e")
|
||||
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
||||
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
||||
|
||||
|
||||
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
||||
|
||||
|
||||
// 1 match left
|
||||
manager.onSearchEvent new SearchEvent(searchTerms: ["c"])
|
||||
Thread.sleep(20)
|
||||
assert results != null
|
||||
assert results.results.size() == 1
|
||||
assert results.results.contains(sf1)
|
||||
|
||||
|
||||
// no match
|
||||
results = null
|
||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"])
|
||||
assert results == null
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ class HasherServiceTest {
|
||||
offer evt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
eventBus = new EventBus()
|
||||
@ -30,12 +30,12 @@ class HasherServiceTest {
|
||||
eventBus.register(FileSharedEvent.class, service)
|
||||
service.start()
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
void after() {
|
||||
listener.clear()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSingleFile() {
|
||||
File f = new File("build.gradle")
|
||||
@ -47,7 +47,7 @@ class HasherServiceTest {
|
||||
assert hashed.sharedFile.infoHash != null
|
||||
assert listener.isEmpty()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDirectory() {
|
||||
File f = new File(".")
|
||||
|
@ -23,35 +23,35 @@ class PersisterServiceLoadingTest {
|
||||
publishedFiles.add(e.loadedFile)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EventBus eventBus
|
||||
Listener listener
|
||||
File sharedDir
|
||||
File sharedFile1, sharedFile2
|
||||
|
||||
|
||||
@Before
|
||||
void setup() {
|
||||
eventBus = new EventBus()
|
||||
listener = new Listener()
|
||||
eventBus.register(FileLoadedEvent.class, listener)
|
||||
|
||||
|
||||
sharedDir = new File("sharedDir")
|
||||
sharedDir.mkdir()
|
||||
sharedDir.deleteOnExit()
|
||||
|
||||
|
||||
sharedFile1 = new File(sharedDir,"file1")
|
||||
sharedFile1.deleteOnExit()
|
||||
|
||||
|
||||
sharedFile2 = new File(sharedDir,"file2")
|
||||
sharedFile2.deleteOnExit()
|
||||
}
|
||||
|
||||
|
||||
private void writeToSharedFile(File file, int size) {
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write new byte[size]
|
||||
fos.close()
|
||||
}
|
||||
|
||||
|
||||
private File initPersisted() {
|
||||
File persisted = new File("persisted")
|
||||
if (persisted.exists())
|
||||
@ -59,28 +59,28 @@ class PersisterServiceLoadingTest {
|
||||
persisted.deleteOnExit()
|
||||
persisted
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void test1SharedFile1Piece() {
|
||||
writeToSharedFile(sharedFile1, 1)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
|
||||
|
||||
def json = [:]
|
||||
json.file = getSharedFileJsonName(sharedFile1)
|
||||
json.length = 1
|
||||
json.infoHash = Base64.encode(ih1.getRoot())
|
||||
json.hashList = [Base64.encode(ih1.getHashList())]
|
||||
|
||||
|
||||
json = JsonOutput.toJson(json)
|
||||
|
||||
|
||||
File persisted = initPersisted()
|
||||
persisted.write json
|
||||
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
|
||||
|
||||
assert listener.publishedFiles.size() == 1
|
||||
def loadedFile = listener.publishedFiles[0]
|
||||
assert loadedFile != null
|
||||
@ -92,20 +92,20 @@ class PersisterServiceLoadingTest {
|
||||
def encoded = DataUtil.encodei18nString(sharedFile.getCanonicalFile().toString())
|
||||
Base64.encode(encoded)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void test1SharedFile2Pieces() {
|
||||
writeToSharedFile(sharedFile1, (0x1 << 18) + 1)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
|
||||
|
||||
assert ih1.getHashList().length == 96
|
||||
|
||||
|
||||
def json = [:]
|
||||
json.file = getSharedFileJsonName(sharedFile1)
|
||||
json.length = sharedFile1.length()
|
||||
json.infoHash = Base64.encode ih1.getRoot()
|
||||
|
||||
|
||||
byte [] tmp = new byte[32]
|
||||
System.arraycopy(ih1.getHashList(), 0, tmp, 0, 32)
|
||||
String hash1 = Base64.encode(tmp)
|
||||
@ -114,23 +114,23 @@ class PersisterServiceLoadingTest {
|
||||
System.arraycopy(ih1.getHashList(), 64, tmp, 0, 32)
|
||||
String hash3 = Base64.encode(tmp)
|
||||
json.hashList = [hash1, hash2, hash3]
|
||||
|
||||
|
||||
json = JsonOutput.toJson(json)
|
||||
|
||||
|
||||
File persisted = initPersisted()
|
||||
persisted.write json
|
||||
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
|
||||
|
||||
assert listener.publishedFiles.size() == 1
|
||||
def loadedFile = listener.publishedFiles[0]
|
||||
assert loadedFile != null
|
||||
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
||||
assert loadedFile.infoHash == ih1
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void test2SharedFiles() {
|
||||
writeToSharedFile(sharedFile1, 1)
|
||||
@ -138,34 +138,34 @@ class PersisterServiceLoadingTest {
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
InfoHash ih2 = fh.hashFile(sharedFile2)
|
||||
|
||||
|
||||
assert ih1 != ih2
|
||||
|
||||
|
||||
File persisted = initPersisted()
|
||||
|
||||
|
||||
def json1 = [:]
|
||||
json1.file = getSharedFileJsonName(sharedFile1)
|
||||
json1.length = 1
|
||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||
|
||||
|
||||
json1 = JsonOutput.toJson(json1)
|
||||
|
||||
|
||||
def json2 = [:]
|
||||
json2.file = getSharedFileJsonName(sharedFile2)
|
||||
json2.length = 2
|
||||
json2.infoHash = Base64.encode(ih2.getRoot())
|
||||
json2.hashList = [Base64.encode(ih2.getHashList())]
|
||||
|
||||
|
||||
json2 = JsonOutput.toJson(json2)
|
||||
|
||||
|
||||
persisted.append "$json1\n"
|
||||
persisted.append "$json2\n"
|
||||
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
|
||||
|
||||
assert listener.publishedFiles.size() == 2
|
||||
def loadedFile1 = listener.publishedFiles[0]
|
||||
assert loadedFile1.file == sharedFile1.getCanonicalFile()
|
||||
@ -174,15 +174,15 @@ class PersisterServiceLoadingTest {
|
||||
assert loadedFile2.file == sharedFile2.getCanonicalFile()
|
||||
assert loadedFile2.infoHash == ih2
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDownloadedFile() {
|
||||
writeToSharedFile(sharedFile1, 1)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
|
||||
|
||||
File persisted = initPersisted()
|
||||
|
||||
|
||||
Destinations dests = new Destinations()
|
||||
def json1 = [:]
|
||||
json1.file = getSharedFileJsonName(sharedFile1)
|
||||
@ -190,20 +190,20 @@ class PersisterServiceLoadingTest {
|
||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||
json1.sources = [ dests.dest1.toBase64(), dests.dest2.toBase64()]
|
||||
|
||||
|
||||
json1 = JsonOutput.toJson(json1)
|
||||
persisted.write json1
|
||||
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
|
||||
|
||||
assert listener.publishedFiles.size() == 1
|
||||
def loadedFile1 = listener.publishedFiles[0]
|
||||
assert loadedFile1 instanceof DownloadedFile
|
||||
assert loadedFile1.sources.size() == 2
|
||||
assert loadedFile1.sources.contains(dests.dest1)
|
||||
assert loadedFile1.sources.contains(dests.dest2)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class PersisterServiceSavingTest {
|
||||
EventBus eventBus = new EventBus()
|
||||
File persisted
|
||||
PersisterService ps
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
f = new File("build.gradle")
|
||||
@ -43,26 +43,26 @@ class PersisterServiceSavingTest {
|
||||
persisted.delete()
|
||||
persisted.deleteOnExit()
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
void after() {
|
||||
ps?.stop()
|
||||
}
|
||||
|
||||
|
||||
private static String fromB64(String text) {
|
||||
DataUtil.readi18nString(Base64.decode(text))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSavingSharedFile() {
|
||||
sf = new SharedFile(f, ih, 0)
|
||||
|
||||
|
||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(1500)
|
||||
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
persisted.eachLine {
|
||||
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
persisted.eachLine {
|
||||
def json = jsonSlurper.parseText(it)
|
||||
assert fromB64(json.file) == f.toString()
|
||||
assert json.length == f.length()
|
||||
@ -70,16 +70,16 @@ class PersisterServiceSavingTest {
|
||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSavingDownloadedFile() {
|
||||
Destinations dests = new Destinations()
|
||||
sf = new DownloadedFile(f, ih, 0, new HashSet([dests.dest1, dests.dest2]))
|
||||
|
||||
|
||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(1500)
|
||||
|
||||
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
persisted.eachLine {
|
||||
def json = jsonSlurper.parseText(it)
|
||||
|
@ -23,25 +23,25 @@ class HostCacheTest {
|
||||
|
||||
File persist
|
||||
HostCache cache
|
||||
|
||||
|
||||
def trustMock
|
||||
TrustService trust
|
||||
|
||||
|
||||
def settingsMock
|
||||
MuWireSettings settings
|
||||
|
||||
|
||||
Destinations destinations = new Destinations()
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
persist = new File("hostPersist")
|
||||
persist.delete()
|
||||
persist.deleteOnExit()
|
||||
|
||||
|
||||
trustMock = new MockFor(TrustService.class)
|
||||
settingsMock = new MockFor(MuWireSettings.class)
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
void after() {
|
||||
cache?.stop()
|
||||
@ -49,7 +49,7 @@ class HostCacheTest {
|
||||
settingsMock.verify settings
|
||||
Thread.sleep(150)
|
||||
}
|
||||
|
||||
|
||||
private void initMocks() {
|
||||
trust = trustMock.proxyInstance()
|
||||
settings = settingsMock.proxyInstance()
|
||||
@ -57,7 +57,7 @@ class HostCacheTest {
|
||||
cache.start()
|
||||
Thread.sleep(150)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testEmpty() {
|
||||
initMocks()
|
||||
@ -65,38 +65,38 @@ class HostCacheTest {
|
||||
assert cache.getGoodHosts(5).size() == 0
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test
|
||||
void testOnDiscoveredEvent() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { true }
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
|
||||
|
||||
assert cache.getGoodHosts(5).size() == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testOnDiscoveredUntrustedHost() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.DISTRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testOnDiscoverNeutralHostsProhibited() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
@ -104,13 +104,13 @@ class HostCacheTest {
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { false }
|
||||
|
||||
|
||||
initMocks()
|
||||
|
||||
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void test2DiscoveredGoodHosts() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
@ -123,86 +123,86 @@ class HostCacheTest {
|
||||
}
|
||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest2))
|
||||
|
||||
|
||||
def rv = cache.getHosts(1)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1) || rv.contains(destinations.dest2)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHostFailed() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
|
||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||
|
||||
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testFailedHostSuceeds() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
|
||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||
|
||||
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
|
||||
|
||||
rv = cache.getGoodHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testFailedOnceNoLongerGood() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
|
||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||
|
||||
|
||||
def rv = cache.getHosts(5)
|
||||
def rv2 = cache.getGoodHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
assert rv == rv2
|
||||
|
||||
|
||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.FAILED))
|
||||
|
||||
|
||||
rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
assert cache.getGoodHosts(5).size() == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDuplicateHostDiscovered() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
@ -213,16 +213,16 @@ class HostCacheTest {
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
|
||||
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSaving() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
@ -232,7 +232,7 @@ class HostCacheTest {
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
Thread.sleep(150)
|
||||
|
||||
|
||||
assert persist.exists()
|
||||
int lines = 0
|
||||
persist.eachLine {
|
||||
@ -245,7 +245,7 @@ class HostCacheTest {
|
||||
}
|
||||
assert lines == 1
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testLoading() {
|
||||
def json = [:]
|
||||
@ -254,17 +254,17 @@ class HostCacheTest {
|
||||
json.successes = 1
|
||||
json = JsonOutput.toJson(json)
|
||||
persist.append("${json}\n")
|
||||
|
||||
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
|
||||
initMocks()
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
|
||||
|
||||
assert cache.getGoodHosts(5) == rv
|
||||
}
|
||||
}
|
||||
|
@ -5,34 +5,34 @@ import org.junit.Test
|
||||
class SearchIndexTest {
|
||||
|
||||
SearchIndex index
|
||||
|
||||
|
||||
private void initIndex(List<String> entries) {
|
||||
index = new SearchIndex()
|
||||
entries.each {
|
||||
index.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSingleTerm() {
|
||||
initIndex(["a b.c", "d e.f"])
|
||||
|
||||
|
||||
def found = index.search(["a"])
|
||||
assert found.size() == 1
|
||||
assert found.contains("a b.c")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSingleTermOverlap() {
|
||||
initIndex(["a b.c", "c d.e"])
|
||||
|
||||
|
||||
def found = index.search(["c"])
|
||||
assert found.size() == 2
|
||||
assert found.contains("a b.c")
|
||||
assert found.contains("c d.e")
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDrillDownDoesNotModifyIndex() {
|
||||
initIndex(["a b.c", "c d.e"])
|
||||
@ -42,31 +42,31 @@ class SearchIndexTest {
|
||||
assert found.contains("a b.c")
|
||||
assert found.contains("c d.e")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDrillDown() {
|
||||
initIndex(["a b.c", "c d.e"])
|
||||
|
||||
|
||||
def found = index.search(["c", "e"])
|
||||
assert found.size() == 1
|
||||
assert found.contains("c d.e")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testNotFound() {
|
||||
initIndex(["a b.c"])
|
||||
def found = index.search(["d"])
|
||||
assert found.size() == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSomeNotFound() {
|
||||
initIndex(["a b.c"])
|
||||
def found = index.search(["a","d"])
|
||||
assert found.size() == 0
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testRemove() {
|
||||
initIndex(["a b.c"])
|
||||
@ -74,7 +74,7 @@ class SearchIndexTest {
|
||||
def found = index.search(["a"])
|
||||
assert found.size() == 0
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testRemoveOverlap() {
|
||||
initIndex(["a b.c", "b c.d"])
|
||||
@ -83,7 +83,7 @@ class SearchIndexTest {
|
||||
assert found.size() == 1
|
||||
assert found.contains("b c.d")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDuplicateTerm() {
|
||||
initIndex(["MuWire-0.3.3.jar"])
|
||||
|
@ -16,7 +16,7 @@ class TrustServiceTest {
|
||||
TrustService service
|
||||
File persistGood, persistBad
|
||||
Personas personas = new Personas()
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
persistGood = new File("good.trust")
|
||||
@ -28,32 +28,32 @@ class TrustServiceTest {
|
||||
service = new TrustService(persistGood, persistBad, 100)
|
||||
service.start()
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
void after() {
|
||||
service.stop()
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testEmpty() {
|
||||
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona1.destination)
|
||||
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona2.destination)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testOnEvent() {
|
||||
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||
|
||||
|
||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@Test
|
||||
void testPersist() {
|
||||
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||
|
||||
|
||||
Thread.sleep(250)
|
||||
def trusted = new HashSet<>()
|
||||
persistGood.eachLine {
|
||||
@ -63,13 +63,13 @@ class TrustServiceTest {
|
||||
persistBad.eachLine {
|
||||
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||
}
|
||||
|
||||
|
||||
assert trusted.size() == 1
|
||||
assert trusted.contains(personas.persona1)
|
||||
assert distrusted.size() == 1
|
||||
assert distrusted.contains(personas.persona2)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testLoad() {
|
||||
service.stop()
|
||||
@ -78,7 +78,7 @@ class TrustServiceTest {
|
||||
service = new TrustService(persistGood, persistBad, 100)
|
||||
service.start()
|
||||
Thread.sleep(50)
|
||||
|
||||
|
||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||
}
|
||||
|
@ -8,15 +8,15 @@ import org.junit.Test
|
||||
import com.muwire.core.InfoHash
|
||||
|
||||
class RequestParsingTest {
|
||||
|
||||
|
||||
ContentRequest request
|
||||
|
||||
|
||||
private void fromString(String requestString) {
|
||||
def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII))
|
||||
request = Request.parseContentRequest(new InfoHash(new byte[InfoHash.SIZE]), is)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static void failed(String requestString) {
|
||||
try {
|
||||
def is = new ByteArrayInputStream(requestString.getBytes(StandardCharsets.US_ASCII))
|
||||
@ -24,12 +24,12 @@ class RequestParsingTest {
|
||||
assert false
|
||||
} catch (IOException expected) {}
|
||||
}
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
request = null
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSuccessful() {
|
||||
fromString("Range: 1-2\r\n\r\n")
|
||||
@ -37,37 +37,37 @@ class RequestParsingTest {
|
||||
assert request.getRange().start == 1
|
||||
assert request.getRange().end == 2
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRNMissing() {
|
||||
failed("Range: 1-2")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRNMissing2() {
|
||||
failed("Range: 1-2\r\n")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRR() {
|
||||
failed("Range: 1-2\r\r")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNR() {
|
||||
failed("Range: 1-2\n\r")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testR() {
|
||||
failed("Range: 1-2\r")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRX() {
|
||||
failed("Range: 1-2\rx")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTwoHeaders() {
|
||||
fromString("Range: 1-2\r\nA:B\r\n\r\n")
|
||||
@ -78,17 +78,17 @@ class RequestParsingTest {
|
||||
assert request.getHeaders().get("Range").trim() == "1-2"
|
||||
assert request.getHeaders().get("A").trim() == "B"
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRangeMissing() {
|
||||
failed("A:B\r\n")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoHeaders() {
|
||||
failed("\r\n")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInvalidRange() {
|
||||
failed("Range 1-2\r\n\r\n")
|
||||
@ -98,7 +98,7 @@ class RequestParsingTest {
|
||||
failed("Range: 1-x\r\n\r\n")
|
||||
failed("Range: x")
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderTooLong() {
|
||||
StringBuilder sb = new StringBuilder()
|
||||
@ -106,4 +106,4 @@ class RequestParsingTest {
|
||||
sb.append("x")
|
||||
failed(sb.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,19 +11,19 @@ import com.muwire.core.InfoHash
|
||||
import com.muwire.core.connection.Endpoint
|
||||
|
||||
class UploaderTest {
|
||||
|
||||
|
||||
Endpoint endpoint
|
||||
File file
|
||||
Thread uploadThread
|
||||
|
||||
|
||||
InputStream is
|
||||
OutputStream os
|
||||
|
||||
|
||||
ContentRequest request
|
||||
Uploader uploader
|
||||
|
||||
|
||||
byte[] inFile
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
file?.delete()
|
||||
@ -33,14 +33,14 @@ class UploaderTest {
|
||||
os = new PipedOutputStream(is)
|
||||
endpoint = new Endpoint(null, is, os, null)
|
||||
}
|
||||
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
file?.delete()
|
||||
uploadThread?.interrupt()
|
||||
Thread.sleep(50)
|
||||
}
|
||||
|
||||
|
||||
private void fillFile(int length) {
|
||||
byte [] data = new byte[length]
|
||||
def random = new Random()
|
||||
@ -50,14 +50,14 @@ class UploaderTest {
|
||||
fos.close()
|
||||
inFile = data
|
||||
}
|
||||
|
||||
|
||||
private void startUpload() {
|
||||
uploader = new ContentUploader(file, request, endpoint)
|
||||
uploadThread = new Thread(uploader.respond() as Runnable)
|
||||
uploadThread.setDaemon(true)
|
||||
uploadThread.start()
|
||||
}
|
||||
|
||||
|
||||
private String readUntilRN() {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
while(true) {
|
||||
@ -73,7 +73,7 @@ class UploaderTest {
|
||||
}
|
||||
new String(baos.toByteArray(), StandardCharsets.US_ASCII)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSmallFile() {
|
||||
fillFile(20)
|
||||
@ -82,13 +82,13 @@ class UploaderTest {
|
||||
assert "200 OK" == readUntilRN()
|
||||
assert "Content-Range: 0-19" == readUntilRN()
|
||||
assert "" == readUntilRN()
|
||||
|
||||
|
||||
byte [] data = new byte[20]
|
||||
DataInputStream dis = new DataInputStream(is)
|
||||
dis.readFully(data)
|
||||
assert inFile == data
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRequestMiddle() {
|
||||
fillFile(20)
|
||||
@ -97,14 +97,14 @@ class UploaderTest {
|
||||
assert "200 OK" == readUntilRN()
|
||||
assert "Content-Range: 5-15" == readUntilRN()
|
||||
assert "" == readUntilRN()
|
||||
|
||||
|
||||
byte [] data = new byte[11]
|
||||
DataInputStream dis = new DataInputStream(is)
|
||||
dis.readFully(data)
|
||||
for (int i = 0; i < data.length; i++)
|
||||
assert inFile[i+5] == data[i]
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testOutOfRange() {
|
||||
fillFile(20)
|
||||
@ -123,10 +123,10 @@ class UploaderTest {
|
||||
readUntilRN()
|
||||
readUntilRN()
|
||||
readUntilRN()
|
||||
|
||||
|
||||
byte [] data = new byte[length]
|
||||
DataInputStream dis = new DataInputStream(is)
|
||||
dis.readFully(data)
|
||||
assert data == inFile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import org.junit.Test
|
||||
|
||||
class DataUtilTest {
|
||||
|
||||
|
||||
|
||||
private static void usVal(int value) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
DataUtil.writeUnsignedShort(value, baos)
|
||||
@ -20,19 +20,19 @@ class DataUtilTest {
|
||||
usVal(Short.MAX_VALUE)
|
||||
usVal(Short.MAX_VALUE + 1)
|
||||
usVal(0xFFFF)
|
||||
|
||||
|
||||
try {
|
||||
usVal(0xFFFF + 1)
|
||||
fail()
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
}
|
||||
|
||||
|
||||
private static header(int value) {
|
||||
byte [] header = new byte[3]
|
||||
DataUtil.packHeader(value, header)
|
||||
assert value == DataUtil.readLength(header)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testHeader() {
|
||||
header(0)
|
||||
|
@ -30,9 +30,9 @@ class I2PStatusController {
|
||||
model.receiveBps = router._context.bandwidthLimiter().getReceiveBps15s()
|
||||
model.sendBps = router._context.bandwidthLimiter().getSendBps15s()
|
||||
model.participatingBW = router._context.bandwidthLimiter().getCurrentParticipatingBandwidth()
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void close() {
|
||||
view.dialog.setVisible(false)
|
||||
|
@ -36,19 +36,19 @@ class MainFrameController {
|
||||
@Inject @Nonnull GriffonApplication application
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
|
||||
|
||||
@MVCMember @Nonnull
|
||||
MainFrameModel model
|
||||
@MVCMember @Nonnull
|
||||
MainFrameView view
|
||||
|
||||
private volatile Core core
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void search() {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
|
||||
|
||||
def search = builder.getVariable("search-field").text
|
||||
search = search.trim()
|
||||
if (search.length() == 0)
|
||||
@ -72,7 +72,7 @@ class MainFrameController {
|
||||
// not a hash search
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def searchEvent
|
||||
if (hashSearch) {
|
||||
searchEvent = new SearchEvent(searchHash : root, uuid : uuid, oobInfohash: true)
|
||||
@ -84,11 +84,11 @@ class MainFrameController {
|
||||
terms.each { if (it.length() > 0) nonEmpty << it }
|
||||
searchEvent = new SearchEvent(searchTerms : nonEmpty, uuid : uuid, oobInfohash: true)
|
||||
}
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true,
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true,
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me))
|
||||
}
|
||||
|
||||
|
||||
void search(String infoHash, String tabTitle) {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
@ -98,14 +98,14 @@ class MainFrameController {
|
||||
params["uuid"] = uuid.toString()
|
||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||
model.results[uuid.toString()] = group
|
||||
|
||||
|
||||
def searchEvent = new SearchEvent(searchHash : Base64.decode(infoHash), uuid:uuid,
|
||||
oobInfohash: true)
|
||||
core.eventBus.publish(new QueryEvent(searchEvent : searchEvent, firstHop : true,
|
||||
replyTo: core.me.destination, receivedOn: core.me.destination,
|
||||
originator : core.me))
|
||||
}
|
||||
|
||||
|
||||
private def selectedResult() {
|
||||
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
||||
def group = selected.getClientProperty("mvc-group")
|
||||
@ -116,10 +116,10 @@ class MainFrameController {
|
||||
def sortEvt = group.view.lastSortEvent
|
||||
if (sortEvt != null) {
|
||||
row = group.view.resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||
}
|
||||
group.model.results[row]
|
||||
}
|
||||
group.model.results[row]
|
||||
}
|
||||
|
||||
|
||||
private int selectedDownload() {
|
||||
def downloadsTable = builder.getVariable("downloads-table")
|
||||
def selected = downloadsTable.getSelectedRow()
|
||||
@ -128,27 +128,27 @@ class MainFrameController {
|
||||
selected = downloadsTable.rowSorter.convertRowIndexToModel(selected)
|
||||
selected
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void download() {
|
||||
def result = selectedResult()
|
||||
if (result == null)
|
||||
return
|
||||
|
||||
|
||||
if (!model.canDownload(result.infohash))
|
||||
return
|
||||
|
||||
return
|
||||
|
||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||
|
||||
|
||||
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
||||
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, sources: sources, target : file))
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void trust() {
|
||||
def result = selectedResult()
|
||||
@ -156,7 +156,7 @@ class MainFrameController {
|
||||
return // TODO disable button
|
||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.TRUSTED))
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void distrust() {
|
||||
def result = selectedResult()
|
||||
@ -164,22 +164,22 @@ class MainFrameController {
|
||||
return // TODO disable button
|
||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
|
||||
@ControllerAction
|
||||
void cancel() {
|
||||
def downloader = model.downloads[selectedDownload()].downloader
|
||||
downloader.cancel()
|
||||
model.downloadInfoHashes.remove(downloader.getInfoHash())
|
||||
core.eventBus.publish(new UIDownloadCancelledEvent(downloader : downloader))
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void resume() {
|
||||
def downloader = model.downloads[selectedDownload()].downloader
|
||||
downloader.resume()
|
||||
core.eventBus.publish(new UIDownloadResumedEvent())
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void pause() {
|
||||
def downloader = model.downloads[selectedDownload()].downloader
|
||||
@ -194,21 +194,21 @@ class MainFrameController {
|
||||
builder.getVariable(tableName).model.fireTableDataChanged()
|
||||
core.eventBus.publish(new TrustEvent(persona : list[row], level : level))
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void markTrusted() {
|
||||
markTrust("distrusted-table", TrustLevel.TRUSTED, model.distrusted)
|
||||
model.markTrustedButtonEnabled = false
|
||||
model.markNeutralFromDistrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void markNeutralFromDistrusted() {
|
||||
markTrust("distrusted-table", TrustLevel.NEUTRAL, model.distrusted)
|
||||
model.markTrustedButtonEnabled = false
|
||||
model.markNeutralFromDistrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void markDistrusted() {
|
||||
markTrust("trusted-table", TrustLevel.DISTRUSTED, model.trusted)
|
||||
@ -216,7 +216,7 @@ class MainFrameController {
|
||||
model.markDistrustedButtonEnabled = false
|
||||
model.markNeutralFromTrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void markNeutralFromTrusted() {
|
||||
markTrust("trusted-table", TrustLevel.NEUTRAL, model.trusted)
|
||||
@ -224,7 +224,7 @@ class MainFrameController {
|
||||
model.markDistrustedButtonEnabled = false
|
||||
model.markNeutralFromTrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void subscribe() {
|
||||
int row = view.getSelectedTrustTablesRow("trusted-table")
|
||||
@ -238,7 +238,7 @@ class MainFrameController {
|
||||
model.markDistrustedButtonEnabled = false
|
||||
model.markNeutralFromTrustedButtonEnabled = false
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void review() {
|
||||
RemoteTrustList list = getSelectedTrustList()
|
||||
@ -249,9 +249,9 @@ class MainFrameController {
|
||||
env["trustService"] = core.trustService
|
||||
env["eventBus"] = core.eventBus
|
||||
mvcGroup.createMVCGroup("trust-list", env)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void update() {
|
||||
RemoteTrustList list = getSelectedTrustList()
|
||||
@ -259,7 +259,7 @@ class MainFrameController {
|
||||
return
|
||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : true))
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void unsubscribe() {
|
||||
RemoteTrustList list = getSelectedTrustList()
|
||||
@ -272,42 +272,42 @@ class MainFrameController {
|
||||
table.model.fireTableDataChanged()
|
||||
core.eventBus.publish(new TrustSubscriptionEvent(persona : list.persona, subscribe : false))
|
||||
}
|
||||
|
||||
|
||||
private RemoteTrustList getSelectedTrustList() {
|
||||
int row = view.getSelectedTrustTablesRow("subscription-table")
|
||||
if (row < 0)
|
||||
return null
|
||||
model.subscriptions[row]
|
||||
}
|
||||
|
||||
|
||||
void unshareSelectedFile() {
|
||||
SharedFile sf = view.selectedSharedFile()
|
||||
if (sf == null)
|
||||
return
|
||||
core.eventBus.publish(new FileUnsharedEvent(unsharedFile : sf))
|
||||
}
|
||||
|
||||
|
||||
void stopWatchingDirectory() {
|
||||
String directory = mvcGroup.view.getSelectedWatchedDirectory()
|
||||
if (directory == null)
|
||||
return
|
||||
core.muOptions.watchedDirectories.remove(directory)
|
||||
saveMuWireSettings()
|
||||
saveMuWireSettings()
|
||||
core.eventBus.publish(new DirectoryUnsharedEvent(directory : new File(directory)))
|
||||
|
||||
|
||||
model.watched.remove(directory)
|
||||
builder.getVariable("watched-directories-table").model.fireTableDataChanged()
|
||||
}
|
||||
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
f.withOutputStream {
|
||||
core.muOptions.write(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void mvcGroupInit(Map<String, String> args) {
|
||||
application.addPropertyChangeListener("core", {e->
|
||||
application.addPropertyChangeListener("core", {e->
|
||||
core = e.getNewValue()
|
||||
})
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ class MuWireStatusController {
|
||||
@ControllerAction
|
||||
void refresh() {
|
||||
Core core = application.context.get("core")
|
||||
|
||||
|
||||
int incoming = 0
|
||||
int outgoing = 0
|
||||
core.connectionManager.getConnections().each {
|
||||
@ -29,14 +29,14 @@ class MuWireStatusController {
|
||||
}
|
||||
model.incomingConnections = incoming
|
||||
model.outgoingConnections = outgoing
|
||||
|
||||
|
||||
model.knownHosts = core.hostCache.hosts.size()
|
||||
|
||||
|
||||
model.sharedFiles = core.fileManager.fileToSharedFile.size()
|
||||
|
||||
|
||||
model.downloads = core.downloadManager.downloaders.size()
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void close() {
|
||||
view.dialog.setVisible(false)
|
||||
|
@ -26,41 +26,41 @@ class OptionsController {
|
||||
String text
|
||||
Core core = application.context.get("core")
|
||||
MuWireSettings settings = application.context.get("muwire-settings")
|
||||
|
||||
|
||||
def i2pProps = core.i2pOptions
|
||||
|
||||
|
||||
text = view.inboundLengthField.text
|
||||
model.inboundLength = text
|
||||
i2pProps["inbound.length"] = text
|
||||
|
||||
|
||||
text = view.inboundQuantityField.text
|
||||
model.inboundQuantity = text
|
||||
i2pProps["inbound.quantity"] = text
|
||||
|
||||
|
||||
text = view.outboundQuantityField.text
|
||||
model.outboundQuantity = text
|
||||
i2pProps["outbound.quantity"] = text
|
||||
|
||||
|
||||
text = view.outboundLengthField.text
|
||||
model.outboundLength = text
|
||||
i2pProps["outbound.length"] = text
|
||||
|
||||
|
||||
if (settings.embeddedRouter) {
|
||||
text = view.i2pNTCPPortField.text
|
||||
model.i2pNTCPPort = text
|
||||
i2pProps["i2np.ntcp.port"] = text
|
||||
|
||||
|
||||
text = view.i2pUDPPortField.text
|
||||
model.i2pUDPPort = text
|
||||
i2pProps["i2np.udp.port"] = text
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
File i2pSettingsFile = new File(core.home, "i2p.properties")
|
||||
i2pSettingsFile.withOutputStream {
|
||||
i2pSettingsFile.withOutputStream {
|
||||
i2pProps.store(it,"")
|
||||
}
|
||||
|
||||
|
||||
text = view.retryField.text
|
||||
model.downloadRetryInterval = text
|
||||
|
||||
@ -73,12 +73,12 @@ class OptionsController {
|
||||
boolean autoDownloadUpdate = view.autoDownloadUpdateCheckbox.model.isSelected()
|
||||
model.autoDownloadUpdate = autoDownloadUpdate
|
||||
settings.autoDownloadUpdate = autoDownloadUpdate
|
||||
|
||||
|
||||
|
||||
|
||||
boolean shareDownloaded = view.shareDownloadedCheckbox.model.isSelected()
|
||||
model.shareDownloadedFiles = shareDownloaded
|
||||
settings.shareDownloadedFiles = shareDownloaded
|
||||
|
||||
|
||||
String downloadLocation = model.downloadLocation
|
||||
settings.downloadLocation = new File(downloadLocation)
|
||||
|
||||
@ -90,70 +90,70 @@ class OptionsController {
|
||||
model.outBw = text
|
||||
settings.outBw = Integer.valueOf(text)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
boolean onlyTrusted = view.allowUntrustedCheckbox.model.isSelected()
|
||||
model.onlyTrusted = onlyTrusted
|
||||
settings.setAllowUntrusted(!onlyTrusted)
|
||||
|
||||
|
||||
boolean trustLists = view.allowTrustListsCheckbox.model.isSelected()
|
||||
model.trustLists = trustLists
|
||||
settings.allowTrustLists = trustLists
|
||||
|
||||
|
||||
String trustListInterval = view.trustListIntervalField.text
|
||||
model.trustListInterval = trustListInterval
|
||||
settings.trustListInterval = Integer.parseInt(trustListInterval)
|
||||
|
||||
File settingsFile = new File(core.home, "MuWire.properties")
|
||||
settingsFile.withOutputStream {
|
||||
settingsFile.withOutputStream {
|
||||
settings.write(it)
|
||||
}
|
||||
|
||||
|
||||
// UI Setttings
|
||||
|
||||
|
||||
UISettings uiSettings = application.context.get("ui-settings")
|
||||
text = view.lnfField.text
|
||||
model.lnf = text
|
||||
uiSettings.lnf = text
|
||||
|
||||
|
||||
text = view.fontField.text
|
||||
model.font = text
|
||||
uiSettings.font = text
|
||||
|
||||
|
||||
// boolean showMonitor = view.monitorCheckbox.model.isSelected()
|
||||
// model.showMonitor = showMonitor
|
||||
// uiSettings.showMonitor = showMonitor
|
||||
|
||||
|
||||
boolean clearCancelledDownloads = view.clearCancelledDownloadsCheckbox.model.isSelected()
|
||||
model.clearCancelledDownloads = clearCancelledDownloads
|
||||
uiSettings.clearCancelledDownloads = clearCancelledDownloads
|
||||
|
||||
|
||||
boolean clearFinishedDownloads = view.clearFinishedDownloadsCheckbox.model.isSelected()
|
||||
model.clearFinishedDownloads = clearFinishedDownloads
|
||||
uiSettings.clearFinishedDownloads = clearFinishedDownloads
|
||||
|
||||
|
||||
boolean excludeLocalResult = view.excludeLocalResultCheckbox.model.isSelected()
|
||||
model.excludeLocalResult = excludeLocalResult
|
||||
uiSettings.excludeLocalResult = excludeLocalResult
|
||||
|
||||
|
||||
// boolean showSearchHashes = view.showSearchHashesCheckbox.model.isSelected()
|
||||
// model.showSearchHashes = showSearchHashes
|
||||
// uiSettings.showSearchHashes = showSearchHashes
|
||||
|
||||
|
||||
File uiSettingsFile = new File(core.home, "gui.properties")
|
||||
uiSettingsFile.withOutputStream {
|
||||
uiSettingsFile.withOutputStream {
|
||||
uiSettings.write(it)
|
||||
}
|
||||
|
||||
|
||||
cancel()
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void cancel() {
|
||||
view.d.setVisible(false)
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void downloadLocation() {
|
||||
def chooser = new JFileChooser()
|
||||
|
@ -17,9 +17,9 @@ class TrustListController {
|
||||
TrustListModel model
|
||||
@MVCMember @Nonnull
|
||||
TrustListView view
|
||||
|
||||
|
||||
EventBus eventBus
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void trustFromTrusted() {
|
||||
int selectedRow = view.getSelectedRow("trusted-table")
|
||||
@ -29,7 +29,7 @@ class TrustListController {
|
||||
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED))
|
||||
view.fireUpdate("trusted-table")
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void trustFromDistrusted() {
|
||||
int selectedRow = view.getSelectedRow("distrusted-table")
|
||||
@ -39,7 +39,7 @@ class TrustListController {
|
||||
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.TRUSTED))
|
||||
view.fireUpdate("distrusted-table")
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void distrustFromTrusted() {
|
||||
int selectedRow = view.getSelectedRow("trusted-table")
|
||||
@ -49,7 +49,7 @@ class TrustListController {
|
||||
eventBus.publish(new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED))
|
||||
view.fireUpdate("trusted-table")
|
||||
}
|
||||
|
||||
|
||||
@ControllerAction
|
||||
void distrustFromDistrusted() {
|
||||
int selectedRow = view.getSelectedRow("distrusted-table")
|
||||
|
@ -34,24 +34,24 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
def home = portableHome == null ?
|
||||
selectHome() :
|
||||
portableHome
|
||||
|
||||
|
||||
home = new File(home)
|
||||
if (!home.exists()) {
|
||||
log.info("creating home dir $home")
|
||||
home.mkdirs()
|
||||
}
|
||||
|
||||
|
||||
application.context.put("muwire-home", home.getAbsolutePath())
|
||||
|
||||
|
||||
System.getProperties().setProperty("awt.useSystemAAFontSettings", "true")
|
||||
|
||||
|
||||
def guiPropsFile = new File(home, "gui.properties")
|
||||
UISettings uiSettings
|
||||
if (guiPropsFile.exists()) {
|
||||
Properties props = new Properties()
|
||||
guiPropsFile.withInputStream { props.load(it) }
|
||||
uiSettings = new UISettings(props)
|
||||
|
||||
|
||||
log.info("settting user-specified lnf $uiSettings.lnf")
|
||||
try {
|
||||
lookAndFeel(uiSettings.lnf)
|
||||
@ -59,7 +59,7 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
log.log(Level.WARNING,"couldn't set desired look and feeel, switching to defaults", bad)
|
||||
uiSettings.lnf = lookAndFeel("system","gtk","metal").getID()
|
||||
}
|
||||
|
||||
|
||||
if (uiSettings.font != null) {
|
||||
log.info("setting user-specified font $uiSettings.font")
|
||||
Font font = new Font(uiSettings.font, Font.PLAIN, 12)
|
||||
@ -90,10 +90,10 @@ class Initialize extends AbstractLifecycleHandler {
|
||||
log.info("ended up applying $chosen.name")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
application.context.put("ui-settings", uiSettings)
|
||||
}
|
||||
|
||||
|
||||
private static String selectHome() {
|
||||
def home = new File(System.properties["user.home"])
|
||||
def defaultHome = new File(home, ".MuWire")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user