Compare commits
54 Commits
muwire-0.4
...
split-ui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a54b2dcda | ||
|
|
581293b24f | ||
|
|
cd072b9f76 | ||
|
|
6b74fc5956 | ||
|
|
3de2f872bb | ||
|
|
fcde917d08 | ||
|
|
4ded065010 | ||
|
|
18a1c7091a | ||
|
|
46aee19f80 | ||
|
|
92dd7064c6 | ||
|
|
b2e4dda677 | ||
|
|
e77a2c8961 | ||
|
|
ee2fd2ef68 | ||
|
|
3f95d2bf1d | ||
|
|
1390983732 | ||
|
|
ce660cefe9 | ||
|
|
72b81eb886 | ||
|
|
57d593a68a | ||
|
|
39a81a3376 | ||
|
|
fd0bf17c24 | ||
|
|
ac12bff69b | ||
|
|
feef773bac | ||
|
|
239d8f12a7 | ||
|
|
8bbc61a7cb | ||
|
|
7f31c4477f | ||
|
|
6bad67c1bf | ||
|
|
c76e6dc99f | ||
|
|
acf9db0db3 | ||
|
|
69b4f0b547 | ||
|
|
80e165b505 | ||
|
|
bcce55b873 | ||
|
|
d5c92560db | ||
|
|
f827c1c9bf | ||
|
|
88c5f1a02d | ||
|
|
d8e44f5f39 | ||
|
|
72ff47ffe5 | ||
|
|
066ee2c96d | ||
|
|
0a8016dea7 | ||
|
|
db36367b11 | ||
|
|
b6c9ccb7f6 | ||
|
|
a9dc636bce | ||
|
|
3cc0574d11 | ||
|
|
20fab9b16d | ||
|
|
4015818323 | ||
|
|
f569d45c8c | ||
|
|
3773647869 | ||
|
|
29cdbf018c | ||
|
|
94bb7022eb | ||
|
|
39808302df | ||
|
|
2d22f9c39e | ||
|
|
ee8f80bab6 | ||
|
|
3e6242e583 | ||
|
|
41181616ee | ||
|
|
eb2530ca32 |
@@ -25,7 +25,11 @@ Some of the UI tests will fail because they haven't been written yet :-/
|
||||
|
||||
After you build the application, look inside `gui/build/distributions`. Untar/unzip one of the `shadow` files and then run the jar contained inside by typing `java -jar MuWire-x.y.z.jar` in a terminal or command prompt.
|
||||
|
||||
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `$HOME/.MuWire/i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there.
|
||||
If you have an I2P router running on the same machine that is all you need to do. If you use a custom I2CP host and port, create a file `i2p.properties` and put `i2cp.tcp.host=<host>` and `i2cp.tcp.port=<port>` in there. On Windows that file should go into `%HOME%\AppData\Roaming\MuWire`, on Mac into `$HOME/Library/Application Support/MuWire` and on Linux `$HOME/.MuWire`
|
||||
|
||||
If you do not have an I2P router, pass the following switch to the Java process: `-DembeddedRouter=true`. This will launch MuWire's embedded router. Be aware that this causes startup to take a lot longer.
|
||||
|
||||
### GPG Fingerprint
|
||||
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
||||
|
||||
You can find the full key at https://keybase.io/zlatinb
|
||||
|
||||
@@ -2,7 +2,7 @@ subprojects {
|
||||
apply plugin: 'groovy'
|
||||
|
||||
dependencies {
|
||||
compile 'net.i2p:i2p:0.9.40'
|
||||
compile 'net.i2p:i2p:0.9.41'
|
||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class Cli {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.7")
|
||||
core = new Core(props, home, "0.4.9")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
||||
@@ -53,7 +53,7 @@ class CliDownloader {
|
||||
|
||||
Core core
|
||||
try {
|
||||
core = new Core(props, home, "0.4.7")
|
||||
core = new Core(props, home, "0.4.9")
|
||||
} catch (Exception bad) {
|
||||
bad.printStackTrace(System.out)
|
||||
println "Failed to initialize core, exiting"
|
||||
|
||||
@@ -2,9 +2,9 @@ apply plugin : 'application'
|
||||
mainClassName = 'com.muwire.core.Core'
|
||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||
dependencies {
|
||||
compile 'net.i2p:router:0.9.40'
|
||||
compile 'net.i2p.client:mstreaming:0.9.40'
|
||||
compile 'net.i2p.client:streaming:0.9.40'
|
||||
compile 'net.i2p:router:0.9.41'
|
||||
compile 'net.i2p.client:mstreaming:0.9.41'
|
||||
compile 'net.i2p.client:streaming:0.9.41'
|
||||
|
||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
||||
@@ -20,6 +20,7 @@ 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
|
||||
@@ -47,6 +48,8 @@ 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 com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.content.ContentManager
|
||||
|
||||
import groovy.util.logging.Log
|
||||
import net.i2p.I2PAppContext
|
||||
@@ -89,6 +92,7 @@ public class Core {
|
||||
private final DirectoryWatcher directoryWatcher
|
||||
final FileManager fileManager
|
||||
final UploadManager uploadManager
|
||||
final ContentManager contentManager
|
||||
|
||||
private final Router router
|
||||
|
||||
@@ -140,33 +144,33 @@ public class Core {
|
||||
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
||||
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
||||
router = new Router(routerProps)
|
||||
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass()
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
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()
|
||||
i2pSession = socketManager.getSession()
|
||||
|
||||
def destination = new Destination()
|
||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||
@@ -175,7 +179,7 @@ public class Core {
|
||||
def privateKey = new PrivateKey()
|
||||
privateKey.readBytes(it)
|
||||
spk.readBytes(it)
|
||||
}
|
||||
}
|
||||
|
||||
def baos = new ByteArrayOutputStream()
|
||||
def daos = new DataOutputStream(baos)
|
||||
@@ -193,65 +197,65 @@ public class Core {
|
||||
me = new Persona(new ByteArrayInputStream(baos.toByteArray()))
|
||||
log.info("Loaded myself as "+me.getHumanReadableName())
|
||||
|
||||
eventBus = new EventBus()
|
||||
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 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)
|
||||
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)
|
||||
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")
|
||||
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)
|
||||
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) :
|
||||
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(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 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 connector")
|
||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||
|
||||
log.info "initializing results sender"
|
||||
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
||||
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 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)
|
||||
@@ -269,9 +273,9 @@ public class Core {
|
||||
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,
|
||||
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")
|
||||
@@ -288,7 +292,12 @@ public class Core {
|
||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||
eventBus.register(TrustSubscriptionEvent.class, trustSubscriber)
|
||||
}
|
||||
|
||||
log.info("initializing content manager")
|
||||
contentManager = new ContentManager()
|
||||
eventBus.register(ContentControlEvent.class, contentManager)
|
||||
eventBus.register(QueryEvent.class, contentManager)
|
||||
}
|
||||
|
||||
public void startServices() {
|
||||
hasherService.start()
|
||||
@@ -328,19 +337,6 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
static class RouterContextMetaClass extends DelegatingMetaClass {
|
||||
private final Object logManager = new MuWireLogManager()
|
||||
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)
|
||||
@@ -365,7 +361,7 @@ public class Core {
|
||||
}
|
||||
}
|
||||
|
||||
Core core = new Core(props, home, "0.4.7")
|
||||
Core core = new Core(props, home, "0.4.9")
|
||||
core.startServices()
|
||||
|
||||
// ... at the end, sleep or execute script
|
||||
|
||||
@@ -4,17 +4,17 @@ import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class Event {
|
||||
|
||||
private static final AtomicLong SEQ_NO = new AtomicLong();
|
||||
final long seqNo
|
||||
final long timestamp
|
||||
private static final AtomicLong SEQ_NO = new AtomicLong();
|
||||
final long seqNo
|
||||
final long timestamp
|
||||
|
||||
Event() {
|
||||
seqNo = SEQ_NO.getAndIncrement()
|
||||
timestamp = System.currentTimeMillis()
|
||||
}
|
||||
Event() {
|
||||
seqNo = SEQ_NO.getAndIncrement()
|
||||
timestamp = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"seqNo $seqNo timestamp $timestamp"
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
"seqNo $seqNo timestamp $timestamp"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,41 +11,46 @@ 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)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("event-bus")
|
||||
rv
|
||||
}
|
||||
private Map handlers = new HashMap()
|
||||
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("event-bus")
|
||||
rv
|
||||
}
|
||||
|
||||
void publish(Event e) {
|
||||
executor.execute({publishInternal(e)} as Runnable)
|
||||
}
|
||||
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
|
||||
final def clazz = e.getClass()
|
||||
synchronized(this) {
|
||||
currentHandlers = handlers.getOrDefault(clazz, [])
|
||||
}
|
||||
currentHandlers.each {
|
||||
private void publishInternal(Event e) {
|
||||
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
||||
def currentHandlers
|
||||
final def clazz = e.getClass()
|
||||
synchronized(this) {
|
||||
currentHandlers = handlers.getOrDefault(clazz, [])
|
||||
}
|
||||
currentHandlers.each {
|
||||
try {
|
||||
it."on${clazz.getSimpleName()}"(e)
|
||||
} catch (Exception bad) {
|
||||
log.log(Level.SEVERE, "exception dispatching event",bad)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void register(Class<? extends Event> eventType, def handler) {
|
||||
log.info "Registering $handler for type $eventType"
|
||||
def currentHandlers = handlers.get(eventType)
|
||||
if (currentHandlers == null) {
|
||||
currentHandlers = new CopyOnWriteArrayList()
|
||||
handlers.put(eventType, currentHandlers)
|
||||
}
|
||||
currentHandlers.add handler
|
||||
}
|
||||
synchronized void register(Class<? extends Event> eventType, def handler) {
|
||||
log.info "Registering $handler for type $eventType"
|
||||
def currentHandlers = handlers.get(eventType)
|
||||
if (currentHandlers == null) {
|
||||
currentHandlers = new CopyOnWriteArrayList()
|
||||
handlers.put(eventType, currentHandlers)
|
||||
}
|
||||
currentHandlers.add handler
|
||||
}
|
||||
|
||||
synchronized void unregister(Class<? extends Event> eventType, def handler) {
|
||||
log.info("Unregistering $handler for type $eventType")
|
||||
handlers[eventType]?.remove(handler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,17 +28,19 @@ class MuWireSettings {
|
||||
int meshExpiration
|
||||
boolean embeddedRouter
|
||||
int inBw, outBw
|
||||
Set<String> watchedKeywords
|
||||
Set<String> watchedRegexes
|
||||
|
||||
MuWireSettings() {
|
||||
MuWireSettings() {
|
||||
this(new Properties())
|
||||
}
|
||||
|
||||
MuWireSettings(Properties props) {
|
||||
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
||||
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
||||
MuWireSettings(Properties props) {
|
||||
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
||||
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
||||
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
||||
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
||||
crawlerResponse = CrawlerResponse.valueOf(props.get("crawlerResponse","REGISTERED"))
|
||||
nickname = props.getProperty("nickname","MuWireUser")
|
||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||
System.getProperty("user.home")))
|
||||
@@ -54,11 +56,9 @@ class MuWireSettings {
|
||||
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)) }
|
||||
}
|
||||
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
||||
|
||||
trustSubscriptions = new HashSet<>()
|
||||
if (props.containsKey("trustSubscriptions")) {
|
||||
@@ -66,7 +66,9 @@ class MuWireSettings {
|
||||
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void write(OutputStream out) throws IOException {
|
||||
Properties props = new Properties()
|
||||
@@ -89,12 +91,9 @@ class MuWireSettings {
|
||||
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)
|
||||
}
|
||||
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||
|
||||
if (!trustSubscriptions.isEmpty()) {
|
||||
String encoded = trustSubscriptions.stream().
|
||||
@@ -106,25 +105,43 @@ class MuWireSettings {
|
||||
props.store(out, "")
|
||||
}
|
||||
|
||||
boolean isLeaf() {
|
||||
isLeaf
|
||||
}
|
||||
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||
Set<String> rv = new HashSet<>()
|
||||
if (props.containsKey(property)) {
|
||||
String[] encoded = props.getProperty(property).split(",")
|
||||
encoded.each { rv << DataUtil.readi18nString(Base64.decode(it)) }
|
||||
}
|
||||
rv
|
||||
}
|
||||
|
||||
boolean allowUntrusted() {
|
||||
allowUntrusted
|
||||
}
|
||||
private static void writeEncodedSet(Set<String> set, String property, Properties props) {
|
||||
if (set.isEmpty())
|
||||
return
|
||||
String encoded = set.stream().
|
||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
||||
collect(Collectors.joining(","))
|
||||
props.setProperty(property, encoded)
|
||||
}
|
||||
|
||||
void setAllowUntrusted(boolean allowUntrusted) {
|
||||
this.allowUntrusted = allowUntrusted
|
||||
}
|
||||
boolean isLeaf() {
|
||||
isLeaf
|
||||
}
|
||||
|
||||
CrawlerResponse getCrawlerResponse() {
|
||||
crawlerResponse
|
||||
}
|
||||
boolean allowUntrusted() {
|
||||
allowUntrusted
|
||||
}
|
||||
|
||||
void setCrawlerResponse(CrawlerResponse crawlerResponse) {
|
||||
this.crawlerResponse = crawlerResponse
|
||||
}
|
||||
void setAllowUntrusted(boolean allowUntrusted) {
|
||||
this.allowUntrusted = allowUntrusted
|
||||
}
|
||||
|
||||
CrawlerResponse getCrawlerResponse() {
|
||||
crawlerResponse
|
||||
}
|
||||
|
||||
void setCrawlerResponse(CrawlerResponse crawlerResponse) {
|
||||
this.crawlerResponse = crawlerResponse
|
||||
}
|
||||
|
||||
String getNickname() {
|
||||
nickname
|
||||
|
||||
@@ -2,12 +2,12 @@ package com.muwire.core
|
||||
|
||||
abstract class Service {
|
||||
|
||||
volatile boolean loaded
|
||||
volatile boolean loaded
|
||||
|
||||
abstract void load()
|
||||
abstract void load()
|
||||
|
||||
void waitForLoad() {
|
||||
while (!loaded)
|
||||
Thread.sleep(10)
|
||||
}
|
||||
void waitForLoad() {
|
||||
while (!loaded)
|
||||
Thread.sleep(10)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,104 +25,104 @@ abstract class Connection implements Closeable {
|
||||
private static final int SEARCHES = 10
|
||||
private static final long INTERVAL = 1000
|
||||
|
||||
final EventBus eventBus
|
||||
final Endpoint endpoint
|
||||
final boolean incoming
|
||||
final HostCache hostCache
|
||||
final EventBus eventBus
|
||||
final Endpoint endpoint
|
||||
final boolean incoming
|
||||
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 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
|
||||
protected final String name
|
||||
|
||||
long lastPingSentTime, lastPongReceivedTime
|
||||
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
|
||||
this.endpoint = endpoint
|
||||
this.hostCache = hostCache
|
||||
this.eventBus = eventBus
|
||||
this.incoming = incoming
|
||||
this.endpoint = endpoint
|
||||
this.hostCache = hostCache
|
||||
this.trustService = trustService
|
||||
this.settings = settings
|
||||
|
||||
this.name = endpoint.destination.toBase32().substring(0,8)
|
||||
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.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)
|
||||
}
|
||||
this.writer = new Thread({writeLoop()} as Runnable)
|
||||
this.writer.setName("writer-$name")
|
||||
this.writer.setDaemon(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the connection threads
|
||||
*/
|
||||
void start() {
|
||||
if (!running.compareAndSet(false, true)) {
|
||||
log.log(Level.WARNING,"$name already running", new Exception())
|
||||
return
|
||||
}
|
||||
reader.start()
|
||||
writer.start()
|
||||
}
|
||||
/**
|
||||
* starts the connection threads
|
||||
*/
|
||||
void start() {
|
||||
if (!running.compareAndSet(false, true)) {
|
||||
log.log(Level.WARNING,"$name already running", new Exception())
|
||||
return
|
||||
}
|
||||
reader.start()
|
||||
writer.start()
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!running.compareAndSet(true, false)) {
|
||||
log.log(Level.WARNING, "$name already closed", new Exception() )
|
||||
return
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
if (!running.compareAndSet(true, false)) {
|
||||
log.log(Level.WARNING, "$name already closed", new Exception() )
|
||||
return
|
||||
}
|
||||
log.info("closing $name")
|
||||
reader.interrupt()
|
||||
writer.interrupt()
|
||||
endpoint.close()
|
||||
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
||||
}
|
||||
reader.interrupt()
|
||||
writer.interrupt()
|
||||
endpoint.close()
|
||||
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
||||
}
|
||||
|
||||
protected void readLoop() {
|
||||
try {
|
||||
while(running.get()) {
|
||||
read()
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
protected void readLoop() {
|
||||
try {
|
||||
while(running.get()) {
|
||||
read()
|
||||
}
|
||||
} catch (SocketTimeoutException e) {
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING,"unhandled exception in reader",e)
|
||||
} finally {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void read()
|
||||
protected abstract void read()
|
||||
|
||||
protected void writeLoop() {
|
||||
try {
|
||||
while(running.get()) {
|
||||
def message = messages.take()
|
||||
write(message)
|
||||
}
|
||||
} catch (Exception e) {
|
||||
protected void writeLoop() {
|
||||
try {
|
||||
while(running.get()) {
|
||||
def message = messages.take()
|
||||
write(message)
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "unhandled exception in writer",e)
|
||||
} finally {
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void write(def message);
|
||||
protected abstract void write(def message);
|
||||
|
||||
void sendPing() {
|
||||
def ping = [:]
|
||||
ping.type = "Ping"
|
||||
ping.version = 1
|
||||
messages.put(ping)
|
||||
lastPingSentTime = System.currentTimeMillis()
|
||||
}
|
||||
void sendPing() {
|
||||
def ping = [:]
|
||||
ping.type = "Ping"
|
||||
ping.version = 1
|
||||
messages.put(ping)
|
||||
lastPingSentTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
void sendQuery(QueryEvent e) {
|
||||
def query = [:]
|
||||
@@ -140,25 +140,25 @@ abstract class Connection implements Closeable {
|
||||
messages.put(query)
|
||||
}
|
||||
|
||||
protected void handlePing() {
|
||||
log.fine("$name received ping")
|
||||
def pong = [:]
|
||||
pong.type = "Pong"
|
||||
pong.version = 1
|
||||
pong.pongs = hostCache.getGoodHosts(10).collect { d -> d.toBase64() }
|
||||
messages.put(pong)
|
||||
}
|
||||
protected void handlePing() {
|
||||
log.fine("$name received ping")
|
||||
def pong = [:]
|
||||
pong.type = "Pong"
|
||||
pong.version = 1
|
||||
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 {
|
||||
def dest = new Destination(it)
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: dest))
|
||||
}
|
||||
}
|
||||
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 {
|
||||
def dest = new Destination(it)
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: dest))
|
||||
}
|
||||
}
|
||||
|
||||
private boolean throttleSearch() {
|
||||
final long now = System.currentTimeMillis()
|
||||
|
||||
@@ -29,97 +29,97 @@ import groovy.util.logging.Log
|
||||
@Log
|
||||
class ConnectionAcceptor {
|
||||
|
||||
final EventBus eventBus
|
||||
final UltrapeerConnectionManager manager
|
||||
final MuWireSettings settings
|
||||
final I2PAcceptor acceptor
|
||||
final HostCache hostCache
|
||||
final TrustService trustService
|
||||
final EventBus eventBus
|
||||
final UltrapeerConnectionManager manager
|
||||
final MuWireSettings settings
|
||||
final I2PAcceptor acceptor
|
||||
final HostCache hostCache
|
||||
final TrustService trustService
|
||||
final SearchManager searchManager
|
||||
final UploadManager uploadManager
|
||||
final ConnectionEstablisher establisher
|
||||
|
||||
final ExecutorService acceptorThread
|
||||
final ExecutorService handshakerThreads
|
||||
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,
|
||||
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
||||
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
||||
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
||||
ConnectionEstablisher establisher) {
|
||||
this.eventBus = eventBus
|
||||
this.manager = manager
|
||||
this.settings = settings
|
||||
this.acceptor = acceptor
|
||||
this.hostCache = hostCache
|
||||
this.trustService = trustService
|
||||
this.eventBus = eventBus
|
||||
this.manager = manager
|
||||
this.settings = settings
|
||||
this.acceptor = acceptor
|
||||
this.hostCache = hostCache
|
||||
this.trustService = trustService
|
||||
this.searchManager = searchManager
|
||||
this.uploadManager = uploadManager
|
||||
this.establisher = establisher
|
||||
this.establisher = establisher
|
||||
|
||||
acceptorThread = Executors.newSingleThreadExecutor { r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("acceptor")
|
||||
rv
|
||||
}
|
||||
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)
|
||||
rv.setName("acceptor-processor-${System.currentTimeMillis()}")
|
||||
rv
|
||||
}
|
||||
}
|
||||
handshakerThreads = Executors.newCachedThreadPool { r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("acceptor-processor-${System.currentTimeMillis()}")
|
||||
rv
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
acceptorThread.execute({acceptLoop()} as Runnable)
|
||||
}
|
||||
void start() {
|
||||
acceptorThread.execute({acceptLoop()} as Runnable)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
void stop() {
|
||||
shutdown = true
|
||||
acceptorThread.shutdownNow()
|
||||
handshakerThreads.shutdownNow()
|
||||
}
|
||||
acceptorThread.shutdownNow()
|
||||
handshakerThreads.shutdownNow()
|
||||
}
|
||||
|
||||
private void acceptLoop() {
|
||||
private void acceptLoop() {
|
||||
try {
|
||||
while(true) {
|
||||
def incoming = acceptor.accept()
|
||||
log.info("accepted connection from ${incoming.destination.toBase32()}")
|
||||
switch(trustService.getLevel(incoming.destination)) {
|
||||
case TrustLevel.TRUSTED : break
|
||||
case TrustLevel.NEUTRAL :
|
||||
if (settings.allowUntrusted())
|
||||
break
|
||||
case TrustLevel.DISTRUSTED :
|
||||
log.info("Disallowing distrusted connection")
|
||||
incoming.close()
|
||||
continue
|
||||
}
|
||||
handshakerThreads.execute({processIncoming(incoming)} as Runnable)
|
||||
}
|
||||
while(true) {
|
||||
def incoming = acceptor.accept()
|
||||
log.info("accepted connection from ${incoming.destination.toBase32()}")
|
||||
switch(trustService.getLevel(incoming.destination)) {
|
||||
case TrustLevel.TRUSTED : break
|
||||
case TrustLevel.NEUTRAL :
|
||||
if (settings.allowUntrusted())
|
||||
break
|
||||
case TrustLevel.DISTRUSTED :
|
||||
log.info("Disallowing distrusted connection")
|
||||
incoming.close()
|
||||
continue
|
||||
}
|
||||
handshakerThreads.execute({processIncoming(incoming)} as Runnable)
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "exception in accept loop",e)
|
||||
if (!shutdown)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processIncoming(Endpoint e) {
|
||||
InputStream is = e.inputStream
|
||||
try {
|
||||
int read = is.read()
|
||||
switch(read) {
|
||||
case (byte)'M':
|
||||
private void processIncoming(Endpoint e) {
|
||||
InputStream is = e.inputStream
|
||||
try {
|
||||
int read = is.read()
|
||||
switch(read) {
|
||||
case (byte)'M':
|
||||
if (settings.isLeaf())
|
||||
throw new IOException("Incoming connection as leaf")
|
||||
processMuWire(e)
|
||||
break
|
||||
case (byte)'G':
|
||||
processGET(e)
|
||||
break
|
||||
processMuWire(e)
|
||||
break
|
||||
case (byte)'G':
|
||||
processGET(e)
|
||||
break
|
||||
case (byte)'H':
|
||||
processHashList(e)
|
||||
break
|
||||
@@ -129,28 +129,28 @@ class ConnectionAcceptor {
|
||||
case (byte)'T':
|
||||
processTRUST(e)
|
||||
break
|
||||
default:
|
||||
throw new Exception("Invalid read $read")
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.log(Level.WARNING, "incoming connection failed",ex)
|
||||
e.close()
|
||||
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new Exception("Invalid read $read")
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
log.log(Level.WARNING, "incoming connection failed",ex)
|
||||
e.close()
|
||||
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++) {
|
||||
int read = e.inputStream.read()
|
||||
if (read != uWire[i]) {
|
||||
throw new IOException("unexpected value $read at position $i")
|
||||
}
|
||||
}
|
||||
private void processMuWire(Endpoint e) {
|
||||
byte[] uWire = "uWire ".bytes
|
||||
for (int i = 0; i < uWire.length; i++) {
|
||||
int read = e.inputStream.read()
|
||||
if (read != uWire[i]) {
|
||||
throw new IOException("unexpected value $read at position $i")
|
||||
}
|
||||
}
|
||||
|
||||
byte[] type = new byte[4]
|
||||
DataInputStream dis = new DataInputStream(e.inputStream)
|
||||
dis.readFully(type)
|
||||
byte[] type = new byte[4]
|
||||
DataInputStream dis = new DataInputStream(e.inputStream)
|
||||
dis.readFully(type)
|
||||
|
||||
if (type == "leaf".bytes)
|
||||
handleIncoming(e, true)
|
||||
@@ -160,44 +160,44 @@ class ConnectionAcceptor {
|
||||
throw new IOException("unknown connection type $type")
|
||||
}
|
||||
|
||||
private void handleIncoming(Endpoint e, boolean leaf) {
|
||||
boolean accept = !manager.isConnected(e.destination) &&
|
||||
private void handleIncoming(Endpoint e, boolean leaf) {
|
||||
boolean accept = !manager.isConnected(e.destination) &&
|
||||
!establisher.isInProgress(e.destination) &&
|
||||
(leaf ? manager.hasLeafSlots() : manager.hasPeerSlots())
|
||||
if (accept) {
|
||||
log.info("accepting connection, leaf:$leaf")
|
||||
e.outputStream.write("OK".bytes)
|
||||
e.outputStream.flush()
|
||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
||||
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||
} else {
|
||||
log.info("rejecting connection, leaf:$leaf")
|
||||
e.outputStream.write("REJECT".bytes)
|
||||
def hosts = hostCache.getGoodHosts(10)
|
||||
if (!hosts.isEmpty()) {
|
||||
def json = [:]
|
||||
json.tryHosts = hosts.collect { d -> d.toBase64() }
|
||||
json = JsonOutput.toJson(json)
|
||||
def os = new DataOutputStream(e.outputStream)
|
||||
os.writeShort(json.bytes.length)
|
||||
os.write(json.bytes)
|
||||
}
|
||||
e.outputStream.flush()
|
||||
e.close()
|
||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.REJECTED))
|
||||
}
|
||||
}
|
||||
if (accept) {
|
||||
log.info("accepting connection, leaf:$leaf")
|
||||
e.outputStream.write("OK".bytes)
|
||||
e.outputStream.flush()
|
||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
||||
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||
} else {
|
||||
log.info("rejecting connection, leaf:$leaf")
|
||||
e.outputStream.write("REJECT".bytes)
|
||||
def hosts = hostCache.getGoodHosts(10)
|
||||
if (!hosts.isEmpty()) {
|
||||
def json = [:]
|
||||
json.tryHosts = hosts.collect { d -> d.toBase64() }
|
||||
json = JsonOutput.toJson(json)
|
||||
def os = new DataOutputStream(e.outputStream)
|
||||
os.writeShort(json.bytes.length)
|
||||
os.write(json.bytes)
|
||||
}
|
||||
e.outputStream.flush()
|
||||
e.close()
|
||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.REJECTED))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void processGET(Endpoint e) {
|
||||
private void processGET(Endpoint e) {
|
||||
byte[] et = new byte[3]
|
||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||
dis.readFully(et)
|
||||
if (et != "ET ".getBytes(StandardCharsets.US_ASCII))
|
||||
throw new IOException("Invalid GET connection")
|
||||
uploadManager.processGET(e)
|
||||
}
|
||||
}
|
||||
|
||||
private void processHashList(Endpoint e) {
|
||||
byte[] ashList = new byte[8]
|
||||
|
||||
@@ -22,162 +22,162 @@ import net.i2p.util.ConcurrentHashSet
|
||||
@Log
|
||||
class ConnectionEstablisher {
|
||||
|
||||
private static final int CONCURRENT = 4
|
||||
private static final int CONCURRENT = 4
|
||||
|
||||
final EventBus eventBus
|
||||
final I2PConnector i2pConnector
|
||||
final MuWireSettings settings
|
||||
final ConnectionManager connectionManager
|
||||
final HostCache hostCache
|
||||
final EventBus eventBus
|
||||
final I2PConnector i2pConnector
|
||||
final MuWireSettings settings
|
||||
final ConnectionManager connectionManager
|
||||
final HostCache hostCache
|
||||
|
||||
final Timer timer
|
||||
final ExecutorService executor
|
||||
final Timer timer
|
||||
final ExecutorService executor
|
||||
|
||||
final Set inProgress = new ConcurrentHashSet()
|
||||
final Set inProgress = new ConcurrentHashSet()
|
||||
|
||||
ConnectionEstablisher(){}
|
||||
|
||||
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
||||
ConnectionManager connectionManager, HostCache hostCache) {
|
||||
this.eventBus = eventBus
|
||||
this.i2pConnector = i2pConnector
|
||||
this.settings = settings
|
||||
this.connectionManager = connectionManager
|
||||
this.hostCache = hostCache
|
||||
timer = new Timer("connection-timer",true)
|
||||
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("connector-${System.currentTimeMillis()}")
|
||||
rv
|
||||
} as ThreadFactory)
|
||||
}
|
||||
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
||||
ConnectionManager connectionManager, HostCache hostCache) {
|
||||
this.eventBus = eventBus
|
||||
this.i2pConnector = i2pConnector
|
||||
this.settings = settings
|
||||
this.connectionManager = connectionManager
|
||||
this.hostCache = hostCache
|
||||
timer = new Timer("connection-timer",true)
|
||||
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
||||
def rv = new Thread(r)
|
||||
rv.setDaemon(true)
|
||||
rv.setName("connector-${System.currentTimeMillis()}")
|
||||
rv
|
||||
} as ThreadFactory)
|
||||
}
|
||||
|
||||
void start() {
|
||||
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
||||
}
|
||||
void start() {
|
||||
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
executor.shutdownNow()
|
||||
}
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
executor.shutdownNow()
|
||||
}
|
||||
|
||||
private void connectIfNeeded() {
|
||||
if (!connectionManager.needsConnections())
|
||||
return
|
||||
if (inProgress.size() >= CONCURRENT)
|
||||
return
|
||||
private void connectIfNeeded() {
|
||||
if (!connectionManager.needsConnections())
|
||||
return
|
||||
if (inProgress.size() >= CONCURRENT)
|
||||
return
|
||||
|
||||
def toTry = null
|
||||
for (int i = 0; i < 5; i++) {
|
||||
toTry = hostCache.getHosts(1)
|
||||
if (toTry.isEmpty())
|
||||
return
|
||||
toTry = toTry[0]
|
||||
if (!connectionManager.isConnected(toTry) &&
|
||||
!inProgress.contains(toTry)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (toTry == null)
|
||||
return
|
||||
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
||||
executor.execute({connect(toTry)} as Runnable)
|
||||
}
|
||||
def toTry = null
|
||||
for (int i = 0; i < 5; i++) {
|
||||
toTry = hostCache.getHosts(1)
|
||||
if (toTry.isEmpty())
|
||||
return
|
||||
toTry = toTry[0]
|
||||
if (!connectionManager.isConnected(toTry) &&
|
||||
!inProgress.contains(toTry)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (toTry == null)
|
||||
return
|
||||
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()}")
|
||||
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()
|
||||
// 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) {
|
||||
fail endpoint
|
||||
return
|
||||
}
|
||||
switch(read) {
|
||||
case (byte)'O': readK(endpoint); break
|
||||
case (byte)'R': readEJECT(endpoint); break
|
||||
default :
|
||||
log.warning("unknown response $read")
|
||||
fail endpoint
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
||||
def endpoint = new Endpoint(toTry, null, null, null)
|
||||
fail(endpoint)
|
||||
} finally {
|
||||
inProgress.remove(toTry)
|
||||
}
|
||||
}
|
||||
InputStream is = endpoint.inputStream
|
||||
int read = is.read()
|
||||
if (read == -1) {
|
||||
fail endpoint
|
||||
return
|
||||
}
|
||||
switch(read) {
|
||||
case (byte)'O': readK(endpoint); break
|
||||
case (byte)'R': readEJECT(endpoint); break
|
||||
default :
|
||||
log.warning("unknown response $read")
|
||||
fail endpoint
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
||||
def endpoint = new Endpoint(toTry, null, null, null)
|
||||
fail(endpoint)
|
||||
} finally {
|
||||
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 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') {
|
||||
log.warning("unknown response after O: $read")
|
||||
fail e
|
||||
return
|
||||
}
|
||||
private void readK(Endpoint e) {
|
||||
int read = e.inputStream.read()
|
||||
if (read != 'K') {
|
||||
log.warning("unknown response after O: $read")
|
||||
fail e
|
||||
return
|
||||
}
|
||||
|
||||
log.info("connection to ${e.destination.toBase32()} established")
|
||||
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))
|
||||
}
|
||||
// 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++) {
|
||||
int read = e.inputStream.read()
|
||||
if (read != eject[i]) {
|
||||
log.warning("Unknown response after R at position $i")
|
||||
fail e
|
||||
return
|
||||
}
|
||||
}
|
||||
log.info("connection to ${e.destination.toBase32()} rejected")
|
||||
private void readEJECT(Endpoint e) {
|
||||
byte[] eject = "EJECT".bytes
|
||||
for (int i = 0; i < eject.length; i++) {
|
||||
int read = e.inputStream.read()
|
||||
if (read != eject[i]) {
|
||||
log.warning("Unknown response after R at position $i")
|
||||
fail e
|
||||
return
|
||||
}
|
||||
}
|
||||
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)
|
||||
int payloadSize = dais.readUnsignedShort()
|
||||
byte[] payload = new byte[payloadSize]
|
||||
dais.readFully(payload)
|
||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
||||
try {
|
||||
DataInputStream dais = new DataInputStream(e.inputStream)
|
||||
int payloadSize = dais.readUnsignedShort()
|
||||
byte[] payload = new byte[payloadSize]
|
||||
dais.readFully(payload)
|
||||
|
||||
def json = new JsonSlurper()
|
||||
json = json.parse(payload)
|
||||
def json = new JsonSlurper()
|
||||
json = json.parse(payload)
|
||||
|
||||
if (json.tryHosts == null) {
|
||||
log.warning("post-rejection json didn't contain hosts to try")
|
||||
return
|
||||
}
|
||||
if (json.tryHosts == null) {
|
||||
log.warning("post-rejection json didn't contain hosts to try")
|
||||
return
|
||||
}
|
||||
|
||||
json.tryHosts.asList().each {
|
||||
Destination suggested = new Destination(it)
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
||||
} finally {
|
||||
// the end
|
||||
e.close()
|
||||
}
|
||||
}
|
||||
json.tryHosts.asList().each {
|
||||
Destination suggested = new Destination(it)
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
||||
} finally {
|
||||
// the end
|
||||
e.close()
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInProgress(Destination d) {
|
||||
inProgress.contains(d)
|
||||
|
||||
@@ -6,14 +6,14 @@ import net.i2p.data.Destination
|
||||
|
||||
class ConnectionEvent extends Event {
|
||||
|
||||
Endpoint endpoint
|
||||
boolean incoming
|
||||
Boolean leaf // can be null if uknown
|
||||
ConnectionAttemptStatus status
|
||||
Endpoint endpoint
|
||||
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"
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,63 +12,63 @@ import net.i2p.data.Destination
|
||||
|
||||
abstract class ConnectionManager {
|
||||
|
||||
private static final int PING_TIME = 20000
|
||||
private static final int PING_TIME = 20000
|
||||
|
||||
final EventBus eventBus
|
||||
final EventBus eventBus
|
||||
|
||||
private final Timer timer
|
||||
private final Timer timer
|
||||
|
||||
protected final HostCache hostCache
|
||||
protected final HostCache hostCache
|
||||
protected final Persona me
|
||||
protected final MuWireSettings settings
|
||||
|
||||
ConnectionManager() {}
|
||||
ConnectionManager() {}
|
||||
|
||||
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
||||
this.eventBus = eventBus
|
||||
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
||||
this.eventBus = eventBus
|
||||
this.me = me
|
||||
this.hostCache = hostCache
|
||||
this.hostCache = hostCache
|
||||
this.settings = settings
|
||||
this.timer = new Timer("connections-pinger",true)
|
||||
}
|
||||
this.timer = new Timer("connections-pinger",true)
|
||||
}
|
||||
|
||||
void start() {
|
||||
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
||||
}
|
||||
void start() {
|
||||
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
getConnections().each { it.close() }
|
||||
}
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
getConnections().each { it.close() }
|
||||
}
|
||||
|
||||
void onTrustEvent(TrustEvent e) {
|
||||
if (e.level == TrustLevel.DISTRUSTED)
|
||||
drop(e.persona.destination)
|
||||
}
|
||||
void onTrustEvent(TrustEvent e) {
|
||||
if (e.level == TrustLevel.DISTRUSTED)
|
||||
drop(e.persona.destination)
|
||||
}
|
||||
|
||||
abstract void drop(Destination d)
|
||||
abstract void drop(Destination d)
|
||||
|
||||
abstract Collection<Connection> getConnections()
|
||||
abstract Collection<Connection> getConnections()
|
||||
|
||||
protected abstract int getDesiredConnections()
|
||||
protected abstract int getDesiredConnections()
|
||||
|
||||
boolean needsConnections() {
|
||||
return getConnections().size() < getDesiredConnections()
|
||||
}
|
||||
boolean needsConnections() {
|
||||
return getConnections().size() < getDesiredConnections()
|
||||
}
|
||||
|
||||
abstract boolean isConnected(Destination d)
|
||||
abstract boolean isConnected(Destination d)
|
||||
|
||||
abstract void onConnectionEvent(ConnectionEvent e)
|
||||
abstract void onConnectionEvent(ConnectionEvent e)
|
||||
|
||||
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
||||
abstract void onDisconnectionEvent(DisconnectionEvent e)
|
||||
|
||||
abstract void shutdown()
|
||||
|
||||
protected void sendPings() {
|
||||
final long now = System.currentTimeMillis()
|
||||
getConnections().each {
|
||||
if (now - it.lastPingSentTime > PING_TIME)
|
||||
it.sendPing()
|
||||
}
|
||||
}
|
||||
protected void sendPings() {
|
||||
final long now = System.currentTimeMillis()
|
||||
getConnections().each {
|
||||
if (now - it.lastPingSentTime > PING_TIME)
|
||||
it.sendPing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import net.i2p.data.Destination
|
||||
|
||||
class DisconnectionEvent extends Event {
|
||||
|
||||
Destination destination
|
||||
Destination destination
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,39 +8,39 @@ import net.i2p.data.Destination
|
||||
|
||||
@Log
|
||||
class Endpoint implements Closeable {
|
||||
final Destination destination
|
||||
final InputStream inputStream
|
||||
final OutputStream outputStream
|
||||
final Destination destination
|
||||
final InputStream inputStream
|
||||
final OutputStream outputStream
|
||||
final def toClose
|
||||
|
||||
private final AtomicBoolean closed = new AtomicBoolean()
|
||||
private final AtomicBoolean closed = new AtomicBoolean()
|
||||
|
||||
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
||||
this.destination = destination
|
||||
this.inputStream = inputStream
|
||||
this.outputStream = outputStream
|
||||
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)) {
|
||||
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
||||
return
|
||||
}
|
||||
if (inputStream != null) {
|
||||
try {inputStream.close()} catch (Exception ignore) {}
|
||||
}
|
||||
if (outputStream != null) {
|
||||
try {outputStream.close()} catch (Exception ignore) {}
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
if (!closed.compareAndSet(false, true)) {
|
||||
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
||||
return
|
||||
}
|
||||
if (inputStream != null) {
|
||||
try {inputStream.close()} catch (Exception ignore) {}
|
||||
}
|
||||
if (outputStream != null) {
|
||||
try {outputStream.close()} catch (Exception ignore) {}
|
||||
}
|
||||
if (toClose != null) {
|
||||
try {toClose.reset()} catch (Exception ignore) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"destination: ${destination.toBase32()}"
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
"destination: ${destination.toBase32()}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,18 +5,18 @@ import net.i2p.client.streaming.I2PSocketManager
|
||||
|
||||
class I2PAcceptor {
|
||||
|
||||
final I2PSocketManager socketManager
|
||||
final I2PServerSocket serverSocket
|
||||
final I2PSocketManager socketManager
|
||||
final I2PServerSocket serverSocket
|
||||
|
||||
I2PAcceptor() {}
|
||||
I2PAcceptor() {}
|
||||
|
||||
I2PAcceptor(I2PSocketManager socketManager) {
|
||||
this.socketManager = socketManager
|
||||
this.serverSocket = socketManager.getServerSocket()
|
||||
}
|
||||
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)
|
||||
}
|
||||
Endpoint accept() {
|
||||
def socket = serverSocket.accept()
|
||||
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,17 @@ import net.i2p.data.Destination
|
||||
|
||||
class I2PConnector {
|
||||
|
||||
final I2PSocketManager socketManager
|
||||
final I2PSocketManager socketManager
|
||||
|
||||
I2PConnector() {}
|
||||
I2PConnector() {}
|
||||
|
||||
I2PConnector(I2PSocketManager socketManager) {
|
||||
this.socketManager = socketManager
|
||||
}
|
||||
I2PConnector(I2PSocketManager socketManager) {
|
||||
this.socketManager = socketManager
|
||||
}
|
||||
|
||||
Endpoint connect(Destination dest) {
|
||||
def socket = socketManager.connect(dest)
|
||||
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
||||
}
|
||||
Endpoint connect(Destination dest) {
|
||||
def socket = socketManager.connect(dest)
|
||||
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,21 +17,21 @@ import net.i2p.data.Destination
|
||||
*/
|
||||
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);
|
||||
}
|
||||
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void read() {
|
||||
// TODO Auto-generated method stub
|
||||
@Override
|
||||
protected void read() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(Object message) {
|
||||
// TODO Auto-generated method stub
|
||||
@Override
|
||||
protected void write(Object message) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,21 +14,21 @@ import net.i2p.data.Destination
|
||||
@Log
|
||||
class LeafConnectionManager extends ConnectionManager {
|
||||
|
||||
final int maxConnections
|
||||
final int maxConnections
|
||||
|
||||
final Map<Destination, UltrapeerConnection> connections = new ConcurrentHashMap()
|
||||
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
|
||||
}
|
||||
super(eventBus, me, hostCache, settings)
|
||||
this.maxConnections = maxConnections
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drop(Destination d) {
|
||||
// TODO Auto-generated method stub
|
||||
@Override
|
||||
public void drop(Destination d) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
if (me.destination == e.receivedOn) {
|
||||
@@ -37,41 +37,41 @@ class LeafConnectionManager extends ConnectionManager {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Connection> getConnections() {
|
||||
connections.values()
|
||||
}
|
||||
@Override
|
||||
public Collection<Connection> getConnections() {
|
||||
connections.values()
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDesiredConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
@Override
|
||||
protected int getDesiredConnections() {
|
||||
return maxConnections;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected(Destination d) {
|
||||
connections.containsKey(d)
|
||||
}
|
||||
@Override
|
||||
public boolean isConnected(Destination d) {
|
||||
connections.containsKey(d)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionEvent(ConnectionEvent e) {
|
||||
if (e.incoming || e.leaf) {
|
||||
log.severe("Got inconsistent event as a leaf! $e")
|
||||
return
|
||||
}
|
||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||
return
|
||||
@Override
|
||||
public void onConnectionEvent(ConnectionEvent e) {
|
||||
if (e.incoming || e.leaf) {
|
||||
log.severe("Got inconsistent event as a leaf! $e")
|
||||
return
|
||||
}
|
||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||
return
|
||||
|
||||
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
||||
connections.put(e.endpoint.destination, c)
|
||||
c.start()
|
||||
}
|
||||
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
||||
connections.put(e.endpoint.destination, c)
|
||||
c.start()
|
||||
}
|
||||
|
||||
@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
|
||||
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() {
|
||||
|
||||
@@ -21,62 +21,62 @@ import net.i2p.data.Destination
|
||||
@Log
|
||||
class PeerConnection extends Connection {
|
||||
|
||||
private final DataInputStream dis
|
||||
private final DataOutputStream dos
|
||||
private final DataInputStream dis
|
||||
private final DataOutputStream dos
|
||||
|
||||
private final byte[] readHeader = new byte[3]
|
||||
private final byte[] writeHeader = new byte[3]
|
||||
private final byte[] readHeader = new byte[3]
|
||||
private final byte[] writeHeader = new byte[3]
|
||||
|
||||
private final JsonSlurper slurper = new JsonSlurper()
|
||||
private final JsonSlurper slurper = new JsonSlurper()
|
||||
|
||||
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
||||
boolean incoming, HostCache hostCache, TrustService trustService,
|
||||
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
||||
boolean incoming, HostCache hostCache, TrustService trustService,
|
||||
MuWireSettings settings) {
|
||||
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
||||
this.dis = new DataInputStream(endpoint.inputStream)
|
||||
this.dos = new DataOutputStream(endpoint.outputStream)
|
||||
}
|
||||
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
||||
this.dis = new DataInputStream(endpoint.inputStream)
|
||||
this.dos = new DataOutputStream(endpoint.outputStream)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void read() {
|
||||
dis.readFully(readHeader)
|
||||
int length = DataUtil.readLength(readHeader)
|
||||
log.fine("$name read length $length")
|
||||
@Override
|
||||
protected void read() {
|
||||
dis.readFully(readHeader)
|
||||
int length = DataUtil.readLength(readHeader)
|
||||
log.fine("$name read length $length")
|
||||
|
||||
byte[] payload = new byte[length]
|
||||
dis.readFully(payload)
|
||||
byte[] payload = new byte[length]
|
||||
dis.readFully(payload)
|
||||
|
||||
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
||||
// TODO process binary
|
||||
} else {
|
||||
def json = slurper.parse(payload)
|
||||
if (json.type == null)
|
||||
throw new Exception("missing json type")
|
||||
switch(json.type) {
|
||||
case "Ping" : handlePing(); break;
|
||||
case "Pong" : handlePong(json); break;
|
||||
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
||||
// TODO process binary
|
||||
} else {
|
||||
def json = slurper.parse(payload)
|
||||
if (json.type == null)
|
||||
throw new Exception("missing json type")
|
||||
switch(json.type) {
|
||||
case "Ping" : handlePing(); break;
|
||||
case "Pong" : handlePong(json); break;
|
||||
case "Search": handleSearch(json); break
|
||||
default :
|
||||
throw new Exception("unknown json type ${json.type}")
|
||||
}
|
||||
}
|
||||
}
|
||||
default :
|
||||
throw new Exception("unknown json type ${json.type}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(Object message) {
|
||||
byte[] payload
|
||||
if (message instanceof Map) {
|
||||
payload = JsonOutput.toJson(message).bytes
|
||||
DataUtil.packHeader(payload.length, writeHeader)
|
||||
log.fine "$name writing message type ${message.type} length $payload.length"
|
||||
writeHeader[0] &= (byte)0x7F
|
||||
} else {
|
||||
// TODO: write binary
|
||||
}
|
||||
@Override
|
||||
protected void write(Object message) {
|
||||
byte[] payload
|
||||
if (message instanceof Map) {
|
||||
payload = JsonOutput.toJson(message).bytes
|
||||
DataUtil.packHeader(payload.length, writeHeader)
|
||||
log.fine "$name writing message type ${message.type} length $payload.length"
|
||||
writeHeader[0] &= (byte)0x7F
|
||||
} else {
|
||||
// TODO: write binary
|
||||
}
|
||||
|
||||
dos.write(writeHeader)
|
||||
dos.write(payload)
|
||||
dos.flush()
|
||||
}
|
||||
dos.write(writeHeader)
|
||||
dos.write(payload)
|
||||
dos.flush()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,30 +17,30 @@ import net.i2p.data.Destination
|
||||
*/
|
||||
class UltrapeerConnection extends Connection {
|
||||
|
||||
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
||||
super(eventBus, endpoint, false, hostCache, trustService)
|
||||
}
|
||||
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
||||
super(eventBus, endpoint, false, hostCache, trustService)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void read() {
|
||||
// TODO Auto-generated method stub
|
||||
@Override
|
||||
protected void read() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(Object message) {
|
||||
if (message instanceof Map) {
|
||||
writeJsonMessage(message)
|
||||
} else {
|
||||
writeBinaryMessage(message)
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void write(Object message) {
|
||||
if (message instanceof Map) {
|
||||
writeJsonMessage(message)
|
||||
} else {
|
||||
writeBinaryMessage(message)
|
||||
}
|
||||
}
|
||||
|
||||
private void writeJsonMessage(def message) {
|
||||
private void writeJsonMessage(def message) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBinaryMessage(def message) {
|
||||
private void writeBinaryMessage(def message) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,26 +16,26 @@ import net.i2p.data.Destination
|
||||
@Log
|
||||
class UltrapeerConnectionManager extends ConnectionManager {
|
||||
|
||||
final int maxPeers, maxLeafs
|
||||
final int maxPeers, maxLeafs
|
||||
final TrustService trustService
|
||||
|
||||
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
||||
final Map<Destination, LeafConnection> leafConnections = new ConcurrentHashMap()
|
||||
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
||||
final Map<Destination, LeafConnection> leafConnections = new ConcurrentHashMap()
|
||||
|
||||
UltrapeerConnectionManager() {}
|
||||
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
|
||||
this.maxLeafs = maxLeafs
|
||||
super(eventBus, me, hostCache, settings)
|
||||
this.maxPeers = maxPeers
|
||||
this.maxLeafs = maxLeafs
|
||||
this.trustService = trustService
|
||||
}
|
||||
@Override
|
||||
public void drop(Destination d) {
|
||||
peerConnections.get(d)?.close()
|
||||
}
|
||||
@Override
|
||||
public void drop(Destination d) {
|
||||
peerConnections.get(d)?.close()
|
||||
leafConnections.get(d)?.close()
|
||||
}
|
||||
}
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
forwardQueryToLeafs(e)
|
||||
@@ -50,57 +50,57 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Connection> getConnections() {
|
||||
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
||||
rv.addAll(peerConnections.values())
|
||||
rv.addAll(leafConnections.values())
|
||||
rv
|
||||
}
|
||||
@Override
|
||||
public Collection<Connection> getConnections() {
|
||||
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
||||
rv.addAll(peerConnections.values())
|
||||
rv.addAll(leafConnections.values())
|
||||
rv
|
||||
}
|
||||
|
||||
boolean hasLeafSlots() {
|
||||
leafConnections.size() < maxLeafs
|
||||
}
|
||||
boolean hasLeafSlots() {
|
||||
leafConnections.size() < maxLeafs
|
||||
}
|
||||
|
||||
boolean hasPeerSlots() {
|
||||
peerConnections.size() < maxPeers
|
||||
}
|
||||
boolean hasPeerSlots() {
|
||||
peerConnections.size() < maxPeers
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getDesiredConnections() {
|
||||
return maxPeers / 2;
|
||||
}
|
||||
@Override
|
||||
public boolean isConnected(Destination d) {
|
||||
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
||||
}
|
||||
@Override
|
||||
protected int getDesiredConnections() {
|
||||
return maxPeers / 2;
|
||||
}
|
||||
@Override
|
||||
public boolean isConnected(Destination d) {
|
||||
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionEvent(ConnectionEvent e) {
|
||||
if (!e.incoming && e.leaf) {
|
||||
log.severe("Inconsistent event $e")
|
||||
return
|
||||
}
|
||||
@Override
|
||||
public void onConnectionEvent(ConnectionEvent e) {
|
||||
if (!e.incoming && e.leaf) {
|
||||
log.severe("Inconsistent event $e")
|
||||
return
|
||||
}
|
||||
|
||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||
return
|
||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||
return
|
||||
|
||||
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()
|
||||
}
|
||||
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)
|
||||
if (removed == null)
|
||||
removed = leafConnections.remove(e.destination)
|
||||
if (removed == null)
|
||||
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
||||
}
|
||||
@Override
|
||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||
def removed = peerConnections.remove(e.destination)
|
||||
if (removed == null)
|
||||
removed = leafConnections.remove(e.destination)
|
||||
if (removed == null)
|
||||
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
||||
}
|
||||
|
||||
@Override
|
||||
void shutdown() {
|
||||
@@ -110,7 +110,7 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
||||
leafConnections.clear()
|
||||
}
|
||||
|
||||
void forwardQueryToLeafs(QueryEvent e) {
|
||||
void forwardQueryToLeafs(QueryEvent e) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import com.muwire.core.Event
|
||||
|
||||
class ContentControlEvent extends Event {
|
||||
String term
|
||||
boolean regex
|
||||
boolean add
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import com.muwire.core.search.QueryEvent
|
||||
|
||||
import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class ContentManager {
|
||||
|
||||
Set<Matcher> matchers = new ConcurrentHashSet()
|
||||
|
||||
void onContentControlEvent(ContentControlEvent e) {
|
||||
Matcher m
|
||||
if (e.regex)
|
||||
m = new RegexMatcher(e.term)
|
||||
else
|
||||
m = new KeywordMatcher(e.term)
|
||||
if (e.add)
|
||||
matchers.add(m)
|
||||
else
|
||||
matchers.remove(m)
|
||||
}
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
if (e.searchEvent.searchTerms == null)
|
||||
return
|
||||
matchers.each { it.process(e) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
class KeywordMatcher extends Matcher {
|
||||
private final String keyword
|
||||
KeywordMatcher(String keyword) {
|
||||
this.keyword = keyword
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(List<String> searchTerms) {
|
||||
boolean found = false
|
||||
searchTerms.each {
|
||||
if (keyword == it)
|
||||
found = true
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTerm() {
|
||||
keyword
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
keyword.hashCode()
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof KeywordMatcher))
|
||||
return false
|
||||
KeywordMatcher other = (KeywordMatcher) o
|
||||
keyword.equals(other.keyword)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import com.muwire.core.Persona
|
||||
|
||||
class Match {
|
||||
Persona persona
|
||||
String [] keywords
|
||||
long timestamp
|
||||
}
|
||||
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
20
core/src/main/groovy/com/muwire/core/content/Matcher.groovy
Normal file
@@ -0,0 +1,20 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import com.muwire.core.search.QueryEvent
|
||||
|
||||
abstract class Matcher {
|
||||
final List<Match> matches = Collections.synchronizedList(new ArrayList<>())
|
||||
final Set<UUID> uuids = new HashSet<>()
|
||||
|
||||
protected abstract boolean match(List<String> searchTerms);
|
||||
|
||||
public abstract String getTerm();
|
||||
|
||||
public void process(QueryEvent qe) {
|
||||
def terms = qe.searchEvent.searchTerms
|
||||
if (match(terms) && uuids.add(qe.searchEvent.uuid)) {
|
||||
long now = System.currentTimeMillis()
|
||||
matches << new Match(persona : qe.originator, keywords : terms, timestamp : now)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.muwire.core.content
|
||||
|
||||
import java.util.regex.Pattern
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class RegexMatcher extends Matcher {
|
||||
private final Pattern pattern
|
||||
RegexMatcher(String pattern) {
|
||||
this.pattern = Pattern.compile(pattern)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean match(List<String> keywords) {
|
||||
String combined = keywords.join(" ")
|
||||
return pattern.matcher(combined).find()
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTerm() {
|
||||
pattern.pattern()
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
pattern.pattern().hashCode()
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof RegexMatcher))
|
||||
return false
|
||||
RegexMatcher other = (RegexMatcher) o
|
||||
pattern.pattern() == other.pattern.pattern()
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import static com.muwire.core.util.DataUtil.readTillRN
|
||||
import groovy.util.logging.Log
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.MappedByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
@@ -39,7 +40,7 @@ class DownloadSession {
|
||||
private long lastSpeedRead = System.currentTimeMillis()
|
||||
private long dataSinceLastRead
|
||||
|
||||
private ByteBuffer mapped
|
||||
private MappedByteBuffer mapped
|
||||
|
||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||
int pieceSize, long fileLength, Set<Integer> available) {
|
||||
@@ -69,21 +70,23 @@ class DownloadSession {
|
||||
OutputStream os = endpoint.getOutputStream()
|
||||
InputStream is = endpoint.getInputStream()
|
||||
|
||||
int piece
|
||||
int[] pieceAndPosition
|
||||
if (available.isEmpty())
|
||||
piece = pieces.claim()
|
||||
pieceAndPosition = pieces.claim()
|
||||
else
|
||||
piece = pieces.claim(new HashSet<>(available))
|
||||
if (piece == -1)
|
||||
pieceAndPosition = pieces.claim(new HashSet<>(available))
|
||||
if (pieceAndPosition == null)
|
||||
return false
|
||||
int piece = pieceAndPosition[0]
|
||||
int position = pieceAndPosition[1]
|
||||
boolean steal = pieceAndPosition[2] == 1
|
||||
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
|
||||
log.info("will download piece $piece from position $position steal $steal")
|
||||
|
||||
long pieceStart = piece * ((long)pieceSize)
|
||||
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
|
||||
long start = pieceStart + position
|
||||
String root = Base64.encode(infoHash.getRoot())
|
||||
|
||||
try {
|
||||
@@ -172,8 +175,9 @@ class DownloadSession {
|
||||
FileChannel channel
|
||||
try {
|
||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
||||
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW
|
||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1)
|
||||
StandardOpenOption.SPARSE, StandardOpenOption.CREATE))
|
||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, pieceStart, end - pieceStart + 1)
|
||||
mapped.position(position)
|
||||
|
||||
byte[] tmp = new byte[0x1 << 13]
|
||||
while(mapped.hasRemaining()) {
|
||||
@@ -185,24 +189,27 @@ class DownloadSession {
|
||||
synchronized(this) {
|
||||
mapped.put(tmp, 0, read)
|
||||
dataSinceLastRead += read
|
||||
pieces.markPartial(piece, mapped.position())
|
||||
}
|
||||
}
|
||||
|
||||
mapped.clear()
|
||||
digest.update(mapped)
|
||||
DataUtil.tryUnmap(mapped)
|
||||
byte [] hash = digest.digest()
|
||||
byte [] expected = new byte[32]
|
||||
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
||||
if (hash != expected)
|
||||
throw new BadHashException()
|
||||
if (hash != expected) {
|
||||
pieces.markPartial(piece, 0)
|
||||
throw new BadHashException("bad hash on piece $piece")
|
||||
}
|
||||
} finally {
|
||||
try { channel?.close() } catch (IOException ignore) {}
|
||||
DataUtil.tryUnmap(mapped)
|
||||
}
|
||||
pieces.markDownloaded(piece)
|
||||
unclaim = false
|
||||
} finally {
|
||||
if (unclaim)
|
||||
if (unclaim && !steal)
|
||||
pieces.unclaim(piece)
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -111,8 +111,14 @@ public class Downloader {
|
||||
if (!piecesFile.exists())
|
||||
return
|
||||
piecesFile.eachLine {
|
||||
int piece = Integer.parseInt(it)
|
||||
pieces.markDownloaded(piece)
|
||||
String [] split = it.split(",")
|
||||
int piece = Integer.parseInt(split[0])
|
||||
if (split.length == 1)
|
||||
pieces.markDownloaded(piece)
|
||||
else {
|
||||
int position = Integer.parseInt(split[1])
|
||||
pieces.markPartial(piece, position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,9 +127,7 @@ public class Downloader {
|
||||
if (piecesFileClosed)
|
||||
return
|
||||
piecesFile.withPrintWriter { writer ->
|
||||
pieces.getDownloaded().each { piece ->
|
||||
writer.println(piece)
|
||||
}
|
||||
pieces.write(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,12 +307,17 @@ public class Downloader {
|
||||
} catch (Exception bad) {
|
||||
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
|
||||
} finally {
|
||||
writePieces()
|
||||
currentState = WorkerState.FINISHED
|
||||
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
||||
synchronized(piecesFile) {
|
||||
piecesFileClosed = true
|
||||
piecesFile.delete()
|
||||
}
|
||||
activeWorkers.values().each {
|
||||
if (it.destination != destination)
|
||||
it.cancel()
|
||||
}
|
||||
try {
|
||||
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
|
||||
@@ -5,6 +5,7 @@ class Pieces {
|
||||
private final int nPieces
|
||||
private final float ratio
|
||||
private final Random random = new Random()
|
||||
private final Map<Integer,Integer> partials = new HashMap<>()
|
||||
|
||||
Pieces(int nPieces) {
|
||||
this(nPieces, 1.0f)
|
||||
@@ -17,16 +18,22 @@ class Pieces {
|
||||
claimed = new BitSet(nPieces)
|
||||
}
|
||||
|
||||
synchronized int claim() {
|
||||
synchronized int[] claim() {
|
||||
int claimedCardinality = claimed.cardinality()
|
||||
if (claimedCardinality == nPieces)
|
||||
return -1
|
||||
if (claimedCardinality == nPieces) {
|
||||
// steal
|
||||
int downloadedCardinality = done.cardinality()
|
||||
if (downloadedCardinality == nPieces)
|
||||
return null
|
||||
int rv = done.nextClearBit(0)
|
||||
return [rv, partials.getOrDefault(rv, 0), 1]
|
||||
}
|
||||
|
||||
// if fuller than ratio just do sequential
|
||||
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
||||
int rv = claimed.nextClearBit(0)
|
||||
claimed.set(rv)
|
||||
return rv
|
||||
return [rv, partials.getOrDefault(rv, 0), 0]
|
||||
}
|
||||
|
||||
while(true) {
|
||||
@@ -34,20 +41,28 @@ class Pieces {
|
||||
if (claimed.get(start))
|
||||
continue
|
||||
claimed.set(start)
|
||||
return start
|
||||
return [start, partials.getOrDefault(start,0), 0]
|
||||
}
|
||||
}
|
||||
|
||||
synchronized int claim(Set<Integer> available) {
|
||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
||||
synchronized int[] claim(Set<Integer> available) {
|
||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1))
|
||||
available.remove(i)
|
||||
if (available.isEmpty())
|
||||
return -1
|
||||
List<Integer> toList = available.toList()
|
||||
return null
|
||||
Set<Integer> availableCopy = new HashSet<>(available)
|
||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
||||
availableCopy.remove(i)
|
||||
if (availableCopy.isEmpty()) {
|
||||
// steal
|
||||
int rv = available.first()
|
||||
return [rv, partials.getOrDefault(rv, 0), 1]
|
||||
}
|
||||
List<Integer> toList = availableCopy.toList()
|
||||
Collections.shuffle(toList)
|
||||
int rv = toList[0]
|
||||
claimed.set(rv)
|
||||
rv
|
||||
[rv, partials.getOrDefault(rv, 0), 0]
|
||||
}
|
||||
|
||||
synchronized def getDownloaded() {
|
||||
@@ -61,6 +76,11 @@ class Pieces {
|
||||
synchronized void markDownloaded(int piece) {
|
||||
done.set(piece)
|
||||
claimed.set(piece)
|
||||
partials.remove(piece)
|
||||
}
|
||||
|
||||
synchronized void markPartial(int piece, int position) {
|
||||
partials.put(piece, position)
|
||||
}
|
||||
|
||||
synchronized void unclaim(int piece) {
|
||||
@@ -82,5 +102,15 @@ class Pieces {
|
||||
synchronized void clearAll() {
|
||||
done.clear()
|
||||
claimed.clear()
|
||||
partials.clear()
|
||||
}
|
||||
|
||||
synchronized void write(PrintWriter writer) {
|
||||
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1)) {
|
||||
writer.println(i)
|
||||
}
|
||||
partials.each { piece, position ->
|
||||
writer.println("$piece,$position")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ import net.i2p.data.Destination
|
||||
|
||||
class FileDownloadedEvent extends Event {
|
||||
Downloader downloader
|
||||
DownloadedFile downloadedFile
|
||||
DownloadedFile downloadedFile
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import com.muwire.core.SharedFile
|
||||
|
||||
class FileHashedEvent extends Event {
|
||||
|
||||
SharedFile sharedFile
|
||||
String error
|
||||
SharedFile sharedFile
|
||||
String error
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -13,67 +13,67 @@ import java.security.NoSuchAlgorithmException
|
||||
|
||||
class FileHasher {
|
||||
|
||||
/** max size of shared file is 128 GB */
|
||||
public static final long MAX_SIZE = 0x1L << 37
|
||||
/** 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
|
||||
/**
|
||||
* @param size of the file to be shared
|
||||
* @return the size of each piece in power of 2
|
||||
* piece size is minimum 128 KBytees and maximum 16 MBytes in power of 2 steps (2^17 - 2^24)
|
||||
* there can be up to 8192 pieces maximum per file
|
||||
*/
|
||||
static int getPieceSize(long size) {
|
||||
if (size <= 0x1 << 30)
|
||||
return 17
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
for (int i = 31; i <= 37; i++) {
|
||||
if (size <= 0x1L << i) {
|
||||
return i-13
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("File too large $size")
|
||||
}
|
||||
throw new IllegalArgumentException("File too large $size")
|
||||
}
|
||||
|
||||
final MessageDigest digest
|
||||
final MessageDigest digest
|
||||
|
||||
FileHasher() {
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256")
|
||||
} catch (NoSuchAlgorithmException impossible) {
|
||||
digest = null
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
||||
FileHasher() {
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-256")
|
||||
} catch (NoSuchAlgorithmException impossible) {
|
||||
digest = null
|
||||
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++
|
||||
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 {
|
||||
MappedByteBuffer buf
|
||||
for (int i = 0; i < numPieces - 1; i++) {
|
||||
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
||||
digest.update buf
|
||||
def output = new ByteArrayOutputStream()
|
||||
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
||||
try {
|
||||
MappedByteBuffer buf
|
||||
for (int i = 0; i < numPieces - 1; i++) {
|
||||
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
||||
digest.update buf
|
||||
DataUtil.tryUnmap(buf)
|
||||
output.write(digest.digest(), 0, 32)
|
||||
}
|
||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
||||
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
||||
digest.update buf
|
||||
output.write(digest.digest(), 0, 32)
|
||||
} finally {
|
||||
raf.close()
|
||||
}
|
||||
output.write(digest.digest(), 0, 32)
|
||||
}
|
||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
||||
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
||||
digest.update buf
|
||||
output.write(digest.digest(), 0, 32)
|
||||
} finally {
|
||||
raf.close()
|
||||
}
|
||||
|
||||
byte [] hashList = output.toByteArray()
|
||||
InfoHash.fromHashList(hashList)
|
||||
}
|
||||
byte [] hashList = output.toByteArray()
|
||||
InfoHash.fromHashList(hashList)
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length != 1) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.muwire.core.files
|
||||
|
||||
import com.muwire.core.Event
|
||||
import com.muwire.core.SharedFile
|
||||
|
||||
class FileHashingEvent extends Event {
|
||||
|
||||
File hashingFile
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
super.toString() + " hashingFile " + hashingFile.getAbsolutePath()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,5 +5,5 @@ import com.muwire.core.SharedFile
|
||||
|
||||
class FileLoadedEvent extends Event {
|
||||
|
||||
SharedFile loadedFile
|
||||
SharedFile loadedFile
|
||||
}
|
||||
|
||||
@@ -15,26 +15,26 @@ import groovy.util.logging.Log
|
||||
class FileManager {
|
||||
|
||||
|
||||
final EventBus eventBus
|
||||
final EventBus eventBus
|
||||
final MuWireSettings settings
|
||||
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
||||
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||
final SearchIndex index = new SearchIndex()
|
||||
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
||||
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) {
|
||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||
this.settings = settings
|
||||
this.eventBus = eventBus
|
||||
}
|
||||
this.eventBus = eventBus
|
||||
}
|
||||
|
||||
void onFileHashedEvent(FileHashedEvent e) {
|
||||
if (e.sharedFile != null)
|
||||
addToIndex(e.sharedFile)
|
||||
}
|
||||
|
||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||
addToIndex(e.loadedFile)
|
||||
}
|
||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||
addToIndex(e.loadedFile)
|
||||
}
|
||||
|
||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||
if (settings.shareDownloadedFiles) {
|
||||
@@ -42,88 +42,88 @@ class FileManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void addToIndex(SharedFile sf) {
|
||||
private void addToIndex(SharedFile sf) {
|
||||
log.info("Adding shared file " + sf.getFile())
|
||||
InfoHash infoHash = sf.getInfoHash()
|
||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||
if (existing == null) {
|
||||
InfoHash infoHash = sf.getInfoHash()
|
||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||
if (existing == null) {
|
||||
log.info("adding new root")
|
||||
existing = new HashSet<>()
|
||||
rootToFiles.put(infoHash, existing);
|
||||
}
|
||||
existing.add(sf)
|
||||
fileToSharedFile.put(sf.file, sf)
|
||||
existing = new HashSet<>()
|
||||
rootToFiles.put(infoHash, existing);
|
||||
}
|
||||
existing.add(sf)
|
||||
fileToSharedFile.put(sf.file, sf)
|
||||
|
||||
String name = sf.getFile().getName()
|
||||
Set<File> existingFiles = nameToFiles.get(name)
|
||||
if (existingFiles == null) {
|
||||
existingFiles = new HashSet<>()
|
||||
nameToFiles.put(name, existingFiles)
|
||||
}
|
||||
existingFiles.add(sf.getFile())
|
||||
String name = sf.getFile().getName()
|
||||
Set<File> existingFiles = nameToFiles.get(name)
|
||||
if (existingFiles == null) {
|
||||
existingFiles = new HashSet<>()
|
||||
nameToFiles.put(name, existingFiles)
|
||||
}
|
||||
existingFiles.add(sf.getFile())
|
||||
|
||||
index.add(name)
|
||||
}
|
||||
index.add(name)
|
||||
}
|
||||
|
||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||
SharedFile sf = e.unsharedFile
|
||||
InfoHash infoHash = sf.getInfoHash()
|
||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||
if (existing != null) {
|
||||
existing.remove(sf)
|
||||
if (existing.isEmpty()) {
|
||||
rootToFiles.remove(infoHash)
|
||||
}
|
||||
}
|
||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||
SharedFile sf = e.unsharedFile
|
||||
InfoHash infoHash = sf.getInfoHash()
|
||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||
if (existing != null) {
|
||||
existing.remove(sf)
|
||||
if (existing.isEmpty()) {
|
||||
rootToFiles.remove(infoHash)
|
||||
}
|
||||
}
|
||||
|
||||
fileToSharedFile.remove(sf.file)
|
||||
fileToSharedFile.remove(sf.file)
|
||||
|
||||
String name = sf.getFile().getName()
|
||||
Set<File> existingFiles = nameToFiles.get(name)
|
||||
if (existingFiles != null) {
|
||||
existingFiles.remove(sf.file)
|
||||
if (existingFiles.isEmpty()) {
|
||||
nameToFiles.remove(name)
|
||||
}
|
||||
}
|
||||
String name = sf.getFile().getName()
|
||||
Set<File> existingFiles = nameToFiles.get(name)
|
||||
if (existingFiles != null) {
|
||||
existingFiles.remove(sf.file)
|
||||
if (existingFiles.isEmpty()) {
|
||||
nameToFiles.remove(name)
|
||||
}
|
||||
}
|
||||
|
||||
index.remove(name)
|
||||
}
|
||||
index.remove(name)
|
||||
}
|
||||
|
||||
Map<File, SharedFile> getSharedFiles() {
|
||||
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
|
||||
if (e.searchHash != null) {
|
||||
void onSearchEvent(SearchEvent e) {
|
||||
// hash takes precedence
|
||||
ResultsEvent re = null
|
||||
if (e.searchHash != null) {
|
||||
Set<SharedFile> found
|
||||
found = rootToFiles.get new InfoHash(e.searchHash)
|
||||
found = filter(found, e.oobInfohash)
|
||||
if (found != null && !found.isEmpty())
|
||||
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
||||
} else {
|
||||
def names = index.search e.searchTerms
|
||||
Set<File> files = new HashSet<>()
|
||||
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
||||
Set<SharedFile> sharedFiles = new HashSet<>()
|
||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||
if (found != null && !found.isEmpty())
|
||||
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
||||
} else {
|
||||
def names = index.search e.searchTerms
|
||||
Set<File> files = new HashSet<>()
|
||||
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
||||
Set<SharedFile> sharedFiles = new HashSet<>()
|
||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||
files = filter(sharedFiles, e.oobInfohash)
|
||||
if (!sharedFiles.isEmpty())
|
||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||
if (!sharedFiles.isEmpty())
|
||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (re != null)
|
||||
eventBus.publish(re)
|
||||
}
|
||||
if (re != null)
|
||||
eventBus.publish(re)
|
||||
}
|
||||
|
||||
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
||||
if (!oob)
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.muwire.core.Event
|
||||
|
||||
class FileSharedEvent extends Event {
|
||||
|
||||
File file
|
||||
File file
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -4,5 +4,5 @@ import com.muwire.core.Event
|
||||
import com.muwire.core.SharedFile
|
||||
|
||||
class FileUnsharedEvent extends Event {
|
||||
SharedFile unsharedFile
|
||||
SharedFile unsharedFile
|
||||
}
|
||||
|
||||
@@ -8,40 +8,41 @@ import com.muwire.core.SharedFile
|
||||
|
||||
class HasherService {
|
||||
|
||||
final FileHasher hasher
|
||||
final EventBus eventBus
|
||||
final FileHasher hasher
|
||||
final EventBus eventBus
|
||||
final FileManager fileManager
|
||||
Executor executor
|
||||
Executor executor
|
||||
|
||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||
this.hasher = hasher
|
||||
this.eventBus = eventBus
|
||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||
this.hasher = hasher
|
||||
this.eventBus = eventBus
|
||||
this.fileManager = fileManager
|
||||
}
|
||||
}
|
||||
|
||||
void start() {
|
||||
executor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
void start() {
|
||||
executor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
void onFileSharedEvent(FileSharedEvent evt) {
|
||||
void onFileSharedEvent(FileSharedEvent evt) {
|
||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||
return
|
||||
executor.execute( { -> process(evt.file) } as Runnable)
|
||||
}
|
||||
executor.execute( { -> process(evt.file) } as Runnable)
|
||||
}
|
||||
|
||||
private void process(File f) {
|
||||
f = f.getCanonicalFile()
|
||||
if (f.isDirectory()) {
|
||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
||||
} else {
|
||||
if (f.length() == 0) {
|
||||
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
||||
} else if (f.length() > FileHasher.MAX_SIZE) {
|
||||
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
||||
} else {
|
||||
def hash = hasher.hashFile f
|
||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
||||
}
|
||||
}
|
||||
}
|
||||
private void process(File f) {
|
||||
f = f.getCanonicalFile()
|
||||
if (f.isDirectory()) {
|
||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
||||
} else {
|
||||
if (f.length() == 0) {
|
||||
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
||||
} else if (f.length() > FileHasher.MAX_SIZE) {
|
||||
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
||||
} else {
|
||||
eventBus.publish new FileHashingEvent(hashingFile: f)
|
||||
def hash = hasher.hashFile f
|
||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,135 +23,135 @@ import net.i2p.data.Destination
|
||||
@Log
|
||||
class PersisterService extends Service {
|
||||
|
||||
final File location
|
||||
final EventBus listener
|
||||
final int interval
|
||||
final Timer timer
|
||||
final FileManager fileManager
|
||||
final File location
|
||||
final EventBus listener
|
||||
final int interval
|
||||
final Timer timer
|
||||
final FileManager fileManager
|
||||
|
||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||
this.location = location
|
||||
this.listener = listener
|
||||
this.interval = interval
|
||||
this.fileManager = fileManager
|
||||
timer = new Timer("file persister", true)
|
||||
}
|
||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||
this.location = location
|
||||
this.listener = listener
|
||||
this.interval = interval
|
||||
this.fileManager = fileManager
|
||||
timer = new Timer("file persister", true)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
void onUILoadedEvent(UILoadedEvent e) {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
void load() {
|
||||
if (location.exists() && location.isFile()) {
|
||||
def slurper = new JsonSlurper()
|
||||
try {
|
||||
location.eachLine {
|
||||
if (it.trim().length() > 0) {
|
||||
def parsed = slurper.parseText it
|
||||
def event = fromJson parsed
|
||||
if (event != null) {
|
||||
void load() {
|
||||
if (location.exists() && location.isFile()) {
|
||||
def slurper = new JsonSlurper()
|
||||
try {
|
||||
location.eachLine {
|
||||
if (it.trim().length() > 0) {
|
||||
def parsed = slurper.parseText it
|
||||
def event = fromJson parsed
|
||||
if (event != null) {
|
||||
log.fine("loaded file $event.loadedFile.file")
|
||||
listener.publish event
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.publish event
|
||||
}
|
||||
}
|
||||
}
|
||||
listener.publish(new AllFilesLoadedEvent())
|
||||
} catch (IllegalArgumentException|NumberFormatException e) {
|
||||
} catch (IllegalArgumentException|NumberFormatException e) {
|
||||
log.log(Level.WARNING, "couldn't load files",e)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
} else {
|
||||
listener.publish(new AllFilesLoadedEvent())
|
||||
}
|
||||
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
||||
loaded = true
|
||||
}
|
||||
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()
|
||||
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())
|
||||
return null
|
||||
long length = Long.valueOf(json.length)
|
||||
if (length != file.length())
|
||||
return null
|
||||
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||
file = file.getCanonicalFile()
|
||||
if (!file.exists() || file.isDirectory())
|
||||
return null
|
||||
long length = Long.valueOf(json.length)
|
||||
if (length != file.length())
|
||||
return null
|
||||
|
||||
List hashList = (List) json.hashList
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
hashList.each {
|
||||
byte [] hash = Base64.decode it.toString()
|
||||
if (hash == null)
|
||||
throw new IllegalArgumentException()
|
||||
baos.write hash
|
||||
}
|
||||
byte[] hashListBytes = baos.toByteArray()
|
||||
List hashList = (List) json.hashList
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
hashList.each {
|
||||
byte [] hash = Base64.decode it.toString()
|
||||
if (hash == null)
|
||||
throw new IllegalArgumentException()
|
||||
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
|
||||
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)
|
||||
}
|
||||
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)
|
||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||
return new FileLoadedEvent(loadedFile: sf)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void persistFiles() {
|
||||
def sharedFiles = fileManager.getSharedFiles()
|
||||
private void persistFiles() {
|
||||
def sharedFiles = fileManager.getSharedFiles()
|
||||
|
||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||
tmp.deleteOnExit()
|
||||
tmp.withPrintWriter { writer ->
|
||||
sharedFiles.each { k, v ->
|
||||
def json = toJson(k,v)
|
||||
json = JsonOutput.toJson(json)
|
||||
writer.println json
|
||||
}
|
||||
}
|
||||
tmp.withPrintWriter { writer ->
|
||||
sharedFiles.each { k, v ->
|
||||
def json = toJson(k,v)
|
||||
json = JsonOutput.toJson(json)
|
||||
writer.println json
|
||||
}
|
||||
}
|
||||
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())
|
||||
json.length = f.length()
|
||||
InfoHash ih = sf.getInfoHash()
|
||||
json.infoHash = Base64.encode ih.getRoot()
|
||||
private def toJson(File f, SharedFile sf) {
|
||||
def json = [:]
|
||||
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
||||
json.length = f.length()
|
||||
InfoHash ih = sf.getInfoHash()
|
||||
json.infoHash = Base64.encode ih.getRoot()
|
||||
json.pieceSize = sf.getPieceSize()
|
||||
byte [] tmp = new byte [32]
|
||||
json.hashList = []
|
||||
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
||||
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
||||
json.hashList.add Base64.encode(tmp)
|
||||
}
|
||||
byte [] tmp = new byte [32]
|
||||
json.hashList = []
|
||||
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
||||
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())
|
||||
}
|
||||
if (sf instanceof DownloadedFile) {
|
||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||
}
|
||||
|
||||
json
|
||||
}
|
||||
json
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,176 +18,176 @@ import net.i2p.data.Destination
|
||||
@Log
|
||||
class CacheClient {
|
||||
|
||||
private static final int CRAWLER_RETURN = 10
|
||||
private static final int CRAWLER_RETURN = 10
|
||||
|
||||
final EventBus eventBus
|
||||
final HostCache cache
|
||||
final ConnectionManager manager
|
||||
final I2PSession session
|
||||
final long interval
|
||||
final MuWireSettings settings
|
||||
final Timer timer
|
||||
final EventBus eventBus
|
||||
final HostCache cache
|
||||
final ConnectionManager manager
|
||||
final I2PSession session
|
||||
final long interval
|
||||
final MuWireSettings settings
|
||||
final Timer timer
|
||||
|
||||
public CacheClient(EventBus eventBus, HostCache cache,
|
||||
ConnectionManager manager, I2PSession session,
|
||||
MuWireSettings settings, long interval) {
|
||||
this.eventBus = eventBus
|
||||
this.cache = cache
|
||||
this.manager = manager
|
||||
this.session = session
|
||||
this.settings = settings
|
||||
this.interval = interval
|
||||
this.timer = new Timer("hostcache-client",true)
|
||||
}
|
||||
public CacheClient(EventBus eventBus, HostCache cache,
|
||||
ConnectionManager manager, I2PSession session,
|
||||
MuWireSettings settings, long interval) {
|
||||
this.eventBus = eventBus
|
||||
this.cache = cache
|
||||
this.manager = manager
|
||||
this.session = session
|
||||
this.settings = settings
|
||||
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 start() {
|
||||
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
||||
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
private void queryIfNeeded() {
|
||||
if (!manager.getConnections().isEmpty())
|
||||
return
|
||||
if (!cache.getHosts(1).isEmpty())
|
||||
return
|
||||
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)
|
||||
ping = maker.makeI2PDatagram(ping.bytes)
|
||||
def options = new SendMessageOptions()
|
||||
options.setSendLeaseSet(true)
|
||||
CacheServers.getCacheServers().each {
|
||||
log.info "Querying hostcache ${it.toBase32()}"
|
||||
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
||||
}
|
||||
}
|
||||
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
||||
ping = JsonOutput.toJson(ping)
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
ping = maker.makeI2PDatagram(ping.bytes)
|
||||
def options = new SendMessageOptions()
|
||||
options.setSendLeaseSet(true)
|
||||
CacheServers.getCacheServers().each {
|
||||
log.info "Querying hostcache ${it.toBase32()}"
|
||||
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
||||
}
|
||||
}
|
||||
|
||||
class Listener implements I2PSessionMuxedListener {
|
||||
class Listener implements I2PSessionMuxedListener {
|
||||
|
||||
private final JsonSlurper slurper = new JsonSlurper()
|
||||
private final JsonSlurper slurper = new JsonSlurper()
|
||||
|
||||
@Override
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
}
|
||||
@Override
|
||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
||||
@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
|
||||
}
|
||||
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()}")
|
||||
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)
|
||||
payload = dissector.getPayload()
|
||||
payload = slurper.parse(payload)
|
||||
|
||||
if (payload.type == null) {
|
||||
log.warning("type missing")
|
||||
return
|
||||
}
|
||||
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
|
||||
default : log.warning("unknown type ${payload.type}")
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Invalid datagram $e")
|
||||
}
|
||||
}
|
||||
switch(payload.type) {
|
||||
case "Pong" : handlePong(sender, payload); break
|
||||
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
||||
default : log.warning("unknown type ${payload.type}")
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("Invalid datagram $e")
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
}
|
||||
@Override
|
||||
public void reportAbuse(I2PSession session, int severity) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(I2PSession session) {
|
||||
log.severe "I2P session disconnected"
|
||||
}
|
||||
@Override
|
||||
public void disconnected(I2PSession session) {
|
||||
log.severe "I2P session disconnected"
|
||||
}
|
||||
|
||||
@Override
|
||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||
log.severe "I2P error occured $message $error"
|
||||
}
|
||||
@Override
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
if (pong.pongs == null) {
|
||||
log.warning("malformed pong - no pongs")
|
||||
return
|
||||
}
|
||||
|
||||
pong.pongs.asList().each {
|
||||
Destination dest = new Destination(it)
|
||||
if (!session.getMyDestination().equals(dest))
|
||||
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
||||
}
|
||||
pong.pongs.asList().each {
|
||||
Destination dest = new Destination(it)
|
||||
if (!session.getMyDestination().equals(dest))
|
||||
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
|
||||
}
|
||||
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")
|
||||
break
|
||||
case CrawlerResponse.ALL:
|
||||
respondToCrawler(session, from, ping)
|
||||
break;
|
||||
case CrawlerResponse.REGISTERED:
|
||||
if (CacheServers.isRegistered(from))
|
||||
respondToCrawler(session, from, ping)
|
||||
else
|
||||
log.warning("Ignoring crawler ping from non-registered crawler")
|
||||
break
|
||||
}
|
||||
}
|
||||
switch(settings.getCrawlerResponse()) {
|
||||
case CrawlerResponse.NONE:
|
||||
log.info("Responding to crawlers is disabled by user")
|
||||
break
|
||||
case CrawlerResponse.ALL:
|
||||
respondToCrawler(session, from, ping)
|
||||
break;
|
||||
case CrawlerResponse.REGISTERED:
|
||||
if (CacheServers.isRegistered(from))
|
||||
respondToCrawler(session, from, ping)
|
||||
else
|
||||
log.warning("Ignoring crawler ping from non-registered crawler")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
||||
log.info "responding to crawler ping"
|
||||
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 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
|
||||
pong.uuid = ping.uuid
|
||||
pong.type = "CrawlerPong"
|
||||
pong.version = 1
|
||||
pong.leafSlots = upManager.hasLeafSlots()
|
||||
pong.peerSlots = upManager.hasPeerSlots()
|
||||
pong = JsonOutput.toJson(pong)
|
||||
def upManager = (UltrapeerConnectionManager) manager;
|
||||
def pong = [:]
|
||||
pong.peers = neighbors
|
||||
pong.uuid = ping.uuid
|
||||
pong.type = "CrawlerPong"
|
||||
pong.version = 1
|
||||
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)
|
||||
}
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
pong = maker.makeI2PDatagram(pong.bytes)
|
||||
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,21 +4,21 @@ import net.i2p.data.Destination
|
||||
|
||||
class CacheServers {
|
||||
|
||||
private static final int TO_GIVE = 3
|
||||
private static Set<Destination> CACHES = [
|
||||
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
||||
private static final int TO_GIVE = 3
|
||||
private static Set<Destination> CACHES = [
|
||||
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
||||
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
|
||||
]
|
||||
]
|
||||
|
||||
static List<Destination> getCacheServers() {
|
||||
List<Destination> allCaches = new ArrayList<>(CACHES)
|
||||
Collections.shuffle(allCaches)
|
||||
if (allCaches.size() <= TO_GIVE)
|
||||
return allCaches
|
||||
allCaches[0..TO_GIVE-1]
|
||||
}
|
||||
static List<Destination> getCacheServers() {
|
||||
List<Destination> allCaches = new ArrayList<>(CACHES)
|
||||
Collections.shuffle(allCaches)
|
||||
if (allCaches.size() <= TO_GIVE)
|
||||
return allCaches
|
||||
allCaches[0..TO_GIVE-1]
|
||||
}
|
||||
|
||||
static boolean isRegistered(Destination d) {
|
||||
return CACHES.contains(d)
|
||||
}
|
||||
static boolean isRegistered(Destination d) {
|
||||
return CACHES.contains(d)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,37 +4,37 @@ import net.i2p.data.Destination
|
||||
|
||||
class Host {
|
||||
|
||||
private static final int MAX_FAILURES = 3
|
||||
private static final int MAX_FAILURES = 3
|
||||
|
||||
final Destination destination
|
||||
final Destination destination
|
||||
private final int clearInterval
|
||||
int failures,successes
|
||||
int failures,successes
|
||||
long lastAttempt
|
||||
|
||||
public Host(Destination destination, int clearInterval) {
|
||||
this.destination = destination
|
||||
public Host(Destination destination, int clearInterval) {
|
||||
this.destination = destination
|
||||
this.clearInterval = clearInterval
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void onConnect() {
|
||||
failures = 0
|
||||
successes++
|
||||
synchronized void onConnect() {
|
||||
failures = 0
|
||||
successes++
|
||||
lastAttempt = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void onFailure() {
|
||||
failures++
|
||||
successes = 0
|
||||
synchronized void onFailure() {
|
||||
failures++
|
||||
successes = 0
|
||||
lastAttempt = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean isFailed() {
|
||||
failures >= MAX_FAILURES
|
||||
}
|
||||
synchronized boolean isFailed() {
|
||||
failures >= MAX_FAILURES
|
||||
}
|
||||
|
||||
synchronized boolean hasSucceeded() {
|
||||
successes > 0
|
||||
}
|
||||
synchronized boolean hasSucceeded() {
|
||||
successes > 0
|
||||
}
|
||||
|
||||
synchronized void clearFailures() {
|
||||
failures = 0
|
||||
|
||||
@@ -15,141 +15,141 @@ import net.i2p.data.Destination
|
||||
|
||||
class HostCache extends Service {
|
||||
|
||||
final TrustService trustService
|
||||
final File storage
|
||||
final int interval
|
||||
final Timer timer
|
||||
final MuWireSettings settings
|
||||
final Destination myself
|
||||
final Map<Destination, Host> hosts = new ConcurrentHashMap<>()
|
||||
final TrustService trustService
|
||||
final File storage
|
||||
final int interval
|
||||
final Timer timer
|
||||
final MuWireSettings settings
|
||||
final Destination myself
|
||||
final Map<Destination, Host> hosts = new ConcurrentHashMap<>()
|
||||
|
||||
HostCache(){}
|
||||
HostCache(){}
|
||||
|
||||
public HostCache(TrustService trustService, File storage, int interval,
|
||||
MuWireSettings settings, Destination myself) {
|
||||
this.trustService = trustService
|
||||
this.storage = storage
|
||||
this.interval = interval
|
||||
this.settings = settings
|
||||
this.myself = myself
|
||||
this.timer = new Timer("host-persister",true)
|
||||
}
|
||||
public HostCache(TrustService trustService, File storage, int interval,
|
||||
MuWireSettings settings, Destination myself) {
|
||||
this.trustService = trustService
|
||||
this.storage = storage
|
||||
this.interval = interval
|
||||
this.settings = settings
|
||||
this.myself = myself
|
||||
this.timer = new Timer("host-persister",true)
|
||||
}
|
||||
|
||||
void start() {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
void start() {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||
if (myself == e.destination)
|
||||
return
|
||||
if (hosts.containsKey(e.destination)) {
|
||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||
if (myself == e.destination)
|
||||
return
|
||||
if (hosts.containsKey(e.destination)) {
|
||||
if (!e.fromHostcache)
|
||||
return
|
||||
hosts.get(e.destination).clearFailures()
|
||||
return
|
||||
}
|
||||
Host host = new Host(e.destination, settings.hostClearInterval)
|
||||
if (allowHost(host)) {
|
||||
hosts.put(e.destination, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
Host host = new Host(e.destination, settings.hostClearInterval)
|
||||
if (allowHost(host)) {
|
||||
hosts.put(e.destination, host)
|
||||
}
|
||||
}
|
||||
|
||||
void onConnectionEvent(ConnectionEvent e) {
|
||||
if (e.leaf)
|
||||
return
|
||||
Destination dest = e.endpoint.destination
|
||||
Host host = hosts.get(dest)
|
||||
if (host == null) {
|
||||
host = new Host(dest, settings.hostClearInterval)
|
||||
hosts.put(dest, host)
|
||||
}
|
||||
void onConnectionEvent(ConnectionEvent e) {
|
||||
if (e.leaf)
|
||||
return
|
||||
Destination dest = e.endpoint.destination
|
||||
Host host = hosts.get(dest)
|
||||
if (host == null) {
|
||||
host = new Host(dest, settings.hostClearInterval)
|
||||
hosts.put(dest, host)
|
||||
}
|
||||
|
||||
switch(e.status) {
|
||||
case ConnectionAttemptStatus.SUCCESSFUL:
|
||||
case ConnectionAttemptStatus.REJECTED:
|
||||
host.onConnect()
|
||||
break
|
||||
case ConnectionAttemptStatus.FAILED:
|
||||
host.onFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
switch(e.status) {
|
||||
case ConnectionAttemptStatus.SUCCESSFUL:
|
||||
case ConnectionAttemptStatus.REJECTED:
|
||||
host.onConnect()
|
||||
break
|
||||
case ConnectionAttemptStatus.FAILED:
|
||||
host.onFailure()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
List<Destination> getHosts(int n) {
|
||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||
rv.retainAll {allowHost(hosts[it])}
|
||||
if (rv.size() <= n)
|
||||
return rv
|
||||
Collections.shuffle(rv)
|
||||
rv[0..n-1]
|
||||
}
|
||||
List<Destination> getHosts(int n) {
|
||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||
rv.retainAll {allowHost(hosts[it])}
|
||||
if (rv.size() <= n)
|
||||
return rv
|
||||
Collections.shuffle(rv)
|
||||
rv[0..n-1]
|
||||
}
|
||||
|
||||
List<Destination> getGoodHosts(int n) {
|
||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||
rv.retainAll {
|
||||
Host host = hosts[it]
|
||||
allowHost(host) && host.hasSucceeded()
|
||||
}
|
||||
if (rv.size() <= n)
|
||||
return rv
|
||||
Collections.shuffle(rv)
|
||||
rv[0..n-1]
|
||||
}
|
||||
List<Destination> getGoodHosts(int n) {
|
||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||
rv.retainAll {
|
||||
Host host = hosts[it]
|
||||
allowHost(host) && host.hasSucceeded()
|
||||
}
|
||||
if (rv.size() <= n)
|
||||
return rv
|
||||
Collections.shuffle(rv)
|
||||
rv[0..n-1]
|
||||
}
|
||||
|
||||
void load() {
|
||||
if (storage.exists()) {
|
||||
JsonSlurper slurper = new JsonSlurper()
|
||||
storage.eachLine {
|
||||
def entry = slurper.parseText(it)
|
||||
Destination dest = new Destination(entry.destination)
|
||||
Host host = new Host(dest, settings.hostClearInterval)
|
||||
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
||||
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||
void load() {
|
||||
if (storage.exists()) {
|
||||
JsonSlurper slurper = new JsonSlurper()
|
||||
storage.eachLine {
|
||||
def entry = slurper.parseText(it)
|
||||
Destination dest = new Destination(entry.destination)
|
||||
Host host = new Host(dest, settings.hostClearInterval)
|
||||
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
||||
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||
if (entry.lastAttempt != null)
|
||||
host.lastAttempt = entry.lastAttempt
|
||||
if (allowHost(host))
|
||||
hosts.put(dest, host)
|
||||
}
|
||||
}
|
||||
timer.schedule({save()} as TimerTask, interval, interval)
|
||||
loaded = true
|
||||
}
|
||||
if (allowHost(host))
|
||||
hosts.put(dest, host)
|
||||
}
|
||||
}
|
||||
timer.schedule({save()} as TimerTask, interval, interval)
|
||||
loaded = true
|
||||
}
|
||||
|
||||
private boolean allowHost(Host host) {
|
||||
if (host.isFailed() && !host.canTryAgain())
|
||||
return false
|
||||
if (host.destination == myself)
|
||||
return false
|
||||
TrustLevel trust = trustService.getLevel(host.destination)
|
||||
switch(trust) {
|
||||
case TrustLevel.DISTRUSTED :
|
||||
return false
|
||||
case TrustLevel.TRUSTED :
|
||||
return true
|
||||
case TrustLevel.NEUTRAL :
|
||||
return settings.allowUntrusted()
|
||||
}
|
||||
false
|
||||
}
|
||||
private boolean allowHost(Host host) {
|
||||
if (host.isFailed() && !host.canTryAgain())
|
||||
return false
|
||||
if (host.destination == myself)
|
||||
return false
|
||||
TrustLevel trust = trustService.getLevel(host.destination)
|
||||
switch(trust) {
|
||||
case TrustLevel.DISTRUSTED :
|
||||
return false
|
||||
case TrustLevel.TRUSTED :
|
||||
return true
|
||||
case TrustLevel.NEUTRAL :
|
||||
return settings.allowUntrusted()
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
private void save() {
|
||||
storage.delete()
|
||||
storage.withPrintWriter { writer ->
|
||||
hosts.each { dest, host ->
|
||||
if (allowHost(host)) {
|
||||
def map = [:]
|
||||
map.destination = dest.toBase64()
|
||||
map.failures = host.failures
|
||||
map.successes = host.successes
|
||||
private void save() {
|
||||
storage.delete()
|
||||
storage.withPrintWriter { writer ->
|
||||
hosts.each { dest, host ->
|
||||
if (allowHost(host)) {
|
||||
def map = [:]
|
||||
map.destination = dest.toBase64()
|
||||
map.failures = host.failures
|
||||
map.successes = host.successes
|
||||
map.lastAttempt = host.lastAttempt
|
||||
def json = JsonOutput.toJson(map)
|
||||
writer.println json
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def json = JsonOutput.toJson(map)
|
||||
writer.println json
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import net.i2p.data.Destination
|
||||
|
||||
class HostDiscoveredEvent extends Event {
|
||||
|
||||
Destination destination
|
||||
Destination destination
|
||||
boolean fromHostcache
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import net.i2p.data.Base32
|
||||
import net.i2p.data.Destination
|
||||
|
||||
class DeleteEvent extends Event {
|
||||
byte [] infoHash
|
||||
Destination leaf
|
||||
byte [] infoHash
|
||||
Destination leaf
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,32 +7,32 @@ import net.i2p.data.Destination
|
||||
|
||||
class LeafSearcher {
|
||||
|
||||
final UltrapeerConnectionManager connectionManager
|
||||
final SearchIndex searchIndex = new SearchIndex()
|
||||
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<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
||||
final Map<byte[], Set<Destination>> hashToLeafs = new HashMap<>()
|
||||
|
||||
final Map<Destination, Map<byte[], Set<String>>> leafToFiles = new HashMap<>()
|
||||
final Map<Destination, Map<byte[], Set<String>>> leafToFiles = new HashMap<>()
|
||||
|
||||
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager
|
||||
}
|
||||
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
||||
this.connectionManager = connectionManager
|
||||
}
|
||||
|
||||
void onUpsertEvent(UpsertEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
void onUpsertEvent(UpsertEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
void onDeleteEvent(DeleteEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
void onDeleteEvent(DeleteEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
void onDisconnectionEvent(DisconnectionEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
void onDisconnectionEvent(DisconnectionEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
void onQueryEvent(QueryEvent e) {
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import net.i2p.data.Destination
|
||||
class QueryEvent extends Event {
|
||||
|
||||
SearchEvent searchEvent
|
||||
boolean firstHop
|
||||
Destination replyTo
|
||||
boolean firstHop
|
||||
Destination replyTo
|
||||
Persona originator
|
||||
Destination receivedOn
|
||||
Destination receivedOn
|
||||
|
||||
String toString() {
|
||||
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
||||
|
||||
@@ -6,6 +6,6 @@ import com.muwire.core.SharedFile
|
||||
class ResultsEvent extends Event {
|
||||
|
||||
SearchEvent searchEvent
|
||||
SharedFile[] results
|
||||
UUID uuid
|
||||
SharedFile[] results
|
||||
UUID uuid
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import com.muwire.core.InfoHash
|
||||
|
||||
class SearchEvent extends Event {
|
||||
|
||||
List<String> searchTerms
|
||||
byte [] searchHash
|
||||
UUID uuid
|
||||
List<String> searchTerms
|
||||
byte [] searchHash
|
||||
UUID uuid
|
||||
boolean oobInfohash
|
||||
|
||||
String toString() {
|
||||
|
||||
@@ -4,56 +4,56 @@ import com.muwire.core.Constants
|
||||
|
||||
class SearchIndex {
|
||||
|
||||
final Map<String, Set<String>> keywords = new HashMap<>()
|
||||
final Map<String, Set<String>> keywords = new HashMap<>()
|
||||
|
||||
void add(String string) {
|
||||
String [] split = split(string)
|
||||
split.each {
|
||||
Set<String> existing = keywords.get(it)
|
||||
if (existing == null) {
|
||||
existing = new HashSet<>()
|
||||
keywords.put(it, existing)
|
||||
}
|
||||
existing.add(string)
|
||||
}
|
||||
}
|
||||
void add(String string) {
|
||||
String [] split = split(string)
|
||||
split.each {
|
||||
Set<String> existing = keywords.get(it)
|
||||
if (existing == null) {
|
||||
existing = new HashSet<>()
|
||||
keywords.put(it, existing)
|
||||
}
|
||||
existing.add(string)
|
||||
}
|
||||
}
|
||||
|
||||
void remove(String string) {
|
||||
String [] split = split(string)
|
||||
split.each {
|
||||
Set<String> existing = keywords.get it
|
||||
if (existing != null) {
|
||||
existing.remove(string)
|
||||
if (existing.isEmpty()) {
|
||||
keywords.remove(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void remove(String string) {
|
||||
String [] split = split(string)
|
||||
split.each {
|
||||
Set<String> existing = keywords.get it
|
||||
if (existing != null) {
|
||||
existing.remove(string)
|
||||
if (existing.isEmpty()) {
|
||||
keywords.remove(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] split(String source) {
|
||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||
String [] split = source.split(" ")
|
||||
private static String[] split(String source) {
|
||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||
String [] split = source.split(" ")
|
||||
def rv = []
|
||||
split.each { if (it.length() > 0) rv << it }
|
||||
rv.toArray(new String[0])
|
||||
}
|
||||
}
|
||||
|
||||
String[] search(List<String> terms) {
|
||||
Set<String> rv = null;
|
||||
String[] search(List<String> terms) {
|
||||
Set<String> rv = null;
|
||||
|
||||
terms.each {
|
||||
Set<String> forWord = keywords.getOrDefault(it,[])
|
||||
if (rv == null) {
|
||||
rv = new HashSet<>(forWord)
|
||||
} else {
|
||||
rv.retainAll(forWord)
|
||||
}
|
||||
terms.each {
|
||||
Set<String> forWord = keywords.getOrDefault(it,[])
|
||||
if (rv == null) {
|
||||
rv = new HashSet<>(forWord)
|
||||
} else {
|
||||
rv.retainAll(forWord)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (rv != null)
|
||||
return rv.asList()
|
||||
[]
|
||||
}
|
||||
if (rv != null)
|
||||
return rv.asList()
|
||||
[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import net.i2p.data.Destination
|
||||
|
||||
class UpsertEvent extends Event {
|
||||
|
||||
Set<String> names
|
||||
byte [] infoHash
|
||||
Destination leaf
|
||||
Set<String> names
|
||||
byte [] infoHash
|
||||
Destination leaf
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ import com.muwire.core.Persona
|
||||
|
||||
class TrustEvent extends Event {
|
||||
|
||||
Persona persona
|
||||
TrustLevel level
|
||||
Persona persona
|
||||
TrustLevel level
|
||||
}
|
||||
|
||||
@@ -11,87 +11,87 @@ import net.i2p.util.ConcurrentHashSet
|
||||
|
||||
class TrustService extends Service {
|
||||
|
||||
final File persistGood, persistBad
|
||||
final long persistInterval
|
||||
final File persistGood, persistBad
|
||||
final long persistInterval
|
||||
|
||||
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
||||
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
||||
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
||||
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
||||
|
||||
final Timer timer
|
||||
final Timer timer
|
||||
|
||||
TrustService() {}
|
||||
TrustService() {}
|
||||
|
||||
TrustService(File persistGood, File persistBad, long persistInterval) {
|
||||
this.persistBad = persistBad
|
||||
this.persistGood = persistGood
|
||||
this.persistInterval = persistInterval
|
||||
this.timer = new Timer("trust-persister",true)
|
||||
}
|
||||
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 start() {
|
||||
timer.schedule({load()} as TimerTask, 1)
|
||||
}
|
||||
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
void stop() {
|
||||
timer.cancel()
|
||||
}
|
||||
|
||||
void load() {
|
||||
if (persistGood.exists()) {
|
||||
persistGood.eachLine {
|
||||
void load() {
|
||||
if (persistGood.exists()) {
|
||||
persistGood.eachLine {
|
||||
byte [] decoded = Base64.decode(it)
|
||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||
good.put(persona.destination, persona)
|
||||
}
|
||||
}
|
||||
if (persistBad.exists()) {
|
||||
persistBad.eachLine {
|
||||
good.put(persona.destination, persona)
|
||||
}
|
||||
}
|
||||
if (persistBad.exists()) {
|
||||
persistBad.eachLine {
|
||||
byte [] decoded = Base64.decode(it)
|
||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||
bad.put(persona.destination, persona)
|
||||
}
|
||||
}
|
||||
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
||||
loaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
||||
loaded = true
|
||||
}
|
||||
|
||||
private void persist() {
|
||||
persistGood.delete()
|
||||
persistGood.withPrintWriter { writer ->
|
||||
good.each {k,v ->
|
||||
writer.println v.toBase64()
|
||||
}
|
||||
}
|
||||
persistBad.delete()
|
||||
persistBad.withPrintWriter { writer ->
|
||||
bad.each { k,v ->
|
||||
writer.println v.toBase64()
|
||||
}
|
||||
}
|
||||
}
|
||||
private void persist() {
|
||||
persistGood.delete()
|
||||
persistGood.withPrintWriter { writer ->
|
||||
good.each {k,v ->
|
||||
writer.println v.toBase64()
|
||||
}
|
||||
}
|
||||
persistBad.delete()
|
||||
persistBad.withPrintWriter { writer ->
|
||||
bad.each { k,v ->
|
||||
writer.println v.toBase64()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TrustLevel getLevel(Destination dest) {
|
||||
if (good.containsKey(dest))
|
||||
return TrustLevel.TRUSTED
|
||||
else if (bad.containsKey(dest))
|
||||
return TrustLevel.DISTRUSTED
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
TrustLevel getLevel(Destination dest) {
|
||||
if (good.containsKey(dest))
|
||||
return TrustLevel.TRUSTED
|
||||
else if (bad.containsKey(dest))
|
||||
return TrustLevel.DISTRUSTED
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
|
||||
void onTrustEvent(TrustEvent e) {
|
||||
switch(e.level) {
|
||||
case TrustLevel.TRUSTED:
|
||||
bad.remove(e.persona.destination)
|
||||
good.put(e.persona.destination, e.persona)
|
||||
break
|
||||
case TrustLevel.DISTRUSTED:
|
||||
good.remove(e.persona.destination)
|
||||
bad.put(e.persona.destination, e.persona)
|
||||
break
|
||||
case TrustLevel.NEUTRAL:
|
||||
good.remove(e.persona.destination)
|
||||
bad.remove(e.persona.destination)
|
||||
break
|
||||
}
|
||||
}
|
||||
void onTrustEvent(TrustEvent e) {
|
||||
switch(e.level) {
|
||||
case TrustLevel.TRUSTED:
|
||||
bad.remove(e.persona.destination)
|
||||
good.put(e.persona.destination, e.persona)
|
||||
break
|
||||
case TrustLevel.DISTRUSTED:
|
||||
good.remove(e.persona.destination)
|
||||
bad.put(e.persona.destination, e.persona)
|
||||
break
|
||||
case TrustLevel.NEUTRAL:
|
||||
good.remove(e.persona.destination)
|
||||
bad.remove(e.persona.destination)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class ContentUploader extends Uploader {
|
||||
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)
|
||||
Set<Persona> sources = mesh.getRandom(9, toExclude)
|
||||
if (!sources.isEmpty()) {
|
||||
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
||||
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||
|
||||
@@ -11,40 +11,40 @@ import net.i2p.data.Base64
|
||||
|
||||
class DataUtil {
|
||||
|
||||
private final static int MAX_SHORT = (0x1 << 16) - 1
|
||||
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")
|
||||
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)
|
||||
byte lsb = (byte) (value & 0xFF)
|
||||
byte msb = (byte) (value >> 8)
|
||||
|
||||
os.write(msb)
|
||||
os.write(lsb)
|
||||
}
|
||||
os.write(msb)
|
||||
os.write(lsb)
|
||||
}
|
||||
|
||||
private final static int MAX_HEADER = 0x7FFFFF
|
||||
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")
|
||||
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)
|
||||
}
|
||||
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")
|
||||
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)
|
||||
}
|
||||
return (((int)(header[0] & 0x7F)) << 16) |
|
||||
(((int)(header[1] & 0xFF) << 8)) |
|
||||
((int)header[2] & 0xFF)
|
||||
}
|
||||
|
||||
static String readi18nString(byte [] encoded) {
|
||||
if (encoded.length < 2)
|
||||
|
||||
@@ -8,16 +8,16 @@ import net.i2p.data.Destination;
|
||||
|
||||
public class DownloadedFile extends SharedFile {
|
||||
|
||||
private final Set<Destination> sources;
|
||||
private final Set<Destination> sources;
|
||||
|
||||
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
||||
throws IOException {
|
||||
super(file, infoHash, pieceSize);
|
||||
this.sources = 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;
|
||||
}
|
||||
public Set<Destination> getSources() {
|
||||
return sources;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,83 +11,83 @@ import net.i2p.data.Base64;
|
||||
|
||||
public class InfoHash {
|
||||
|
||||
public static final int SIZE = 0x1 << 5;
|
||||
public static final int SIZE = 0x1 << 5;
|
||||
|
||||
private final byte[] root;
|
||||
private final byte[] hashList;
|
||||
private final byte[] root;
|
||||
private final byte[] hashList;
|
||||
|
||||
private final int hashCode;
|
||||
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)
|
||||
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
||||
this.root = root;
|
||||
this.hashList = hashList;
|
||||
hashCode = root[0] << 24 |
|
||||
root[1] << 16 |
|
||||
root[2] << 8 |
|
||||
root[3];
|
||||
}
|
||||
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)
|
||||
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
||||
this.root = root;
|
||||
this.hashList = hashList;
|
||||
hashCode = root[0] << 24 |
|
||||
root[1] << 16 |
|
||||
root[2] << 8 |
|
||||
root[3];
|
||||
}
|
||||
|
||||
public InfoHash(byte[] root) {
|
||||
this(root, null);
|
||||
}
|
||||
public InfoHash(byte[] root) {
|
||||
this(root, null);
|
||||
}
|
||||
|
||||
public InfoHash(String base32) {
|
||||
this(Base32.decode(base32));
|
||||
}
|
||||
public InfoHash(String base32) {
|
||||
this(Base32.decode(base32));
|
||||
}
|
||||
|
||||
public static InfoHash fromHashList(byte []hashList) {
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
byte[] root = sha256.digest(hashList);
|
||||
return new InfoHash(root, hashList);
|
||||
} catch (NoSuchAlgorithmException impossible) {
|
||||
impossible.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static InfoHash fromHashList(byte []hashList) {
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
byte[] root = sha256.digest(hashList);
|
||||
return new InfoHash(root, hashList);
|
||||
} catch (NoSuchAlgorithmException impossible) {
|
||||
impossible.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public byte[] getRoot() {
|
||||
return root;
|
||||
}
|
||||
public byte[] getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
public byte[] getHashList() {
|
||||
return hashList;
|
||||
}
|
||||
public byte[] getHashList() {
|
||||
return hashList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof InfoHash)) {
|
||||
return false;
|
||||
}
|
||||
InfoHash other = (InfoHash) o;
|
||||
return Arrays.equals(root, other.root);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof InfoHash)) {
|
||||
return false;
|
||||
}
|
||||
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) {
|
||||
byte [] tmp = new byte[SIZE];
|
||||
for (int i = 0; i < hashList.length / SIZE; i++) {
|
||||
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
||||
b64HashList.add(Base64.encode(tmp));
|
||||
}
|
||||
}
|
||||
rv += b64HashList.toString();
|
||||
rv += "]";
|
||||
return rv;
|
||||
}
|
||||
public String toString() {
|
||||
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
||||
List<String> b64HashList = new ArrayList<>();
|
||||
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);
|
||||
b64HashList.add(Base64.encode(tmp));
|
||||
}
|
||||
}
|
||||
rv += b64HashList.toString();
|
||||
rv += "]";
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,60 +5,60 @@ import java.io.IOException;
|
||||
|
||||
public class SharedFile {
|
||||
|
||||
private final File file;
|
||||
private final InfoHash infoHash;
|
||||
private final int pieceSize;
|
||||
private final File file;
|
||||
private final InfoHash infoHash;
|
||||
private final int pieceSize;
|
||||
|
||||
private final String cachedPath;
|
||||
private final long cachedLength;
|
||||
private final String cachedPath;
|
||||
private final long cachedLength;
|
||||
|
||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||
this.file = file;
|
||||
this.infoHash = infoHash;
|
||||
this.pieceSize = pieceSize;
|
||||
this.cachedPath = file.getAbsolutePath();
|
||||
this.cachedLength = file.length();
|
||||
}
|
||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||
this.file = file;
|
||||
this.infoHash = infoHash;
|
||||
this.pieceSize = pieceSize;
|
||||
this.cachedPath = file.getAbsolutePath();
|
||||
this.cachedLength = file.length();
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public InfoHash getInfoHash() {
|
||||
return infoHash;
|
||||
}
|
||||
public InfoHash getInfoHash() {
|
||||
return infoHash;
|
||||
}
|
||||
|
||||
public int getPieceSize() {
|
||||
return pieceSize;
|
||||
}
|
||||
public int getPieceSize() {
|
||||
return pieceSize;
|
||||
}
|
||||
|
||||
public int getNPieces() {
|
||||
long length = file.length();
|
||||
int rawPieceSize = 0x1 << pieceSize;
|
||||
int rv = (int) (length / rawPieceSize);
|
||||
if (length % rawPieceSize != 0)
|
||||
rv++;
|
||||
return rv;
|
||||
}
|
||||
public int getNPieces() {
|
||||
long length = file.length();
|
||||
int rawPieceSize = 0x1 << pieceSize;
|
||||
int rv = (int) (length / rawPieceSize);
|
||||
if (length % rawPieceSize != 0)
|
||||
rv++;
|
||||
return rv;
|
||||
}
|
||||
|
||||
public String getCachedPath() {
|
||||
return cachedPath;
|
||||
}
|
||||
public String getCachedPath() {
|
||||
return cachedPath;
|
||||
}
|
||||
|
||||
public long getCachedLength() {
|
||||
return cachedLength;
|
||||
}
|
||||
public long getCachedLength() {
|
||||
return cachedLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return file.hashCode() ^ infoHash.hashCode();
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return file.hashCode() ^ infoHash.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SharedFile))
|
||||
return false;
|
||||
SharedFile other = (SharedFile)o;
|
||||
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof SharedFile))
|
||||
return false;
|
||||
SharedFile other = (SharedFile)o;
|
||||
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package com.muwire.core.connection;
|
||||
|
||||
public enum ConnectionAttemptStatus {
|
||||
SUCCESSFUL, REJECTED, FAILED
|
||||
SUCCESSFUL, REJECTED, FAILED
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ package com.muwire.core.hostcache;
|
||||
*
|
||||
*/
|
||||
public enum CrawlerResponse {
|
||||
ALL, REGISTERED, NONE
|
||||
ALL, REGISTERED, NONE
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package com.muwire.core.trust;
|
||||
|
||||
public enum TrustLevel {
|
||||
TRUSTED, NEUTRAL, DISTRUSTED
|
||||
TRUSTED, NEUTRAL, DISTRUSTED
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import net.i2p.data.Destination
|
||||
|
||||
class Destinations {
|
||||
|
||||
Destination dest1 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul9AAAA")
|
||||
Destination dest2 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul8AAAA")
|
||||
Destination dest1 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul9AAAA")
|
||||
Destination dest2 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul8AAAA")
|
||||
}
|
||||
|
||||
@@ -4,23 +4,23 @@ import org.junit.Test
|
||||
|
||||
class EventBusTest {
|
||||
|
||||
class FakeEvent extends Event {}
|
||||
class FakeEvent extends Event {}
|
||||
|
||||
class FakeEventHandler {
|
||||
def onFakeEvent(FakeEvent e) {
|
||||
assert e == fakeEvent
|
||||
}
|
||||
}
|
||||
class FakeEventHandler {
|
||||
def onFakeEvent(FakeEvent e) {
|
||||
assert e == fakeEvent
|
||||
}
|
||||
}
|
||||
|
||||
FakeEvent fakeEvent = new FakeEvent()
|
||||
FakeEvent fakeEvent = new FakeEvent()
|
||||
|
||||
EventBus bus = new EventBus()
|
||||
def handler = new FakeEventHandler()
|
||||
EventBus bus = new EventBus()
|
||||
def handler = new FakeEventHandler()
|
||||
|
||||
@Test
|
||||
void testDynamicEvent() {
|
||||
bus.register(FakeEvent.class, handler)
|
||||
bus.publish(fakeEvent)
|
||||
}
|
||||
@Test
|
||||
void testDynamicEvent() {
|
||||
bus.register(FakeEvent.class, handler)
|
||||
bus.publish(fakeEvent)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import org.junit.Test
|
||||
|
||||
class InfoHashTest {
|
||||
|
||||
@Test
|
||||
void testEmpty() {
|
||||
byte [] empty = new byte[0x1 << 6];
|
||||
def ih = InfoHash.fromHashList(empty)
|
||||
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
||||
assertEquals(ih, ih2);
|
||||
}
|
||||
@Test
|
||||
void testEmpty() {
|
||||
byte [] empty = new byte[0x1 << 6];
|
||||
def ih = InfoHash.fromHashList(empty)
|
||||
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
||||
assertEquals(ih, ih2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,21 +22,21 @@ import groovy.mock.interceptor.MockFor
|
||||
|
||||
class ConnectionAcceptorTest {
|
||||
|
||||
EventBus eventBus
|
||||
final Destinations destinations = new Destinations()
|
||||
def settings
|
||||
EventBus eventBus
|
||||
final Destinations destinations = new Destinations()
|
||||
def settings
|
||||
|
||||
def connectionManagerMock
|
||||
UltrapeerConnectionManager connectionManager
|
||||
def connectionManagerMock
|
||||
UltrapeerConnectionManager connectionManager
|
||||
|
||||
def i2pAcceptorMock
|
||||
I2PAcceptor i2pAcceptor
|
||||
def i2pAcceptorMock
|
||||
I2PAcceptor i2pAcceptor
|
||||
|
||||
def hostCacheMock
|
||||
HostCache hostCache
|
||||
def hostCacheMock
|
||||
HostCache hostCache
|
||||
|
||||
def trustServiceMock
|
||||
TrustService trustService
|
||||
def trustServiceMock
|
||||
TrustService trustService
|
||||
|
||||
def searchManagerMock
|
||||
SearchManager searchManager
|
||||
@@ -47,361 +47,361 @@ class ConnectionAcceptorTest {
|
||||
def connectionEstablisherMock
|
||||
ConnectionEstablisher connectionEstablisher
|
||||
|
||||
ConnectionAcceptor acceptor
|
||||
List<ConnectionEvent> connectionEvents
|
||||
InputStream inputStream
|
||||
OutputStream outputStream
|
||||
ConnectionAcceptor acceptor
|
||||
List<ConnectionEvent> connectionEvents
|
||||
InputStream inputStream
|
||||
OutputStream outputStream
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
||||
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
||||
hostCacheMock = new MockFor(HostCache.class)
|
||||
trustServiceMock = new MockFor(TrustService.class)
|
||||
@Before
|
||||
void before() {
|
||||
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
||||
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
||||
hostCacheMock = new MockFor(HostCache.class)
|
||||
trustServiceMock = new MockFor(TrustService.class)
|
||||
searchManagerMock = new MockFor(SearchManager.class)
|
||||
uploadManagerMock = new MockFor(UploadManager.class)
|
||||
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
void after() {
|
||||
acceptor?.stop()
|
||||
connectionManagerMock.verify connectionManager
|
||||
i2pAcceptorMock.verify i2pAcceptor
|
||||
hostCacheMock.verify hostCache
|
||||
trustServiceMock.verify trustService
|
||||
@After
|
||||
void after() {
|
||||
acceptor?.stop()
|
||||
connectionManagerMock.verify connectionManager
|
||||
i2pAcceptorMock.verify i2pAcceptor
|
||||
hostCacheMock.verify hostCache
|
||||
trustServiceMock.verify trustService
|
||||
searchManagerMock.verify searchManager
|
||||
uploadManagerMock.verify uploadManager
|
||||
connectionEstablisherMock.verify connectionEstablisher
|
||||
Thread.sleep(100)
|
||||
}
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
private void initMocks() {
|
||||
connectionEvents = new CopyOnWriteArrayList()
|
||||
eventBus = new EventBus()
|
||||
def listener = new Object() {
|
||||
void onConnectionEvent(ConnectionEvent e) {
|
||||
connectionEvents.add e
|
||||
}
|
||||
}
|
||||
eventBus.register(ConnectionEvent.class, listener)
|
||||
private void initMocks() {
|
||||
connectionEvents = new CopyOnWriteArrayList()
|
||||
eventBus = new EventBus()
|
||||
def listener = new Object() {
|
||||
void onConnectionEvent(ConnectionEvent e) {
|
||||
connectionEvents.add e
|
||||
}
|
||||
}
|
||||
eventBus.register(ConnectionEvent.class, listener)
|
||||
|
||||
connectionManager = connectionManagerMock.proxyInstance()
|
||||
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
||||
hostCache = hostCacheMock.proxyInstance()
|
||||
trustService = trustServiceMock.proxyInstance()
|
||||
connectionManager = connectionManagerMock.proxyInstance()
|
||||
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
||||
hostCache = hostCacheMock.proxyInstance()
|
||||
trustService = trustServiceMock.proxyInstance()
|
||||
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)
|
||||
}
|
||||
acceptor.start()
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccessfulLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
@Test
|
||||
void testSuccessfulLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||
connectionManagerMock.demand.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
connectionManagerMock.demand.hasLeafSlots() { true }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
outputStream.write("MuWire leaf".bytes)
|
||||
byte [] OK = new byte[2]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "OK".bytes
|
||||
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]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||
assert event.incoming == true
|
||||
assert event.leaf == true
|
||||
}
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||
assert event.incoming == true
|
||||
assert event.leaf == true
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSuccessfulPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
@Test
|
||||
void testSuccessfulPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||
connectionManagerMock.demand.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
connectionManagerMock.demand.hasPeerSlots() { true }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
byte [] OK = new byte[2]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "OK".bytes
|
||||
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]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||
assert event.incoming == true
|
||||
assert event.leaf == false
|
||||
}
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||
assert event.incoming == true
|
||||
assert event.leaf == false
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLeafRejectsLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
true
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
@Test
|
||||
void testLeafRejectsLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
true
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
outputStream.write("MuWire leaf".bytes)
|
||||
outputStream.flush()
|
||||
Thread.sleep(50)
|
||||
assert inputStream.read() == -1
|
||||
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]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.FAILED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == null
|
||||
}
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.FAILED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == null
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLeafRejectsPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
true
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
@Test
|
||||
void testLeafRejectsPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
true
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
outputStream.flush()
|
||||
Thread.sleep(50)
|
||||
assert inputStream.read() == -1
|
||||
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]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.FAILED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == null
|
||||
}
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.FAILED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == null
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPeerRejectsPeerSlots() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
@Test
|
||||
void testPeerRejectsPeerSlots() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||
connectionManagerMock.demand.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
byte [] OK = new byte[6]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "REJECT".bytes
|
||||
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 dis.read() == -1
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == false
|
||||
}
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == false
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPeerRejectsLeafSlots() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
@Test
|
||||
void testPeerRejectsLeafSlots() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||
connectionManagerMock.demand.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
connectionManagerMock.demand.hasLeafSlots() { false }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
outputStream.write("MuWire leaf".bytes)
|
||||
byte [] OK = new byte[6]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "REJECT".bytes
|
||||
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 dis.read() == -1
|
||||
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == true
|
||||
}
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
assert event.incoming == true
|
||||
assert event.leaf == true
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPeerRejectsPeerSuggests() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
@Test
|
||||
void testPeerRejectsPeerSuggests() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {
|
||||
false
|
||||
}
|
||||
}
|
||||
i2pAcceptorMock.demand.accept {
|
||||
def is = new PipedInputStream()
|
||||
outputStream = new PipedOutputStream(is)
|
||||
def os = new PipedOutputStream()
|
||||
inputStream = new PipedInputStream(os)
|
||||
new Endpoint(destinations.dest1, is, os, null)
|
||||
}
|
||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||
connectionManagerMock.demand.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
||||
trustServiceMock.demand.getLevel { dest ->
|
||||
assert dest == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
outputStream.write("MuWire peer".bytes)
|
||||
byte [] OK = new byte[6]
|
||||
def dis = new DataInputStream(inputStream)
|
||||
dis.readFully(OK)
|
||||
assert OK == "REJECT".bytes
|
||||
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
|
||||
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())
|
||||
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]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
}
|
||||
Thread.sleep(50)
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,271 +17,271 @@ import groovy.mock.interceptor.MockFor
|
||||
|
||||
class ConnectionEstablisherTest {
|
||||
|
||||
EventBus eventBus
|
||||
final Destinations destinations = new Destinations()
|
||||
List<ConnectionEvent> connectionEvents
|
||||
List<HostDiscoveredEvent> discoveredEvents
|
||||
DataInputStream inputStream
|
||||
DataOutputStream outputStream
|
||||
EventBus eventBus
|
||||
final Destinations destinations = new Destinations()
|
||||
List<ConnectionEvent> connectionEvents
|
||||
List<HostDiscoveredEvent> discoveredEvents
|
||||
DataInputStream inputStream
|
||||
DataOutputStream outputStream
|
||||
|
||||
def i2pConnectorMock
|
||||
I2PConnector i2pConnector
|
||||
def i2pConnectorMock
|
||||
I2PConnector i2pConnector
|
||||
|
||||
MuWireSettings settings
|
||||
MuWireSettings settings
|
||||
|
||||
def connectionManagerMock
|
||||
ConnectionManager connectionManager
|
||||
def connectionManagerMock
|
||||
ConnectionManager connectionManager
|
||||
|
||||
def hostCacheMock
|
||||
HostCache hostCache
|
||||
def hostCacheMock
|
||||
HostCache hostCache
|
||||
|
||||
ConnectionEstablisher establisher
|
||||
ConnectionEstablisher establisher
|
||||
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
connectionEvents = new CopyOnWriteArrayList()
|
||||
discoveredEvents = new CopyOnWriteArrayList()
|
||||
def listener = new Object() {
|
||||
void onConnectionEvent(ConnectionEvent e) {
|
||||
connectionEvents.add(e)
|
||||
}
|
||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||
discoveredEvents.add e
|
||||
}
|
||||
}
|
||||
eventBus = new EventBus()
|
||||
eventBus.register(ConnectionEvent.class, listener)
|
||||
eventBus.register(HostDiscoveredEvent.class, listener)
|
||||
i2pConnectorMock = new MockFor(I2PConnector.class)
|
||||
connectionManagerMock = new MockFor(ConnectionManager.class)
|
||||
hostCacheMock = new MockFor(HostCache.class)
|
||||
}
|
||||
@Before
|
||||
void before() {
|
||||
connectionEvents = new CopyOnWriteArrayList()
|
||||
discoveredEvents = new CopyOnWriteArrayList()
|
||||
def listener = new Object() {
|
||||
void onConnectionEvent(ConnectionEvent e) {
|
||||
connectionEvents.add(e)
|
||||
}
|
||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||
discoveredEvents.add e
|
||||
}
|
||||
}
|
||||
eventBus = new EventBus()
|
||||
eventBus.register(ConnectionEvent.class, listener)
|
||||
eventBus.register(HostDiscoveredEvent.class, listener)
|
||||
i2pConnectorMock = new MockFor(I2PConnector.class)
|
||||
connectionManagerMock = new MockFor(ConnectionManager.class)
|
||||
hostCacheMock = new MockFor(HostCache.class)
|
||||
}
|
||||
|
||||
@After
|
||||
void after() {
|
||||
establisher?.stop()
|
||||
i2pConnectorMock.verify i2pConnector
|
||||
connectionManagerMock.verify connectionManager
|
||||
hostCacheMock.verify hostCache
|
||||
Thread.sleep(100)
|
||||
}
|
||||
@After
|
||||
void after() {
|
||||
establisher?.stop()
|
||||
i2pConnectorMock.verify i2pConnector
|
||||
connectionManagerMock.verify connectionManager
|
||||
hostCacheMock.verify hostCache
|
||||
Thread.sleep(100)
|
||||
}
|
||||
|
||||
private void initMocks() {
|
||||
i2pConnector = i2pConnectorMock.proxyInstance()
|
||||
connectionManager = connectionManagerMock.proxyInstance()
|
||||
hostCache = hostCacheMock.proxyInstance()
|
||||
establisher = new ConnectionEstablisher(eventBus, i2pConnector, settings, connectionManager, hostCache)
|
||||
establisher.start()
|
||||
Thread.sleep(250)
|
||||
}
|
||||
private void initMocks() {
|
||||
i2pConnector = i2pConnectorMock.proxyInstance()
|
||||
connectionManager = connectionManagerMock.proxyInstance()
|
||||
hostCache = hostCacheMock.proxyInstance()
|
||||
establisher = new ConnectionEstablisher(eventBus, i2pConnector, settings, connectionManager, hostCache)
|
||||
establisher.start()
|
||||
Thread.sleep(250)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testConnectFails() {
|
||||
settings = new MuWireSettings()
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
assert dest == destinations.dest1
|
||||
throw new IOException()
|
||||
}
|
||||
@Test
|
||||
void testConnectFails() {
|
||||
settings = new MuWireSettings()
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
assert dest == destinations.dest1
|
||||
throw new IOException()
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
assert connectionEvents.size() == 1
|
||||
def event = connectionEvents[0]
|
||||
assert event.endpoint.destination == destinations.dest1
|
||||
assert event.incoming == false
|
||||
assert event.status == ConnectionAttemptStatus.FAILED
|
||||
}
|
||||
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() {
|
||||
boolean isLeaf() {false}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
@Test
|
||||
void testConnectionSucceedsPeer() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {false}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
|
||||
outputStream.write("OK".bytes)
|
||||
outputStream.flush()
|
||||
outputStream.write("OK".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
Thread.sleep(100)
|
||||
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
|
||||
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() {
|
||||
boolean isLeaf() {true}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
@Test
|
||||
void testConnectionSucceedsLeaf() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {true}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire leaf".bytes
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire leaf".bytes
|
||||
|
||||
outputStream.write("OK".bytes)
|
||||
outputStream.flush()
|
||||
outputStream.write("OK".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
Thread.sleep(100)
|
||||
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
|
||||
}
|
||||
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() {
|
||||
boolean isLeaf() {false}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
@Test
|
||||
void testConnectionRejected() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {false}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
|
||||
outputStream.write("REJECT".bytes)
|
||||
outputStream.flush()
|
||||
outputStream.write("REJECT".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
Thread.sleep(100)
|
||||
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.isEmpty()
|
||||
}
|
||||
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.isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConnectionRejectedSuggestions() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {false}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
@Test
|
||||
void testConnectionRejectedSuggestions() {
|
||||
settings = new MuWireSettings() {
|
||||
boolean isLeaf() {false}
|
||||
}
|
||||
connectionManagerMock.ignore.needsConnections {
|
||||
true
|
||||
}
|
||||
hostCacheMock.ignore.getHosts { num ->
|
||||
assert num == 1
|
||||
[destinations.dest1]
|
||||
}
|
||||
connectionManagerMock.ignore.isConnected { dest ->
|
||||
assert dest == destinations.dest1
|
||||
false
|
||||
}
|
||||
i2pConnectorMock.demand.connect { dest ->
|
||||
PipedOutputStream os = new PipedOutputStream()
|
||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||
PipedInputStream is = new PipedInputStream()
|
||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||
new Endpoint(dest, is, os, null)
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
byte [] header = new byte[11]
|
||||
inputStream.readFully(header)
|
||||
assert header == "MuWire peer".bytes
|
||||
|
||||
outputStream.write("REJECT".bytes)
|
||||
outputStream.flush()
|
||||
outputStream.write("REJECT".bytes)
|
||||
outputStream.flush()
|
||||
|
||||
def json = [:]
|
||||
json.tryHosts = [destinations.dest2.toBase64()]
|
||||
json = JsonOutput.toJson(json)
|
||||
outputStream.writeShort(json.bytes.length)
|
||||
outputStream.write(json.bytes)
|
||||
outputStream.flush()
|
||||
Thread.sleep(100)
|
||||
def json = [:]
|
||||
json.tryHosts = [destinations.dest2.toBase64()]
|
||||
json = JsonOutput.toJson(json)
|
||||
outputStream.writeShort(json.bytes.length)
|
||||
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 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
|
||||
}
|
||||
assert discoveredEvents.size() == 1
|
||||
event = discoveredEvents[0]
|
||||
assert event.destination == destinations.dest2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,76 +8,76 @@ import org.junit.Test
|
||||
|
||||
class FileHasherTest extends GroovyTestCase {
|
||||
|
||||
def hasher = new FileHasher()
|
||||
File tmp
|
||||
def hasher = new FileHasher()
|
||||
File tmp
|
||||
|
||||
@Before
|
||||
void setUp() {
|
||||
tmp = File.createTempFile("testFile", "test")
|
||||
tmp.deleteOnExit()
|
||||
}
|
||||
@Before
|
||||
void setUp() {
|
||||
tmp = File.createTempFile("testFile", "test")
|
||||
tmp.deleteOnExit()
|
||||
}
|
||||
|
||||
@After
|
||||
void tearDown() {
|
||||
tmp?.delete()
|
||||
}
|
||||
@After
|
||||
void tearDown() {
|
||||
tmp?.delete()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPieceSize() {
|
||||
assert 17 == FileHasher.getPieceSize(1000000)
|
||||
assert 17 == FileHasher.getPieceSize(100000000)
|
||||
assert 24 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
||||
shouldFail IllegalArgumentException, {
|
||||
FileHasher.getPieceSize(Long.MAX_VALUE)
|
||||
}
|
||||
}
|
||||
@Test
|
||||
void testPieceSize() {
|
||||
assert 17 == FileHasher.getPieceSize(1000000)
|
||||
assert 17 == FileHasher.getPieceSize(100000000)
|
||||
assert 24 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
||||
shouldFail IllegalArgumentException, {
|
||||
FileHasher.getPieceSize(Long.MAX_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHash1Byte() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
fos.write(0)
|
||||
fos.close()
|
||||
def ih = hasher.hashFile(tmp)
|
||||
assert ih.getHashList().length == 32
|
||||
}
|
||||
@Test
|
||||
void testHash1Byte() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
fos.write(0)
|
||||
fos.close()
|
||||
def ih = hasher.hashFile(tmp)
|
||||
assert ih.getHashList().length == 32
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHash1PieceExact() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ 0x1 << 18]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 64
|
||||
}
|
||||
@Test
|
||||
void testHash1PieceExact() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ 0x1 << 18]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 64
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHash1Piece1Byte() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ (0x1 << 18) + 1]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 96
|
||||
}
|
||||
@Test
|
||||
void testHash1Piece1Byte() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ (0x1 << 18) + 1]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 96
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHash2Pieces() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ (0x1 << 19)]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 128
|
||||
}
|
||||
@Test
|
||||
void testHash2Pieces() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ (0x1 << 19)]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 128
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHash2Pieces2Bytes() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ (0x1 << 19) + 2]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 160
|
||||
}
|
||||
@Test
|
||||
void testHash2Pieces2Bytes() {
|
||||
def fos = new FileOutputStream(tmp)
|
||||
byte [] b = new byte[ (0x1 << 19) + 2]
|
||||
fos.write b
|
||||
fos.close()
|
||||
def ih = hasher.hashFile tmp
|
||||
assert ih.getHashList().length == 160
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,177 +12,177 @@ import com.muwire.core.search.SearchEvent
|
||||
|
||||
class FileManagerTest {
|
||||
|
||||
EventBus eventBus
|
||||
EventBus eventBus
|
||||
|
||||
FileManager manager
|
||||
volatile ResultsEvent results
|
||||
FileManager manager
|
||||
volatile ResultsEvent results
|
||||
|
||||
def listener = new Object() {
|
||||
void onResultsEvent(ResultsEvent e) {
|
||||
results = e
|
||||
}
|
||||
}
|
||||
def listener = new Object() {
|
||||
void onResultsEvent(ResultsEvent e) {
|
||||
results = e
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
eventBus = new EventBus()
|
||||
eventBus.register(ResultsEvent.class, listener)
|
||||
manager = new FileManager(eventBus, new MuWireSettings())
|
||||
results = null
|
||||
}
|
||||
@Before
|
||||
void before() {
|
||||
eventBus = new EventBus()
|
||||
eventBus.register(ResultsEvent.class, listener)
|
||||
manager = new FileManager(eventBus, new MuWireSettings())
|
||||
results = null
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHash1Result() {
|
||||
File f = new File("a b.c")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf = new SharedFile(f,ih, 0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
@Test
|
||||
void testHash1Result() {
|
||||
File f = new File("a b.c")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
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)
|
||||
UUID uuid = UUID.randomUUID()
|
||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||
|
||||
manager.onSearchEvent(se)
|
||||
Thread.sleep(20)
|
||||
manager.onSearchEvent(se)
|
||||
Thread.sleep(20)
|
||||
|
||||
assert results != null
|
||||
assert results.results.size() == 1
|
||||
assert results.results.contains(sf)
|
||||
assert results.uuid == uuid
|
||||
}
|
||||
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])
|
||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
||||
@Test
|
||||
void testHash2Results() {
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
||||
|
||||
UUID uuid = UUID.randomUUID()
|
||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||
UUID uuid = UUID.randomUUID()
|
||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||
|
||||
manager.onSearchEvent(se)
|
||||
Thread.sleep(20)
|
||||
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
|
||||
}
|
||||
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")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf = new SharedFile(f,ih, 0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
@Test
|
||||
void testHash0Results() {
|
||||
File f = new File("a b.c")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
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)
|
||||
manager.onSearchEvent new SearchEvent(searchHash: new byte[32], uuid: UUID.randomUUID())
|
||||
Thread.sleep(20)
|
||||
|
||||
assert results == null
|
||||
}
|
||||
assert results == null
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKeyword1Result() {
|
||||
File f = new File("a b.c")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf = new SharedFile(f,ih,0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
@Test
|
||||
void testKeyword1Result() {
|
||||
File f = new File("a b.c")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
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)
|
||||
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 != null
|
||||
assert results.results.size() == 1
|
||||
assert results.results.contains(sf)
|
||||
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)
|
||||
@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)
|
||||
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)
|
||||
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
|
||||
}
|
||||
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")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf = new SharedFile(f,ih,0)
|
||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||
manager.onFileHashedEvent(fhe)
|
||||
@Test
|
||||
void testKeyword0Results() {
|
||||
File f = new File("a b.c")
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
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)
|
||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"], uuid: UUID.randomUUID())
|
||||
Thread.sleep(20)
|
||||
|
||||
assert results == null
|
||||
}
|
||||
assert results == null
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveFileExistingHash() {
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
||||
@Test
|
||||
void testRemoveFileExistingHash() {
|
||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||
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.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)
|
||||
}
|
||||
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)
|
||||
@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)
|
||||
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)
|
||||
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)
|
||||
// 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
|
||||
// no match
|
||||
results = null
|
||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"])
|
||||
assert results == null
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,54 +12,54 @@ import com.muwire.core.MuWireSettings
|
||||
|
||||
class HasherServiceTest {
|
||||
|
||||
HasherService service
|
||||
FileHasher hasher
|
||||
HasherService service
|
||||
FileHasher hasher
|
||||
EventBus eventBus
|
||||
def listener = new ArrayBlockingQueue(100) {
|
||||
void onFileHashedEvent(FileHashedEvent evt) {
|
||||
offer evt
|
||||
}
|
||||
}
|
||||
def listener = new ArrayBlockingQueue(100) {
|
||||
void onFileHashedEvent(FileHashedEvent evt) {
|
||||
offer evt
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
@Before
|
||||
void before() {
|
||||
eventBus = new EventBus()
|
||||
hasher = new FileHasher()
|
||||
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
||||
hasher = new FileHasher()
|
||||
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
||||
eventBus.register(FileHashedEvent.class, listener)
|
||||
eventBus.register(FileSharedEvent.class, service)
|
||||
service.start()
|
||||
}
|
||||
service.start()
|
||||
}
|
||||
|
||||
@After
|
||||
void after() {
|
||||
listener.clear()
|
||||
}
|
||||
@After
|
||||
void after() {
|
||||
listener.clear()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSingleFile() {
|
||||
File f = new File("build.gradle")
|
||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||
Thread.sleep(100)
|
||||
def hashed = listener.poll()
|
||||
assert hashed instanceof FileHashedEvent
|
||||
assert hashed.sharedFile.file == f.getCanonicalFile()
|
||||
assert hashed.sharedFile.infoHash != null
|
||||
assert listener.isEmpty()
|
||||
}
|
||||
@Test
|
||||
void testSingleFile() {
|
||||
File f = new File("build.gradle")
|
||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||
Thread.sleep(100)
|
||||
def hashed = listener.poll()
|
||||
assert hashed instanceof FileHashedEvent
|
||||
assert hashed.sharedFile.file == f.getCanonicalFile()
|
||||
assert hashed.sharedFile.infoHash != null
|
||||
assert listener.isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDirectory() {
|
||||
File f = new File(".")
|
||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||
Set<String> fileNames = new HashSet<>()
|
||||
while (true) {
|
||||
def hashed = listener.poll(1000, TimeUnit.MILLISECONDS)
|
||||
if (hashed == null)
|
||||
break
|
||||
fileNames.add(hashed.sharedFile?.file?.getName())
|
||||
}
|
||||
assert fileNames.contains("build.gradle")
|
||||
assert fileNames.contains("HasherServiceTest.groovy")
|
||||
}
|
||||
@Test
|
||||
void testDirectory() {
|
||||
File f = new File(".")
|
||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||
Set<String> fileNames = new HashSet<>()
|
||||
while (true) {
|
||||
def hashed = listener.poll(1000, TimeUnit.MILLISECONDS)
|
||||
if (hashed == null)
|
||||
break
|
||||
fileNames.add(hashed.sharedFile?.file?.getName())
|
||||
}
|
||||
assert fileNames.contains("build.gradle")
|
||||
assert fileNames.contains("HasherServiceTest.groovy")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,193 +17,193 @@ import net.i2p.data.Base64
|
||||
|
||||
class PersisterServiceLoadingTest {
|
||||
|
||||
class Listener {
|
||||
def publishedFiles = []
|
||||
def onFileLoadedEvent(FileLoadedEvent e) {
|
||||
publishedFiles.add(e.loadedFile)
|
||||
}
|
||||
}
|
||||
class Listener {
|
||||
def publishedFiles = []
|
||||
def onFileLoadedEvent(FileLoadedEvent e) {
|
||||
publishedFiles.add(e.loadedFile)
|
||||
}
|
||||
}
|
||||
|
||||
EventBus eventBus
|
||||
Listener listener
|
||||
File sharedDir
|
||||
File sharedFile1, sharedFile2
|
||||
EventBus eventBus
|
||||
Listener listener
|
||||
File sharedDir
|
||||
File sharedFile1, sharedFile2
|
||||
|
||||
@Before
|
||||
void setup() {
|
||||
eventBus = new EventBus()
|
||||
listener = new Listener()
|
||||
eventBus.register(FileLoadedEvent.class, listener)
|
||||
@Before
|
||||
void setup() {
|
||||
eventBus = new EventBus()
|
||||
listener = new Listener()
|
||||
eventBus.register(FileLoadedEvent.class, listener)
|
||||
|
||||
sharedDir = new File("sharedDir")
|
||||
sharedDir.mkdir()
|
||||
sharedDir.deleteOnExit()
|
||||
sharedDir = new File("sharedDir")
|
||||
sharedDir.mkdir()
|
||||
sharedDir.deleteOnExit()
|
||||
|
||||
sharedFile1 = new File(sharedDir,"file1")
|
||||
sharedFile1.deleteOnExit()
|
||||
sharedFile1 = new File(sharedDir,"file1")
|
||||
sharedFile1.deleteOnExit()
|
||||
|
||||
sharedFile2 = new File(sharedDir,"file2")
|
||||
sharedFile2.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 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())
|
||||
persisted.delete()
|
||||
persisted.deleteOnExit()
|
||||
persisted
|
||||
}
|
||||
private File initPersisted() {
|
||||
File persisted = new File("persisted")
|
||||
if (persisted.exists())
|
||||
persisted.delete()
|
||||
persisted.deleteOnExit()
|
||||
persisted
|
||||
}
|
||||
|
||||
@Test
|
||||
void test1SharedFile1Piece() {
|
||||
writeToSharedFile(sharedFile1, 1)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
@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())]
|
||||
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)
|
||||
json = JsonOutput.toJson(json)
|
||||
|
||||
File persisted = initPersisted()
|
||||
persisted.write json
|
||||
File persisted = initPersisted()
|
||||
persisted.write json
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
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
|
||||
}
|
||||
assert listener.publishedFiles.size() == 1
|
||||
def loadedFile = listener.publishedFiles[0]
|
||||
assert loadedFile != null
|
||||
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
||||
assert loadedFile.infoHash == ih1
|
||||
}
|
||||
|
||||
private static String getSharedFileJsonName(File sharedFile) {
|
||||
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)
|
||||
@Test
|
||||
public void test1SharedFile2Pieces() {
|
||||
writeToSharedFile(sharedFile1, (0x1 << 18) + 1)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
|
||||
assert ih1.getHashList().length == 96
|
||||
assert ih1.getHashList().length == 96
|
||||
|
||||
def json = [:]
|
||||
json.file = getSharedFileJsonName(sharedFile1)
|
||||
json.length = sharedFile1.length()
|
||||
json.infoHash = Base64.encode ih1.getRoot()
|
||||
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)
|
||||
System.arraycopy(ih1.getHashList(), 32, tmp, 0, 32)
|
||||
String hash2 = Base64.encode(tmp)
|
||||
byte [] tmp = new byte[32]
|
||||
System.arraycopy(ih1.getHashList(), 0, tmp, 0, 32)
|
||||
String hash1 = Base64.encode(tmp)
|
||||
System.arraycopy(ih1.getHashList(), 32, tmp, 0, 32)
|
||||
String hash2 = Base64.encode(tmp)
|
||||
System.arraycopy(ih1.getHashList(), 64, tmp, 0, 32)
|
||||
String hash3 = Base64.encode(tmp)
|
||||
json.hashList = [hash1, hash2, hash3]
|
||||
json.hashList = [hash1, hash2, hash3]
|
||||
|
||||
json = JsonOutput.toJson(json)
|
||||
json = JsonOutput.toJson(json)
|
||||
|
||||
File persisted = initPersisted()
|
||||
persisted.write json
|
||||
File persisted = initPersisted()
|
||||
persisted.write json
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
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
|
||||
}
|
||||
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)
|
||||
writeToSharedFile(sharedFile2, 2)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
InfoHash ih2 = fh.hashFile(sharedFile2)
|
||||
@Test
|
||||
void test2SharedFiles() {
|
||||
writeToSharedFile(sharedFile1, 1)
|
||||
writeToSharedFile(sharedFile2, 2)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
InfoHash ih2 = fh.hashFile(sharedFile2)
|
||||
|
||||
assert ih1 != ih2
|
||||
assert ih1 != ih2
|
||||
|
||||
File persisted = initPersisted()
|
||||
File persisted = initPersisted()
|
||||
|
||||
def json1 = [:]
|
||||
json1.file = getSharedFileJsonName(sharedFile1)
|
||||
json1.length = 1
|
||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||
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)
|
||||
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())]
|
||||
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)
|
||||
json2 = JsonOutput.toJson(json2)
|
||||
|
||||
persisted.append "$json1\n"
|
||||
persisted.append "$json2\n"
|
||||
persisted.append "$json1\n"
|
||||
persisted.append "$json2\n"
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
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()
|
||||
assert loadedFile1.infoHash == ih1
|
||||
def loadedFile2 = listener.publishedFiles[1]
|
||||
assert loadedFile2.file == sharedFile2.getCanonicalFile()
|
||||
assert loadedFile2.infoHash == ih2
|
||||
}
|
||||
assert listener.publishedFiles.size() == 2
|
||||
def loadedFile1 = listener.publishedFiles[0]
|
||||
assert loadedFile1.file == sharedFile1.getCanonicalFile()
|
||||
assert loadedFile1.infoHash == ih1
|
||||
def loadedFile2 = listener.publishedFiles[1]
|
||||
assert loadedFile2.file == sharedFile2.getCanonicalFile()
|
||||
assert loadedFile2.infoHash == ih2
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDownloadedFile() {
|
||||
writeToSharedFile(sharedFile1, 1)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
@Test
|
||||
void testDownloadedFile() {
|
||||
writeToSharedFile(sharedFile1, 1)
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||
|
||||
File persisted = initPersisted()
|
||||
File persisted = initPersisted()
|
||||
|
||||
Destinations dests = new Destinations()
|
||||
def json1 = [:]
|
||||
json1.file = getSharedFileJsonName(sharedFile1)
|
||||
json1.length = 1
|
||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||
json1.sources = [ dests.dest1.toBase64(), dests.dest2.toBase64()]
|
||||
Destinations dests = new Destinations()
|
||||
def json1 = [:]
|
||||
json1.file = getSharedFileJsonName(sharedFile1)
|
||||
json1.length = 1
|
||||
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
|
||||
json1 = JsonOutput.toJson(json1)
|
||||
persisted.write json1
|
||||
|
||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(2000)
|
||||
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)
|
||||
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)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,79 +18,79 @@ import net.i2p.data.Base64
|
||||
|
||||
class PersisterServiceSavingTest {
|
||||
|
||||
File f
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih
|
||||
SharedFile sf
|
||||
def fileSource
|
||||
EventBus eventBus = new EventBus()
|
||||
File persisted
|
||||
PersisterService ps
|
||||
File f
|
||||
FileHasher fh = new FileHasher()
|
||||
InfoHash ih
|
||||
SharedFile sf
|
||||
def fileSource
|
||||
EventBus eventBus = new EventBus()
|
||||
File persisted
|
||||
PersisterService ps
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
f = new File("build.gradle")
|
||||
f = f.getCanonicalFile()
|
||||
ih = fh.hashFile(f)
|
||||
fileSource = new FileManager(eventBus, new MuWireSettings()) {
|
||||
Map<File, SharedFile> getSharedFiles() {
|
||||
Map<File, SharedFile> rv = new HashMap<>()
|
||||
rv.put(f, sf)
|
||||
rv
|
||||
}
|
||||
}
|
||||
persisted = new File("persisted")
|
||||
persisted.delete()
|
||||
persisted.deleteOnExit()
|
||||
}
|
||||
@Before
|
||||
void before() {
|
||||
f = new File("build.gradle")
|
||||
f = f.getCanonicalFile()
|
||||
ih = fh.hashFile(f)
|
||||
fileSource = new FileManager(eventBus, new MuWireSettings()) {
|
||||
Map<File, SharedFile> getSharedFiles() {
|
||||
Map<File, SharedFile> rv = new HashMap<>()
|
||||
rv.put(f, sf)
|
||||
rv
|
||||
}
|
||||
}
|
||||
persisted = new File("persisted")
|
||||
persisted.delete()
|
||||
persisted.deleteOnExit()
|
||||
}
|
||||
|
||||
@After
|
||||
void after() {
|
||||
ps?.stop()
|
||||
}
|
||||
@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)
|
||||
@Test
|
||||
void testSavingSharedFile() {
|
||||
sf = new SharedFile(f, ih, 0)
|
||||
|
||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(1500)
|
||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(1500)
|
||||
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
persisted.eachLine {
|
||||
def json = jsonSlurper.parseText(it)
|
||||
assert fromB64(json.file) == f.toString()
|
||||
assert json.length == f.length()
|
||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||
}
|
||||
}
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
persisted.eachLine {
|
||||
def json = jsonSlurper.parseText(it)
|
||||
assert fromB64(json.file) == f.toString()
|
||||
assert json.length == f.length()
|
||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||
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]))
|
||||
@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)
|
||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||
ps.onUILoadedEvent(null)
|
||||
Thread.sleep(1500)
|
||||
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
persisted.eachLine {
|
||||
def json = jsonSlurper.parseText(it)
|
||||
assert fromB64(json.file) == f.toString()
|
||||
assert json.length == f.length()
|
||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||
assert json.sources.size() == 2
|
||||
assert json.sources.contains(dests.dest1.toBase64())
|
||||
assert json.sources.contains(dests.dest2.toBase64())
|
||||
}
|
||||
}
|
||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||
persisted.eachLine {
|
||||
def json = jsonSlurper.parseText(it)
|
||||
assert fromB64(json.file) == f.toString()
|
||||
assert json.length == f.length()
|
||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||
assert json.sources.size() == 2
|
||||
assert json.sources.contains(dests.dest1.toBase64())
|
||||
assert json.sources.contains(dests.dest2.toBase64())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,250 +21,250 @@ import net.i2p.data.Destination
|
||||
class HostCacheTest {
|
||||
|
||||
|
||||
File persist
|
||||
HostCache cache
|
||||
File persist
|
||||
HostCache cache
|
||||
|
||||
def trustMock
|
||||
TrustService trust
|
||||
def trustMock
|
||||
TrustService trust
|
||||
|
||||
def settingsMock
|
||||
MuWireSettings settings
|
||||
def settingsMock
|
||||
MuWireSettings settings
|
||||
|
||||
Destinations destinations = new Destinations()
|
||||
Destinations destinations = new Destinations()
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
persist = new File("hostPersist")
|
||||
persist.delete()
|
||||
persist.deleteOnExit()
|
||||
@Before
|
||||
void before() {
|
||||
persist = new File("hostPersist")
|
||||
persist.delete()
|
||||
persist.deleteOnExit()
|
||||
|
||||
trustMock = new MockFor(TrustService.class)
|
||||
settingsMock = new MockFor(MuWireSettings.class)
|
||||
}
|
||||
trustMock = new MockFor(TrustService.class)
|
||||
settingsMock = new MockFor(MuWireSettings.class)
|
||||
}
|
||||
|
||||
@After
|
||||
void after() {
|
||||
cache?.stop()
|
||||
trustMock.verify trust
|
||||
settingsMock.verify settings
|
||||
Thread.sleep(150)
|
||||
}
|
||||
@After
|
||||
void after() {
|
||||
cache?.stop()
|
||||
trustMock.verify trust
|
||||
settingsMock.verify settings
|
||||
Thread.sleep(150)
|
||||
}
|
||||
|
||||
private void initMocks() {
|
||||
trust = trustMock.proxyInstance()
|
||||
settings = settingsMock.proxyInstance()
|
||||
cache = new HostCache(trust, persist, 100, settings, new Destination())
|
||||
cache.start()
|
||||
Thread.sleep(150)
|
||||
}
|
||||
private void initMocks() {
|
||||
trust = trustMock.proxyInstance()
|
||||
settings = settingsMock.proxyInstance()
|
||||
cache = new HostCache(trust, persist, 100, settings, new Destination())
|
||||
cache.start()
|
||||
Thread.sleep(150)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmpty() {
|
||||
initMocks()
|
||||
assert cache.getHosts(5).size() == 0
|
||||
assert cache.getGoodHosts(5).size() == 0
|
||||
}
|
||||
@Test
|
||||
void testEmpty() {
|
||||
initMocks()
|
||||
assert cache.getHosts(5).size() == 0
|
||||
assert cache.getGoodHosts(5).size() == 0
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOnDiscoveredEvent() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { true }
|
||||
@Test
|
||||
void testOnDiscoveredEvent() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { true }
|
||||
|
||||
initMocks()
|
||||
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)
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
|
||||
assert cache.getGoodHosts(5).size() == 0
|
||||
}
|
||||
assert cache.getGoodHosts(5).size() == 0
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOnDiscoveredUntrustedHost() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.DISTRUSTED
|
||||
}
|
||||
@Test
|
||||
void testOnDiscoveredUntrustedHost() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.DISTRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOnDiscoverNeutralHostsProhibited() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { false }
|
||||
@Test
|
||||
void testOnDiscoverNeutralHostsProhibited() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.NEUTRAL
|
||||
}
|
||||
settingsMock.ignore.allowUntrusted { false }
|
||||
|
||||
initMocks()
|
||||
initMocks()
|
||||
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
|
||||
@Test
|
||||
void test2DiscoveredGoodHosts() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest2
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||
@Test
|
||||
void test2DiscoveredGoodHosts() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest2
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
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))
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
@Test
|
||||
void testHostFailed() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
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))
|
||||
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
|
||||
}
|
||||
assert cache.getHosts(5).size() == 0
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailedHostSuceeds() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
@Test
|
||||
void testFailedHostSuceeds() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
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 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)
|
||||
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)
|
||||
}
|
||||
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
|
||||
}
|
||||
@Test
|
||||
void testFailedOnceNoLongerGood() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
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 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
|
||||
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))
|
||||
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
|
||||
}
|
||||
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 ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
@Test
|
||||
void testDuplicateHostDiscovered() {
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
trustMock.demand.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
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)
|
||||
}
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaving() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
Thread.sleep(150)
|
||||
@Test
|
||||
void testSaving() {
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
initMocks()
|
||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||
Thread.sleep(150)
|
||||
|
||||
assert persist.exists()
|
||||
int lines = 0
|
||||
persist.eachLine {
|
||||
lines++
|
||||
JsonSlurper slurper = new JsonSlurper()
|
||||
def json = slurper.parseText(it)
|
||||
assert json.destination == destinations.dest1.toBase64()
|
||||
assert json.failures == 0
|
||||
assert json.successes == 0
|
||||
}
|
||||
assert lines == 1
|
||||
}
|
||||
assert persist.exists()
|
||||
int lines = 0
|
||||
persist.eachLine {
|
||||
lines++
|
||||
JsonSlurper slurper = new JsonSlurper()
|
||||
def json = slurper.parseText(it)
|
||||
assert json.destination == destinations.dest1.toBase64()
|
||||
assert json.failures == 0
|
||||
assert json.successes == 0
|
||||
}
|
||||
assert lines == 1
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoading() {
|
||||
def json = [:]
|
||||
json.destination = destinations.dest1.toBase64()
|
||||
json.failures = 0
|
||||
json.successes = 1
|
||||
json = JsonOutput.toJson(json)
|
||||
persist.append("${json}\n")
|
||||
@Test
|
||||
void testLoading() {
|
||||
def json = [:]
|
||||
json.destination = destinations.dest1.toBase64()
|
||||
json.failures = 0
|
||||
json.successes = 1
|
||||
json = JsonOutput.toJson(json)
|
||||
persist.append("${json}\n")
|
||||
|
||||
trustMock.ignore.getLevel { d ->
|
||||
assert d == destinations.dest1
|
||||
TrustLevel.TRUSTED
|
||||
}
|
||||
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)
|
||||
initMocks()
|
||||
def rv = cache.getHosts(5)
|
||||
assert rv.size() == 1
|
||||
assert rv.contains(destinations.dest1)
|
||||
|
||||
assert cache.getGoodHosts(5) == rv
|
||||
}
|
||||
assert cache.getGoodHosts(5) == rv
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,34 @@ import org.junit.Test
|
||||
|
||||
class SearchIndexTest {
|
||||
|
||||
SearchIndex index
|
||||
SearchIndex index
|
||||
|
||||
private void initIndex(List<String> entries) {
|
||||
index = new SearchIndex()
|
||||
entries.each {
|
||||
index.add(it)
|
||||
}
|
||||
}
|
||||
private void initIndex(List<String> entries) {
|
||||
index = new SearchIndex()
|
||||
entries.each {
|
||||
index.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSingleTerm() {
|
||||
initIndex(["a b.c", "d e.f"])
|
||||
@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")
|
||||
}
|
||||
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"])
|
||||
@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")
|
||||
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() {
|
||||
@@ -43,46 +43,46 @@ class SearchIndexTest {
|
||||
assert found.contains("c d.e")
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDrillDown() {
|
||||
initIndex(["a b.c", "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")
|
||||
}
|
||||
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 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 testSomeNotFound() {
|
||||
initIndex(["a b.c"])
|
||||
def found = index.search(["a","d"])
|
||||
assert found.size() == 0
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemove() {
|
||||
initIndex(["a b.c"])
|
||||
index.remove("a b.c")
|
||||
def found = index.search(["a"])
|
||||
assert found.size() == 0
|
||||
}
|
||||
@Test
|
||||
void testRemove() {
|
||||
initIndex(["a b.c"])
|
||||
index.remove("a b.c")
|
||||
def found = index.search(["a"])
|
||||
assert found.size() == 0
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveOverlap() {
|
||||
initIndex(["a b.c", "b c.d"])
|
||||
index.remove("a b.c")
|
||||
def found = index.search(["b"])
|
||||
assert found.size() == 1
|
||||
assert found.contains("b c.d")
|
||||
}
|
||||
@Test
|
||||
void testRemoveOverlap() {
|
||||
initIndex(["a b.c", "b c.d"])
|
||||
index.remove("a b.c")
|
||||
def found = index.search(["b"])
|
||||
assert found.size() == 1
|
||||
assert found.contains("b c.d")
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDuplicateTerm() {
|
||||
|
||||
@@ -13,73 +13,73 @@ import net.i2p.data.Destination
|
||||
|
||||
class TrustServiceTest {
|
||||
|
||||
TrustService service
|
||||
File persistGood, persistBad
|
||||
Personas personas = new Personas()
|
||||
TrustService service
|
||||
File persistGood, persistBad
|
||||
Personas personas = new Personas()
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
persistGood = new File("good.trust")
|
||||
persistBad = new File("bad.trust")
|
||||
persistGood.delete()
|
||||
persistBad.delete()
|
||||
persistGood.deleteOnExit()
|
||||
persistBad.deleteOnExit()
|
||||
service = new TrustService(persistGood, persistBad, 100)
|
||||
service.start()
|
||||
}
|
||||
@Before
|
||||
void before() {
|
||||
persistGood = new File("good.trust")
|
||||
persistBad = new File("bad.trust")
|
||||
persistGood.delete()
|
||||
persistBad.delete()
|
||||
persistGood.deleteOnExit()
|
||||
persistBad.deleteOnExit()
|
||||
service = new TrustService(persistGood, persistBad, 100)
|
||||
service.start()
|
||||
}
|
||||
|
||||
@After
|
||||
void after() {
|
||||
service.stop()
|
||||
}
|
||||
@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 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)
|
||||
@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)
|
||||
}
|
||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPersist() {
|
||||
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||
@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 {
|
||||
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||
}
|
||||
def distrusted = new HashSet<>()
|
||||
persistBad.eachLine {
|
||||
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||
}
|
||||
Thread.sleep(250)
|
||||
def trusted = new HashSet<>()
|
||||
persistGood.eachLine {
|
||||
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||
}
|
||||
def distrusted = new HashSet<>()
|
||||
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)
|
||||
}
|
||||
assert trusted.size() == 1
|
||||
assert trusted.contains(personas.persona1)
|
||||
assert distrusted.size() == 1
|
||||
assert distrusted.contains(personas.persona2)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoad() {
|
||||
service.stop()
|
||||
persistGood.append("${personas.persona1.toBase64()}\n")
|
||||
persistBad.append("${personas.persona2.toBase64()}\n")
|
||||
service = new TrustService(persistGood, persistBad, 100)
|
||||
service.start()
|
||||
Thread.sleep(50)
|
||||
@Test
|
||||
void testLoad() {
|
||||
service.stop()
|
||||
persistGood.append("${personas.persona1.toBase64()}\n")
|
||||
persistBad.append("${personas.persona2.toBase64()}\n")
|
||||
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)
|
||||
}
|
||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,41 +7,41 @@ import org.junit.Test
|
||||
class DataUtilTest {
|
||||
|
||||
|
||||
private static void usVal(int value) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
DataUtil.writeUnsignedShort(value, baos)
|
||||
def is = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))
|
||||
assert is.readUnsignedShort() == value
|
||||
}
|
||||
@Test
|
||||
void testUnsignedShort() {
|
||||
usVal(0)
|
||||
usVal(20)
|
||||
usVal(Short.MAX_VALUE)
|
||||
usVal(Short.MAX_VALUE + 1)
|
||||
usVal(0xFFFF)
|
||||
private static void usVal(int value) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||
DataUtil.writeUnsignedShort(value, baos)
|
||||
def is = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))
|
||||
assert is.readUnsignedShort() == value
|
||||
}
|
||||
@Test
|
||||
void testUnsignedShort() {
|
||||
usVal(0)
|
||||
usVal(20)
|
||||
usVal(Short.MAX_VALUE)
|
||||
usVal(Short.MAX_VALUE + 1)
|
||||
usVal(0xFFFF)
|
||||
|
||||
try {
|
||||
usVal(0xFFFF + 1)
|
||||
fail()
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
}
|
||||
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)
|
||||
}
|
||||
private static header(int value) {
|
||||
byte [] header = new byte[3]
|
||||
DataUtil.packHeader(value, header)
|
||||
assert value == DataUtil.readLength(header)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHeader() {
|
||||
header(0)
|
||||
header(1)
|
||||
header(556)
|
||||
header(8 * 1024 * 1024 - 1)
|
||||
try {
|
||||
header(8 * 1024 * 1024)
|
||||
fail()
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
}
|
||||
@Test
|
||||
void testHeader() {
|
||||
header(0)
|
||||
header(1)
|
||||
header(556)
|
||||
header(8 * 1024 * 1024 - 1)
|
||||
try {
|
||||
header(8 * 1024 * 1024)
|
||||
fail()
|
||||
} catch (IllegalArgumentException expected) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
group = com.muwire
|
||||
version = 0.4.7
|
||||
version = 0.4.9
|
||||
groovyVersion = 2.4.15
|
||||
slf4jVersion = 1.7.25
|
||||
spockVersion = 1.1-groovy-2.4
|
||||
|
||||
34
gradlew
vendored
34
gradlew
vendored
@@ -11,21 +11,21 @@
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
ls=$(ls -ld "$PRG")
|
||||
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
PRG=$(dirname "$PRG")"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
SAVED="$(pwd)"
|
||||
cd "$(dirname "$PRG")/" >/dev/null
|
||||
APP_HOME="$(pwd -P)"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
APP_BASE_NAME=$(basename "$0")
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
@@ -49,7 +49,7 @@ cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
case "$(uname)" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
@@ -90,7 +90,7 @@ fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
MAX_FD_LIMIT=$(ulimit -H -n)
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
@@ -111,12 +111,12 @@ fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
|
||||
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
|
||||
JAVACMD=$(cygpath --unix "$JAVACMD")
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null)
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
@@ -130,13 +130,13 @@ if $cygwin ; then
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
|
||||
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg")
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
eval $(echo args$i)="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
|
||||
@@ -41,4 +41,9 @@ mvcGroups {
|
||||
view = 'com.muwire.gui.TrustListView'
|
||||
controller = 'com.muwire.gui.TrustListController'
|
||||
}
|
||||
'content-panel' {
|
||||
model = 'com.muwire.gui.ContentPanelModel'
|
||||
view = 'com.muwire.gui.ContentPanelView'
|
||||
controller = 'com.muwire.gui.ContentPanelController'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonController
|
||||
import griffon.core.controller.ControllerAction
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.content.Match
|
||||
import com.muwire.core.content.Matcher
|
||||
import com.muwire.core.content.RegexMatcher
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
import com.muwire.core.trust.TrustLevel
|
||||
|
||||
@ArtifactProviderFor(GriffonController)
|
||||
class ContentPanelController {
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelModel model
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelView view
|
||||
|
||||
Core core
|
||||
|
||||
@ControllerAction
|
||||
void addRule() {
|
||||
def term = view.ruleTextField.text
|
||||
|
||||
if (model.regex)
|
||||
core.muOptions.watchedRegexes.add(term)
|
||||
else
|
||||
core.muOptions.watchedKeywords.add(term)
|
||||
saveMuWireSettings()
|
||||
|
||||
core.eventBus.publish(new ContentControlEvent(term : term, regex : model.regex, add:true))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void deleteRule() {
|
||||
int rule = view.getSelectedRule()
|
||||
if (rule < 0)
|
||||
return
|
||||
Matcher matcher = model.rules[rule]
|
||||
String term = matcher.getTerm()
|
||||
if (matcher instanceof RegexMatcher)
|
||||
core.muOptions.watchedRegexes.remove(term)
|
||||
else
|
||||
core.muOptions.watchedKeywords.remove(term)
|
||||
saveMuWireSettings()
|
||||
|
||||
core.eventBus.publish(new ContentControlEvent(term : term, regex : (matcher instanceof RegexMatcher), add: false))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void keyword() {
|
||||
model.regex = false
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void regex() {
|
||||
model.regex = true
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void refresh() {
|
||||
model.refresh()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void trust() {
|
||||
int selectedHit = view.getSelectedHit()
|
||||
if (selectedHit < 0)
|
||||
return
|
||||
Match m = model.hits[selectedHit]
|
||||
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.TRUSTED))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void distrust() {
|
||||
int selectedHit = view.getSelectedHit()
|
||||
if (selectedHit < 0)
|
||||
return
|
||||
Match m = model.hits[selectedHit]
|
||||
core.eventBus.publish(new TrustEvent(persona : m.persona, level : TrustLevel.DISTRUSTED))
|
||||
}
|
||||
|
||||
void saveMuWireSettings() {
|
||||
File f = new File(core.home, "MuWire.properties")
|
||||
f.withOutputStream {
|
||||
core.muOptions.write(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,7 @@ class MainFrameController {
|
||||
Map<String, Object> params = new HashMap<>()
|
||||
params["search-terms"] = search
|
||||
params["uuid"] = uuid.toString()
|
||||
params["core"] = core
|
||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||
model.results[uuid.toString()] = group
|
||||
|
||||
@@ -96,6 +97,7 @@ class MainFrameController {
|
||||
Map<String, Object> params = new HashMap<>()
|
||||
params["search-terms"] = tabTitle
|
||||
params["uuid"] = uuid.toString()
|
||||
params["core"] = core
|
||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||
model.results[uuid.toString()] = group
|
||||
|
||||
@@ -106,20 +108,6 @@ class MainFrameController {
|
||||
originator : core.me))
|
||||
}
|
||||
|
||||
private def selectedResult() {
|
||||
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
||||
def group = selected.getClientProperty("mvc-group")
|
||||
def table = selected.getClientProperty("results-table")
|
||||
int row = table.getSelectedRow()
|
||||
if (row == -1)
|
||||
return
|
||||
def sortEvt = group.view.lastSortEvent
|
||||
if (sortEvt != null) {
|
||||
row = group.view.resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||
}
|
||||
group.model.results[row]
|
||||
}
|
||||
|
||||
private int selectedDownload() {
|
||||
def downloadsTable = builder.getVariable("downloads-table")
|
||||
def selected = downloadsTable.getSelectedRow()
|
||||
@@ -130,39 +118,21 @@ class MainFrameController {
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void download() {
|
||||
def result = selectedResult()
|
||||
if (result == null)
|
||||
void trustPersonaFromSearch() {
|
||||
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||
if (selected < 0)
|
||||
return
|
||||
|
||||
if (!model.canDownload(result.infohash))
|
||||
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))
|
||||
Persona p = model.searches[selected].originator
|
||||
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED) )
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void trust() {
|
||||
def result = selectedResult()
|
||||
if (result == null)
|
||||
return // TODO disable button
|
||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.TRUSTED))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void distrust() {
|
||||
def result = selectedResult()
|
||||
if (result == null)
|
||||
return // TODO disable button
|
||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
|
||||
void distrustPersonaFromSearch() {
|
||||
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||
if (selected < 0)
|
||||
return
|
||||
Persona p = model.searches[selected].originator
|
||||
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED) )
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
|
||||
@@ -6,6 +6,65 @@ import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.download.UIDownloadEvent
|
||||
import com.muwire.core.trust.TrustEvent
|
||||
import com.muwire.core.trust.TrustLevel
|
||||
|
||||
@ArtifactProviderFor(GriffonController)
|
||||
class SearchTabController {
|
||||
|
||||
@MVCMember @Nonnull
|
||||
SearchTabModel model
|
||||
@MVCMember @Nonnull
|
||||
SearchTabView view
|
||||
|
||||
Core core
|
||||
|
||||
private def selectedResult() {
|
||||
int row = view.resultsTable.getSelectedRow()
|
||||
if (row == -1)
|
||||
return null
|
||||
def sortEvt = view.lastSortEvent
|
||||
if (sortEvt != null) {
|
||||
row = view.resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||
}
|
||||
model.results[row]
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void download() {
|
||||
def result = selectedResult()
|
||||
if (result == null)
|
||||
return
|
||||
|
||||
if (!mvcGroup.parentGroup.model.canDownload(result.infohash))
|
||||
return
|
||||
|
||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
||||
|
||||
def resultsBucket = model.hashBucket[result.infohash]
|
||||
def sources = model.sourcesBucket[result.infohash]
|
||||
|
||||
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
|
||||
mvcGroup.parentGroup.view.showDownloadsWindow.call()
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void trust() {
|
||||
int row = view.selectedSenderRow()
|
||||
if (row < 0)
|
||||
return
|
||||
def sender = model.senders[row]
|
||||
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.TRUSTED))
|
||||
}
|
||||
|
||||
@ControllerAction
|
||||
void distrust() {
|
||||
int row = view.selectedSenderRow()
|
||||
if (row < 0)
|
||||
return
|
||||
def sender = model.senders[row]
|
||||
core.eventBus.publish( new TrustEvent(persona : sender, level : TrustLevel.DISTRUSTED))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.EventBus
|
||||
import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.content.ContentManager
|
||||
|
||||
import griffon.core.artifact.GriffonModel
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.transform.Observable
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
@ArtifactProviderFor(GriffonModel)
|
||||
class ContentPanelModel {
|
||||
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelView view
|
||||
|
||||
Core core
|
||||
|
||||
private ContentManager contentManager
|
||||
|
||||
def rules = []
|
||||
def hits = []
|
||||
|
||||
@Observable boolean regex
|
||||
@Observable boolean deleteButtonEnabled
|
||||
@Observable boolean trustButtonsEnabled
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
contentManager = application.context.get("core").contentManager
|
||||
rules.addAll(contentManager.matchers)
|
||||
core.eventBus.register(ContentControlEvent.class, this)
|
||||
}
|
||||
|
||||
void mvcGroupDestroy() {
|
||||
core.eventBus.unregister(ContentControlEvent.class, this)
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
rules.clear()
|
||||
rules.addAll(contentManager.matchers)
|
||||
hits.clear()
|
||||
view.rulesTable.model.fireTableDataChanged()
|
||||
view.hitsTable.model.fireTableDataChanged()
|
||||
}
|
||||
|
||||
void onContentControlEvent(ContentControlEvent e) {
|
||||
runInsideUIAsync {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.Calendar
|
||||
import java.util.UUID
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
import javax.inject.Inject
|
||||
@@ -15,11 +17,13 @@ import com.muwire.core.RouterDisconnectedEvent
|
||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||
import com.muwire.core.connection.ConnectionEvent
|
||||
import com.muwire.core.connection.DisconnectionEvent
|
||||
import com.muwire.core.content.ContentControlEvent
|
||||
import com.muwire.core.download.DownloadStartedEvent
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.files.AllFilesLoadedEvent
|
||||
import com.muwire.core.files.FileDownloadedEvent
|
||||
import com.muwire.core.files.FileHashedEvent
|
||||
import com.muwire.core.files.FileHashingEvent
|
||||
import com.muwire.core.files.FileLoadedEvent
|
||||
import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.files.FileUnsharedEvent
|
||||
@@ -70,8 +74,8 @@ class MainFrameModel {
|
||||
|
||||
@Observable int connections
|
||||
@Observable String me
|
||||
@Observable boolean downloadActionEnabled
|
||||
@Observable boolean trustButtonsEnabled
|
||||
@Observable int loadedFiles
|
||||
@Observable File hashingFile
|
||||
@Observable boolean cancelButtonEnabled
|
||||
@Observable boolean retryButtonEnabled
|
||||
@Observable boolean pauseButtonEnabled
|
||||
@@ -85,6 +89,12 @@ class MainFrameModel {
|
||||
@Observable boolean updateButtonEnabled
|
||||
@Observable boolean unsubscribeButtonEnabled
|
||||
|
||||
@Observable boolean searchesPaneButtonEnabled
|
||||
@Observable boolean downloadsPaneButtonEnabled
|
||||
@Observable boolean uploadsPaneButtonEnabled
|
||||
@Observable boolean monitorPaneButtonEnabled
|
||||
@Observable boolean trustPaneButtonEnabled
|
||||
|
||||
private final Set<InfoHash> infoHashes = new HashSet<>()
|
||||
|
||||
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
|
||||
@@ -145,6 +155,7 @@ class MainFrameModel {
|
||||
core.eventBus.register(ConnectionEvent.class, this)
|
||||
core.eventBus.register(DisconnectionEvent.class, this)
|
||||
core.eventBus.register(FileHashedEvent.class, this)
|
||||
core.eventBus.register(FileHashingEvent.class, this)
|
||||
core.eventBus.register(FileLoadedEvent.class, this)
|
||||
core.eventBus.register(UploadEvent.class, this)
|
||||
core.eventBus.register(UploadFinishedEvent.class, this)
|
||||
@@ -158,6 +169,13 @@ class MainFrameModel {
|
||||
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
||||
core.eventBus.register(TrustSubscriptionUpdatedEvent.class, this)
|
||||
|
||||
core.muOptions.watchedKeywords.each {
|
||||
core.eventBus.publish(new ContentControlEvent(term : it, regex: false, add: true))
|
||||
}
|
||||
core.muOptions.watchedRegexes.each {
|
||||
core.eventBus.publish(new ContentControlEvent(term : it, regex: true, add: true))
|
||||
}
|
||||
|
||||
timer.schedule({
|
||||
if (core.shutdown.get())
|
||||
return
|
||||
@@ -186,6 +204,12 @@ class MainFrameModel {
|
||||
distrusted.addAll(core.trustService.bad.values())
|
||||
|
||||
resumeButtonText = "Retry"
|
||||
|
||||
searchesPaneButtonEnabled = false
|
||||
downloadsPaneButtonEnabled = true
|
||||
uploadsPaneButtonEnabled = true
|
||||
monitorPaneButtonEnabled = true
|
||||
trustPaneButtonEnabled = true
|
||||
}
|
||||
})
|
||||
|
||||
@@ -261,7 +285,17 @@ class MainFrameModel {
|
||||
}
|
||||
}
|
||||
|
||||
void onFileHashingEvent(FileHashingEvent e) {
|
||||
runInsideUIAsync {
|
||||
loadedFiles = shared.size()
|
||||
hashingFile = e.hashingFile
|
||||
}
|
||||
}
|
||||
|
||||
void onFileHashedEvent(FileHashedEvent e) {
|
||||
runInsideUIAsync {
|
||||
hashingFile = null
|
||||
}
|
||||
if (e.error != null)
|
||||
return // TODO do something
|
||||
if (infoHashes.contains(e.sharedFile.infoHash))
|
||||
@@ -269,6 +303,7 @@ class MainFrameModel {
|
||||
infoHashes.add(e.sharedFile.infoHash)
|
||||
runInsideUIAsync {
|
||||
shared << e.sharedFile
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
@@ -280,6 +315,7 @@ class MainFrameModel {
|
||||
infoHashes.add(e.loadedFile.infoHash)
|
||||
runInsideUIAsync {
|
||||
shared << e.loadedFile
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
@@ -291,6 +327,7 @@ class MainFrameModel {
|
||||
return
|
||||
runInsideUIAsync {
|
||||
shared.remove(e.unsharedFile)
|
||||
loadedFiles = shared.size()
|
||||
JTable table = builder.getVariable("shared-files-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
@@ -360,10 +397,32 @@ class MainFrameModel {
|
||||
return
|
||||
}
|
||||
runInsideUIAsync {
|
||||
searches.addFirst(new IncomingSearch(search : search, replyTo : e.replyTo, originator : e.originator))
|
||||
JTable table = builder.getVariable("searches-table")
|
||||
|
||||
Boolean searchFound = false
|
||||
Iterator searchIter = searches.iterator()
|
||||
while ( searchIter.hasNext() ) {
|
||||
IncomingSearch searchEle = searchIter.next()
|
||||
if ( searchEle.search == search
|
||||
&& searchEle.originator == e.originator
|
||||
&& searchEle.uuid == e.searchEvent.getUuid() ) {
|
||||
searchIter.remove()
|
||||
table.model.fireTableDataChanged()
|
||||
searchFound = true
|
||||
searchEle.count++
|
||||
searchEle.timestamp = Calendar.getInstance()
|
||||
searches.addFirst(searchEle)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!searchFound) {
|
||||
searches.addFirst(new IncomingSearch(search, e.replyTo, e.originator, e.searchEvent.getUuid()))
|
||||
}
|
||||
|
||||
while(searches.size() > 200)
|
||||
searches.removeLast()
|
||||
JTable table = builder.getVariable("searches-table")
|
||||
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
}
|
||||
@@ -372,6 +431,18 @@ class MainFrameModel {
|
||||
String search
|
||||
Destination replyTo
|
||||
Persona originator
|
||||
long count
|
||||
UUID uuid
|
||||
Calendar timestamp
|
||||
|
||||
IncomingSearch( String search, Destination replyTo, Persona originator, UUID uuid ) {
|
||||
this.search = search
|
||||
this.replyTo = replyTo
|
||||
this.originator = originator
|
||||
this.uuid = uuid
|
||||
this.count = 1
|
||||
this.timestamp = Calendar.getInstance()
|
||||
}
|
||||
}
|
||||
|
||||
void onUpdateAvailableEvent(UpdateAvailableEvent e) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import javax.inject.Inject
|
||||
import javax.swing.JTable
|
||||
|
||||
import com.muwire.core.Core
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.search.UIResultEvent
|
||||
|
||||
import griffon.core.artifact.GriffonModel
|
||||
@@ -18,12 +19,17 @@ class SearchTabModel {
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
|
||||
@Observable boolean downloadActionEnabled
|
||||
@Observable boolean trustButtonsEnabled
|
||||
|
||||
Core core
|
||||
UISettings uiSettings
|
||||
String uuid
|
||||
def senders = []
|
||||
def results = []
|
||||
def hashBucket = [:]
|
||||
def sourcesBucket = [:]
|
||||
def sendersBucket = new LinkedHashMap<>()
|
||||
|
||||
|
||||
void mvcGroupInit(Map<String, String> args) {
|
||||
@@ -48,6 +54,15 @@ class SearchTabModel {
|
||||
}
|
||||
bucket << e
|
||||
|
||||
def senderBucket = sendersBucket.get(e.sender)
|
||||
if (senderBucket == null) {
|
||||
senderBucket = []
|
||||
sendersBucket[e.sender] = senderBucket
|
||||
senders.clear()
|
||||
senders.addAll(sendersBucket.keySet())
|
||||
}
|
||||
senderBucket << e
|
||||
|
||||
Set sourceBucket = sourcesBucket.get(e.infohash)
|
||||
if (sourceBucket == null) {
|
||||
sourceBucket = new HashSet()
|
||||
@@ -55,8 +70,7 @@ class SearchTabModel {
|
||||
}
|
||||
sourceBucket.addAll(e.sources)
|
||||
|
||||
results << e
|
||||
JTable table = builder.getVariable("results-table")
|
||||
JTable table = builder.getVariable("senders-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
}
|
||||
@@ -73,6 +87,14 @@ class SearchTabModel {
|
||||
hashBucket[it.infohash] = bucket
|
||||
}
|
||||
|
||||
def senderBucket = sendersBucket.get(it.sender)
|
||||
if (senderBucket == null) {
|
||||
senderBucket = []
|
||||
sendersBucket[it.sender] = senderBucket
|
||||
senders.clear()
|
||||
senders.addAll(sendersBucket.keySet())
|
||||
}
|
||||
|
||||
Set sourceBucket = sourcesBucket.get(it.infohash)
|
||||
if (sourceBucket == null) {
|
||||
sourceBucket = new HashSet()
|
||||
@@ -81,9 +103,9 @@ class SearchTabModel {
|
||||
sourceBucket.addAll(it.sources)
|
||||
|
||||
bucket << it
|
||||
results << it
|
||||
senderBucket << it
|
||||
}
|
||||
JTable table = builder.getVariable("results-table")
|
||||
JTable table = builder.getVariable("senders-table")
|
||||
table.model.fireTableDataChanged()
|
||||
}
|
||||
}
|
||||
|
||||
154
gui/griffon-app/views/com/muwire/gui/ContentPanelView.groovy
Normal file
154
gui/griffon-app/views/com/muwire/gui/ContentPanelView.groovy
Normal file
@@ -0,0 +1,154 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.artifact.GriffonView
|
||||
import griffon.inject.MVCMember
|
||||
import griffon.metadata.ArtifactProviderFor
|
||||
|
||||
import javax.swing.JDialog
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.ListSelectionModel
|
||||
import javax.swing.SwingConstants
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
|
||||
import com.muwire.core.content.Matcher
|
||||
import com.muwire.core.content.RegexMatcher
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
|
||||
import javax.annotation.Nonnull
|
||||
|
||||
@ArtifactProviderFor(GriffonView)
|
||||
class ContentPanelView {
|
||||
@MVCMember @Nonnull
|
||||
FactoryBuilderSupport builder
|
||||
@MVCMember @Nonnull
|
||||
ContentPanelModel model
|
||||
|
||||
def dialog
|
||||
def mainFrame
|
||||
def mainPanel
|
||||
|
||||
def rulesTable
|
||||
def ruleTextField
|
||||
def lastRulesSortEvent
|
||||
def hitsTable
|
||||
def lastHitsSortEvent
|
||||
|
||||
void initUI() {
|
||||
mainFrame = application.windowManager.findWindow("main-frame")
|
||||
dialog = new JDialog(mainFrame, "Content Control Panel", true)
|
||||
|
||||
mainPanel = builder.panel {
|
||||
gridLayout(rows:1, cols:2)
|
||||
panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : "Rules")
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
rulesTable = table(id : "rules-table", autoCreateRowSorter : true) {
|
||||
tableModel(list : model.rules) {
|
||||
closureColumn(header: "Term", type:String, read: {row -> row.getTerm()})
|
||||
closureColumn(header: "Regex?", type:Boolean, read: {row -> row instanceof RegexMatcher})
|
||||
closureColumn(header: "Hits", type:Integer, read : {row -> row.matches.size()})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
borderLayout()
|
||||
ruleTextField = textField(constraints: BorderLayout.CENTER, action: addRuleAction)
|
||||
panel (constraints: BorderLayout.EAST) {
|
||||
buttonGroup(id : "ruleType")
|
||||
radioButton(text: "Keyword", selected : true, buttonGroup: ruleType, keywordAction)
|
||||
radioButton(text: "Regex", selected : false, buttonGroup: ruleType, regexAction)
|
||||
button(text : "Add Rule", addRuleAction)
|
||||
button(text : "Delete Rule", enabled : bind {model.deleteButtonEnabled}, deleteRuleAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (border : etchedBorder()){
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : "Hits")
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
hitsTable = table(id : "hits-table", autoCreateRowSorter : true) {
|
||||
tableModel(list : model.hits) {
|
||||
closureColumn(header : "Searcher", type : String, read : {row -> row.persona.getHumanReadableName()})
|
||||
closureColumn(header : "Keywords", type : String, read : {row -> row.keywords.join(" ")})
|
||||
closureColumn(header : "Date", type : String, read : {row -> String.valueOf(new Date(row.timestamp))})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
button(text : "Refresh", refreshAction)
|
||||
button(text : "Trust", enabled : bind {model.trustButtonsEnabled}, trustAction)
|
||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getSelectedRule() {
|
||||
int selectedRow = rulesTable.getSelectedRow()
|
||||
if (selectedRow < 0)
|
||||
return -1
|
||||
if (lastRulesSortEvent != null)
|
||||
selectedRow = rulesTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
selectedRow
|
||||
}
|
||||
|
||||
int getSelectedHit() {
|
||||
int selectedRow = hitsTable.getSelectedRow()
|
||||
if (selectedRow < 0)
|
||||
return -1
|
||||
if (lastHitsSortEvent != null)
|
||||
selectedRow = hitsTable.rowSorter.convertRowIndexToModel(selectedRow)
|
||||
selectedRow
|
||||
}
|
||||
|
||||
void mvcGroupInit(Map<String,String> args) {
|
||||
def centerRenderer = new DefaultTableCellRenderer()
|
||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||
rulesTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
rulesTable.rowSorter.addRowSorterListener({evt -> lastRulesSortEvent = evt})
|
||||
rulesTable.rowSorter.setSortsOnUpdates(true)
|
||||
def selectionModel = rulesTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int selectedRow = getSelectedRule()
|
||||
if (selectedRow < 0) {
|
||||
model.deleteButtonEnabled = false
|
||||
return
|
||||
} else {
|
||||
model.deleteButtonEnabled = true
|
||||
model.hits.clear()
|
||||
Matcher matcher = model.rules[selectedRow]
|
||||
model.hits.addAll(matcher.matches)
|
||||
hitsTable.model.fireTableDataChanged()
|
||||
}
|
||||
})
|
||||
|
||||
hitsTable.rowSorter.addRowSorterListener({evt -> lastHitsSortEvent = evt})
|
||||
hitsTable.rowSorter.setSortsOnUpdates(true)
|
||||
selectionModel = hitsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int selectedRow = getSelectedHit()
|
||||
model.trustButtonsEnabled = selectedRow >= 0
|
||||
})
|
||||
|
||||
dialog.getContentPane().add(mainPanel)
|
||||
dialog.pack()
|
||||
dialog.setLocationRelativeTo(mainFrame)
|
||||
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||
dialog.addWindowListener(new WindowAdapter() {
|
||||
public void windowClosed(WindowEvent e) {
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
})
|
||||
dialog.show()
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import com.muwire.core.MuWireSettings
|
||||
import com.muwire.core.download.Downloader
|
||||
import com.muwire.core.files.FileSharedEvent
|
||||
import com.muwire.core.trust.RemoteTrustList
|
||||
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.CardLayout
|
||||
import java.awt.FlowLayout
|
||||
@@ -73,6 +72,11 @@ class MainFrameView {
|
||||
menuBar {
|
||||
menu (text : "Options") {
|
||||
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("Options")})
|
||||
menuItem("Content Control", actionPerformed : {
|
||||
def env = [:]
|
||||
env["core"] = model.core
|
||||
mvcGroup.createMVCGroup("content-panel", env)
|
||||
})
|
||||
}
|
||||
menu (text : "Status") {
|
||||
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
||||
@@ -85,11 +89,12 @@ class MainFrameView {
|
||||
borderLayout()
|
||||
panel (constraints: BorderLayout.WEST) {
|
||||
gridLayout(rows:1, cols: 2)
|
||||
button(text: "Searches", actionPerformed : showSearchWindow)
|
||||
button(text: "Uploads", actionPerformed : showUploadsWindow)
|
||||
button(text: "Searches", enabled : bind{model.searchesPaneButtonEnabled},actionPerformed : showSearchWindow)
|
||||
button(text: "Downloads", enabled : bind{model.downloadsPaneButtonEnabled}, actionPerformed : showDownloadsWindow)
|
||||
button(text: "Uploads", enabled : bind{model.uploadsPaneButtonEnabled}, actionPerformed : showUploadsWindow)
|
||||
if (settings.showMonitor)
|
||||
button(text: "Monitor", actionPerformed : showMonitorWindow)
|
||||
button(text: "Trust", actionPerformed : showTrustWindow)
|
||||
button(text: "Monitor", enabled: bind{model.monitorPaneButtonEnabled},actionPerformed : showMonitorWindow)
|
||||
button(text: "Trust", enabled:bind{model.trustPaneButtonEnabled},actionPerformed : showTrustWindow)
|
||||
}
|
||||
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
|
||||
cardLayout()
|
||||
@@ -113,84 +118,91 @@ class MainFrameView {
|
||||
cardLayout()
|
||||
panel (constraints : "search window") {
|
||||
borderLayout()
|
||||
splitPane( orientation : JSplitPane.VERTICAL_SPLIT, dividerLocation : 500,
|
||||
continuousLayout : true, constraints : BorderLayout.CENTER) {
|
||||
panel (constraints : JSplitPane.TOP) {
|
||||
borderLayout()
|
||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||
panel(constraints : BorderLayout.SOUTH) {
|
||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||
}
|
||||
}
|
||||
panel (constraints : JSplitPane.BOTTOM) {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||
tableModel(list: model.downloads) {
|
||||
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
||||
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
||||
closureColumn(header: "Progress", preferredWidth: 70, type: String, read: { row ->
|
||||
int pieces = row.downloader.nPieces
|
||||
int done = row.downloader.donePieces()
|
||||
int percent = -1
|
||||
if ( row.downloader.nPieces != 0 ) {
|
||||
percent = (done * 100) / pieces
|
||||
}
|
||||
long size = row.downloader.pieceSize
|
||||
size *= pieces
|
||||
String totalSize = DataHelper.formatSize2Decimal(size, false) + "B"
|
||||
"${percent}% of " + totalSize + " ($done/$pieces pcs)"
|
||||
})
|
||||
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
|
||||
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
||||
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
||||
})
|
||||
}
|
||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||
}
|
||||
panel (constraints: "downloads window") {
|
||||
gridLayout(rows : 2, cols: 1)
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||
tableModel(list: model.downloads) {
|
||||
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
||||
closureColumn(header: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
||||
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
|
||||
closureColumn(header: "Sources", preferredWidth : 10, type: Integer, read : {row -> row.downloader.activeWorkers()})
|
||||
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
||||
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
||||
})
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
|
||||
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
|
||||
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
|
||||
}
|
||||
}
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
|
||||
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction )
|
||||
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
|
||||
}
|
||||
}
|
||||
panel {
|
||||
borderLayout()
|
||||
panel(constraints : BorderLayout.NORTH) {
|
||||
label(text : "Download Details")
|
||||
}
|
||||
panel(constraints : BorderLayout.CENTER) {
|
||||
label(text : "Details go here...")
|
||||
}
|
||||
}
|
||||
}
|
||||
panel (constraints: "uploads window"){
|
||||
gridLayout(cols : 1, rows : 2)
|
||||
panel {
|
||||
gridLayout(cols : 2, rows : 1)
|
||||
panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
label(text : bind {
|
||||
if (model.hashingFile == null) {
|
||||
""
|
||||
} else {
|
||||
"hashing: " + model.hashingFile.getAbsolutePath() + " (" + DataHelper.formatSize2Decimal(model.hashingFile.length(), false).toString() + "B)"
|
||||
}
|
||||
})
|
||||
}
|
||||
panel (border : etchedBorder(), constraints : BorderLayout.CENTER) {
|
||||
gridLayout(cols : 2, rows : 1)
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.watched) {
|
||||
closureColumn(header: "Watched Directories", type : String, read : { it })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.watched) {
|
||||
closureColumn(header: "Watched Directories", type : String, read : { it })
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.shared) {
|
||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panel {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH) {
|
||||
panel (constraints : BorderLayout.SOUTH) {
|
||||
gridLayout(rows:1, cols:2)
|
||||
panel {
|
||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
||||
button(text : "Share files", actionPerformed : shareFiles)
|
||||
}
|
||||
scrollPane(constraints : BorderLayout.CENTER) {
|
||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
||||
tableModel(list : model.shared) {
|
||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
||||
}
|
||||
}
|
||||
panel {
|
||||
label("Shared:")
|
||||
label(text : bind {model.loadedFiles.toString()})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel {
|
||||
panel (border : etchedBorder()) {
|
||||
borderLayout()
|
||||
panel (constraints : BorderLayout.NORTH){
|
||||
label("Uploads")
|
||||
@@ -218,7 +230,7 @@ class MainFrameView {
|
||||
if (size >= 0 ) {
|
||||
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
|
||||
}
|
||||
"${percent}%" + totalSize + " ($done/$pieces pcs)"
|
||||
String.format("%02d", percent) + "% ${totalSize} ($done/$pieces pcs)".toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -265,6 +277,14 @@ class MainFrameView {
|
||||
return it.replyTo.toBase32()
|
||||
}
|
||||
})
|
||||
closureColumn(header : "Count", type : String, read : {
|
||||
it.count.toString()
|
||||
})
|
||||
closureColumn(header : "Timestamp", type : String, read : {
|
||||
String.format("%02d", it.timestamp.get(Calendar.HOUR_OF_DAY)) + ":" +
|
||||
String.format("%02d", it.timestamp.get(Calendar.MINUTE)) + ":" +
|
||||
String.format("%02d", it.timestamp.get(Calendar.SECOND))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,9 +413,11 @@ class MainFrameView {
|
||||
def centerRenderer = new DefaultTableCellRenderer()
|
||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
downloadsTable.setDefaultRenderer(Downloader.class, new DownloadProgressRenderer())
|
||||
|
||||
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
|
||||
downloadsTable.rowSorter.setSortsOnUpdates(true)
|
||||
downloadsTable.rowSorter.setComparator(2, new DownloaderComparator())
|
||||
|
||||
downloadsTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
@@ -440,9 +462,18 @@ class MainFrameView {
|
||||
// searches table
|
||||
def searchesTable = builder.getVariable("searches-table")
|
||||
JPopupMenu searchTableMenu = new JPopupMenu()
|
||||
|
||||
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
|
||||
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
|
||||
JMenuItem trustSearcher = new JMenuItem("Trust searcher")
|
||||
trustSearcher.addActionListener({mvcGroup.controller.trustPersonaFromSearch()})
|
||||
JMenuItem distrustSearcher = new JMenuItem("Distrust searcher")
|
||||
distrustSearcher.addActionListener({mvcGroup.controller.distrustPersonaFromSearch()})
|
||||
|
||||
searchTableMenu.add(copySearchToClipboard)
|
||||
searchTableMenu.add(trustSearcher)
|
||||
searchTableMenu.add(distrustSearcher)
|
||||
|
||||
searchesTable.addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
@@ -661,21 +692,51 @@ class MainFrameView {
|
||||
def showSearchWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
||||
model.searchesPaneButtonEnabled = false
|
||||
model.downloadsPaneButtonEnabled = true
|
||||
model.uploadsPaneButtonEnabled = true
|
||||
model.monitorPaneButtonEnabled = true
|
||||
model.trustPaneButtonEnabled = true
|
||||
}
|
||||
|
||||
def showDownloadsWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "downloads window")
|
||||
model.searchesPaneButtonEnabled = true
|
||||
model.downloadsPaneButtonEnabled = false
|
||||
model.uploadsPaneButtonEnabled = true
|
||||
model.monitorPaneButtonEnabled = true
|
||||
model.trustPaneButtonEnabled = true
|
||||
}
|
||||
|
||||
def showUploadsWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel, "uploads window")
|
||||
model.searchesPaneButtonEnabled = true
|
||||
model.downloadsPaneButtonEnabled = true
|
||||
model.uploadsPaneButtonEnabled = false
|
||||
model.monitorPaneButtonEnabled = true
|
||||
model.trustPaneButtonEnabled = true
|
||||
}
|
||||
|
||||
def showMonitorWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel,"monitor window")
|
||||
model.searchesPaneButtonEnabled = true
|
||||
model.downloadsPaneButtonEnabled = true
|
||||
model.uploadsPaneButtonEnabled = true
|
||||
model.monitorPaneButtonEnabled = false
|
||||
model.trustPaneButtonEnabled = true
|
||||
}
|
||||
|
||||
def showTrustWindow = {
|
||||
def cardsPanel = builder.getVariable("cards-panel")
|
||||
cardsPanel.getLayout().show(cardsPanel,"trust window")
|
||||
model.searchesPaneButtonEnabled = true
|
||||
model.downloadsPaneButtonEnabled = true
|
||||
model.uploadsPaneButtonEnabled = true
|
||||
model.monitorPaneButtonEnabled = true
|
||||
model.trustPaneButtonEnabled = false
|
||||
}
|
||||
|
||||
def shareFiles = {
|
||||
|
||||
@@ -16,6 +16,7 @@ import javax.swing.ListSelectionModel
|
||||
import javax.swing.SwingConstants
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
|
||||
import com.muwire.core.Persona
|
||||
import com.muwire.core.util.DataUtil
|
||||
|
||||
import java.awt.BorderLayout
|
||||
@@ -37,23 +38,49 @@ class SearchTabView {
|
||||
def pane
|
||||
def parent
|
||||
def searchTerms
|
||||
def sendersTable
|
||||
def lastSendersSortEvent
|
||||
def resultsTable
|
||||
def lastSortEvent
|
||||
|
||||
void initUI() {
|
||||
builder.with {
|
||||
def resultsTable
|
||||
def pane = scrollPane {
|
||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
||||
tableModel(list: model.results) {
|
||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
||||
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
||||
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
||||
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
|
||||
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
|
||||
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
|
||||
model.core.trustService.getLevel(row.sender.destination).toString()
|
||||
})
|
||||
def sendersTable
|
||||
def pane = panel {
|
||||
gridLayout(rows : 2, cols: 1)
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
sendersTable = table(id : "senders-table", autoCreateRowSorter : true) {
|
||||
tableModel(list : model.senders) {
|
||||
closureColumn(header : "Sender", preferredWidth : 500, type: String, read : {row -> row.getHumanReadableName()})
|
||||
closureColumn(header : "Results", preferredWidth : 20, type: Integer, read : {row -> model.sendersBucket[row].size()})
|
||||
closureColumn(header : "Trust", preferredWidth : 50, type: String, read : { row ->
|
||||
model.core.trustService.getLevel(row.destination).toString()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel(constraints : BorderLayout.SOUTH) {
|
||||
button(text : "Trust", enabled: bind {model.trustButtonsEnabled }, trustAction)
|
||||
button(text : "Distrust", enabled : bind {model.trustButtonsEnabled}, distrustAction)
|
||||
}
|
||||
}
|
||||
panel {
|
||||
borderLayout()
|
||||
scrollPane (constraints : BorderLayout.CENTER) {
|
||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
||||
tableModel(list: model.results) {
|
||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
||||
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
||||
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
||||
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
|
||||
}
|
||||
}
|
||||
}
|
||||
panel(constraints : BorderLayout.SOUTH) {
|
||||
button(text : "Download", enabled : bind {model.downloadActionEnabled}, downloadAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,17 +90,19 @@ class SearchTabView {
|
||||
this.pane.putClientProperty("results-table",resultsTable)
|
||||
|
||||
this.resultsTable = resultsTable
|
||||
this.sendersTable = sendersTable
|
||||
|
||||
def selectionModel = resultsTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener( {
|
||||
int row = resultsTable.getSelectedRow()
|
||||
if (row < 0)
|
||||
if (row < 0) {
|
||||
model.downloadActionEnabled = false
|
||||
return
|
||||
}
|
||||
if (lastSortEvent != null)
|
||||
row = resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||
mvcGroup.parentGroup.model.trustButtonsEnabled = true
|
||||
mvcGroup.parentGroup.model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
|
||||
model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -98,12 +127,11 @@ class SearchTabView {
|
||||
}
|
||||
|
||||
parent.setTabComponentAt(index, tabPanel)
|
||||
mvcGroup.parentGroup.view.showSearchWindow.call()
|
||||
|
||||
def centerRenderer = new DefaultTableCellRenderer()
|
||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||
resultsTable.columnModel.getColumn(1).setCellRenderer(centerRenderer)
|
||||
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
||||
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
|
||||
|
||||
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||
|
||||
@@ -118,7 +146,7 @@ class SearchTabView {
|
||||
if (e.button == MouseEvent.BUTTON3)
|
||||
showPopupMenu(e)
|
||||
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
|
||||
mvcGroup.parentGroup.controller.download()
|
||||
mvcGroup.controller.download()
|
||||
}
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
@@ -126,21 +154,42 @@ class SearchTabView {
|
||||
showPopupMenu(e)
|
||||
}
|
||||
})
|
||||
|
||||
// senders table
|
||||
sendersTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||
sendersTable.rowSorter.addRowSorterListener({evt -> lastSendersSortEvent = evt})
|
||||
sendersTable.rowSorter.setSortsOnUpdates(true)
|
||||
def selectionModel = sendersTable.getSelectionModel()
|
||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||
selectionModel.addListSelectionListener({
|
||||
int row = selectedSenderRow()
|
||||
if (row < 0) {
|
||||
model.trustButtonsEnabled = false
|
||||
return
|
||||
} else {
|
||||
model.trustButtonsEnabled = true
|
||||
model.results.clear()
|
||||
Persona p = model.senders[row]
|
||||
model.results.addAll(model.sendersBucket[p])
|
||||
resultsTable.model.fireTableDataChanged()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
def closeTab = {
|
||||
int index = parent.indexOfTab(searchTerms)
|
||||
parent.removeTabAt(index)
|
||||
mvcGroup.parentGroup.model.trustButtonsEnabled = false
|
||||
mvcGroup.parentGroup.model.downloadActionEnabled = false
|
||||
model.trustButtonsEnabled = false
|
||||
model.downloadActionEnabled = false
|
||||
mvcGroup.destroy()
|
||||
}
|
||||
|
||||
def showPopupMenu(MouseEvent e) {
|
||||
JPopupMenu menu = new JPopupMenu()
|
||||
if (mvcGroup.parentGroup.model.downloadActionEnabled) {
|
||||
if (model.downloadActionEnabled) {
|
||||
JMenuItem download = new JMenuItem("Download")
|
||||
download.addActionListener({mvcGroup.parentGroup.controller.download()})
|
||||
download.addActionListener({mvcGroup.controller.download()})
|
||||
menu.add(download)
|
||||
}
|
||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||
@@ -160,4 +209,13 @@ class SearchTabView {
|
||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||
clipboard.setContents(selection, null)
|
||||
}
|
||||
|
||||
int selectedSenderRow() {
|
||||
int row = sendersTable.getSelectedRow()
|
||||
if (row < 0)
|
||||
return -1
|
||||
if (lastSendersSortEvent != null)
|
||||
row = sendersTable.rowSorter.convertRowIndexToModel(row)
|
||||
row
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.test.GriffonFestRule
|
||||
import org.fest.swing.fixture.FrameFixture
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.fail
|
||||
|
||||
class ContentPanelIntegrationTest {
|
||||
static {
|
||||
System.setProperty('griffon.swing.edt.violations.check', 'true')
|
||||
System.setProperty('griffon.swing.edt.hang.monitor', 'true')
|
||||
}
|
||||
|
||||
@Rule
|
||||
public final GriffonFestRule fest = new GriffonFestRule()
|
||||
|
||||
private FrameFixture window
|
||||
|
||||
@Test
|
||||
void smokeTest() {
|
||||
fail('Not implemented yet!')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JLabel
|
||||
import javax.swing.JTable
|
||||
import javax.swing.table.DefaultTableCellRenderer
|
||||
|
||||
import com.muwire.core.download.Downloader
|
||||
|
||||
import net.i2p.data.DataHelper
|
||||
|
||||
class DownloadProgressRenderer extends DefaultTableCellRenderer {
|
||||
DownloadProgressRenderer() {
|
||||
setHorizontalAlignment(JLabel.CENTER)
|
||||
}
|
||||
|
||||
@Override
|
||||
JComponent getTableCellRendererComponent(JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
Downloader d = (Downloader) value
|
||||
int pieces = d.nPieces
|
||||
int done = d.donePieces()
|
||||
int percent = -1
|
||||
if (pieces != 0)
|
||||
percent = (done * 100 / pieces)
|
||||
String totalSize = DataHelper.formatSize2Decimal(d.length, false) + "B"
|
||||
setText(String.format("%2d", percent) + "% of ${totalSize} ($done/$pieces pcs)".toString())
|
||||
|
||||
if (isSelected) {
|
||||
setForeground(table.getSelectionForeground())
|
||||
setBackground(table.getSelectionBackground())
|
||||
} else {
|
||||
setForeground(table.getForeground())
|
||||
setBackground(table.getBackground())
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import com.muwire.core.download.Downloader
|
||||
|
||||
class DownloaderComparator implements Comparator<Downloader>{
|
||||
|
||||
@Override
|
||||
public int compare(Downloader o1, Downloader o2) {
|
||||
double d1 = o1.donePieces() * 1.0 / o1.nPieces
|
||||
double d2 = o2.donePieces() * 1.0 / o2.nPieces
|
||||
return Double.compare(d1, d2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.muwire.gui
|
||||
|
||||
import griffon.core.test.GriffonUnitRule
|
||||
import griffon.core.test.TestFor
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.fail
|
||||
|
||||
@TestFor(ContentPanelController)
|
||||
class ContentPanelControllerTest {
|
||||
private ContentPanelController controller
|
||||
|
||||
@Rule
|
||||
public final GriffonUnitRule griffon = new GriffonUnitRule()
|
||||
|
||||
@Test
|
||||
void smokeTest() {
|
||||
fail('Not yet implemented!')
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ class Crawler {
|
||||
|
||||
final def pinger
|
||||
final def hostPool
|
||||
final int parallel
|
||||
final int parallel
|
||||
|
||||
final Map<Destination, Host> inFlight = new HashMap<>()
|
||||
|
||||
@@ -20,7 +20,7 @@ class Crawler {
|
||||
Crawler(pinger, hostPool, int parallel) {
|
||||
this.pinger = pinger
|
||||
this.hostPool = hostPool
|
||||
this.parallel = parallel
|
||||
this.parallel = parallel
|
||||
}
|
||||
|
||||
synchronized def handleCrawlerPong(pong, Destination source) {
|
||||
@@ -74,15 +74,15 @@ class Crawler {
|
||||
return Boolean.parseBoolean(value.toString())
|
||||
}
|
||||
|
||||
synchronized def startCrawl() {
|
||||
if (!inFlight.isEmpty()) {
|
||||
inFlight.values().each { hostPool.fail(it) }
|
||||
inFlight.clear()
|
||||
}
|
||||
currentUUID = UUID.randomUUID()
|
||||
hostPool.getUnverified(parallel).each {
|
||||
inFlight.put(it.destination, it)
|
||||
pinger.ping(it, currentUUID)
|
||||
}
|
||||
}
|
||||
synchronized def startCrawl() {
|
||||
if (!inFlight.isEmpty()) {
|
||||
inFlight.values().each { hostPool.fail(it) }
|
||||
inFlight.clear()
|
||||
}
|
||||
currentUUID = UUID.randomUUID()
|
||||
hostPool.getUnverified(parallel).each {
|
||||
inFlight.put(it.destination, it)
|
||||
pinger.ping(it, currentUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,14 +56,14 @@ public class HostCache {
|
||||
session = i2pClient.createSession(new FileInputStream(keyfile), props)
|
||||
myDest = session.getMyDestination()
|
||||
|
||||
// initialize hostpool and crawler
|
||||
HostPool hostPool = new HostPool(3, 60 * 60 * 1000)
|
||||
Pinger pinger = new Pinger(session)
|
||||
Crawler crawler = new Crawler(pinger, hostPool, 5)
|
||||
// initialize hostpool and crawler
|
||||
HostPool hostPool = new HostPool(3, 60 * 60 * 1000)
|
||||
Pinger pinger = new Pinger(session)
|
||||
Crawler crawler = new Crawler(pinger, hostPool, 5)
|
||||
|
||||
Timer timer = new Timer("timer", true)
|
||||
timer.schedule({hostPool.age()} as TimerTask, 1000,1000)
|
||||
timer.schedule({crawler.startCrawl()} as TimerTask, 10000, 10000)
|
||||
Timer timer = new Timer("timer", true)
|
||||
timer.schedule({hostPool.age()} as TimerTask, 1000,1000)
|
||||
timer.schedule({crawler.startCrawl()} as TimerTask, 10000, 10000)
|
||||
File verified = new File("verified.json")
|
||||
File unverified = new File("unverified.json")
|
||||
timer.schedule({hostPool.serialize(verified, unverified)} as TimerTask, 10000, 60 * 60 * 1000)
|
||||
@@ -78,9 +78,9 @@ public class HostCache {
|
||||
|
||||
static class Listener implements I2PSessionMuxedListener {
|
||||
final def json = new JsonSlurper()
|
||||
def hostPool
|
||||
int toReturn
|
||||
def crawler
|
||||
def hostPool
|
||||
int toReturn
|
||||
def crawler
|
||||
|
||||
void reportAbuse(I2PSession sesison, int severity) {}
|
||||
void disconnected(I2PSession session) {
|
||||
@@ -113,18 +113,18 @@ public class HostCache {
|
||||
switch(payload.type) {
|
||||
case "Ping" :
|
||||
log.info("ping from $b32")
|
||||
if (payload.leaf == null) {
|
||||
log.warning("ping didn't specify if leaf from $b32")
|
||||
return
|
||||
}
|
||||
payload.leaf = Boolean.parseBoolean(payload.leaf.toString())
|
||||
if (!payload.leaf)
|
||||
hostPool.addUnverified(new Host(destination: sender))
|
||||
respond(session, sender, payload)
|
||||
if (payload.leaf == null) {
|
||||
log.warning("ping didn't specify if leaf from $b32")
|
||||
return
|
||||
}
|
||||
payload.leaf = Boolean.parseBoolean(payload.leaf.toString())
|
||||
if (!payload.leaf)
|
||||
hostPool.addUnverified(new Host(destination: sender))
|
||||
respond(session, sender, payload)
|
||||
break
|
||||
case "CrawlerPong":
|
||||
log.info("CrawlerPong from $b32")
|
||||
crawler.handleCrawlerPong(payload, sender)
|
||||
crawler.handleCrawlerPong(payload, sender)
|
||||
break
|
||||
default:
|
||||
log.warning("Unexpected message type ${payload.type}, dropping from $b32")
|
||||
@@ -136,16 +136,16 @@ public class HostCache {
|
||||
void messageAvailable(I2PSession session, int msgId, long size) {
|
||||
}
|
||||
|
||||
def respond(session, destination, ping) {
|
||||
def respond(session, destination, ping) {
|
||||
|
||||
def pongs = hostPool.getVerified(toReturn, ping.leaf)
|
||||
pongs = pongs.stream().map({ x -> x.destination.toBase64() }).collect(Collectors.toList())
|
||||
def pongs = hostPool.getVerified(toReturn, ping.leaf)
|
||||
pongs = pongs.stream().map({ x -> x.destination.toBase64() }).collect(Collectors.toList())
|
||||
|
||||
def pong = [type:"Pong", version: 1, pongs: pongs]
|
||||
pong = JsonOutput.toJson(pong)
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
pong = maker.makeI2PDatagram(pong.bytes)
|
||||
session.sendMessage(destination, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
||||
}
|
||||
def pong = [type:"Pong", version: 1, pongs: pongs]
|
||||
pong = JsonOutput.toJson(pong)
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
pong = maker.makeI2PDatagram(pong.bytes)
|
||||
session.sendMessage(destination, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class HostPool {
|
||||
def verified = new HashMap()
|
||||
def unverified = new HashMap()
|
||||
|
||||
HostPool() {}
|
||||
HostPool() {}
|
||||
HostPool(maxFailures, maxAge) {
|
||||
this.maxAge = maxAge
|
||||
this.maxFailures = maxFailures
|
||||
@@ -53,7 +53,7 @@ class HostPool {
|
||||
|
||||
synchronized def fail(host) {
|
||||
if (!unverified.containsKey(host.destination))
|
||||
return
|
||||
return
|
||||
host.verificationFailures++
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class Pinger {
|
||||
}
|
||||
|
||||
def ping(host, uuid) {
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
def maker = new I2PDatagramMaker(session)
|
||||
def payload = new HashMap()
|
||||
payload.type = "CrawlerPing"
|
||||
payload.version = 1
|
||||
|
||||
@@ -10,129 +10,129 @@ import net.i2p.data.Destination
|
||||
|
||||
class CrawlerTest {
|
||||
|
||||
def pingerMock
|
||||
def pinger
|
||||
def pingerMock
|
||||
def pinger
|
||||
|
||||
def hostPoolMock
|
||||
def hostPool
|
||||
def hostPoolMock
|
||||
def hostPool
|
||||
|
||||
def crawler
|
||||
def crawler
|
||||
|
||||
Destinations destinations = new Destinations()
|
||||
final Host host = new Host(destination: new Destination())
|
||||
final Host host1 = new Host(destination: destinations.dest1)
|
||||
final Host host2 = new Host(destination: destinations.dest2)
|
||||
Destinations destinations = new Destinations()
|
||||
final Host host = new Host(destination: new Destination())
|
||||
final Host host1 = new Host(destination: destinations.dest1)
|
||||
final Host host2 = new Host(destination: destinations.dest2)
|
||||
|
||||
final int parallel = 5
|
||||
final int parallel = 5
|
||||
|
||||
@Before
|
||||
void before() {
|
||||
pingerMock = new MockFor(Pinger)
|
||||
hostPoolMock = new MockFor(HostPool)
|
||||
}
|
||||
@Before
|
||||
void before() {
|
||||
pingerMock = new MockFor(Pinger)
|
||||
hostPoolMock = new MockFor(HostPool)
|
||||
}
|
||||
|
||||
@After
|
||||
void after() {
|
||||
hostPoolMock.verify hostPool
|
||||
pingerMock.verify pinger
|
||||
}
|
||||
@After
|
||||
void after() {
|
||||
hostPoolMock.verify hostPool
|
||||
pingerMock.verify pinger
|
||||
}
|
||||
|
||||
private def initCrawler() {
|
||||
pinger = pingerMock.proxyInstance()
|
||||
hostPool = hostPoolMock.proxyInstance()
|
||||
crawler = new Crawler(pinger, hostPool, parallel)
|
||||
private def initCrawler() {
|
||||
pinger = pingerMock.proxyInstance()
|
||||
hostPool = hostPoolMock.proxyInstance()
|
||||
crawler = new Crawler(pinger, hostPool, parallel)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBadJson() {
|
||||
initCrawler()
|
||||
def unpingedHost = new Host(destination : new Destination())
|
||||
crawler.handleCrawlerPong(null, new Destination())
|
||||
}
|
||||
@Test
|
||||
void testBadJson() {
|
||||
initCrawler()
|
||||
def unpingedHost = new Host(destination : new Destination())
|
||||
crawler.handleCrawlerPong(null, new Destination())
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStartCrawl() {
|
||||
hostPoolMock.demand.getUnverified { n ->
|
||||
assert n == parallel
|
||||
[host]
|
||||
}
|
||||
pingerMock.demand.ping { h,uuid -> assert h == host }
|
||||
@Test
|
||||
void testStartCrawl() {
|
||||
hostPoolMock.demand.getUnverified { n ->
|
||||
assert n == parallel
|
||||
[host]
|
||||
}
|
||||
pingerMock.demand.ping { h,uuid -> assert h == host }
|
||||
|
||||
initCrawler()
|
||||
crawler.startCrawl()
|
||||
initCrawler()
|
||||
crawler.startCrawl()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFailsUnanswered() {
|
||||
hostPoolMock.demand.getUnverified {n -> [host]}
|
||||
hostPoolMock.demand.fail { h -> assert h == host }
|
||||
hostPoolMock.demand.getUnverified {n -> [:]}
|
||||
pingerMock.demand.ping {h,uuid -> }
|
||||
initCrawler()
|
||||
@Test
|
||||
void testFailsUnanswered() {
|
||||
hostPoolMock.demand.getUnverified {n -> [host]}
|
||||
hostPoolMock.demand.fail { h -> assert h == host }
|
||||
hostPoolMock.demand.getUnverified {n -> [:]}
|
||||
pingerMock.demand.ping {h,uuid -> }
|
||||
initCrawler()
|
||||
|
||||
crawler.startCrawl()
|
||||
crawler.startCrawl()
|
||||
}
|
||||
crawler.startCrawl()
|
||||
crawler.startCrawl()
|
||||
}
|
||||
|
||||
@Test
|
||||
void testVerifiesAnswered() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
hostPoolMock.demand.verify { h -> assert h == host1 }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
@Test
|
||||
void testVerifiesAnswered() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
hostPoolMock.demand.verify { h -> assert h == host1 }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
|
||||
initCrawler()
|
||||
initCrawler()
|
||||
|
||||
crawler.startCrawl()
|
||||
crawler.startCrawl()
|
||||
|
||||
def pong = [uuid : currentUUID.toString(), leafSlots : "false", peerSlots: "false", peers: [] ]
|
||||
crawler.handleCrawlerPong(pong, host1.destination)
|
||||
}
|
||||
def pong = [uuid : currentUUID.toString(), leafSlots : "false", peerSlots: "false", peers: [] ]
|
||||
crawler.handleCrawlerPong(pong, host1.destination)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongSourceIgnored() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
@Test
|
||||
void testWrongSourceIgnored() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
|
||||
initCrawler()
|
||||
initCrawler()
|
||||
|
||||
crawler.startCrawl()
|
||||
crawler.startCrawl()
|
||||
|
||||
def pong = [uuid : currentUUID.toString(), leafSlots : "false", peerSlots: "false", peers: [] ]
|
||||
crawler.handleCrawlerPong(pong, host2.destination)
|
||||
}
|
||||
def pong = [uuid : currentUUID.toString(), leafSlots : "false", peerSlots: "false", peers: [] ]
|
||||
crawler.handleCrawlerPong(pong, host2.destination)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHost1CarriesHost2() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
hostPoolMock.demand.addUnverified { h -> assert h == host2 }
|
||||
hostPoolMock.demand.verify { h -> assert h == host1 }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
@Test
|
||||
void testHost1CarriesHost2() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
hostPoolMock.demand.addUnverified { h -> assert h == host2 }
|
||||
hostPoolMock.demand.verify { h -> assert h == host1 }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
|
||||
initCrawler()
|
||||
initCrawler()
|
||||
|
||||
crawler.startCrawl()
|
||||
crawler.startCrawl()
|
||||
|
||||
def pong = [uuid : currentUUID.toString(), leafSlots : "false", peerSlots: "false", peers: [destinations.dest2.toBase64()] ]
|
||||
crawler.handleCrawlerPong(pong, host1.destination)
|
||||
}
|
||||
def pong = [uuid : currentUUID.toString(), leafSlots : "false", peerSlots: "false", peers: [destinations.dest2.toBase64()] ]
|
||||
crawler.handleCrawlerPong(pong, host1.destination)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongUUID() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
hostPoolMock.demand.fail { h -> assert h == host1 }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
@Test
|
||||
void testWrongUUID() {
|
||||
def currentUUID
|
||||
hostPoolMock.demand.getUnverified { n -> [host1] }
|
||||
hostPoolMock.demand.fail { h -> assert h == host1 }
|
||||
pingerMock.demand.ping { h, uuid -> currentUUID = uuid }
|
||||
|
||||
initCrawler()
|
||||
initCrawler()
|
||||
|
||||
crawler.startCrawl()
|
||||
crawler.startCrawl()
|
||||
|
||||
def pong = [uuid : UUID.randomUUID().toString(), leafSlots : "false", peerSlots: "false", peers: [] ]
|
||||
crawler.handleCrawlerPong(pong, host1.destination)
|
||||
}
|
||||
def pong = [uuid : UUID.randomUUID().toString(), leafSlots : "false", peerSlots: "false", peers: [] ]
|
||||
crawler.handleCrawlerPong(pong, host1.destination)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user