Compare commits
85 Commits
muwire-0.4
...
muwire-0.4
Author | SHA1 | Date | |
---|---|---|---|
![]() |
549e8c2d98 | ||
![]() |
b54d24db0d | ||
![]() |
fa12e84345 | ||
![]() |
6430ff2691 | ||
![]() |
591313c81c | ||
![]() |
ce7b6a0c65 | ||
![]() |
5c4d4c4580 | ||
![]() |
4cb864ff9f | ||
![]() |
417675ad07 | ||
![]() |
9513e5ba3c | ||
![]() |
85610cf169 | ||
![]() |
e8322384b8 | ||
![]() |
179279ed30 | ||
![]() |
ae79f0fded | ||
![]() |
ed878b3762 | ||
![]() |
623cca0ef2 | ||
![]() |
eaa883c3ba | ||
![]() |
7ae8076865 | ||
![]() |
b1aa92661c | ||
![]() |
9ed94c8376 | ||
![]() |
fa6aea1abe | ||
![]() |
0de84e704b | ||
![]() |
a767dda044 | ||
![]() |
56e9235d7b | ||
![]() |
2fba9a74ce | ||
![]() |
2bb6826906 | ||
![]() |
9f339629a9 | ||
![]() |
58d4207f94 | ||
![]() |
32577a28dc | ||
![]() |
f7b43304d4 | ||
![]() |
dcbe09886d | ||
![]() |
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 |
18
README.md
18
README.md
@@ -4,7 +4,7 @@ MuWire is an easy to use file-sharing program which offers anonymity using [I2P
|
|||||||
|
|
||||||
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
It is inspired by the LimeWire Gnutella client and developped by a former LimeWire developer.
|
||||||
|
|
||||||
The current stable release - 0.4.6 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
The current stable release - 0.4.11 is avaiable for download at https://muwire.com. You can find technical documentation in the "doc" folder.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
@@ -21,11 +21,23 @@ If you want to run the unit tests, type
|
|||||||
|
|
||||||
Some of the UI tests will fail because they haven't been written yet :-/
|
Some of the UI tests will fail because they haven't been written yet :-/
|
||||||
|
|
||||||
|
If you want to build binary bundles for Windows and Mac that do not depend on Java or I2P, see the https://github.com/zlatinb/muwire-pkg project
|
||||||
|
|
||||||
### Running
|
### Running
|
||||||
|
|
||||||
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.
|
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.
|
[Default I2CP port]\: `7654`
|
||||||
|
|
||||||
|
### GPG Fingerprint
|
||||||
|
|
||||||
|
```
|
||||||
|
471B 9FD4 5517 A5ED 101F C57D A728 3207 2D52 5E41
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find the full key at https://keybase.io/zlatinb
|
||||||
|
|
||||||
|
|
||||||
|
[Default I2CP port]: https://geti2p.net/en/docs/ports
|
||||||
|
5
TODO.md
5
TODO.md
@@ -12,10 +12,6 @@ This reduces query traffic by not sending last hop queries to peers that definit
|
|||||||
|
|
||||||
This helps with scalability
|
This helps with scalability
|
||||||
|
|
||||||
##### Content Control Panel
|
|
||||||
|
|
||||||
To allow every user to not route queries for content they do not like. This is mostly GUI work, the backend part is simple
|
|
||||||
|
|
||||||
##### Web UI, REST Interface, etc.
|
##### Web UI, REST Interface, etc.
|
||||||
|
|
||||||
Basically any non-gui non-cli user interface
|
Basically any non-gui non-cli user interface
|
||||||
@@ -29,3 +25,4 @@ To enable parsing of metadata from known file types and the user editing it or a
|
|||||||
* Wrapper of some kind for in-place upgrades
|
* Wrapper of some kind for in-place upgrades
|
||||||
* Download file sequentially
|
* Download file sequentially
|
||||||
* Multiple-selection download, Ctrl-A
|
* Multiple-selection download, Ctrl-A
|
||||||
|
* Automatic adjustment of number of I2P tunnels
|
||||||
|
@@ -2,7 +2,7 @@ subprojects {
|
|||||||
apply plugin: 'groovy'
|
apply plugin: 'groovy'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:i2p:0.9.40'
|
compile 'net.i2p:i2p:0.9.42'
|
||||||
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
compile 'org.codehaus.groovy:groovy-all:2.4.15'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -35,7 +35,7 @@ class Cli {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.4.7")
|
core = new Core(props, home, "0.4.12")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -53,7 +53,7 @@ class CliDownloader {
|
|||||||
|
|
||||||
Core core
|
Core core
|
||||||
try {
|
try {
|
||||||
core = new Core(props, home, "0.4.7")
|
core = new Core(props, home, "0.4.12")
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
bad.printStackTrace(System.out)
|
bad.printStackTrace(System.out)
|
||||||
println "Failed to initialize core, exiting"
|
println "Failed to initialize core, exiting"
|
||||||
|
@@ -2,9 +2,9 @@ apply plugin : 'application'
|
|||||||
mainClassName = 'com.muwire.core.Core'
|
mainClassName = 'com.muwire.core.Core'
|
||||||
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
applicationDefaultJvmArgs = ['-Djava.util.logging.config.file=logging.properties']
|
||||||
dependencies {
|
dependencies {
|
||||||
compile 'net.i2p:router:0.9.40'
|
compile 'net.i2p:router:0.9.42'
|
||||||
compile 'net.i2p.client:mstreaming:0.9.40'
|
compile 'net.i2p.client:mstreaming:0.9.42'
|
||||||
compile 'net.i2p.client:streaming:0.9.40'
|
compile 'net.i2p.client:streaming:0.9.42'
|
||||||
|
|
||||||
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.2'
|
||||||
testCompile 'junit:junit:4.12'
|
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.download.UIDownloadResumedEvent
|
||||||
import com.muwire.core.files.FileDownloadedEvent
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
import com.muwire.core.files.FileHashedEvent
|
import com.muwire.core.files.FileHashedEvent
|
||||||
|
import com.muwire.core.files.FileHashingEvent
|
||||||
import com.muwire.core.files.FileHasher
|
import com.muwire.core.files.FileHasher
|
||||||
import com.muwire.core.files.FileLoadedEvent
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
import com.muwire.core.files.FileManager
|
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.update.UpdateClient
|
||||||
import com.muwire.core.upload.UploadManager
|
import com.muwire.core.upload.UploadManager
|
||||||
import com.muwire.core.util.MuWireLogManager
|
import com.muwire.core.util.MuWireLogManager
|
||||||
|
import com.muwire.core.content.ContentControlEvent
|
||||||
|
import com.muwire.core.content.ContentManager
|
||||||
|
|
||||||
import groovy.util.logging.Log
|
import groovy.util.logging.Log
|
||||||
import net.i2p.I2PAppContext
|
import net.i2p.I2PAppContext
|
||||||
@@ -89,6 +92,7 @@ public class Core {
|
|||||||
private final DirectoryWatcher directoryWatcher
|
private final DirectoryWatcher directoryWatcher
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
final UploadManager uploadManager
|
final UploadManager uploadManager
|
||||||
|
final ContentManager contentManager
|
||||||
|
|
||||||
private final Router router
|
private final Router router
|
||||||
|
|
||||||
@@ -140,33 +144,33 @@ public class Core {
|
|||||||
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
routerProps.setProperty("i2np.udp.port", i2pOptions["i2np.udp.port"])
|
||||||
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
routerProps.setProperty("i2np.udp.internalPort", i2pOptions["i2np.udp.port"])
|
||||||
router = new Router(routerProps)
|
router = new Router(routerProps)
|
||||||
I2PAppContext.getGlobalContext().metaClass = new RouterContextMetaClass()
|
router.getContext().setLogManager(new MuWireLogManager())
|
||||||
router.runRouter()
|
router.runRouter()
|
||||||
while(!router.isRunning())
|
while(!router.isRunning())
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("initializing I2P socket manager")
|
log.info("initializing I2P socket manager")
|
||||||
def i2pClient = new I2PClientFactory().createClient()
|
def i2pClient = new I2PClientFactory().createClient()
|
||||||
File keyDat = new File(home, "key.dat")
|
File keyDat = new File(home, "key.dat")
|
||||||
if (!keyDat.exists()) {
|
if (!keyDat.exists()) {
|
||||||
log.info("Creating new key.dat")
|
log.info("Creating new key.dat")
|
||||||
keyDat.withOutputStream {
|
keyDat.withOutputStream {
|
||||||
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
i2pClient.createDestination(it, Constants.SIG_TYPE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// options like tunnel length and quantity
|
// options like tunnel length and quantity
|
||||||
I2PSession i2pSession
|
I2PSession i2pSession
|
||||||
I2PSocketManager socketManager
|
I2PSocketManager socketManager
|
||||||
keyDat.withInputStream {
|
keyDat.withInputStream {
|
||||||
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
socketManager = new I2PSocketManagerFactory().createManager(it, i2pOptions["i2cp.tcp.host"], i2pOptions["i2cp.tcp.port"].toInteger(), i2pOptions)
|
||||||
}
|
}
|
||||||
socketManager.getDefaultOptions().setReadTimeout(60000)
|
socketManager.getDefaultOptions().setReadTimeout(60000)
|
||||||
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
socketManager.getDefaultOptions().setConnectTimeout(30000)
|
||||||
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
|
socketManager.addDisconnectListener({eventBus.publish(new RouterDisconnectedEvent())} as DisconnectListener)
|
||||||
i2pSession = socketManager.getSession()
|
i2pSession = socketManager.getSession()
|
||||||
|
|
||||||
def destination = new Destination()
|
def destination = new Destination()
|
||||||
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
def spk = new SigningPrivateKey(Constants.SIG_TYPE)
|
||||||
@@ -175,7 +179,7 @@ public class Core {
|
|||||||
def privateKey = new PrivateKey()
|
def privateKey = new PrivateKey()
|
||||||
privateKey.readBytes(it)
|
privateKey.readBytes(it)
|
||||||
spk.readBytes(it)
|
spk.readBytes(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
def baos = new ByteArrayOutputStream()
|
def baos = new ByteArrayOutputStream()
|
||||||
def daos = new DataOutputStream(baos)
|
def daos = new DataOutputStream(baos)
|
||||||
@@ -193,65 +197,65 @@ public class Core {
|
|||||||
me = new Persona(new ByteArrayInputStream(baos.toByteArray()))
|
me = new Persona(new ByteArrayInputStream(baos.toByteArray()))
|
||||||
log.info("Loaded myself as "+me.getHumanReadableName())
|
log.info("Loaded myself as "+me.getHumanReadableName())
|
||||||
|
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
|
|
||||||
log.info("initializing trust service")
|
log.info("initializing trust service")
|
||||||
File goodTrust = new File(home, "trusted")
|
File goodTrust = new File(home, "trusted")
|
||||||
File badTrust = new File(home, "distrusted")
|
File badTrust = new File(home, "distrusted")
|
||||||
trustService = new TrustService(goodTrust, badTrust, 5000)
|
trustService = new TrustService(goodTrust, badTrust, 5000)
|
||||||
eventBus.register(TrustEvent.class, trustService)
|
eventBus.register(TrustEvent.class, trustService)
|
||||||
|
|
||||||
|
|
||||||
log.info "initializing file manager"
|
log.info "initializing file manager"
|
||||||
fileManager = new FileManager(eventBus, props)
|
fileManager = new FileManager(eventBus, props)
|
||||||
eventBus.register(FileHashedEvent.class, fileManager)
|
eventBus.register(FileHashedEvent.class, fileManager)
|
||||||
eventBus.register(FileLoadedEvent.class, fileManager)
|
eventBus.register(FileLoadedEvent.class, fileManager)
|
||||||
eventBus.register(FileDownloadedEvent.class, fileManager)
|
eventBus.register(FileDownloadedEvent.class, fileManager)
|
||||||
eventBus.register(FileUnsharedEvent.class, fileManager)
|
eventBus.register(FileUnsharedEvent.class, fileManager)
|
||||||
eventBus.register(SearchEvent.class, fileManager)
|
eventBus.register(SearchEvent.class, fileManager)
|
||||||
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
eventBus.register(DirectoryUnsharedEvent.class, fileManager)
|
||||||
|
|
||||||
log.info("initializing mesh manager")
|
log.info("initializing mesh manager")
|
||||||
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
MeshManager meshManager = new MeshManager(fileManager, home, props)
|
||||||
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
eventBus.register(SourceDiscoveredEvent.class, meshManager)
|
||||||
|
|
||||||
log.info "initializing persistence service"
|
log.info "initializing persistence service"
|
||||||
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 15000, fileManager)
|
persisterService = new PersisterService(new File(home, "files.json"), eventBus, 60000, fileManager)
|
||||||
eventBus.register(UILoadedEvent.class, persisterService)
|
eventBus.register(UILoadedEvent.class, persisterService)
|
||||||
|
|
||||||
log.info("initializing host cache")
|
log.info("initializing host cache")
|
||||||
File hostStorage = new File(home, "hosts.json")
|
File hostStorage = new File(home, "hosts.json")
|
||||||
hostCache = new HostCache(trustService,hostStorage, 30000, props, i2pSession.getMyDestination())
|
hostCache = new HostCache(trustService,hostStorage, 30000, props, i2pSession.getMyDestination())
|
||||||
eventBus.register(HostDiscoveredEvent.class, hostCache)
|
eventBus.register(HostDiscoveredEvent.class, hostCache)
|
||||||
eventBus.register(ConnectionEvent.class, hostCache)
|
eventBus.register(ConnectionEvent.class, hostCache)
|
||||||
|
|
||||||
log.info("initializing connection manager")
|
log.info("initializing connection manager")
|
||||||
connectionManager = props.isLeaf() ?
|
connectionManager = props.isLeaf() ?
|
||||||
new LeafConnectionManager(eventBus, me, 3, hostCache, props) :
|
new LeafConnectionManager(eventBus, me, 3, hostCache, props) :
|
||||||
new UltrapeerConnectionManager(eventBus, me, 512, 512, hostCache, trustService, props)
|
new UltrapeerConnectionManager(eventBus, me, 512, 512, hostCache, trustService, props)
|
||||||
eventBus.register(TrustEvent.class, connectionManager)
|
eventBus.register(TrustEvent.class, connectionManager)
|
||||||
eventBus.register(ConnectionEvent.class, connectionManager)
|
eventBus.register(ConnectionEvent.class, connectionManager)
|
||||||
eventBus.register(DisconnectionEvent.class, connectionManager)
|
eventBus.register(DisconnectionEvent.class, connectionManager)
|
||||||
eventBus.register(QueryEvent.class, connectionManager)
|
eventBus.register(QueryEvent.class, connectionManager)
|
||||||
|
|
||||||
log.info("initializing cache client")
|
log.info("initializing cache client")
|
||||||
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
cacheClient = new CacheClient(eventBus,hostCache, connectionManager, i2pSession, props, 10000)
|
||||||
|
|
||||||
log.info("initializing update client")
|
log.info("initializing update client")
|
||||||
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
updateClient = new UpdateClient(eventBus, i2pSession, myVersion, props, fileManager, me)
|
||||||
eventBus.register(FileDownloadedEvent.class, updateClient)
|
eventBus.register(FileDownloadedEvent.class, updateClient)
|
||||||
eventBus.register(UIResultBatchEvent.class, updateClient)
|
eventBus.register(UIResultBatchEvent.class, updateClient)
|
||||||
|
|
||||||
log.info("initializing connector")
|
log.info("initializing connector")
|
||||||
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
I2PConnector i2pConnector = new I2PConnector(socketManager)
|
||||||
|
|
||||||
log.info "initializing results sender"
|
log.info "initializing results sender"
|
||||||
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
ResultsSender resultsSender = new ResultsSender(eventBus, i2pConnector, me)
|
||||||
|
|
||||||
log.info "initializing search manager"
|
log.info "initializing search manager"
|
||||||
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
SearchManager searchManager = new SearchManager(eventBus, me, resultsSender)
|
||||||
eventBus.register(QueryEvent.class, searchManager)
|
eventBus.register(QueryEvent.class, searchManager)
|
||||||
eventBus.register(ResultsEvent.class, searchManager)
|
eventBus.register(ResultsEvent.class, searchManager)
|
||||||
|
|
||||||
log.info("initializing download manager")
|
log.info("initializing download manager")
|
||||||
downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me)
|
downloadManager = new DownloadManager(eventBus, trustService, meshManager, props, i2pConnector, home, me)
|
||||||
@@ -269,9 +273,9 @@ public class Core {
|
|||||||
log.info("initializing connection establisher")
|
log.info("initializing connection establisher")
|
||||||
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)
|
||||||
|
|
||||||
log.info("initializing acceptor")
|
log.info("initializing acceptor")
|
||||||
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
I2PAcceptor i2pAcceptor = new I2PAcceptor(socketManager)
|
||||||
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
connectionAcceptor = new ConnectionAcceptor(eventBus, connectionManager, props,
|
||||||
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
i2pAcceptor, hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||||
|
|
||||||
log.info("initializing directory watcher")
|
log.info("initializing directory watcher")
|
||||||
@@ -288,7 +292,12 @@ public class Core {
|
|||||||
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
trustSubscriber = new TrustSubscriber(eventBus, i2pConnector, props)
|
||||||
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
eventBus.register(UILoadedEvent.class, trustSubscriber)
|
||||||
eventBus.register(TrustSubscriptionEvent.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() {
|
public void startServices() {
|
||||||
hasherService.start()
|
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) {
|
static main(args) {
|
||||||
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
def home = System.getProperty("user.home") + File.separator + ".MuWire"
|
||||||
home = new File(home)
|
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.12")
|
||||||
core.startServices()
|
core.startServices()
|
||||||
|
|
||||||
// ... at the end, sleep or execute script
|
// ... at the end, sleep or execute script
|
||||||
|
@@ -4,17 +4,17 @@ import java.util.concurrent.atomic.AtomicLong
|
|||||||
|
|
||||||
class Event {
|
class Event {
|
||||||
|
|
||||||
private static final AtomicLong SEQ_NO = new AtomicLong();
|
private static final AtomicLong SEQ_NO = new AtomicLong();
|
||||||
final long seqNo
|
final long seqNo
|
||||||
final long timestamp
|
final long timestamp
|
||||||
|
|
||||||
Event() {
|
Event() {
|
||||||
seqNo = SEQ_NO.getAndIncrement()
|
seqNo = SEQ_NO.getAndIncrement()
|
||||||
timestamp = System.currentTimeMillis()
|
timestamp = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"seqNo $seqNo timestamp $timestamp"
|
"seqNo $seqNo timestamp $timestamp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,41 +11,46 @@ import groovy.util.logging.Log
|
|||||||
@Log
|
@Log
|
||||||
class EventBus {
|
class EventBus {
|
||||||
|
|
||||||
private Map handlers = new HashMap()
|
private Map handlers = new HashMap()
|
||||||
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
private final Executor executor = Executors.newSingleThreadExecutor {r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("event-bus")
|
rv.setName("event-bus")
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
void publish(Event e) {
|
void publish(Event e) {
|
||||||
executor.execute({publishInternal(e)} as Runnable)
|
executor.execute({publishInternal(e)} as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void publishInternal(Event e) {
|
private void publishInternal(Event e) {
|
||||||
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
log.fine "publishing event $e of type ${e.getClass().getSimpleName()} event $e"
|
||||||
def currentHandlers
|
def currentHandlers
|
||||||
final def clazz = e.getClass()
|
final def clazz = e.getClass()
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
currentHandlers = handlers.getOrDefault(clazz, [])
|
currentHandlers = handlers.getOrDefault(clazz, [])
|
||||||
}
|
}
|
||||||
currentHandlers.each {
|
currentHandlers.each {
|
||||||
try {
|
try {
|
||||||
it."on${clazz.getSimpleName()}"(e)
|
it."on${clazz.getSimpleName()}"(e)
|
||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.SEVERE, "exception dispatching event",bad)
|
log.log(Level.SEVERE, "exception dispatching event",bad)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void register(Class<? extends Event> eventType, def handler) {
|
synchronized void register(Class<? extends Event> eventType, def handler) {
|
||||||
log.info "Registering $handler for type $eventType"
|
log.info "Registering $handler for type $eventType"
|
||||||
def currentHandlers = handlers.get(eventType)
|
def currentHandlers = handlers.get(eventType)
|
||||||
if (currentHandlers == null) {
|
if (currentHandlers == null) {
|
||||||
currentHandlers = new CopyOnWriteArrayList()
|
currentHandlers = new CopyOnWriteArrayList()
|
||||||
handlers.put(eventType, currentHandlers)
|
handlers.put(eventType, currentHandlers)
|
||||||
}
|
}
|
||||||
currentHandlers.add handler
|
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
|
int meshExpiration
|
||||||
boolean embeddedRouter
|
boolean embeddedRouter
|
||||||
int inBw, outBw
|
int inBw, outBw
|
||||||
|
Set<String> watchedKeywords
|
||||||
|
Set<String> watchedRegexes
|
||||||
|
|
||||||
MuWireSettings() {
|
MuWireSettings() {
|
||||||
this(new Properties())
|
this(new Properties())
|
||||||
}
|
}
|
||||||
|
|
||||||
MuWireSettings(Properties props) {
|
MuWireSettings(Properties props) {
|
||||||
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
isLeaf = Boolean.valueOf(props.get("leaf","false"))
|
||||||
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
allowUntrusted = Boolean.valueOf(props.getProperty("allowUntrusted","true"))
|
||||||
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
allowTrustLists = Boolean.valueOf(props.getProperty("allowTrustLists","true"))
|
||||||
trustListInterval = Integer.valueOf(props.getProperty("trustListInterval","1"))
|
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")
|
nickname = props.getProperty("nickname","MuWireUser")
|
||||||
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
downloadLocation = new File((String)props.getProperty("downloadLocation",
|
||||||
System.getProperty("user.home")))
|
System.getProperty("user.home")))
|
||||||
@@ -54,11 +56,9 @@ class MuWireSettings {
|
|||||||
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
inBw = Integer.valueOf(props.getProperty("inBw","256"))
|
||||||
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
outBw = Integer.valueOf(props.getProperty("outBw","128"))
|
||||||
|
|
||||||
watchedDirectories = new HashSet<>()
|
watchedDirectories = readEncodedSet(props, "watchedDirectories")
|
||||||
if (props.containsKey("watchedDirectories")) {
|
watchedKeywords = readEncodedSet(props, "watchedKeywords")
|
||||||
String[] encoded = props.getProperty("watchedDirectories").split(",")
|
watchedRegexes = readEncodedSet(props, "watchedRegexes")
|
||||||
encoded.each { watchedDirectories << DataUtil.readi18nString(Base64.decode(it)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
trustSubscriptions = new HashSet<>()
|
trustSubscriptions = new HashSet<>()
|
||||||
if (props.containsKey("trustSubscriptions")) {
|
if (props.containsKey("trustSubscriptions")) {
|
||||||
@@ -66,7 +66,9 @@ class MuWireSettings {
|
|||||||
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
trustSubscriptions.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void write(OutputStream out) throws IOException {
|
void write(OutputStream out) throws IOException {
|
||||||
Properties props = new Properties()
|
Properties props = new Properties()
|
||||||
@@ -89,12 +91,9 @@ class MuWireSettings {
|
|||||||
props.setProperty("inBw", String.valueOf(inBw))
|
props.setProperty("inBw", String.valueOf(inBw))
|
||||||
props.setProperty("outBw", String.valueOf(outBw))
|
props.setProperty("outBw", String.valueOf(outBw))
|
||||||
|
|
||||||
if (!watchedDirectories.isEmpty()) {
|
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
|
||||||
String encoded = watchedDirectories.stream().
|
writeEncodedSet(watchedKeywords, "watchedKeywords", props)
|
||||||
map({Base64.encode(DataUtil.encodei18nString(it))}).
|
writeEncodedSet(watchedRegexes, "watchedRegexes", props)
|
||||||
collect(Collectors.joining(","))
|
|
||||||
props.setProperty("watchedDirectories", encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!trustSubscriptions.isEmpty()) {
|
if (!trustSubscriptions.isEmpty()) {
|
||||||
String encoded = trustSubscriptions.stream().
|
String encoded = trustSubscriptions.stream().
|
||||||
@@ -106,25 +105,43 @@ class MuWireSettings {
|
|||||||
props.store(out, "")
|
props.store(out, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isLeaf() {
|
private static Set<String> readEncodedSet(Properties props, String property) {
|
||||||
isLeaf
|
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() {
|
private static void writeEncodedSet(Set<String> set, String property, Properties props) {
|
||||||
allowUntrusted
|
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) {
|
boolean isLeaf() {
|
||||||
this.allowUntrusted = allowUntrusted
|
isLeaf
|
||||||
}
|
}
|
||||||
|
|
||||||
CrawlerResponse getCrawlerResponse() {
|
boolean allowUntrusted() {
|
||||||
crawlerResponse
|
allowUntrusted
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCrawlerResponse(CrawlerResponse crawlerResponse) {
|
void setAllowUntrusted(boolean allowUntrusted) {
|
||||||
this.crawlerResponse = crawlerResponse
|
this.allowUntrusted = allowUntrusted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CrawlerResponse getCrawlerResponse() {
|
||||||
|
crawlerResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCrawlerResponse(CrawlerResponse crawlerResponse) {
|
||||||
|
this.crawlerResponse = crawlerResponse
|
||||||
|
}
|
||||||
|
|
||||||
String getNickname() {
|
String getNickname() {
|
||||||
nickname
|
nickname
|
||||||
|
@@ -2,12 +2,12 @@ package com.muwire.core
|
|||||||
|
|
||||||
abstract class Service {
|
abstract class Service {
|
||||||
|
|
||||||
volatile boolean loaded
|
volatile boolean loaded
|
||||||
|
|
||||||
abstract void load()
|
abstract void load()
|
||||||
|
|
||||||
void waitForLoad() {
|
void waitForLoad() {
|
||||||
while (!loaded)
|
while (!loaded)
|
||||||
Thread.sleep(10)
|
Thread.sleep(10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,104 +25,104 @@ abstract class Connection implements Closeable {
|
|||||||
private static final int SEARCHES = 10
|
private static final int SEARCHES = 10
|
||||||
private static final long INTERVAL = 1000
|
private static final long INTERVAL = 1000
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final Endpoint endpoint
|
final Endpoint endpoint
|
||||||
final boolean incoming
|
final boolean incoming
|
||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
|
|
||||||
private final AtomicBoolean running = new AtomicBoolean()
|
private final AtomicBoolean running = new AtomicBoolean()
|
||||||
private final BlockingQueue messages = new LinkedBlockingQueue()
|
private final BlockingQueue messages = new LinkedBlockingQueue()
|
||||||
private final Thread reader, writer
|
private final Thread reader, writer
|
||||||
private final LinkedList<Long> searchTimestamps = new LinkedList<>()
|
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) {
|
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.incoming = incoming
|
this.incoming = incoming
|
||||||
this.endpoint = endpoint
|
this.endpoint = endpoint
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
this.settings = settings
|
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 = new Thread({readLoop()} as Runnable)
|
||||||
this.reader.setName("reader-$name")
|
this.reader.setName("reader-$name")
|
||||||
this.reader.setDaemon(true)
|
this.reader.setDaemon(true)
|
||||||
|
|
||||||
this.writer = new Thread({writeLoop()} as Runnable)
|
this.writer = new Thread({writeLoop()} as Runnable)
|
||||||
this.writer.setName("writer-$name")
|
this.writer.setName("writer-$name")
|
||||||
this.writer.setDaemon(true)
|
this.writer.setDaemon(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* starts the connection threads
|
* starts the connection threads
|
||||||
*/
|
*/
|
||||||
void start() {
|
void start() {
|
||||||
if (!running.compareAndSet(false, true)) {
|
if (!running.compareAndSet(false, true)) {
|
||||||
log.log(Level.WARNING,"$name already running", new Exception())
|
log.log(Level.WARNING,"$name already running", new Exception())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
reader.start()
|
reader.start()
|
||||||
writer.start()
|
writer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!running.compareAndSet(true, false)) {
|
if (!running.compareAndSet(true, false)) {
|
||||||
log.log(Level.WARNING, "$name already closed", new Exception() )
|
log.log(Level.WARNING, "$name already closed", new Exception() )
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.info("closing $name")
|
log.info("closing $name")
|
||||||
reader.interrupt()
|
reader.interrupt()
|
||||||
writer.interrupt()
|
writer.interrupt()
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
eventBus.publish(new DisconnectionEvent(destination: endpoint.destination))
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void readLoop() {
|
protected void readLoop() {
|
||||||
try {
|
try {
|
||||||
while(running.get()) {
|
while(running.get()) {
|
||||||
read()
|
read()
|
||||||
}
|
}
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING,"unhandled exception in reader",e)
|
log.log(Level.WARNING,"unhandled exception in reader",e)
|
||||||
} finally {
|
} finally {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void read()
|
protected abstract void read()
|
||||||
|
|
||||||
protected void writeLoop() {
|
protected void writeLoop() {
|
||||||
try {
|
try {
|
||||||
while(running.get()) {
|
while(running.get()) {
|
||||||
def message = messages.take()
|
def message = messages.take()
|
||||||
write(message)
|
write(message)
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "unhandled exception in writer",e)
|
log.log(Level.WARNING, "unhandled exception in writer",e)
|
||||||
} finally {
|
} finally {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void write(def message);
|
protected abstract void write(def message);
|
||||||
|
|
||||||
void sendPing() {
|
void sendPing() {
|
||||||
def ping = [:]
|
def ping = [:]
|
||||||
ping.type = "Ping"
|
ping.type = "Ping"
|
||||||
ping.version = 1
|
ping.version = 1
|
||||||
messages.put(ping)
|
messages.put(ping)
|
||||||
lastPingSentTime = System.currentTimeMillis()
|
lastPingSentTime = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendQuery(QueryEvent e) {
|
void sendQuery(QueryEvent e) {
|
||||||
def query = [:]
|
def query = [:]
|
||||||
@@ -140,25 +140,25 @@ abstract class Connection implements Closeable {
|
|||||||
messages.put(query)
|
messages.put(query)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handlePing() {
|
protected void handlePing() {
|
||||||
log.fine("$name received ping")
|
log.fine("$name received ping")
|
||||||
def pong = [:]
|
def pong = [:]
|
||||||
pong.type = "Pong"
|
pong.type = "Pong"
|
||||||
pong.version = 1
|
pong.version = 1
|
||||||
pong.pongs = hostCache.getGoodHosts(10).collect { d -> d.toBase64() }
|
pong.pongs = hostCache.getGoodHosts(10).collect { d -> d.toBase64() }
|
||||||
messages.put(pong)
|
messages.put(pong)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void handlePong(def pong) {
|
protected void handlePong(def pong) {
|
||||||
log.fine("$name received pong")
|
log.fine("$name received pong")
|
||||||
lastPongReceivedTime = System.currentTimeMillis()
|
lastPongReceivedTime = System.currentTimeMillis()
|
||||||
if (pong.pongs == null)
|
if (pong.pongs == null)
|
||||||
throw new Exception("Pong doesn't have pongs")
|
throw new Exception("Pong doesn't have pongs")
|
||||||
pong.pongs.each {
|
pong.pongs.each {
|
||||||
def dest = new Destination(it)
|
def dest = new Destination(it)
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: dest))
|
eventBus.publish(new HostDiscoveredEvent(destination: dest))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean throttleSearch() {
|
private boolean throttleSearch() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
|
@@ -29,97 +29,97 @@ import groovy.util.logging.Log
|
|||||||
@Log
|
@Log
|
||||||
class ConnectionAcceptor {
|
class ConnectionAcceptor {
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final UltrapeerConnectionManager manager
|
final UltrapeerConnectionManager manager
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final I2PAcceptor acceptor
|
final I2PAcceptor acceptor
|
||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final SearchManager searchManager
|
final SearchManager searchManager
|
||||||
final UploadManager uploadManager
|
final UploadManager uploadManager
|
||||||
final ConnectionEstablisher establisher
|
final ConnectionEstablisher establisher
|
||||||
|
|
||||||
final ExecutorService acceptorThread
|
final ExecutorService acceptorThread
|
||||||
final ExecutorService handshakerThreads
|
final ExecutorService handshakerThreads
|
||||||
|
|
||||||
private volatile shutdown
|
private volatile shutdown
|
||||||
|
|
||||||
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
ConnectionAcceptor(EventBus eventBus, UltrapeerConnectionManager manager,
|
||||||
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
MuWireSettings settings, I2PAcceptor acceptor, HostCache hostCache,
|
||||||
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
TrustService trustService, SearchManager searchManager, UploadManager uploadManager,
|
||||||
ConnectionEstablisher establisher) {
|
ConnectionEstablisher establisher) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.acceptor = acceptor
|
this.acceptor = acceptor
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
this.searchManager = searchManager
|
this.searchManager = searchManager
|
||||||
this.uploadManager = uploadManager
|
this.uploadManager = uploadManager
|
||||||
this.establisher = establisher
|
this.establisher = establisher
|
||||||
|
|
||||||
acceptorThread = Executors.newSingleThreadExecutor { r ->
|
acceptorThread = Executors.newSingleThreadExecutor { r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("acceptor")
|
rv.setName("acceptor")
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
handshakerThreads = Executors.newCachedThreadPool { r ->
|
handshakerThreads = Executors.newCachedThreadPool { r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("acceptor-processor-${System.currentTimeMillis()}")
|
rv.setName("acceptor-processor-${System.currentTimeMillis()}")
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
acceptorThread.execute({acceptLoop()} as Runnable)
|
acceptorThread.execute({acceptLoop()} as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
shutdown = true
|
shutdown = true
|
||||||
acceptorThread.shutdownNow()
|
acceptorThread.shutdownNow()
|
||||||
handshakerThreads.shutdownNow()
|
handshakerThreads.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acceptLoop() {
|
private void acceptLoop() {
|
||||||
try {
|
try {
|
||||||
while(true) {
|
while(true) {
|
||||||
def incoming = acceptor.accept()
|
def incoming = acceptor.accept()
|
||||||
log.info("accepted connection from ${incoming.destination.toBase32()}")
|
log.info("accepted connection from ${incoming.destination.toBase32()}")
|
||||||
switch(trustService.getLevel(incoming.destination)) {
|
switch(trustService.getLevel(incoming.destination)) {
|
||||||
case TrustLevel.TRUSTED : break
|
case TrustLevel.TRUSTED : break
|
||||||
case TrustLevel.NEUTRAL :
|
case TrustLevel.NEUTRAL :
|
||||||
if (settings.allowUntrusted())
|
if (settings.allowUntrusted())
|
||||||
break
|
break
|
||||||
case TrustLevel.DISTRUSTED :
|
case TrustLevel.DISTRUSTED :
|
||||||
log.info("Disallowing distrusted connection")
|
log.info("Disallowing distrusted connection")
|
||||||
incoming.close()
|
incoming.close()
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
handshakerThreads.execute({processIncoming(incoming)} as Runnable)
|
handshakerThreads.execute({processIncoming(incoming)} as Runnable)
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "exception in accept loop",e)
|
log.log(Level.WARNING, "exception in accept loop",e)
|
||||||
if (!shutdown)
|
if (!shutdown)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processIncoming(Endpoint e) {
|
private void processIncoming(Endpoint e) {
|
||||||
InputStream is = e.inputStream
|
InputStream is = e.inputStream
|
||||||
try {
|
try {
|
||||||
int read = is.read()
|
int read = is.read()
|
||||||
switch(read) {
|
switch(read) {
|
||||||
case (byte)'M':
|
case (byte)'M':
|
||||||
if (settings.isLeaf())
|
if (settings.isLeaf())
|
||||||
throw new IOException("Incoming connection as leaf")
|
throw new IOException("Incoming connection as leaf")
|
||||||
processMuWire(e)
|
processMuWire(e)
|
||||||
break
|
break
|
||||||
case (byte)'G':
|
case (byte)'G':
|
||||||
processGET(e)
|
processGET(e)
|
||||||
break
|
break
|
||||||
case (byte)'H':
|
case (byte)'H':
|
||||||
processHashList(e)
|
processHashList(e)
|
||||||
break
|
break
|
||||||
@@ -129,28 +129,28 @@ class ConnectionAcceptor {
|
|||||||
case (byte)'T':
|
case (byte)'T':
|
||||||
processTRUST(e)
|
processTRUST(e)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
throw new Exception("Invalid read $read")
|
throw new Exception("Invalid read $read")
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.log(Level.WARNING, "incoming connection failed",ex)
|
log.log(Level.WARNING, "incoming connection failed",ex)
|
||||||
e.close()
|
e.close()
|
||||||
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
eventBus.publish new ConnectionEvent(endpoint: e, incoming: true, leaf: null, status: ConnectionAttemptStatus.FAILED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processMuWire(Endpoint e) {
|
private void processMuWire(Endpoint e) {
|
||||||
byte[] uWire = "uWire ".bytes
|
byte[] uWire = "uWire ".bytes
|
||||||
for (int i = 0; i < uWire.length; i++) {
|
for (int i = 0; i < uWire.length; i++) {
|
||||||
int read = e.inputStream.read()
|
int read = e.inputStream.read()
|
||||||
if (read != uWire[i]) {
|
if (read != uWire[i]) {
|
||||||
throw new IOException("unexpected value $read at position $i")
|
throw new IOException("unexpected value $read at position $i")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] type = new byte[4]
|
byte[] type = new byte[4]
|
||||||
DataInputStream dis = new DataInputStream(e.inputStream)
|
DataInputStream dis = new DataInputStream(e.inputStream)
|
||||||
dis.readFully(type)
|
dis.readFully(type)
|
||||||
|
|
||||||
if (type == "leaf".bytes)
|
if (type == "leaf".bytes)
|
||||||
handleIncoming(e, true)
|
handleIncoming(e, true)
|
||||||
@@ -160,44 +160,44 @@ class ConnectionAcceptor {
|
|||||||
throw new IOException("unknown connection type $type")
|
throw new IOException("unknown connection type $type")
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIncoming(Endpoint e, boolean leaf) {
|
private void handleIncoming(Endpoint e, boolean leaf) {
|
||||||
boolean accept = !manager.isConnected(e.destination) &&
|
boolean accept = !manager.isConnected(e.destination) &&
|
||||||
!establisher.isInProgress(e.destination) &&
|
!establisher.isInProgress(e.destination) &&
|
||||||
(leaf ? manager.hasLeafSlots() : manager.hasPeerSlots())
|
(leaf ? manager.hasLeafSlots() : manager.hasPeerSlots())
|
||||||
if (accept) {
|
if (accept) {
|
||||||
log.info("accepting connection, leaf:$leaf")
|
log.info("accepting connection, leaf:$leaf")
|
||||||
e.outputStream.write("OK".bytes)
|
e.outputStream.write("OK".bytes)
|
||||||
e.outputStream.flush()
|
e.outputStream.flush()
|
||||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
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))
|
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
} else {
|
} else {
|
||||||
log.info("rejecting connection, leaf:$leaf")
|
log.info("rejecting connection, leaf:$leaf")
|
||||||
e.outputStream.write("REJECT".bytes)
|
e.outputStream.write("REJECT".bytes)
|
||||||
def hosts = hostCache.getGoodHosts(10)
|
def hosts = hostCache.getGoodHosts(10)
|
||||||
if (!hosts.isEmpty()) {
|
if (!hosts.isEmpty()) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.tryHosts = hosts.collect { d -> d.toBase64() }
|
json.tryHosts = hosts.collect { d -> d.toBase64() }
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
def os = new DataOutputStream(e.outputStream)
|
def os = new DataOutputStream(e.outputStream)
|
||||||
os.writeShort(json.bytes.length)
|
os.writeShort(json.bytes.length)
|
||||||
os.write(json.bytes)
|
os.write(json.bytes)
|
||||||
}
|
}
|
||||||
e.outputStream.flush()
|
e.outputStream.flush()
|
||||||
e.close()
|
e.close()
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: true, leaf: leaf, status: ConnectionAttemptStatus.REJECTED))
|
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]
|
byte[] et = new byte[3]
|
||||||
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
final DataInputStream dis = new DataInputStream(e.getInputStream())
|
||||||
dis.readFully(et)
|
dis.readFully(et)
|
||||||
if (et != "ET ".getBytes(StandardCharsets.US_ASCII))
|
if (et != "ET ".getBytes(StandardCharsets.US_ASCII))
|
||||||
throw new IOException("Invalid GET connection")
|
throw new IOException("Invalid GET connection")
|
||||||
uploadManager.processGET(e)
|
uploadManager.processGET(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processHashList(Endpoint e) {
|
private void processHashList(Endpoint e) {
|
||||||
byte[] ashList = new byte[8]
|
byte[] ashList = new byte[8]
|
||||||
|
@@ -22,162 +22,162 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
@Log
|
@Log
|
||||||
class ConnectionEstablisher {
|
class ConnectionEstablisher {
|
||||||
|
|
||||||
private static final int CONCURRENT = 4
|
private static final int CONCURRENT = 4
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final I2PConnector i2pConnector
|
final I2PConnector i2pConnector
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final ConnectionManager connectionManager
|
final ConnectionManager connectionManager
|
||||||
final HostCache hostCache
|
final HostCache hostCache
|
||||||
|
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final ExecutorService executor
|
final ExecutorService executor
|
||||||
|
|
||||||
final Set inProgress = new ConcurrentHashSet()
|
final Set inProgress = new ConcurrentHashSet()
|
||||||
|
|
||||||
ConnectionEstablisher(){}
|
ConnectionEstablisher(){}
|
||||||
|
|
||||||
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
ConnectionEstablisher(EventBus eventBus, I2PConnector i2pConnector, MuWireSettings settings,
|
||||||
ConnectionManager connectionManager, HostCache hostCache) {
|
ConnectionManager connectionManager, HostCache hostCache) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.i2pConnector = i2pConnector
|
this.i2pConnector = i2pConnector
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.connectionManager = connectionManager
|
this.connectionManager = connectionManager
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
timer = new Timer("connection-timer",true)
|
timer = new Timer("connection-timer",true)
|
||||||
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
executor = Executors.newFixedThreadPool(CONCURRENT, { r ->
|
||||||
def rv = new Thread(r)
|
def rv = new Thread(r)
|
||||||
rv.setDaemon(true)
|
rv.setDaemon(true)
|
||||||
rv.setName("connector-${System.currentTimeMillis()}")
|
rv.setName("connector-${System.currentTimeMillis()}")
|
||||||
rv
|
rv
|
||||||
} as ThreadFactory)
|
} as ThreadFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
timer.schedule({connectIfNeeded()} as TimerTask, 100, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
executor.shutdownNow()
|
executor.shutdownNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectIfNeeded() {
|
private void connectIfNeeded() {
|
||||||
if (!connectionManager.needsConnections())
|
if (!connectionManager.needsConnections())
|
||||||
return
|
return
|
||||||
if (inProgress.size() >= CONCURRENT)
|
if (inProgress.size() >= CONCURRENT)
|
||||||
return
|
return
|
||||||
|
|
||||||
def toTry = null
|
def toTry = null
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
toTry = hostCache.getHosts(1)
|
toTry = hostCache.getHosts(1)
|
||||||
if (toTry.isEmpty())
|
if (toTry.isEmpty())
|
||||||
return
|
return
|
||||||
toTry = toTry[0]
|
toTry = toTry[0]
|
||||||
if (!connectionManager.isConnected(toTry) &&
|
if (!connectionManager.isConnected(toTry) &&
|
||||||
!inProgress.contains(toTry)) {
|
!inProgress.contains(toTry)) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toTry == null)
|
if (toTry == null)
|
||||||
return
|
return
|
||||||
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
if (!connectionManager.isConnected(toTry) && inProgress.add(toTry))
|
||||||
executor.execute({connect(toTry)} as Runnable)
|
executor.execute({connect(toTry)} as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connect(Destination toTry) {
|
private void connect(Destination toTry) {
|
||||||
log.info("starting connect to ${toTry.toBase32()}")
|
log.info("starting connect to ${toTry.toBase32()}")
|
||||||
try {
|
try {
|
||||||
def endpoint = i2pConnector.connect(toTry)
|
def endpoint = i2pConnector.connect(toTry)
|
||||||
log.info("successful transport connect to ${toTry.toBase32()}")
|
log.info("successful transport connect to ${toTry.toBase32()}")
|
||||||
|
|
||||||
// outgoing handshake
|
// outgoing handshake
|
||||||
endpoint.outputStream.write("MuWire ".bytes)
|
endpoint.outputStream.write("MuWire ".bytes)
|
||||||
def type = settings.isLeaf() ? "leaf" : "peer"
|
def type = settings.isLeaf() ? "leaf" : "peer"
|
||||||
endpoint.outputStream.write(type.bytes)
|
endpoint.outputStream.write(type.bytes)
|
||||||
endpoint.outputStream.flush()
|
endpoint.outputStream.flush()
|
||||||
|
|
||||||
InputStream is = endpoint.inputStream
|
InputStream is = endpoint.inputStream
|
||||||
int read = is.read()
|
int read = is.read()
|
||||||
if (read == -1) {
|
if (read == -1) {
|
||||||
fail endpoint
|
fail endpoint
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch(read) {
|
switch(read) {
|
||||||
case (byte)'O': readK(endpoint); break
|
case (byte)'O': readK(endpoint); break
|
||||||
case (byte)'R': readEJECT(endpoint); break
|
case (byte)'R': readEJECT(endpoint); break
|
||||||
default :
|
default :
|
||||||
log.warning("unknown response $read")
|
log.warning("unknown response $read")
|
||||||
fail endpoint
|
fail endpoint
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
log.log(Level.WARNING, "Couldn't connect to ${toTry.toBase32()}", e)
|
||||||
def endpoint = new Endpoint(toTry, null, null, null)
|
def endpoint = new Endpoint(toTry, null, null, null)
|
||||||
fail(endpoint)
|
fail(endpoint)
|
||||||
} finally {
|
} finally {
|
||||||
inProgress.remove(toTry)
|
inProgress.remove(toTry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fail(Endpoint endpoint) {
|
private void fail(Endpoint endpoint) {
|
||||||
endpoint.close()
|
endpoint.close()
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
eventBus.publish(new ConnectionEvent(endpoint: endpoint, incoming: false, leaf: false, status: ConnectionAttemptStatus.FAILED))
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readK(Endpoint e) {
|
private void readK(Endpoint e) {
|
||||||
int read = e.inputStream.read()
|
int read = e.inputStream.read()
|
||||||
if (read != 'K') {
|
if (read != 'K') {
|
||||||
log.warning("unknown response after O: $read")
|
log.warning("unknown response after O: $read")
|
||||||
fail e
|
fail e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("connection to ${e.destination.toBase32()} established")
|
log.info("connection to ${e.destination.toBase32()} established")
|
||||||
|
|
||||||
// wrap into deflater / inflater streams and publish
|
// wrap into deflater / inflater streams and publish
|
||||||
def wrapped = new Endpoint(e.destination, new InflaterInputStream(e.inputStream), new DeflaterOutputStream(e.outputStream, true), e.toClose)
|
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))
|
eventBus.publish(new ConnectionEvent(endpoint: wrapped, incoming: false, leaf: false, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readEJECT(Endpoint e) {
|
private void readEJECT(Endpoint e) {
|
||||||
byte[] eject = "EJECT".bytes
|
byte[] eject = "EJECT".bytes
|
||||||
for (int i = 0; i < eject.length; i++) {
|
for (int i = 0; i < eject.length; i++) {
|
||||||
int read = e.inputStream.read()
|
int read = e.inputStream.read()
|
||||||
if (read != eject[i]) {
|
if (read != eject[i]) {
|
||||||
log.warning("Unknown response after R at position $i")
|
log.warning("Unknown response after R at position $i")
|
||||||
fail e
|
fail e
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("connection to ${e.destination.toBase32()} rejected")
|
log.info("connection to ${e.destination.toBase32()} rejected")
|
||||||
|
|
||||||
|
|
||||||
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
eventBus.publish(new ConnectionEvent(endpoint: e, incoming: false, leaf: false, status: ConnectionAttemptStatus.REJECTED))
|
||||||
try {
|
try {
|
||||||
DataInputStream dais = new DataInputStream(e.inputStream)
|
DataInputStream dais = new DataInputStream(e.inputStream)
|
||||||
int payloadSize = dais.readUnsignedShort()
|
int payloadSize = dais.readUnsignedShort()
|
||||||
byte[] payload = new byte[payloadSize]
|
byte[] payload = new byte[payloadSize]
|
||||||
dais.readFully(payload)
|
dais.readFully(payload)
|
||||||
|
|
||||||
def json = new JsonSlurper()
|
def json = new JsonSlurper()
|
||||||
json = json.parse(payload)
|
json = json.parse(payload)
|
||||||
|
|
||||||
if (json.tryHosts == null) {
|
if (json.tryHosts == null) {
|
||||||
log.warning("post-rejection json didn't contain hosts to try")
|
log.warning("post-rejection json didn't contain hosts to try")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
json.tryHosts.asList().each {
|
json.tryHosts.asList().each {
|
||||||
Destination suggested = new Destination(it)
|
Destination suggested = new Destination(it)
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
eventBus.publish(new HostDiscoveredEvent(destination: suggested))
|
||||||
}
|
}
|
||||||
} catch (Exception ignore) {
|
} catch (Exception ignore) {
|
||||||
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
log.log(Level.WARNING,"Problem parsing post-rejection payload",ignore)
|
||||||
} finally {
|
} finally {
|
||||||
// the end
|
// the end
|
||||||
e.close()
|
e.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInProgress(Destination d) {
|
public boolean isInProgress(Destination d) {
|
||||||
inProgress.contains(d)
|
inProgress.contains(d)
|
||||||
|
@@ -6,14 +6,14 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class ConnectionEvent extends Event {
|
class ConnectionEvent extends Event {
|
||||||
|
|
||||||
Endpoint endpoint
|
Endpoint endpoint
|
||||||
boolean incoming
|
boolean incoming
|
||||||
Boolean leaf // can be null if uknown
|
Boolean leaf // can be null if uknown
|
||||||
ConnectionAttemptStatus status
|
ConnectionAttemptStatus status
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
"ConnectionEvent ${super.toString()} endpoint: $endpoint incoming: $incoming leaf : $leaf status : $status"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -12,63 +12,63 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
abstract class ConnectionManager {
|
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 Persona me
|
||||||
protected final MuWireSettings settings
|
protected final MuWireSettings settings
|
||||||
|
|
||||||
ConnectionManager() {}
|
ConnectionManager() {}
|
||||||
|
|
||||||
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
ConnectionManager(EventBus eventBus, Persona me, HostCache hostCache, MuWireSettings settings) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.me = me
|
this.me = me
|
||||||
this.hostCache = hostCache
|
this.hostCache = hostCache
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.timer = new Timer("connections-pinger",true)
|
this.timer = new Timer("connections-pinger",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
timer.schedule({sendPings()} as TimerTask, 1000,1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
getConnections().each { it.close() }
|
getConnections().each { it.close() }
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustEvent(TrustEvent e) {
|
void onTrustEvent(TrustEvent e) {
|
||||||
if (e.level == TrustLevel.DISTRUSTED)
|
if (e.level == TrustLevel.DISTRUSTED)
|
||||||
drop(e.persona.destination)
|
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() {
|
boolean needsConnections() {
|
||||||
return getConnections().size() < getDesiredConnections()
|
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()
|
abstract void shutdown()
|
||||||
|
|
||||||
protected void sendPings() {
|
protected void sendPings() {
|
||||||
final long now = System.currentTimeMillis()
|
final long now = System.currentTimeMillis()
|
||||||
getConnections().each {
|
getConnections().each {
|
||||||
if (now - it.lastPingSentTime > PING_TIME)
|
if (now - it.lastPingSentTime > PING_TIME)
|
||||||
it.sendPing()
|
it.sendPing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,10 +6,10 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class DisconnectionEvent extends Event {
|
class DisconnectionEvent extends Event {
|
||||||
|
|
||||||
Destination destination
|
Destination destination
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
"DisconnectionEvent ${super.toString()} destination:${destination.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,39 +8,39 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
@Log
|
@Log
|
||||||
class Endpoint implements Closeable {
|
class Endpoint implements Closeable {
|
||||||
final Destination destination
|
final Destination destination
|
||||||
final InputStream inputStream
|
final InputStream inputStream
|
||||||
final OutputStream outputStream
|
final OutputStream outputStream
|
||||||
final def toClose
|
final def toClose
|
||||||
|
|
||||||
private final AtomicBoolean closed = new AtomicBoolean()
|
private final AtomicBoolean closed = new AtomicBoolean()
|
||||||
|
|
||||||
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
Endpoint(Destination destination, InputStream inputStream, OutputStream outputStream, def toClose) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
this.inputStream = inputStream
|
this.inputStream = inputStream
|
||||||
this.outputStream = outputStream
|
this.outputStream = outputStream
|
||||||
this.toClose = toClose
|
this.toClose = toClose
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
if (!closed.compareAndSet(false, true)) {
|
if (!closed.compareAndSet(false, true)) {
|
||||||
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
log.log(Level.WARNING,"Close loop detected for ${destination.toBase32()}", new Exception())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
try {inputStream.close()} catch (Exception ignore) {}
|
try {inputStream.close()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
if (outputStream != null) {
|
if (outputStream != null) {
|
||||||
try {outputStream.close()} catch (Exception ignore) {}
|
try {outputStream.close()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
if (toClose != null) {
|
if (toClose != null) {
|
||||||
try {toClose.reset()} catch (Exception ignore) {}
|
try {toClose.reset()} catch (Exception ignore) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"destination: ${destination.toBase32()}"
|
"destination: ${destination.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,18 +5,18 @@ import net.i2p.client.streaming.I2PSocketManager
|
|||||||
|
|
||||||
class I2PAcceptor {
|
class I2PAcceptor {
|
||||||
|
|
||||||
final I2PSocketManager socketManager
|
final I2PSocketManager socketManager
|
||||||
final I2PServerSocket serverSocket
|
final I2PServerSocket serverSocket
|
||||||
|
|
||||||
I2PAcceptor() {}
|
I2PAcceptor() {}
|
||||||
|
|
||||||
I2PAcceptor(I2PSocketManager socketManager) {
|
I2PAcceptor(I2PSocketManager socketManager) {
|
||||||
this.socketManager = socketManager
|
this.socketManager = socketManager
|
||||||
this.serverSocket = socketManager.getServerSocket()
|
this.serverSocket = socketManager.getServerSocket()
|
||||||
}
|
}
|
||||||
|
|
||||||
Endpoint accept() {
|
Endpoint accept() {
|
||||||
def socket = serverSocket.accept()
|
def socket = serverSocket.accept()
|
||||||
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
new Endpoint(socket.getPeerDestination(), socket.getInputStream(), socket.getOutputStream(), socket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,17 +5,17 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class I2PConnector {
|
class I2PConnector {
|
||||||
|
|
||||||
final I2PSocketManager socketManager
|
final I2PSocketManager socketManager
|
||||||
|
|
||||||
I2PConnector() {}
|
I2PConnector() {}
|
||||||
|
|
||||||
I2PConnector(I2PSocketManager socketManager) {
|
I2PConnector(I2PSocketManager socketManager) {
|
||||||
this.socketManager = socketManager
|
this.socketManager = socketManager
|
||||||
}
|
}
|
||||||
|
|
||||||
Endpoint connect(Destination dest) {
|
Endpoint connect(Destination dest) {
|
||||||
def socket = socketManager.connect(dest)
|
def socket = socketManager.connect(dest)
|
||||||
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
new Endpoint(dest, socket.getInputStream(), socket.getOutputStream(), socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,21 +17,21 @@ import net.i2p.data.Destination
|
|||||||
*/
|
*/
|
||||||
class LeafConnection extends Connection {
|
class LeafConnection extends Connection {
|
||||||
|
|
||||||
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
public LeafConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache,
|
||||||
TrustService trustService, MuWireSettings settings) {
|
TrustService trustService, MuWireSettings settings) {
|
||||||
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
super(eventBus, endpoint, true, hostCache, trustService, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -14,21 +14,21 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class LeafConnectionManager extends ConnectionManager {
|
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) {
|
HostCache hostCache, MuWireSettings settings) {
|
||||||
super(eventBus, me, hostCache, settings)
|
super(eventBus, me, hostCache, settings)
|
||||||
this.maxConnections = maxConnections
|
this.maxConnections = maxConnections
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drop(Destination d) {
|
public void drop(Destination d) {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
if (me.destination == e.receivedOn) {
|
if (me.destination == e.receivedOn) {
|
||||||
@@ -37,41 +37,41 @@ class LeafConnectionManager extends ConnectionManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Connection> getConnections() {
|
public Collection<Connection> getConnections() {
|
||||||
connections.values()
|
connections.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDesiredConnections() {
|
protected int getDesiredConnections() {
|
||||||
return maxConnections;
|
return maxConnections;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected(Destination d) {
|
public boolean isConnected(Destination d) {
|
||||||
connections.containsKey(d)
|
connections.containsKey(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
if (e.incoming || e.leaf) {
|
if (e.incoming || e.leaf) {
|
||||||
log.severe("Got inconsistent event as a leaf! $e")
|
log.severe("Got inconsistent event as a leaf! $e")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||||
return
|
return
|
||||||
|
|
||||||
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
Connection c = new UltrapeerConnection(eventBus, e.endpoint)
|
||||||
connections.put(e.endpoint.destination, c)
|
connections.put(e.endpoint.destination, c)
|
||||||
c.start()
|
c.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
def removed = connections.remove(e.destination)
|
def removed = connections.remove(e.destination)
|
||||||
if (removed == null)
|
if (removed == null)
|
||||||
log.severe("removed destination not present in connection manager ${e.destination.toBase32()}")
|
log.severe("removed destination not present in connection manager ${e.destination.toBase32()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
|
@@ -21,62 +21,62 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class PeerConnection extends Connection {
|
class PeerConnection extends Connection {
|
||||||
|
|
||||||
private final DataInputStream dis
|
private final DataInputStream dis
|
||||||
private final DataOutputStream dos
|
private final DataOutputStream dos
|
||||||
|
|
||||||
private final byte[] readHeader = new byte[3]
|
private final byte[] readHeader = new byte[3]
|
||||||
private final byte[] writeHeader = 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,
|
public PeerConnection(EventBus eventBus, Endpoint endpoint,
|
||||||
boolean incoming, HostCache hostCache, TrustService trustService,
|
boolean incoming, HostCache hostCache, TrustService trustService,
|
||||||
MuWireSettings settings) {
|
MuWireSettings settings) {
|
||||||
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
super(eventBus, endpoint, incoming, hostCache, trustService, settings)
|
||||||
this.dis = new DataInputStream(endpoint.inputStream)
|
this.dis = new DataInputStream(endpoint.inputStream)
|
||||||
this.dos = new DataOutputStream(endpoint.outputStream)
|
this.dos = new DataOutputStream(endpoint.outputStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
dis.readFully(readHeader)
|
dis.readFully(readHeader)
|
||||||
int length = DataUtil.readLength(readHeader)
|
int length = DataUtil.readLength(readHeader)
|
||||||
log.fine("$name read length $length")
|
log.fine("$name read length $length")
|
||||||
|
|
||||||
byte[] payload = new byte[length]
|
byte[] payload = new byte[length]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
|
|
||||||
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
if ((readHeader[0] & (byte)0x80) == 0x80) {
|
||||||
// TODO process binary
|
// TODO process binary
|
||||||
} else {
|
} else {
|
||||||
def json = slurper.parse(payload)
|
def json = slurper.parse(payload)
|
||||||
if (json.type == null)
|
if (json.type == null)
|
||||||
throw new Exception("missing json type")
|
throw new Exception("missing json type")
|
||||||
switch(json.type) {
|
switch(json.type) {
|
||||||
case "Ping" : handlePing(); break;
|
case "Ping" : handlePing(); break;
|
||||||
case "Pong" : handlePong(json); break;
|
case "Pong" : handlePong(json); break;
|
||||||
case "Search": handleSearch(json); break
|
case "Search": handleSearch(json); break
|
||||||
default :
|
default :
|
||||||
throw new Exception("unknown json type ${json.type}")
|
throw new Exception("unknown json type ${json.type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
byte[] payload
|
byte[] payload
|
||||||
if (message instanceof Map) {
|
if (message instanceof Map) {
|
||||||
payload = JsonOutput.toJson(message).bytes
|
payload = JsonOutput.toJson(message).bytes
|
||||||
DataUtil.packHeader(payload.length, writeHeader)
|
DataUtil.packHeader(payload.length, writeHeader)
|
||||||
log.fine "$name writing message type ${message.type} length $payload.length"
|
log.fine "$name writing message type ${message.type} length $payload.length"
|
||||||
writeHeader[0] &= (byte)0x7F
|
writeHeader[0] &= (byte)0x7F
|
||||||
} else {
|
} else {
|
||||||
// TODO: write binary
|
// TODO: write binary
|
||||||
}
|
}
|
||||||
|
|
||||||
dos.write(writeHeader)
|
dos.write(writeHeader)
|
||||||
dos.write(payload)
|
dos.write(payload)
|
||||||
dos.flush()
|
dos.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -17,30 +17,30 @@ import net.i2p.data.Destination
|
|||||||
*/
|
*/
|
||||||
class UltrapeerConnection extends Connection {
|
class UltrapeerConnection extends Connection {
|
||||||
|
|
||||||
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
public UltrapeerConnection(EventBus eventBus, Endpoint endpoint, HostCache hostCache, TrustService trustService) {
|
||||||
super(eventBus, endpoint, false, hostCache, trustService)
|
super(eventBus, endpoint, false, hostCache, trustService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read() {
|
protected void read() {
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void write(Object message) {
|
protected void write(Object message) {
|
||||||
if (message instanceof Map) {
|
if (message instanceof Map) {
|
||||||
writeJsonMessage(message)
|
writeJsonMessage(message)
|
||||||
} else {
|
} else {
|
||||||
writeBinaryMessage(message)
|
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
|
@Log
|
||||||
class UltrapeerConnectionManager extends ConnectionManager {
|
class UltrapeerConnectionManager extends ConnectionManager {
|
||||||
|
|
||||||
final int maxPeers, maxLeafs
|
final int maxPeers, maxLeafs
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
|
|
||||||
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
final Map<Destination, PeerConnection> peerConnections = new ConcurrentHashMap()
|
||||||
final Map<Destination, LeafConnection> leafConnections = 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) {
|
HostCache hostCache, TrustService trustService, MuWireSettings settings) {
|
||||||
super(eventBus, me, hostCache, settings)
|
super(eventBus, me, hostCache, settings)
|
||||||
this.maxPeers = maxPeers
|
this.maxPeers = maxPeers
|
||||||
this.maxLeafs = maxLeafs
|
this.maxLeafs = maxLeafs
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void drop(Destination d) {
|
public void drop(Destination d) {
|
||||||
peerConnections.get(d)?.close()
|
peerConnections.get(d)?.close()
|
||||||
leafConnections.get(d)?.close()
|
leafConnections.get(d)?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
forwardQueryToLeafs(e)
|
forwardQueryToLeafs(e)
|
||||||
@@ -50,57 +50,57 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Connection> getConnections() {
|
public Collection<Connection> getConnections() {
|
||||||
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
def rv = new ArrayList(peerConnections.size() + leafConnections.size())
|
||||||
rv.addAll(peerConnections.values())
|
rv.addAll(peerConnections.values())
|
||||||
rv.addAll(leafConnections.values())
|
rv.addAll(leafConnections.values())
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasLeafSlots() {
|
boolean hasLeafSlots() {
|
||||||
leafConnections.size() < maxLeafs
|
leafConnections.size() < maxLeafs
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasPeerSlots() {
|
boolean hasPeerSlots() {
|
||||||
peerConnections.size() < maxPeers
|
peerConnections.size() < maxPeers
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected int getDesiredConnections() {
|
protected int getDesiredConnections() {
|
||||||
return maxPeers / 2;
|
return maxPeers / 2;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected(Destination d) {
|
public boolean isConnected(Destination d) {
|
||||||
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
peerConnections.containsKey(d) || leafConnections.containsKey(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionEvent(ConnectionEvent e) {
|
public void onConnectionEvent(ConnectionEvent e) {
|
||||||
if (!e.incoming && e.leaf) {
|
if (!e.incoming && e.leaf) {
|
||||||
log.severe("Inconsistent event $e")
|
log.severe("Inconsistent event $e")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
if (e.status != ConnectionAttemptStatus.SUCCESSFUL)
|
||||||
return
|
return
|
||||||
|
|
||||||
Connection c = e.leaf ?
|
Connection c = e.leaf ?
|
||||||
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
new LeafConnection(eventBus, e.endpoint, hostCache, trustService, settings) :
|
||||||
new PeerConnection(eventBus, e.endpoint, e.incoming, hostCache, trustService, settings)
|
new PeerConnection(eventBus, e.endpoint, e.incoming, hostCache, trustService, settings)
|
||||||
def map = e.leaf ? leafConnections : peerConnections
|
def map = e.leaf ? leafConnections : peerConnections
|
||||||
map.put(e.endpoint.destination, c)
|
map.put(e.endpoint.destination, c)
|
||||||
c.start()
|
c.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnectionEvent(DisconnectionEvent e) {
|
public void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
def removed = peerConnections.remove(e.destination)
|
def removed = peerConnections.remove(e.destination)
|
||||||
if (removed == null)
|
if (removed == null)
|
||||||
removed = leafConnections.remove(e.destination)
|
removed = leafConnections.remove(e.destination)
|
||||||
if (removed == null)
|
if (removed == null)
|
||||||
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
log.severe("Removed connection not present in either leaf or peer map ${e.destination.toBase32()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
@@ -110,7 +110,7 @@ class UltrapeerConnectionManager extends ConnectionManager {
|
|||||||
leafConnections.clear()
|
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 groovy.util.logging.Log
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.MappedByteBuffer
|
||||||
import java.nio.channels.FileChannel
|
import java.nio.channels.FileChannel
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
@@ -39,7 +40,7 @@ class DownloadSession {
|
|||||||
private long lastSpeedRead = System.currentTimeMillis()
|
private long lastSpeedRead = System.currentTimeMillis()
|
||||||
private long dataSinceLastRead
|
private long dataSinceLastRead
|
||||||
|
|
||||||
private ByteBuffer mapped
|
private MappedByteBuffer mapped
|
||||||
|
|
||||||
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
DownloadSession(EventBus eventBus, String meB64, Pieces pieces, InfoHash infoHash, Endpoint endpoint, File file,
|
||||||
int pieceSize, long fileLength, Set<Integer> available) {
|
int pieceSize, long fileLength, Set<Integer> available) {
|
||||||
@@ -69,21 +70,23 @@ class DownloadSession {
|
|||||||
OutputStream os = endpoint.getOutputStream()
|
OutputStream os = endpoint.getOutputStream()
|
||||||
InputStream is = endpoint.getInputStream()
|
InputStream is = endpoint.getInputStream()
|
||||||
|
|
||||||
int piece
|
int[] pieceAndPosition
|
||||||
if (available.isEmpty())
|
if (available.isEmpty())
|
||||||
piece = pieces.claim()
|
pieceAndPosition = pieces.claim()
|
||||||
else
|
else
|
||||||
piece = pieces.claim(new HashSet<>(available))
|
pieceAndPosition = pieces.claim(new HashSet<>(available))
|
||||||
if (piece == -1)
|
if (pieceAndPosition == null)
|
||||||
return false
|
return false
|
||||||
|
int piece = pieceAndPosition[0]
|
||||||
|
int position = pieceAndPosition[1]
|
||||||
|
boolean steal = pieceAndPosition[2] == 1
|
||||||
boolean unclaim = true
|
boolean unclaim = true
|
||||||
|
|
||||||
log.info("will download piece $piece")
|
log.info("will download piece $piece from position $position steal $steal")
|
||||||
|
|
||||||
long start = piece * pieceSize
|
|
||||||
long end = Math.min(fileLength, start + pieceSize) - 1
|
|
||||||
long length = end - start + 1
|
|
||||||
|
|
||||||
|
long pieceStart = piece * ((long)pieceSize)
|
||||||
|
long end = Math.min(fileLength, pieceStart + pieceSize) - 1
|
||||||
|
long start = pieceStart + position
|
||||||
String root = Base64.encode(infoHash.getRoot())
|
String root = Base64.encode(infoHash.getRoot())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -172,8 +175,9 @@ class DownloadSession {
|
|||||||
FileChannel channel
|
FileChannel channel
|
||||||
try {
|
try {
|
||||||
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
channel = Files.newByteChannel(file.toPath(), EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE,
|
||||||
StandardOpenOption.SPARSE, StandardOpenOption.CREATE)) // TODO: double-check, maybe CREATE_NEW
|
StandardOpenOption.SPARSE, StandardOpenOption.CREATE))
|
||||||
mapped = channel.map(FileChannel.MapMode.READ_WRITE, start, end - start + 1)
|
mapped = channel.map(FileChannel.MapMode.READ_WRITE, pieceStart, end - pieceStart + 1)
|
||||||
|
mapped.position(position)
|
||||||
|
|
||||||
byte[] tmp = new byte[0x1 << 13]
|
byte[] tmp = new byte[0x1 << 13]
|
||||||
while(mapped.hasRemaining()) {
|
while(mapped.hasRemaining()) {
|
||||||
@@ -185,24 +189,27 @@ class DownloadSession {
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
mapped.put(tmp, 0, read)
|
mapped.put(tmp, 0, read)
|
||||||
dataSinceLastRead += read
|
dataSinceLastRead += read
|
||||||
|
pieces.markPartial(piece, mapped.position())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mapped.clear()
|
mapped.clear()
|
||||||
digest.update(mapped)
|
digest.update(mapped)
|
||||||
DataUtil.tryUnmap(mapped)
|
|
||||||
byte [] hash = digest.digest()
|
byte [] hash = digest.digest()
|
||||||
byte [] expected = new byte[32]
|
byte [] expected = new byte[32]
|
||||||
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
System.arraycopy(infoHash.getHashList(), piece * 32, expected, 0, 32)
|
||||||
if (hash != expected)
|
if (hash != expected) {
|
||||||
throw new BadHashException()
|
pieces.markPartial(piece, 0)
|
||||||
|
throw new BadHashException("bad hash on piece $piece")
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try { channel?.close() } catch (IOException ignore) {}
|
try { channel?.close() } catch (IOException ignore) {}
|
||||||
|
DataUtil.tryUnmap(mapped)
|
||||||
}
|
}
|
||||||
pieces.markDownloaded(piece)
|
pieces.markDownloaded(piece)
|
||||||
unclaim = false
|
unclaim = false
|
||||||
} finally {
|
} finally {
|
||||||
if (unclaim)
|
if (unclaim && !steal)
|
||||||
pieces.unclaim(piece)
|
pieces.unclaim(piece)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@@ -111,8 +111,14 @@ public class Downloader {
|
|||||||
if (!piecesFile.exists())
|
if (!piecesFile.exists())
|
||||||
return
|
return
|
||||||
piecesFile.eachLine {
|
piecesFile.eachLine {
|
||||||
int piece = Integer.parseInt(it)
|
String [] split = it.split(",")
|
||||||
pieces.markDownloaded(piece)
|
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)
|
if (piecesFileClosed)
|
||||||
return
|
return
|
||||||
piecesFile.withPrintWriter { writer ->
|
piecesFile.withPrintWriter { writer ->
|
||||||
pieces.getDownloaded().each { piece ->
|
pieces.write(writer)
|
||||||
writer.println(piece)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,12 +307,17 @@ public class Downloader {
|
|||||||
} catch (Exception bad) {
|
} catch (Exception bad) {
|
||||||
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
|
log.log(Level.WARNING,"Exception while downloading",DataUtil.findRoot(bad))
|
||||||
} finally {
|
} finally {
|
||||||
|
writePieces()
|
||||||
currentState = WorkerState.FINISHED
|
currentState = WorkerState.FINISHED
|
||||||
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
if (pieces.isComplete() && eventFired.compareAndSet(false, true)) {
|
||||||
synchronized(piecesFile) {
|
synchronized(piecesFile) {
|
||||||
piecesFileClosed = true
|
piecesFileClosed = true
|
||||||
piecesFile.delete()
|
piecesFile.delete()
|
||||||
}
|
}
|
||||||
|
activeWorkers.values().each {
|
||||||
|
if (it.destination != destination)
|
||||||
|
it.cancel()
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
Files.move(incompleteFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE)
|
||||||
} catch (AtomicMoveNotSupportedException e) {
|
} catch (AtomicMoveNotSupportedException e) {
|
||||||
@@ -317,7 +326,7 @@ public class Downloader {
|
|||||||
}
|
}
|
||||||
eventBus.publish(
|
eventBus.publish(
|
||||||
new FileDownloadedEvent(
|
new FileDownloadedEvent(
|
||||||
downloadedFile : new DownloadedFile(file, getInfoHash(), pieceSizePow2, successfulDestinations),
|
downloadedFile : new DownloadedFile(file.getCanonicalFile(), getInfoHash(), pieceSizePow2, successfulDestinations),
|
||||||
downloader : Downloader.this))
|
downloader : Downloader.this))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ class Pieces {
|
|||||||
private final int nPieces
|
private final int nPieces
|
||||||
private final float ratio
|
private final float ratio
|
||||||
private final Random random = new Random()
|
private final Random random = new Random()
|
||||||
|
private final Map<Integer,Integer> partials = new HashMap<>()
|
||||||
|
|
||||||
Pieces(int nPieces) {
|
Pieces(int nPieces) {
|
||||||
this(nPieces, 1.0f)
|
this(nPieces, 1.0f)
|
||||||
@@ -17,16 +18,22 @@ class Pieces {
|
|||||||
claimed = new BitSet(nPieces)
|
claimed = new BitSet(nPieces)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int claim() {
|
synchronized int[] claim() {
|
||||||
int claimedCardinality = claimed.cardinality()
|
int claimedCardinality = claimed.cardinality()
|
||||||
if (claimedCardinality == nPieces)
|
if (claimedCardinality == nPieces) {
|
||||||
return -1
|
// 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 fuller than ratio just do sequential
|
||||||
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
if ( (1.0f * claimedCardinality) / nPieces > ratio) {
|
||||||
int rv = claimed.nextClearBit(0)
|
int rv = claimed.nextClearBit(0)
|
||||||
claimed.set(rv)
|
claimed.set(rv)
|
||||||
return rv
|
return [rv, partials.getOrDefault(rv, 0), 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
@@ -34,20 +41,28 @@ class Pieces {
|
|||||||
if (claimed.get(start))
|
if (claimed.get(start))
|
||||||
continue
|
continue
|
||||||
claimed.set(start)
|
claimed.set(start)
|
||||||
return start
|
return [start, partials.getOrDefault(start,0), 0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized int claim(Set<Integer> available) {
|
synchronized int[] claim(Set<Integer> available) {
|
||||||
for (int i = claimed.nextSetBit(0); i >= 0; i = claimed.nextSetBit(i+1))
|
for (int i = done.nextSetBit(0); i >= 0; i = done.nextSetBit(i+1))
|
||||||
available.remove(i)
|
available.remove(i)
|
||||||
if (available.isEmpty())
|
if (available.isEmpty())
|
||||||
return -1
|
return null
|
||||||
List<Integer> toList = available.toList()
|
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)
|
Collections.shuffle(toList)
|
||||||
int rv = toList[0]
|
int rv = toList[0]
|
||||||
claimed.set(rv)
|
claimed.set(rv)
|
||||||
rv
|
[rv, partials.getOrDefault(rv, 0), 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized def getDownloaded() {
|
synchronized def getDownloaded() {
|
||||||
@@ -61,6 +76,11 @@ class Pieces {
|
|||||||
synchronized void markDownloaded(int piece) {
|
synchronized void markDownloaded(int piece) {
|
||||||
done.set(piece)
|
done.set(piece)
|
||||||
claimed.set(piece)
|
claimed.set(piece)
|
||||||
|
partials.remove(piece)
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized void markPartial(int piece, int position) {
|
||||||
|
partials.put(piece, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void unclaim(int piece) {
|
synchronized void unclaim(int piece) {
|
||||||
@@ -82,5 +102,15 @@ class Pieces {
|
|||||||
synchronized void clearAll() {
|
synchronized void clearAll() {
|
||||||
done.clear()
|
done.clear()
|
||||||
claimed.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 {
|
class FileDownloadedEvent extends Event {
|
||||||
Downloader downloader
|
Downloader downloader
|
||||||
DownloadedFile downloadedFile
|
DownloadedFile downloadedFile
|
||||||
}
|
}
|
||||||
|
@@ -5,8 +5,8 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class FileHashedEvent extends Event {
|
class FileHashedEvent extends Event {
|
||||||
|
|
||||||
SharedFile sharedFile
|
SharedFile sharedFile
|
||||||
String error
|
String error
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@@ -13,67 +13,67 @@ import java.security.NoSuchAlgorithmException
|
|||||||
|
|
||||||
class FileHasher {
|
class FileHasher {
|
||||||
|
|
||||||
/** max size of shared file is 128 GB */
|
/** max size of shared file is 128 GB */
|
||||||
public static final long MAX_SIZE = 0x1L << 37
|
public static final long MAX_SIZE = 0x1L << 37
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param size of the file to be shared
|
* @param size of the file to be shared
|
||||||
* @return the size of each piece in power of 2
|
* @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)
|
* 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
|
* there can be up to 8192 pieces maximum per file
|
||||||
*/
|
*/
|
||||||
static int getPieceSize(long size) {
|
static int getPieceSize(long size) {
|
||||||
if (size <= 0x1 << 30)
|
if (size <= 0x1 << 30)
|
||||||
return 17
|
return 17
|
||||||
|
|
||||||
for (int i = 31; i <= 37; i++) {
|
for (int i = 31; i <= 37; i++) {
|
||||||
if (size <= 0x1L << i) {
|
if (size <= 0x1L << i) {
|
||||||
return i-13
|
return i-13
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("File too large $size")
|
throw new IllegalArgumentException("File too large $size")
|
||||||
}
|
}
|
||||||
|
|
||||||
final MessageDigest digest
|
final MessageDigest digest
|
||||||
|
|
||||||
FileHasher() {
|
FileHasher() {
|
||||||
try {
|
try {
|
||||||
digest = MessageDigest.getInstance("SHA-256")
|
digest = MessageDigest.getInstance("SHA-256")
|
||||||
} catch (NoSuchAlgorithmException impossible) {
|
} catch (NoSuchAlgorithmException impossible) {
|
||||||
digest = null
|
digest = null
|
||||||
System.exit(1)
|
System.exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InfoHash hashFile(File file) {
|
InfoHash hashFile(File file) {
|
||||||
final long length = file.length()
|
final long length = file.length()
|
||||||
final int size = 0x1 << getPieceSize(length)
|
final int size = 0x1 << getPieceSize(length)
|
||||||
int numPieces = (int) (length / size)
|
int numPieces = (int) (length / size)
|
||||||
if (numPieces * size < length)
|
if (numPieces * size < length)
|
||||||
numPieces++
|
numPieces++
|
||||||
|
|
||||||
def output = new ByteArrayOutputStream()
|
def output = new ByteArrayOutputStream()
|
||||||
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
RandomAccessFile raf = new RandomAccessFile(file, "r")
|
||||||
try {
|
try {
|
||||||
MappedByteBuffer buf
|
MappedByteBuffer buf
|
||||||
for (int i = 0; i < numPieces - 1; i++) {
|
for (int i = 0; i < numPieces - 1; i++) {
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
buf = raf.getChannel().map(MapMode.READ_ONLY, ((long)size) * i, size)
|
||||||
digest.update buf
|
digest.update buf
|
||||||
DataUtil.tryUnmap(buf)
|
DataUtil.tryUnmap(buf)
|
||||||
output.write(digest.digest(), 0, 32)
|
output.write(digest.digest(), 0, 32)
|
||||||
}
|
}
|
||||||
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
def lastPieceLength = length - (numPieces - 1) * ((long)size)
|
||||||
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
buf = raf.getChannel().map(MapMode.READ_ONLY, length - lastPieceLength, lastPieceLength)
|
||||||
digest.update buf
|
digest.update buf
|
||||||
output.write(digest.digest(), 0, 32)
|
output.write(digest.digest(), 0, 32)
|
||||||
} finally {
|
} finally {
|
||||||
raf.close()
|
raf.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
byte [] hashList = output.toByteArray()
|
byte [] hashList = output.toByteArray()
|
||||||
InfoHash.fromHashList(hashList)
|
InfoHash.fromHashList(hashList)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length != 1) {
|
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 {
|
class FileLoadedEvent extends Event {
|
||||||
|
|
||||||
SharedFile loadedFile
|
SharedFile loadedFile
|
||||||
}
|
}
|
||||||
|
@@ -15,26 +15,26 @@ import groovy.util.logging.Log
|
|||||||
class FileManager {
|
class FileManager {
|
||||||
|
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
final Map<InfoHash, Set<SharedFile>> rootToFiles = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
final Map<File, SharedFile> fileToSharedFile = Collections.synchronizedMap(new HashMap<>())
|
||||||
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
final Map<String, Set<File>> nameToFiles = new HashMap<>()
|
||||||
final SearchIndex index = new SearchIndex()
|
final SearchIndex index = new SearchIndex()
|
||||||
|
|
||||||
FileManager(EventBus eventBus, MuWireSettings settings) {
|
FileManager(EventBus eventBus, MuWireSettings settings) {
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileHashedEvent(FileHashedEvent e) {
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
if (e.sharedFile != null)
|
if (e.sharedFile != null)
|
||||||
addToIndex(e.sharedFile)
|
addToIndex(e.sharedFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
addToIndex(e.loadedFile)
|
addToIndex(e.loadedFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
if (settings.shareDownloadedFiles) {
|
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())
|
log.info("Adding shared file " + sf.getFile())
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = sf.getInfoHash()
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
log.info("adding new root")
|
log.info("adding new root")
|
||||||
existing = new HashSet<>()
|
existing = new HashSet<>()
|
||||||
rootToFiles.put(infoHash, existing);
|
rootToFiles.put(infoHash, existing);
|
||||||
}
|
}
|
||||||
existing.add(sf)
|
existing.add(sf)
|
||||||
fileToSharedFile.put(sf.file, sf)
|
fileToSharedFile.put(sf.file, sf)
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
if (existingFiles == null) {
|
if (existingFiles == null) {
|
||||||
existingFiles = new HashSet<>()
|
existingFiles = new HashSet<>()
|
||||||
nameToFiles.put(name, existingFiles)
|
nameToFiles.put(name, existingFiles)
|
||||||
}
|
}
|
||||||
existingFiles.add(sf.getFile())
|
existingFiles.add(sf.getFile())
|
||||||
|
|
||||||
index.add(name)
|
index.add(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
SharedFile sf = e.unsharedFile
|
SharedFile sf = e.unsharedFile
|
||||||
InfoHash infoHash = sf.getInfoHash()
|
InfoHash infoHash = sf.getInfoHash()
|
||||||
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
Set<SharedFile> existing = rootToFiles.get(infoHash)
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.remove(sf)
|
existing.remove(sf)
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
rootToFiles.remove(infoHash)
|
rootToFiles.remove(infoHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileToSharedFile.remove(sf.file)
|
fileToSharedFile.remove(sf.file)
|
||||||
|
|
||||||
String name = sf.getFile().getName()
|
String name = sf.getFile().getName()
|
||||||
Set<File> existingFiles = nameToFiles.get(name)
|
Set<File> existingFiles = nameToFiles.get(name)
|
||||||
if (existingFiles != null) {
|
if (existingFiles != null) {
|
||||||
existingFiles.remove(sf.file)
|
existingFiles.remove(sf.file)
|
||||||
if (existingFiles.isEmpty()) {
|
if (existingFiles.isEmpty()) {
|
||||||
nameToFiles.remove(name)
|
nameToFiles.remove(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
index.remove(name)
|
index.remove(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<File, SharedFile> getSharedFiles() {
|
Map<File, SharedFile> getSharedFiles() {
|
||||||
synchronized(fileToSharedFile) {
|
synchronized(fileToSharedFile) {
|
||||||
return new HashMap<>(fileToSharedFile)
|
return new HashMap<>(fileToSharedFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<SharedFile> getSharedFiles(byte []root) {
|
Set<SharedFile> getSharedFiles(byte []root) {
|
||||||
return rootToFiles.get(new InfoHash(root))
|
return rootToFiles.get(new InfoHash(root))
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSearchEvent(SearchEvent e) {
|
void onSearchEvent(SearchEvent e) {
|
||||||
// hash takes precedence
|
// hash takes precedence
|
||||||
ResultsEvent re = null
|
ResultsEvent re = null
|
||||||
if (e.searchHash != null) {
|
if (e.searchHash != null) {
|
||||||
Set<SharedFile> found
|
Set<SharedFile> found
|
||||||
found = rootToFiles.get new InfoHash(e.searchHash)
|
found = rootToFiles.get new InfoHash(e.searchHash)
|
||||||
found = filter(found, e.oobInfohash)
|
found = filter(found, e.oobInfohash)
|
||||||
if (found != null && !found.isEmpty())
|
if (found != null && !found.isEmpty())
|
||||||
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: found.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
} else {
|
} else {
|
||||||
def names = index.search e.searchTerms
|
def names = index.search e.searchTerms
|
||||||
Set<File> files = new HashSet<>()
|
Set<File> files = new HashSet<>()
|
||||||
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
names.each { files.addAll nameToFiles.getOrDefault(it, []) }
|
||||||
Set<SharedFile> sharedFiles = new HashSet<>()
|
Set<SharedFile> sharedFiles = new HashSet<>()
|
||||||
files.each { sharedFiles.add fileToSharedFile[it] }
|
files.each { sharedFiles.add fileToSharedFile[it] }
|
||||||
files = filter(sharedFiles, e.oobInfohash)
|
files = filter(sharedFiles, e.oobInfohash)
|
||||||
if (!sharedFiles.isEmpty())
|
if (!sharedFiles.isEmpty())
|
||||||
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
re = new ResultsEvent(results: sharedFiles.asList(), uuid: e.uuid, searchEvent: e)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (re != null)
|
if (re != null)
|
||||||
eventBus.publish(re)
|
eventBus.publish(re)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
private static Set<SharedFile> filter(Set<SharedFile> files, boolean oob) {
|
||||||
if (!oob)
|
if (!oob)
|
||||||
|
@@ -4,7 +4,7 @@ import com.muwire.core.Event
|
|||||||
|
|
||||||
class FileSharedEvent extends Event {
|
class FileSharedEvent extends Event {
|
||||||
|
|
||||||
File file
|
File file
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@@ -4,5 +4,5 @@ import com.muwire.core.Event
|
|||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
|
||||||
class FileUnsharedEvent extends Event {
|
class FileUnsharedEvent extends Event {
|
||||||
SharedFile unsharedFile
|
SharedFile unsharedFile
|
||||||
}
|
}
|
||||||
|
@@ -8,40 +8,41 @@ import com.muwire.core.SharedFile
|
|||||||
|
|
||||||
class HasherService {
|
class HasherService {
|
||||||
|
|
||||||
final FileHasher hasher
|
final FileHasher hasher
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
Executor executor
|
Executor executor
|
||||||
|
|
||||||
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
HasherService(FileHasher hasher, EventBus eventBus, FileManager fileManager) {
|
||||||
this.hasher = hasher
|
this.hasher = hasher
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
executor = Executors.newSingleThreadExecutor()
|
executor = Executors.newSingleThreadExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileSharedEvent(FileSharedEvent evt) {
|
void onFileSharedEvent(FileSharedEvent evt) {
|
||||||
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
if (fileManager.fileToSharedFile.containsKey(evt.file.getCanonicalFile()))
|
||||||
return
|
return
|
||||||
executor.execute( { -> process(evt.file) } as Runnable)
|
executor.execute( { -> process(evt.file) } as Runnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void process(File f) {
|
private void process(File f) {
|
||||||
f = f.getCanonicalFile()
|
f = f.getCanonicalFile()
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
f.listFiles().each {eventBus.publish new FileSharedEvent(file: it) }
|
||||||
} else {
|
} else {
|
||||||
if (f.length() == 0) {
|
if (f.length() == 0) {
|
||||||
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
eventBus.publish new FileHashedEvent(error: "Not sharing empty file $f")
|
||||||
} else if (f.length() > FileHasher.MAX_SIZE) {
|
} else if (f.length() > FileHasher.MAX_SIZE) {
|
||||||
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
eventBus.publish new FileHashedEvent(error: "$f is too large to be shared ${f.length()}")
|
||||||
} else {
|
} else {
|
||||||
def hash = hasher.hashFile f
|
eventBus.publish new FileHashingEvent(hashingFile: f)
|
||||||
eventBus.publish new FileHashedEvent(sharedFile: new SharedFile(f, hash, FileHasher.getPieceSize(f.length())))
|
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
|
@Log
|
||||||
class PersisterService extends Service {
|
class PersisterService extends Service {
|
||||||
|
|
||||||
final File location
|
final File location
|
||||||
final EventBus listener
|
final EventBus listener
|
||||||
final int interval
|
final int interval
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final FileManager fileManager
|
final FileManager fileManager
|
||||||
|
|
||||||
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
PersisterService(File location, EventBus listener, int interval, FileManager fileManager) {
|
||||||
this.location = location
|
this.location = location
|
||||||
this.listener = listener
|
this.listener = listener
|
||||||
this.interval = interval
|
this.interval = interval
|
||||||
this.fileManager = fileManager
|
this.fileManager = fileManager
|
||||||
timer = new Timer("file persister", true)
|
timer = new Timer("file persister", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUILoadedEvent(UILoadedEvent e) {
|
void onUILoadedEvent(UILoadedEvent e) {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (location.exists() && location.isFile()) {
|
if (location.exists() && location.isFile()) {
|
||||||
def slurper = new JsonSlurper()
|
def slurper = new JsonSlurper()
|
||||||
try {
|
try {
|
||||||
location.eachLine {
|
location.eachLine {
|
||||||
if (it.trim().length() > 0) {
|
if (it.trim().length() > 0) {
|
||||||
def parsed = slurper.parseText it
|
def parsed = slurper.parseText it
|
||||||
def event = fromJson parsed
|
def event = fromJson parsed
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
log.fine("loaded file $event.loadedFile.file")
|
log.fine("loaded file $event.loadedFile.file")
|
||||||
listener.publish event
|
listener.publish event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
} catch (IllegalArgumentException|NumberFormatException e) {
|
} catch (IllegalArgumentException|NumberFormatException e) {
|
||||||
log.log(Level.WARNING, "couldn't load files",e)
|
log.log(Level.WARNING, "couldn't load files",e)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
listener.publish(new AllFilesLoadedEvent())
|
listener.publish(new AllFilesLoadedEvent())
|
||||||
}
|
}
|
||||||
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
timer.schedule({persistFiles()} as TimerTask, 0, interval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileLoadedEvent fromJson(def json) {
|
private static FileLoadedEvent fromJson(def json) {
|
||||||
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
if (json.file == null || json.length == null || json.infoHash == null || json.hashList == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
if (!(json.hashList instanceof List))
|
if (!(json.hashList instanceof List))
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
|
|
||||||
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
def file = new File(DataUtil.readi18nString(Base64.decode(json.file)))
|
||||||
file = file.getCanonicalFile()
|
file = file.getCanonicalFile()
|
||||||
if (!file.exists() || file.isDirectory())
|
if (!file.exists() || file.isDirectory())
|
||||||
return null
|
return null
|
||||||
long length = Long.valueOf(json.length)
|
long length = Long.valueOf(json.length)
|
||||||
if (length != file.length())
|
if (length != file.length())
|
||||||
return null
|
return null
|
||||||
|
|
||||||
List hashList = (List) json.hashList
|
List hashList = (List) json.hashList
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
hashList.each {
|
hashList.each {
|
||||||
byte [] hash = Base64.decode it.toString()
|
byte [] hash = Base64.decode it.toString()
|
||||||
if (hash == null)
|
if (hash == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
baos.write hash
|
baos.write hash
|
||||||
}
|
}
|
||||||
byte[] hashListBytes = baos.toByteArray()
|
byte[] hashListBytes = baos.toByteArray()
|
||||||
|
|
||||||
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
InfoHash ih = InfoHash.fromHashList(hashListBytes)
|
||||||
byte [] root = Base64.decode(json.infoHash.toString())
|
byte [] root = Base64.decode(json.infoHash.toString())
|
||||||
if (root == null)
|
if (root == null)
|
||||||
throw new IllegalArgumentException()
|
throw new IllegalArgumentException()
|
||||||
if (!Arrays.equals(root, ih.getRoot()))
|
if (!Arrays.equals(root, ih.getRoot()))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
int pieceSize = 0
|
int pieceSize = 0
|
||||||
if (json.pieceSize != null)
|
if (json.pieceSize != null)
|
||||||
pieceSize = json.pieceSize
|
pieceSize = json.pieceSize
|
||||||
|
|
||||||
if (json.sources != null) {
|
if (json.sources != null) {
|
||||||
List sources = (List)json.sources
|
List sources = (List)json.sources
|
||||||
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
Set<Destination> sourceSet = sources.stream().map({d -> new Destination(d.toString())}).collect Collectors.toSet()
|
||||||
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
DownloadedFile df = new DownloadedFile(file, ih, pieceSize, sourceSet)
|
||||||
return new FileLoadedEvent(loadedFile : df)
|
return new FileLoadedEvent(loadedFile : df)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
SharedFile sf = new SharedFile(file, ih, pieceSize)
|
||||||
return new FileLoadedEvent(loadedFile: sf)
|
return new FileLoadedEvent(loadedFile: sf)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persistFiles() {
|
private void persistFiles() {
|
||||||
def sharedFiles = fileManager.getSharedFiles()
|
def sharedFiles = fileManager.getSharedFiles()
|
||||||
|
|
||||||
File tmp = File.createTempFile("muwire-files", "tmp")
|
File tmp = File.createTempFile("muwire-files", "tmp")
|
||||||
tmp.deleteOnExit()
|
tmp.deleteOnExit()
|
||||||
tmp.withPrintWriter { writer ->
|
tmp.withPrintWriter { writer ->
|
||||||
sharedFiles.each { k, v ->
|
sharedFiles.each { k, v ->
|
||||||
def json = toJson(k,v)
|
def json = toJson(k,v)
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
Files.copy(tmp.toPath(), location.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
tmp.delete()
|
tmp.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
private def toJson(File f, SharedFile sf) {
|
private def toJson(File f, SharedFile sf) {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = Base64.encode DataUtil.encodei18nString(f.getCanonicalFile().toString())
|
json.file = Base64.encode DataUtil.encodei18nString(f.toString())
|
||||||
json.length = f.length()
|
json.length = sf.getCachedLength()
|
||||||
InfoHash ih = sf.getInfoHash()
|
InfoHash ih = sf.getInfoHash()
|
||||||
json.infoHash = Base64.encode ih.getRoot()
|
json.infoHash = Base64.encode ih.getRoot()
|
||||||
json.pieceSize = sf.getPieceSize()
|
json.pieceSize = sf.getPieceSize()
|
||||||
byte [] tmp = new byte [32]
|
byte [] tmp = new byte [32]
|
||||||
json.hashList = []
|
json.hashList = []
|
||||||
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
for (int i = 0;i < ih.getHashList().length / 32; i++) {
|
||||||
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
System.arraycopy(ih.getHashList(), i * 32, tmp, 0, 32)
|
||||||
json.hashList.add Base64.encode(tmp)
|
json.hashList.add Base64.encode(tmp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sf instanceof DownloadedFile) {
|
if (sf instanceof DownloadedFile) {
|
||||||
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
json.sources = sf.sources.stream().map( {d -> d.toBase64()}).collect(Collectors.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
json
|
json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,176 +18,176 @@ import net.i2p.data.Destination
|
|||||||
@Log
|
@Log
|
||||||
class CacheClient {
|
class CacheClient {
|
||||||
|
|
||||||
private static final int CRAWLER_RETURN = 10
|
private static final int CRAWLER_RETURN = 10
|
||||||
|
|
||||||
final EventBus eventBus
|
final EventBus eventBus
|
||||||
final HostCache cache
|
final HostCache cache
|
||||||
final ConnectionManager manager
|
final ConnectionManager manager
|
||||||
final I2PSession session
|
final I2PSession session
|
||||||
final long interval
|
final long interval
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Timer timer
|
final Timer timer
|
||||||
|
|
||||||
public CacheClient(EventBus eventBus, HostCache cache,
|
public CacheClient(EventBus eventBus, HostCache cache,
|
||||||
ConnectionManager manager, I2PSession session,
|
ConnectionManager manager, I2PSession session,
|
||||||
MuWireSettings settings, long interval) {
|
MuWireSettings settings, long interval) {
|
||||||
this.eventBus = eventBus
|
this.eventBus = eventBus
|
||||||
this.cache = cache
|
this.cache = cache
|
||||||
this.manager = manager
|
this.manager = manager
|
||||||
this.session = session
|
this.session = session
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.interval = interval
|
this.interval = interval
|
||||||
this.timer = new Timer("hostcache-client",true)
|
this.timer = new Timer("hostcache-client",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
session.addMuxedSessionListener(new Listener(), I2PSession.PROTO_DATAGRAM, 0)
|
||||||
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
timer.schedule({queryIfNeeded()} as TimerTask, 1, interval)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void queryIfNeeded() {
|
private void queryIfNeeded() {
|
||||||
if (!manager.getConnections().isEmpty())
|
if (!manager.getConnections().isEmpty())
|
||||||
return
|
return
|
||||||
if (!cache.getHosts(1).isEmpty())
|
if (!cache.getHosts(1).isEmpty())
|
||||||
return
|
return
|
||||||
|
|
||||||
log.info "Will query hostcaches"
|
log.info "Will query hostcaches"
|
||||||
|
|
||||||
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
def ping = [type: "Ping", version: 1, leaf: settings.isLeaf()]
|
||||||
ping = JsonOutput.toJson(ping)
|
ping = JsonOutput.toJson(ping)
|
||||||
def maker = new I2PDatagramMaker(session)
|
def maker = new I2PDatagramMaker(session)
|
||||||
ping = maker.makeI2PDatagram(ping.bytes)
|
ping = maker.makeI2PDatagram(ping.bytes)
|
||||||
def options = new SendMessageOptions()
|
def options = new SendMessageOptions()
|
||||||
options.setSendLeaseSet(true)
|
options.setSendLeaseSet(true)
|
||||||
CacheServers.getCacheServers().each {
|
CacheServers.getCacheServers().each {
|
||||||
log.info "Querying hostcache ${it.toBase32()}"
|
log.info "Querying hostcache ${it.toBase32()}"
|
||||||
session.sendMessage(it, ping, 0, ping.length, I2PSession.PROTO_DATAGRAM, 1, 0, options)
|
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
|
@Override
|
||||||
public void messageAvailable(I2PSession session, int msgId, long size) {
|
public void messageAvailable(I2PSession session, int msgId, long size) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
public void messageAvailable(I2PSession session, int msgId, long size, int proto, int fromport, int toport) {
|
||||||
|
|
||||||
if (proto != I2PSession.PROTO_DATAGRAM) {
|
if (proto != I2PSession.PROTO_DATAGRAM) {
|
||||||
log.warning "Received unexpected protocol $proto"
|
log.warning "Received unexpected protocol $proto"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
def payload = session.receiveMessage(msgId)
|
def payload = session.receiveMessage(msgId)
|
||||||
def dissector = new I2PDatagramDissector()
|
def dissector = new I2PDatagramDissector()
|
||||||
try {
|
try {
|
||||||
dissector.loadI2PDatagram(payload)
|
dissector.loadI2PDatagram(payload)
|
||||||
def sender = dissector.getSender()
|
def sender = dissector.getSender()
|
||||||
log.info("Received something from ${sender.toBase32()}")
|
log.info("Received something from ${sender.toBase32()}")
|
||||||
|
|
||||||
payload = dissector.getPayload()
|
payload = dissector.getPayload()
|
||||||
payload = slurper.parse(payload)
|
payload = slurper.parse(payload)
|
||||||
|
|
||||||
if (payload.type == null) {
|
if (payload.type == null) {
|
||||||
log.warning("type missing")
|
log.warning("type missing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(payload.type) {
|
switch(payload.type) {
|
||||||
case "Pong" : handlePong(sender, payload); break
|
case "Pong" : handlePong(sender, payload); break
|
||||||
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
case "CrawlerPing": handleCrawlerPing(session, sender, payload); break
|
||||||
default : log.warning("unknown type ${payload.type}")
|
default : log.warning("unknown type ${payload.type}")
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warning("Invalid datagram $e")
|
log.warning("Invalid datagram $e")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reportAbuse(I2PSession session, int severity) {
|
public void reportAbuse(I2PSession session, int severity) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnected(I2PSession session) {
|
public void disconnected(I2PSession session) {
|
||||||
log.severe "I2P session disconnected"
|
log.severe "I2P session disconnected"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
public void errorOccurred(I2PSession session, String message, Throwable error) {
|
||||||
log.severe "I2P error occured $message $error"
|
log.severe "I2P error occured $message $error"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePong(Destination from, def pong) {
|
private void handlePong(Destination from, def pong) {
|
||||||
if (!CacheServers.isRegistered(from)) {
|
if (!CacheServers.isRegistered(from)) {
|
||||||
log.warning("received pong from non-registered destination")
|
log.warning("received pong from non-registered destination")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pong.pongs == null) {
|
if (pong.pongs == null) {
|
||||||
log.warning("malformed pong - no pongs")
|
log.warning("malformed pong - no pongs")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pong.pongs.asList().each {
|
pong.pongs.asList().each {
|
||||||
Destination dest = new Destination(it)
|
Destination dest = new Destination(it)
|
||||||
if (!session.getMyDestination().equals(dest))
|
if (!session.getMyDestination().equals(dest))
|
||||||
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
eventBus.publish(new HostDiscoveredEvent(destination: dest, fromHostcache : true))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCrawlerPing(I2PSession session, Destination from, def ping) {
|
private void handleCrawlerPing(I2PSession session, Destination from, def ping) {
|
||||||
if (settings.isLeaf()) {
|
if (settings.isLeaf()) {
|
||||||
log.warning("Received crawler ping but I'm a leaf")
|
log.warning("Received crawler ping but I'm a leaf")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(settings.getCrawlerResponse()) {
|
switch(settings.getCrawlerResponse()) {
|
||||||
case CrawlerResponse.NONE:
|
case CrawlerResponse.NONE:
|
||||||
log.info("Responding to crawlers is disabled by user")
|
log.info("Responding to crawlers is disabled by user")
|
||||||
break
|
break
|
||||||
case CrawlerResponse.ALL:
|
case CrawlerResponse.ALL:
|
||||||
respondToCrawler(session, from, ping)
|
respondToCrawler(session, from, ping)
|
||||||
break;
|
break;
|
||||||
case CrawlerResponse.REGISTERED:
|
case CrawlerResponse.REGISTERED:
|
||||||
if (CacheServers.isRegistered(from))
|
if (CacheServers.isRegistered(from))
|
||||||
respondToCrawler(session, from, ping)
|
respondToCrawler(session, from, ping)
|
||||||
else
|
else
|
||||||
log.warning("Ignoring crawler ping from non-registered crawler")
|
log.warning("Ignoring crawler ping from non-registered crawler")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
private void respondToCrawler(I2PSession session, Destination from, def ping) {
|
||||||
log.info "responding to crawler ping"
|
log.info "responding to crawler ping"
|
||||||
|
|
||||||
def neighbors = manager.getConnections().collect { c -> c.endpoint.destination.toBase64() }
|
def neighbors = manager.getConnections().collect { c -> c.endpoint.destination.toBase64() }
|
||||||
Collections.shuffle(neighbors)
|
Collections.shuffle(neighbors)
|
||||||
if (neighbors.size() > CRAWLER_RETURN)
|
if (neighbors.size() > CRAWLER_RETURN)
|
||||||
neighbors = neighbors[0..CRAWLER_RETURN - 1]
|
neighbors = neighbors[0..CRAWLER_RETURN - 1]
|
||||||
|
|
||||||
def upManager = (UltrapeerConnectionManager) manager;
|
def upManager = (UltrapeerConnectionManager) manager;
|
||||||
def pong = [:]
|
def pong = [:]
|
||||||
pong.peers = neighbors
|
pong.peers = neighbors
|
||||||
pong.uuid = ping.uuid
|
pong.uuid = ping.uuid
|
||||||
pong.type = "CrawlerPong"
|
pong.type = "CrawlerPong"
|
||||||
pong.version = 1
|
pong.version = 1
|
||||||
pong.leafSlots = upManager.hasLeafSlots()
|
pong.leafSlots = upManager.hasLeafSlots()
|
||||||
pong.peerSlots = upManager.hasPeerSlots()
|
pong.peerSlots = upManager.hasPeerSlots()
|
||||||
pong = JsonOutput.toJson(pong)
|
pong = JsonOutput.toJson(pong)
|
||||||
|
|
||||||
def maker = new I2PDatagramMaker(session)
|
def maker = new I2PDatagramMaker(session)
|
||||||
pong = maker.makeI2PDatagram(pong.bytes)
|
pong = maker.makeI2PDatagram(pong.bytes)
|
||||||
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
session.sendMessage(from, pong, I2PSession.PROTO_DATAGRAM, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -4,21 +4,25 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class CacheServers {
|
class CacheServers {
|
||||||
|
|
||||||
private static final int TO_GIVE = 3
|
private static final int TO_GIVE = 3
|
||||||
private static Set<Destination> CACHES = [
|
private static Set<Destination> CACHES = [
|
||||||
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
// zlatinb
|
||||||
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA==")
|
new Destination("Wddh2E6FyyXBF7SvUYHKdN-vjf3~N6uqQWNeBDTM0P33YjiQCOsyedrjmDZmWFrXUJfJLWnCb5bnKezfk4uDaMyj~uvDG~yvLVcFgcPWSUd7BfGgym-zqcG1q1DcM8vfun-US7YamBlmtC6MZ2j-~Igqzmgshita8aLPCfNAA6S6e2UMjjtG7QIXlxpMec75dkHdJlVWbzrk9z8Qgru3YIk0UztYgEwDNBbm9wInsbHhr3HtAfa02QcgRVqRN2PnQXuqUJs7R7~09FZPEviiIcUpkY3FeyLlX1sgQFBeGeA96blaPvZNGd6KnNdgfLgMebx5SSxC-N4KZMSMBz5cgonQF3~m2HHFRSI85zqZNG5X9bJN85t80ltiv1W1es8ZnQW4es11r7MrvJNXz5bmSH641yJIvS6qI8OJJNpFVBIQSXLD-96TayrLQPaYw~uNZ-eXaE6G5dYhiuN8xHsFI1QkdaUaVZnvDGfsRbpS5GtpUbBDbyLkdPurG0i7dN1wAAAA"),
|
||||||
]
|
// sNL
|
||||||
|
new Destination("JC63wJNOqSJmymkj4~UJWywBTvDGikKMoYP0HX2Wz9c5l3otXSkwnxWAFL4cKr~Ygh3BNNi2t93vuLIiI1W8AsE42kR~PwRx~Y-WvIHXR6KUejRmOp-n8WidtjKg9k4aDy428uSOedqXDxys5mpoeQXwDsv1CoPTTwnmb1GWFy~oTGIsCguCl~aJWGnqiKarPO3GJQ~ev-NbvAQzUfC3HeP1e6pdI5CGGjExahTCID5UjpJw8GaDXWlGmYWWH303Xu4x-vAHQy1dJLsOBCn8dZravsn5BKJk~j0POUon45CCx-~NYtaPe0Itt9cMdD2ciC76Rep1D0X0sm1SjlSs8sZ52KmF3oaLZ6OzgI9QLMIyBUrfi41sK5I0qTuUVBAkvW1xr~L-20dYJ9TrbOaOb2-vDIfKaxVi6xQOuhgQDiSBhd3qv2m0xGu-BM9DQYfNA0FdMjnZmqjmji9RMavzQSsVFIbQGLbrLepiEFlb7TseCK5UtRp8TxnG7L4gbYevBQAEAAcAAA=="),
|
||||||
|
// dark_trion
|
||||||
|
new Destination("Gec9L29FVcQvYDgpcYuEYdltJn06PPoOWAcAM8Af-gDm~ehlrJcwlLXXs0hidq~yP2A0X7QcDi6i6shAfuEofTchxGJl8LRNqj9lio7WnB7cIixXWL~uCkD7Np5LMX0~akNX34oOb9RcBYVT2U5rFGJmJ7OtBv~IBkGeLhsMrqaCjahd0jdBO~QJ-t82ZKZhh044d24~JEfF9zSJxdBoCdAcXzryGNy7sYtFVDFsPKJudAxSW-UsSQiGw2~k-TxyF0r-iAt1IdzfNu8Lu0WPqLdhDYJWcPldx2PR5uJorI~zo~z3I5RX3NwzarlbD4nEP5s65ahPSfVCEkzmaJUBgP8DvBqlFaX89K4nGRYc7jkEjJ8cX4L6YPXUpTPWcfKkW259WdQY3YFh6x7rzijrGZewpczOLCrt-bZRYgDrUibmZxKZmNhy~lQu4gYVVjkz1i4tL~DWlhIc4y0x2vItwkYLArPPi~ejTnt-~Lhb7oPMXRcWa3UrwGKpFvGZY4NXBQAEAAcAAA==")
|
||||||
|
]
|
||||||
|
|
||||||
static List<Destination> getCacheServers() {
|
static List<Destination> getCacheServers() {
|
||||||
List<Destination> allCaches = new ArrayList<>(CACHES)
|
List<Destination> allCaches = new ArrayList<>(CACHES)
|
||||||
Collections.shuffle(allCaches)
|
Collections.shuffle(allCaches)
|
||||||
if (allCaches.size() <= TO_GIVE)
|
if (allCaches.size() <= TO_GIVE)
|
||||||
return allCaches
|
return allCaches
|
||||||
allCaches[0..TO_GIVE-1]
|
allCaches[0..TO_GIVE-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isRegistered(Destination d) {
|
static boolean isRegistered(Destination d) {
|
||||||
return CACHES.contains(d)
|
return CACHES.contains(d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,37 +4,37 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class Host {
|
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
|
private final int clearInterval
|
||||||
int failures,successes
|
int failures,successes
|
||||||
long lastAttempt
|
long lastAttempt
|
||||||
|
|
||||||
public Host(Destination destination, int clearInterval) {
|
public Host(Destination destination, int clearInterval) {
|
||||||
this.destination = destination
|
this.destination = destination
|
||||||
this.clearInterval = clearInterval
|
this.clearInterval = clearInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onConnect() {
|
synchronized void onConnect() {
|
||||||
failures = 0
|
failures = 0
|
||||||
successes++
|
successes++
|
||||||
lastAttempt = System.currentTimeMillis()
|
lastAttempt = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void onFailure() {
|
synchronized void onFailure() {
|
||||||
failures++
|
failures++
|
||||||
successes = 0
|
successes = 0
|
||||||
lastAttempt = System.currentTimeMillis()
|
lastAttempt = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean isFailed() {
|
synchronized boolean isFailed() {
|
||||||
failures >= MAX_FAILURES
|
failures >= MAX_FAILURES
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized boolean hasSucceeded() {
|
synchronized boolean hasSucceeded() {
|
||||||
successes > 0
|
successes > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void clearFailures() {
|
synchronized void clearFailures() {
|
||||||
failures = 0
|
failures = 0
|
||||||
|
@@ -15,141 +15,141 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class HostCache extends Service {
|
class HostCache extends Service {
|
||||||
|
|
||||||
final TrustService trustService
|
final TrustService trustService
|
||||||
final File storage
|
final File storage
|
||||||
final int interval
|
final int interval
|
||||||
final Timer timer
|
final Timer timer
|
||||||
final MuWireSettings settings
|
final MuWireSettings settings
|
||||||
final Destination myself
|
final Destination myself
|
||||||
final Map<Destination, Host> hosts = new ConcurrentHashMap<>()
|
final Map<Destination, Host> hosts = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
HostCache(){}
|
HostCache(){}
|
||||||
|
|
||||||
public HostCache(TrustService trustService, File storage, int interval,
|
public HostCache(TrustService trustService, File storage, int interval,
|
||||||
MuWireSettings settings, Destination myself) {
|
MuWireSettings settings, Destination myself) {
|
||||||
this.trustService = trustService
|
this.trustService = trustService
|
||||||
this.storage = storage
|
this.storage = storage
|
||||||
this.interval = interval
|
this.interval = interval
|
||||||
this.settings = settings
|
this.settings = settings
|
||||||
this.myself = myself
|
this.myself = myself
|
||||||
this.timer = new Timer("host-persister",true)
|
this.timer = new Timer("host-persister",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||||
if (myself == e.destination)
|
if (myself == e.destination)
|
||||||
return
|
return
|
||||||
if (hosts.containsKey(e.destination)) {
|
if (hosts.containsKey(e.destination)) {
|
||||||
if (!e.fromHostcache)
|
if (!e.fromHostcache)
|
||||||
return
|
return
|
||||||
hosts.get(e.destination).clearFailures()
|
hosts.get(e.destination).clearFailures()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Host host = new Host(e.destination, settings.hostClearInterval)
|
Host host = new Host(e.destination, settings.hostClearInterval)
|
||||||
if (allowHost(host)) {
|
if (allowHost(host)) {
|
||||||
hosts.put(e.destination, host)
|
hosts.put(e.destination, host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onConnectionEvent(ConnectionEvent e) {
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
if (e.leaf)
|
if (e.leaf)
|
||||||
return
|
return
|
||||||
Destination dest = e.endpoint.destination
|
Destination dest = e.endpoint.destination
|
||||||
Host host = hosts.get(dest)
|
Host host = hosts.get(dest)
|
||||||
if (host == null) {
|
if (host == null) {
|
||||||
host = new Host(dest, settings.hostClearInterval)
|
host = new Host(dest, settings.hostClearInterval)
|
||||||
hosts.put(dest, host)
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(e.status) {
|
switch(e.status) {
|
||||||
case ConnectionAttemptStatus.SUCCESSFUL:
|
case ConnectionAttemptStatus.SUCCESSFUL:
|
||||||
case ConnectionAttemptStatus.REJECTED:
|
case ConnectionAttemptStatus.REJECTED:
|
||||||
host.onConnect()
|
host.onConnect()
|
||||||
break
|
break
|
||||||
case ConnectionAttemptStatus.FAILED:
|
case ConnectionAttemptStatus.FAILED:
|
||||||
host.onFailure()
|
host.onFailure()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Destination> getHosts(int n) {
|
List<Destination> getHosts(int n) {
|
||||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||||
rv.retainAll {allowHost(hosts[it])}
|
rv.retainAll {allowHost(hosts[it])}
|
||||||
if (rv.size() <= n)
|
if (rv.size() <= n)
|
||||||
return rv
|
return rv
|
||||||
Collections.shuffle(rv)
|
Collections.shuffle(rv)
|
||||||
rv[0..n-1]
|
rv[0..n-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Destination> getGoodHosts(int n) {
|
List<Destination> getGoodHosts(int n) {
|
||||||
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
List<Destination> rv = new ArrayList<>(hosts.keySet())
|
||||||
rv.retainAll {
|
rv.retainAll {
|
||||||
Host host = hosts[it]
|
Host host = hosts[it]
|
||||||
allowHost(host) && host.hasSucceeded()
|
allowHost(host) && host.hasSucceeded()
|
||||||
}
|
}
|
||||||
if (rv.size() <= n)
|
if (rv.size() <= n)
|
||||||
return rv
|
return rv
|
||||||
Collections.shuffle(rv)
|
Collections.shuffle(rv)
|
||||||
rv[0..n-1]
|
rv[0..n-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (storage.exists()) {
|
if (storage.exists()) {
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
storage.eachLine {
|
storage.eachLine {
|
||||||
def entry = slurper.parseText(it)
|
def entry = slurper.parseText(it)
|
||||||
Destination dest = new Destination(entry.destination)
|
Destination dest = new Destination(entry.destination)
|
||||||
Host host = new Host(dest, settings.hostClearInterval)
|
Host host = new Host(dest, settings.hostClearInterval)
|
||||||
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
host.failures = Integer.valueOf(String.valueOf(entry.failures))
|
||||||
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
host.successes = Integer.valueOf(String.valueOf(entry.successes))
|
||||||
if (entry.lastAttempt != null)
|
if (entry.lastAttempt != null)
|
||||||
host.lastAttempt = entry.lastAttempt
|
host.lastAttempt = entry.lastAttempt
|
||||||
if (allowHost(host))
|
if (allowHost(host))
|
||||||
hosts.put(dest, host)
|
hosts.put(dest, host)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.schedule({save()} as TimerTask, interval, interval)
|
timer.schedule({save()} as TimerTask, interval, interval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowHost(Host host) {
|
private boolean allowHost(Host host) {
|
||||||
if (host.isFailed() && !host.canTryAgain())
|
if (host.isFailed() && !host.canTryAgain())
|
||||||
return false
|
return false
|
||||||
if (host.destination == myself)
|
if (host.destination == myself)
|
||||||
return false
|
return false
|
||||||
TrustLevel trust = trustService.getLevel(host.destination)
|
TrustLevel trust = trustService.getLevel(host.destination)
|
||||||
switch(trust) {
|
switch(trust) {
|
||||||
case TrustLevel.DISTRUSTED :
|
case TrustLevel.DISTRUSTED :
|
||||||
return false
|
return false
|
||||||
case TrustLevel.TRUSTED :
|
case TrustLevel.TRUSTED :
|
||||||
return true
|
return true
|
||||||
case TrustLevel.NEUTRAL :
|
case TrustLevel.NEUTRAL :
|
||||||
return settings.allowUntrusted()
|
return settings.allowUntrusted()
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
storage.delete()
|
storage.delete()
|
||||||
storage.withPrintWriter { writer ->
|
storage.withPrintWriter { writer ->
|
||||||
hosts.each { dest, host ->
|
hosts.each { dest, host ->
|
||||||
if (allowHost(host)) {
|
if (allowHost(host)) {
|
||||||
def map = [:]
|
def map = [:]
|
||||||
map.destination = dest.toBase64()
|
map.destination = dest.toBase64()
|
||||||
map.failures = host.failures
|
map.failures = host.failures
|
||||||
map.successes = host.successes
|
map.successes = host.successes
|
||||||
map.lastAttempt = host.lastAttempt
|
map.lastAttempt = host.lastAttempt
|
||||||
def json = JsonOutput.toJson(map)
|
def json = JsonOutput.toJson(map)
|
||||||
writer.println json
|
writer.println json
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class HostDiscoveredEvent extends Event {
|
class HostDiscoveredEvent extends Event {
|
||||||
|
|
||||||
Destination destination
|
Destination destination
|
||||||
boolean fromHostcache
|
boolean fromHostcache
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
"HostDiscoveredEvent ${super.toString()} destination:${destination.toBase32()} from hostcache $fromHostcache"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import net.i2p.data.Base32
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class DeleteEvent extends Event {
|
class DeleteEvent extends Event {
|
||||||
byte [] infoHash
|
byte [] infoHash
|
||||||
Destination leaf
|
Destination leaf
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
"DeleteEvent ${super.toString()} infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,32 +7,32 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class LeafSearcher {
|
class LeafSearcher {
|
||||||
|
|
||||||
final UltrapeerConnectionManager connectionManager
|
final UltrapeerConnectionManager connectionManager
|
||||||
final SearchIndex searchIndex = new SearchIndex()
|
final SearchIndex searchIndex = new SearchIndex()
|
||||||
|
|
||||||
final Map<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
final Map<String, Set<byte[]>> fileNameToHashes = new HashMap<>()
|
||||||
final Map<byte[], Set<Destination>> hashToLeafs = 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) {
|
LeafSearcher(UltrapeerConnectionManager connectionManager) {
|
||||||
this.connectionManager = connectionManager
|
this.connectionManager = connectionManager
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUpsertEvent(UpsertEvent e) {
|
void onUpsertEvent(UpsertEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDeleteEvent(DeleteEvent e) {
|
void onDeleteEvent(DeleteEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onDisconnectionEvent(DisconnectionEvent e) {
|
void onDisconnectionEvent(DisconnectionEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
void onQueryEvent(QueryEvent e) {
|
void onQueryEvent(QueryEvent e) {
|
||||||
// TODO: implement
|
// TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -8,10 +8,10 @@ import net.i2p.data.Destination
|
|||||||
class QueryEvent extends Event {
|
class QueryEvent extends Event {
|
||||||
|
|
||||||
SearchEvent searchEvent
|
SearchEvent searchEvent
|
||||||
boolean firstHop
|
boolean firstHop
|
||||||
Destination replyTo
|
Destination replyTo
|
||||||
Persona originator
|
Persona originator
|
||||||
Destination receivedOn
|
Destination receivedOn
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
"searchEvent: $searchEvent firstHop:$firstHop, replyTo:${replyTo.toBase32()}" +
|
||||||
|
@@ -6,6 +6,6 @@ import com.muwire.core.SharedFile
|
|||||||
class ResultsEvent extends Event {
|
class ResultsEvent extends Event {
|
||||||
|
|
||||||
SearchEvent searchEvent
|
SearchEvent searchEvent
|
||||||
SharedFile[] results
|
SharedFile[] results
|
||||||
UUID uuid
|
UUID uuid
|
||||||
}
|
}
|
||||||
|
@@ -5,9 +5,9 @@ import com.muwire.core.InfoHash
|
|||||||
|
|
||||||
class SearchEvent extends Event {
|
class SearchEvent extends Event {
|
||||||
|
|
||||||
List<String> searchTerms
|
List<String> searchTerms
|
||||||
byte [] searchHash
|
byte [] searchHash
|
||||||
UUID uuid
|
UUID uuid
|
||||||
boolean oobInfohash
|
boolean oobInfohash
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@@ -4,56 +4,56 @@ import com.muwire.core.Constants
|
|||||||
|
|
||||||
class SearchIndex {
|
class SearchIndex {
|
||||||
|
|
||||||
final Map<String, Set<String>> keywords = new HashMap<>()
|
final Map<String, Set<String>> keywords = new HashMap<>()
|
||||||
|
|
||||||
void add(String string) {
|
void add(String string) {
|
||||||
String [] split = split(string)
|
String [] split = split(string)
|
||||||
split.each {
|
split.each {
|
||||||
Set<String> existing = keywords.get(it)
|
Set<String> existing = keywords.get(it)
|
||||||
if (existing == null) {
|
if (existing == null) {
|
||||||
existing = new HashSet<>()
|
existing = new HashSet<>()
|
||||||
keywords.put(it, existing)
|
keywords.put(it, existing)
|
||||||
}
|
}
|
||||||
existing.add(string)
|
existing.add(string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(String string) {
|
void remove(String string) {
|
||||||
String [] split = split(string)
|
String [] split = split(string)
|
||||||
split.each {
|
split.each {
|
||||||
Set<String> existing = keywords.get it
|
Set<String> existing = keywords.get it
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
existing.remove(string)
|
existing.remove(string)
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
keywords.remove(it)
|
keywords.remove(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String[] split(String source) {
|
private static String[] split(String source) {
|
||||||
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
source = source.replaceAll(Constants.SPLIT_PATTERN, " ").toLowerCase()
|
||||||
String [] split = source.split(" ")
|
String [] split = source.split(" ")
|
||||||
def rv = []
|
def rv = []
|
||||||
split.each { if (it.length() > 0) rv << it }
|
split.each { if (it.length() > 0) rv << it }
|
||||||
rv.toArray(new String[0])
|
rv.toArray(new String[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] search(List<String> terms) {
|
String[] search(List<String> terms) {
|
||||||
Set<String> rv = null;
|
Set<String> rv = null;
|
||||||
|
|
||||||
terms.each {
|
terms.each {
|
||||||
Set<String> forWord = keywords.getOrDefault(it,[])
|
Set<String> forWord = keywords.getOrDefault(it,[])
|
||||||
if (rv == null) {
|
if (rv == null) {
|
||||||
rv = new HashSet<>(forWord)
|
rv = new HashSet<>(forWord)
|
||||||
} else {
|
} else {
|
||||||
rv.retainAll(forWord)
|
rv.retainAll(forWord)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rv != null)
|
if (rv != null)
|
||||||
return rv.asList()
|
return rv.asList()
|
||||||
[]
|
[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,12 +7,12 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class UpsertEvent extends Event {
|
class UpsertEvent extends Event {
|
||||||
|
|
||||||
Set<String> names
|
Set<String> names
|
||||||
byte [] infoHash
|
byte [] infoHash
|
||||||
Destination leaf
|
Destination leaf
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
"UpsertEvent ${super.toString()} names:$names infoHash:${Base32.encode(infoHash)} leaf:${leaf.toBase32()}"
|
"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 {
|
class TrustEvent extends Event {
|
||||||
|
|
||||||
Persona persona
|
Persona persona
|
||||||
TrustLevel level
|
TrustLevel level
|
||||||
}
|
}
|
||||||
|
@@ -11,87 +11,87 @@ import net.i2p.util.ConcurrentHashSet
|
|||||||
|
|
||||||
class TrustService extends Service {
|
class TrustService extends Service {
|
||||||
|
|
||||||
final File persistGood, persistBad
|
final File persistGood, persistBad
|
||||||
final long persistInterval
|
final long persistInterval
|
||||||
|
|
||||||
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
final Map<Destination, Persona> good = new ConcurrentHashMap<>()
|
||||||
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
final Map<Destination, Persona> bad = new ConcurrentHashMap<>()
|
||||||
|
|
||||||
final Timer timer
|
final Timer timer
|
||||||
|
|
||||||
TrustService() {}
|
TrustService() {}
|
||||||
|
|
||||||
TrustService(File persistGood, File persistBad, long persistInterval) {
|
TrustService(File persistGood, File persistBad, long persistInterval) {
|
||||||
this.persistBad = persistBad
|
this.persistBad = persistBad
|
||||||
this.persistGood = persistGood
|
this.persistGood = persistGood
|
||||||
this.persistInterval = persistInterval
|
this.persistInterval = persistInterval
|
||||||
this.timer = new Timer("trust-persister",true)
|
this.timer = new Timer("trust-persister",true)
|
||||||
}
|
}
|
||||||
|
|
||||||
void start() {
|
void start() {
|
||||||
timer.schedule({load()} as TimerTask, 1)
|
timer.schedule({load()} as TimerTask, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
timer.cancel()
|
timer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
void load() {
|
void load() {
|
||||||
if (persistGood.exists()) {
|
if (persistGood.exists()) {
|
||||||
persistGood.eachLine {
|
persistGood.eachLine {
|
||||||
byte [] decoded = Base64.decode(it)
|
byte [] decoded = Base64.decode(it)
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||||
good.put(persona.destination, persona)
|
good.put(persona.destination, persona)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (persistBad.exists()) {
|
if (persistBad.exists()) {
|
||||||
persistBad.eachLine {
|
persistBad.eachLine {
|
||||||
byte [] decoded = Base64.decode(it)
|
byte [] decoded = Base64.decode(it)
|
||||||
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
Persona persona = new Persona(new ByteArrayInputStream(decoded))
|
||||||
bad.put(persona.destination, persona)
|
bad.put(persona.destination, persona)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
timer.schedule({persist()} as TimerTask, persistInterval, persistInterval)
|
||||||
loaded = true
|
loaded = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private void persist() {
|
private void persist() {
|
||||||
persistGood.delete()
|
persistGood.delete()
|
||||||
persistGood.withPrintWriter { writer ->
|
persistGood.withPrintWriter { writer ->
|
||||||
good.each {k,v ->
|
good.each {k,v ->
|
||||||
writer.println v.toBase64()
|
writer.println v.toBase64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
persistBad.delete()
|
persistBad.delete()
|
||||||
persistBad.withPrintWriter { writer ->
|
persistBad.withPrintWriter { writer ->
|
||||||
bad.each { k,v ->
|
bad.each { k,v ->
|
||||||
writer.println v.toBase64()
|
writer.println v.toBase64()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TrustLevel getLevel(Destination dest) {
|
TrustLevel getLevel(Destination dest) {
|
||||||
if (good.containsKey(dest))
|
if (good.containsKey(dest))
|
||||||
return TrustLevel.TRUSTED
|
return TrustLevel.TRUSTED
|
||||||
else if (bad.containsKey(dest))
|
else if (bad.containsKey(dest))
|
||||||
return TrustLevel.DISTRUSTED
|
return TrustLevel.DISTRUSTED
|
||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTrustEvent(TrustEvent e) {
|
void onTrustEvent(TrustEvent e) {
|
||||||
switch(e.level) {
|
switch(e.level) {
|
||||||
case TrustLevel.TRUSTED:
|
case TrustLevel.TRUSTED:
|
||||||
bad.remove(e.persona.destination)
|
bad.remove(e.persona.destination)
|
||||||
good.put(e.persona.destination, e.persona)
|
good.put(e.persona.destination, e.persona)
|
||||||
break
|
break
|
||||||
case TrustLevel.DISTRUSTED:
|
case TrustLevel.DISTRUSTED:
|
||||||
good.remove(e.persona.destination)
|
good.remove(e.persona.destination)
|
||||||
bad.put(e.persona.destination, e.persona)
|
bad.put(e.persona.destination, e.persona)
|
||||||
break
|
break
|
||||||
case TrustLevel.NEUTRAL:
|
case TrustLevel.NEUTRAL:
|
||||||
good.remove(e.persona.destination)
|
good.remove(e.persona.destination)
|
||||||
bad.remove(e.persona.destination)
|
bad.remove(e.persona.destination)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,5 +3,5 @@ package com.muwire.core.update
|
|||||||
import net.i2p.data.Destination
|
import net.i2p.data.Destination
|
||||||
|
|
||||||
class UpdateServers {
|
class UpdateServers {
|
||||||
static final Destination UPDATE_SERVER = new Destination("pSWieSRB3czCl3Zz4WpKp4Z8tjv-05zbogRDS7SEnKcSdWOupVwjzQ92GsgQh1VqgoSRk1F8dpZOnHxxz5HFy9D7ri0uFdkMyXdSKoB7IgkkvCfTAyEmeaPwSYnurF3Zk7u286E7YG2rZkQZgJ77tow7ZS0mxFB7Z0Ti-VkZ9~GeGePW~howwNm4iSQACZA0DyTpI8iv5j4I0itPCQRgaGziob~Vfvjk49nd8N4jtaDGo9cEcafikVzQ2OgBgYWL6LRbrrItwuGqsDvITUHWaElUYIDhRQYUq8gYiUA6rwAJputfhFU0J7lIxFR9vVY7YzRvcFckfr0DNI4VQVVlPnRPkUxQa--BlldMaCIppWugjgKLwqiSiHywKpSMlBWgY2z1ry4ueEBo1WEP-mEf88wRk4cFQBCKtctCQnIG2GsnATqTl-VGUAsuzeNWZiFSwXiTy~gQ094yWx-K06fFZUDt4CMiLZVhGlixiInD~34FCRC9LVMtFcqiFB2M-Ql2AAAA")
|
static final Destination UPDATE_SERVER = new Destination("VJYAiCPZHNLraWvLkeRLxRiT4PHAqNqRO1nH240r7u1noBw8Pa~-lJOhKR7CccPkEN8ejSi4H6XjqKYLC8BKLVLeOgnAbedUVx81MV7DETPDdPEGV4RVu6YDFri7-tJOeqauGHxtlXT44YWuR69xKrTG3u4~iTWgxKnlBDht9Q3aVpSPFD2KqEizfVxolqXI0zmAZ2xMi8jfl0oe4GbgHrD9hR2FYj6yKfdqcUgHVobY4kDdJt-u31QqwWdsQMEj8Y3tR2XcNaITEVPiAjoKgBrYwB4jddWPNaT4XdHz76d9p9Iqes7dhOKq3OKpk6kg-bfIKiEOiA1mY49fn5h8pNShTqV7QBhh4CE4EDT3Szl~WsLdrlHUKJufSi7erEMh3coF7HORpF1wah2Xw7q470t~b8dKGKi7N7xQsqhGruDm66PH9oE9Kt9WBVBq2zORdPRtRM61I7EnrwDlbOkL0y~XpvQ3JKUQKdBQ3QsOJt8CHlhHHXMMbvqhntR61RSDBQAEAAcAAA==")
|
||||||
}
|
}
|
||||||
|
@@ -83,7 +83,7 @@ class ContentUploader extends Uploader {
|
|||||||
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
String xHave = DataUtil.encodeXHave(mesh.pieces.getDownloaded(), mesh.pieces.nPieces)
|
||||||
endpoint.getOutputStream().write("X-Have: $xHave\r\n".getBytes(StandardCharsets.US_ASCII))
|
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()) {
|
if (!sources.isEmpty()) {
|
||||||
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
String xAlts = sources.stream().map({ it.toBase64() }).collect(Collectors.joining(","))
|
||||||
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
endpoint.getOutputStream().write("X-Alt: $xAlts\r\n".getBytes(StandardCharsets.US_ASCII))
|
||||||
|
@@ -11,40 +11,40 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
class DataUtil {
|
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) {
|
static void writeUnsignedShort(int value, OutputStream os) {
|
||||||
if (value > MAX_SHORT || value < 0)
|
if (value > MAX_SHORT || value < 0)
|
||||||
throw new IllegalArgumentException("$value invalid")
|
throw new IllegalArgumentException("$value invalid")
|
||||||
|
|
||||||
byte lsb = (byte) (value & 0xFF)
|
byte lsb = (byte) (value & 0xFF)
|
||||||
byte msb = (byte) (value >> 8)
|
byte msb = (byte) (value >> 8)
|
||||||
|
|
||||||
os.write(msb)
|
os.write(msb)
|
||||||
os.write(lsb)
|
os.write(lsb)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static int MAX_HEADER = 0x7FFFFF
|
private final static int MAX_HEADER = 0x7FFFFF
|
||||||
|
|
||||||
static void packHeader(int length, byte [] header) {
|
static void packHeader(int length, byte [] header) {
|
||||||
if (header.length != 3)
|
if (header.length != 3)
|
||||||
throw new IllegalArgumentException("header length $header.length")
|
throw new IllegalArgumentException("header length $header.length")
|
||||||
if (length < 0 || length > MAX_HEADER)
|
if (length < 0 || length > MAX_HEADER)
|
||||||
throw new IllegalArgumentException("length $length")
|
throw new IllegalArgumentException("length $length")
|
||||||
|
|
||||||
header[2] = (byte) (length & 0xFF)
|
header[2] = (byte) (length & 0xFF)
|
||||||
header[1] = (byte) ((length >> 8) & 0xFF)
|
header[1] = (byte) ((length >> 8) & 0xFF)
|
||||||
header[0] = (byte) ((length >> 16) & 0x7F)
|
header[0] = (byte) ((length >> 16) & 0x7F)
|
||||||
}
|
}
|
||||||
|
|
||||||
static int readLength(byte [] header) {
|
static int readLength(byte [] header) {
|
||||||
if (header.length != 3)
|
if (header.length != 3)
|
||||||
throw new IllegalArgumentException("header length $header.length")
|
throw new IllegalArgumentException("header length $header.length")
|
||||||
|
|
||||||
return (((int)(header[0] & 0x7F)) << 16) |
|
return (((int)(header[0] & 0x7F)) << 16) |
|
||||||
(((int)(header[1] & 0xFF) << 8)) |
|
(((int)(header[1] & 0xFF) << 8)) |
|
||||||
((int)header[2] & 0xFF)
|
((int)header[2] & 0xFF)
|
||||||
}
|
}
|
||||||
|
|
||||||
static String readi18nString(byte [] encoded) {
|
static String readi18nString(byte [] encoded) {
|
||||||
if (encoded.length < 2)
|
if (encoded.length < 2)
|
||||||
|
@@ -8,16 +8,16 @@ import net.i2p.data.Destination;
|
|||||||
|
|
||||||
public class DownloadedFile extends SharedFile {
|
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)
|
public DownloadedFile(File file, InfoHash infoHash, int pieceSize, Set<Destination> sources)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
super(file, infoHash, pieceSize);
|
super(file, infoHash, pieceSize);
|
||||||
this.sources = sources;
|
this.sources = sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Destination> getSources() {
|
public Set<Destination> getSources() {
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,83 +11,83 @@ import net.i2p.data.Base64;
|
|||||||
|
|
||||||
public class InfoHash {
|
public class InfoHash {
|
||||||
|
|
||||||
public static final int SIZE = 0x1 << 5;
|
public static final int SIZE = 0x1 << 5;
|
||||||
|
|
||||||
private final byte[] root;
|
private final byte[] root;
|
||||||
private final byte[] hashList;
|
private final byte[] hashList;
|
||||||
|
|
||||||
private final int hashCode;
|
private final int hashCode;
|
||||||
|
|
||||||
public InfoHash(byte[] root, byte[] hashList) {
|
public InfoHash(byte[] root, byte[] hashList) {
|
||||||
if (root.length != SIZE)
|
if (root.length != SIZE)
|
||||||
throw new IllegalArgumentException("invalid root size "+root.length);
|
throw new IllegalArgumentException("invalid root size "+root.length);
|
||||||
if (hashList != null && hashList.length % SIZE != 0)
|
if (hashList != null && hashList.length % SIZE != 0)
|
||||||
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
throw new IllegalArgumentException("invalid hashList size " + hashList.length);
|
||||||
this.root = root;
|
this.root = root;
|
||||||
this.hashList = hashList;
|
this.hashList = hashList;
|
||||||
hashCode = root[0] << 24 |
|
hashCode = root[0] << 24 |
|
||||||
root[1] << 16 |
|
root[1] << 16 |
|
||||||
root[2] << 8 |
|
root[2] << 8 |
|
||||||
root[3];
|
root[3];
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash(byte[] root) {
|
public InfoHash(byte[] root) {
|
||||||
this(root, null);
|
this(root, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash(String base32) {
|
public InfoHash(String base32) {
|
||||||
this(Base32.decode(base32));
|
this(Base32.decode(base32));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InfoHash fromHashList(byte []hashList) {
|
public static InfoHash fromHashList(byte []hashList) {
|
||||||
try {
|
try {
|
||||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||||
byte[] root = sha256.digest(hashList);
|
byte[] root = sha256.digest(hashList);
|
||||||
return new InfoHash(root, hashList);
|
return new InfoHash(root, hashList);
|
||||||
} catch (NoSuchAlgorithmException impossible) {
|
} catch (NoSuchAlgorithmException impossible) {
|
||||||
impossible.printStackTrace();
|
impossible.printStackTrace();
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getRoot() {
|
public byte[] getRoot() {
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getHashList() {
|
public byte[] getHashList() {
|
||||||
return hashList;
|
return hashList;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return hashCode;
|
return hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!(o instanceof InfoHash)) {
|
if (!(o instanceof InfoHash)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
InfoHash other = (InfoHash) o;
|
InfoHash other = (InfoHash) o;
|
||||||
return Arrays.equals(root, other.root);
|
return Arrays.equals(root, other.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
String rv = "InfoHash[root:"+Base64.encode(root) + " hashList:";
|
||||||
List<String> b64HashList = new ArrayList<>();
|
List<String> b64HashList = new ArrayList<>();
|
||||||
if (hashList != null) {
|
if (hashList != null) {
|
||||||
byte [] tmp = new byte[SIZE];
|
byte [] tmp = new byte[SIZE];
|
||||||
for (int i = 0; i < hashList.length / SIZE; i++) {
|
for (int i = 0; i < hashList.length / SIZE; i++) {
|
||||||
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
System.arraycopy(hashList, SIZE * i, tmp, 0, SIZE);
|
||||||
b64HashList.add(Base64.encode(tmp));
|
b64HashList.add(Base64.encode(tmp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rv += b64HashList.toString();
|
rv += b64HashList.toString();
|
||||||
rv += "]";
|
rv += "]";
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,60 +5,60 @@ import java.io.IOException;
|
|||||||
|
|
||||||
public class SharedFile {
|
public class SharedFile {
|
||||||
|
|
||||||
private final File file;
|
private final File file;
|
||||||
private final InfoHash infoHash;
|
private final InfoHash infoHash;
|
||||||
private final int pieceSize;
|
private final int pieceSize;
|
||||||
|
|
||||||
private final String cachedPath;
|
private final String cachedPath;
|
||||||
private final long cachedLength;
|
private final long cachedLength;
|
||||||
|
|
||||||
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
public SharedFile(File file, InfoHash infoHash, int pieceSize) throws IOException {
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.infoHash = infoHash;
|
this.infoHash = infoHash;
|
||||||
this.pieceSize = pieceSize;
|
this.pieceSize = pieceSize;
|
||||||
this.cachedPath = file.getAbsolutePath();
|
this.cachedPath = file.getAbsolutePath();
|
||||||
this.cachedLength = file.length();
|
this.cachedLength = file.length();
|
||||||
}
|
}
|
||||||
|
|
||||||
public File getFile() {
|
public File getFile() {
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InfoHash getInfoHash() {
|
public InfoHash getInfoHash() {
|
||||||
return infoHash;
|
return infoHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPieceSize() {
|
public int getPieceSize() {
|
||||||
return pieceSize;
|
return pieceSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNPieces() {
|
public int getNPieces() {
|
||||||
long length = file.length();
|
long length = file.length();
|
||||||
int rawPieceSize = 0x1 << pieceSize;
|
int rawPieceSize = 0x1 << pieceSize;
|
||||||
int rv = (int) (length / rawPieceSize);
|
int rv = (int) (length / rawPieceSize);
|
||||||
if (length % rawPieceSize != 0)
|
if (length % rawPieceSize != 0)
|
||||||
rv++;
|
rv++;
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCachedPath() {
|
public String getCachedPath() {
|
||||||
return cachedPath;
|
return cachedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getCachedLength() {
|
public long getCachedLength() {
|
||||||
return cachedLength;
|
return cachedLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return file.hashCode() ^ infoHash.hashCode();
|
return file.hashCode() ^ infoHash.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (!(o instanceof SharedFile))
|
if (!(o instanceof SharedFile))
|
||||||
return false;
|
return false;
|
||||||
SharedFile other = (SharedFile)o;
|
SharedFile other = (SharedFile)o;
|
||||||
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
return file.equals(other.file) && infoHash.equals(other.infoHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package com.muwire.core.connection;
|
package com.muwire.core.connection;
|
||||||
|
|
||||||
public enum ConnectionAttemptStatus {
|
public enum ConnectionAttemptStatus {
|
||||||
SUCCESSFUL, REJECTED, FAILED
|
SUCCESSFUL, REJECTED, FAILED
|
||||||
}
|
}
|
||||||
|
@@ -6,5 +6,5 @@ package com.muwire.core.hostcache;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public enum CrawlerResponse {
|
public enum CrawlerResponse {
|
||||||
ALL, REGISTERED, NONE
|
ALL, REGISTERED, NONE
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
package com.muwire.core.trust;
|
package com.muwire.core.trust;
|
||||||
|
|
||||||
public enum TrustLevel {
|
public enum TrustLevel {
|
||||||
TRUSTED, NEUTRAL, DISTRUSTED
|
TRUSTED, NEUTRAL, DISTRUSTED
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,6 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class Destinations {
|
class Destinations {
|
||||||
|
|
||||||
Destination dest1 = new Destination("KvwWPKMSAtzf7Yruj8TQaHi2jaQpSNsXJskbpmSBTxkcYlDB2GllH~QBu-cs4FSYdaRmKDUUx7793jjnYJgTMbrjqeIL5-BTORZ09n6PUfhSejDpJjdkUxaV1OHRatfYs70RNBv7rvdj1-nXUow5tMfOJtoWVocUoKefUGFQFbJLDDkBqjm1kFyKFZv6m6S6YqXxBgVB1qYicooy67cNQF5HLUFtP15pk5fMDNGz5eNCjPfC~2Gp8FF~OpSy92HT0XN7uAMJykPcbdnWfcvVwqD7eS0K4XEnsqnMPLEiMAhqsugEFiFqtB3Wmm7UHVc03lcAfRhr1e2uZBNFTtM2Uol4MD5sCCKRZVHGcH-WGPSEz0BM5YO~Xi~dQ~N3NVud32PVzhh8xoGcAlhTqMqAbRJndCv-H6NflX90pYmbirCTIDOaR9758mThrqX0d4CwCn4jFXer52l8Qe8CErGoLuB-4LL~Gwrn7R1k7ZQc2PthkqeW8MfigyiN7hZVkul9AAAA")
|
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 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 EventBusTest {
|
||||||
|
|
||||||
class FakeEvent extends Event {}
|
class FakeEvent extends Event {}
|
||||||
|
|
||||||
class FakeEventHandler {
|
class FakeEventHandler {
|
||||||
def onFakeEvent(FakeEvent e) {
|
def onFakeEvent(FakeEvent e) {
|
||||||
assert e == fakeEvent
|
assert e == fakeEvent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FakeEvent fakeEvent = new FakeEvent()
|
FakeEvent fakeEvent = new FakeEvent()
|
||||||
|
|
||||||
EventBus bus = new EventBus()
|
EventBus bus = new EventBus()
|
||||||
def handler = new FakeEventHandler()
|
def handler = new FakeEventHandler()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDynamicEvent() {
|
void testDynamicEvent() {
|
||||||
bus.register(FakeEvent.class, handler)
|
bus.register(FakeEvent.class, handler)
|
||||||
bus.publish(fakeEvent)
|
bus.publish(fakeEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -6,11 +6,11 @@ import org.junit.Test
|
|||||||
|
|
||||||
class InfoHashTest {
|
class InfoHashTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmpty() {
|
void testEmpty() {
|
||||||
byte [] empty = new byte[0x1 << 6];
|
byte [] empty = new byte[0x1 << 6];
|
||||||
def ih = InfoHash.fromHashList(empty)
|
def ih = InfoHash.fromHashList(empty)
|
||||||
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
def ih2 = new InfoHash("6ws72qwrniqdaj4y55xngcmxtnbqapjdedm7b2hktay2sj2z7nfq");
|
||||||
assertEquals(ih, ih2);
|
assertEquals(ih, ih2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,21 +22,21 @@ import groovy.mock.interceptor.MockFor
|
|||||||
|
|
||||||
class ConnectionAcceptorTest {
|
class ConnectionAcceptorTest {
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
final Destinations destinations = new Destinations()
|
final Destinations destinations = new Destinations()
|
||||||
def settings
|
def settings
|
||||||
|
|
||||||
def connectionManagerMock
|
def connectionManagerMock
|
||||||
UltrapeerConnectionManager connectionManager
|
UltrapeerConnectionManager connectionManager
|
||||||
|
|
||||||
def i2pAcceptorMock
|
def i2pAcceptorMock
|
||||||
I2PAcceptor i2pAcceptor
|
I2PAcceptor i2pAcceptor
|
||||||
|
|
||||||
def hostCacheMock
|
def hostCacheMock
|
||||||
HostCache hostCache
|
HostCache hostCache
|
||||||
|
|
||||||
def trustServiceMock
|
def trustServiceMock
|
||||||
TrustService trustService
|
TrustService trustService
|
||||||
|
|
||||||
def searchManagerMock
|
def searchManagerMock
|
||||||
SearchManager searchManager
|
SearchManager searchManager
|
||||||
@@ -47,361 +47,361 @@ class ConnectionAcceptorTest {
|
|||||||
def connectionEstablisherMock
|
def connectionEstablisherMock
|
||||||
ConnectionEstablisher connectionEstablisher
|
ConnectionEstablisher connectionEstablisher
|
||||||
|
|
||||||
ConnectionAcceptor acceptor
|
ConnectionAcceptor acceptor
|
||||||
List<ConnectionEvent> connectionEvents
|
List<ConnectionEvent> connectionEvents
|
||||||
InputStream inputStream
|
InputStream inputStream
|
||||||
OutputStream outputStream
|
OutputStream outputStream
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
connectionManagerMock = new MockFor(UltrapeerConnectionManager.class)
|
||||||
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
i2pAcceptorMock = new MockFor(I2PAcceptor.class)
|
||||||
hostCacheMock = new MockFor(HostCache.class)
|
hostCacheMock = new MockFor(HostCache.class)
|
||||||
trustServiceMock = new MockFor(TrustService.class)
|
trustServiceMock = new MockFor(TrustService.class)
|
||||||
searchManagerMock = new MockFor(SearchManager.class)
|
searchManagerMock = new MockFor(SearchManager.class)
|
||||||
uploadManagerMock = new MockFor(UploadManager.class)
|
uploadManagerMock = new MockFor(UploadManager.class)
|
||||||
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
connectionEstablisherMock = new MockFor(ConnectionEstablisher.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
acceptor?.stop()
|
acceptor?.stop()
|
||||||
connectionManagerMock.verify connectionManager
|
connectionManagerMock.verify connectionManager
|
||||||
i2pAcceptorMock.verify i2pAcceptor
|
i2pAcceptorMock.verify i2pAcceptor
|
||||||
hostCacheMock.verify hostCache
|
hostCacheMock.verify hostCache
|
||||||
trustServiceMock.verify trustService
|
trustServiceMock.verify trustService
|
||||||
searchManagerMock.verify searchManager
|
searchManagerMock.verify searchManager
|
||||||
uploadManagerMock.verify uploadManager
|
uploadManagerMock.verify uploadManager
|
||||||
connectionEstablisherMock.verify connectionEstablisher
|
connectionEstablisherMock.verify connectionEstablisher
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMocks() {
|
private void initMocks() {
|
||||||
connectionEvents = new CopyOnWriteArrayList()
|
connectionEvents = new CopyOnWriteArrayList()
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
def listener = new Object() {
|
def listener = new Object() {
|
||||||
void onConnectionEvent(ConnectionEvent e) {
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
connectionEvents.add e
|
connectionEvents.add e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventBus.register(ConnectionEvent.class, listener)
|
eventBus.register(ConnectionEvent.class, listener)
|
||||||
|
|
||||||
connectionManager = connectionManagerMock.proxyInstance()
|
connectionManager = connectionManagerMock.proxyInstance()
|
||||||
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
i2pAcceptor = i2pAcceptorMock.proxyInstance()
|
||||||
hostCache = hostCacheMock.proxyInstance()
|
hostCache = hostCacheMock.proxyInstance()
|
||||||
trustService = trustServiceMock.proxyInstance()
|
trustService = trustServiceMock.proxyInstance()
|
||||||
searchManager = searchManagerMock.proxyInstance()
|
searchManager = searchManagerMock.proxyInstance()
|
||||||
uploadManager = uploadManagerMock.proxyInstance()
|
uploadManager = uploadManagerMock.proxyInstance()
|
||||||
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
connectionEstablisher = connectionEstablisherMock.proxyInstance()
|
||||||
|
|
||||||
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
acceptor = new ConnectionAcceptor(eventBus, connectionManager, settings, i2pAcceptor,
|
||||||
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
hostCache, trustService, searchManager, uploadManager, connectionEstablisher)
|
||||||
acceptor.start()
|
acceptor.start()
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccessfulLeaf() {
|
void testSuccessfulLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasLeafSlots() { true }
|
connectionManagerMock.demand.hasLeafSlots() { true }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
byte [] OK = new byte[2]
|
byte [] OK = new byte[2]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "OK".bytes
|
assert OK == "OK".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == true
|
assert event.leaf == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSuccessfulPeer() {
|
void testSuccessfulPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { true }
|
connectionManagerMock.demand.hasPeerSlots() { true }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[2]
|
byte [] OK = new byte[2]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "OK".bytes
|
assert OK == "OK".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == false
|
assert event.leaf == false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLeafRejectsLeaf() {
|
void testLeafRejectsLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert inputStream.read() == -1
|
assert inputStream.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == null
|
assert event.leaf == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLeafRejectsPeer() {
|
void testLeafRejectsPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert inputStream.read() == -1
|
assert inputStream.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == null
|
assert event.leaf == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsPeerSlots() {
|
void testPeerRejectsPeerSlots() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == false
|
assert event.leaf == false
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsLeafSlots() {
|
void testPeerRejectsLeafSlots() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasLeafSlots() { false }
|
connectionManagerMock.demand.hasLeafSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
hostCacheMock.ignore.getGoodHosts { n -> [] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire leaf".bytes)
|
outputStream.write("MuWire leaf".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert event.incoming == true
|
assert event.incoming == true
|
||||||
assert event.leaf == true
|
assert event.leaf == true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPeerRejectsPeerSuggests() {
|
void testPeerRejectsPeerSuggests() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {
|
boolean isLeaf() {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept {
|
i2pAcceptorMock.demand.accept {
|
||||||
def is = new PipedInputStream()
|
def is = new PipedInputStream()
|
||||||
outputStream = new PipedOutputStream(is)
|
outputStream = new PipedOutputStream(is)
|
||||||
def os = new PipedOutputStream()
|
def os = new PipedOutputStream()
|
||||||
inputStream = new PipedInputStream(os)
|
inputStream = new PipedInputStream(os)
|
||||||
new Endpoint(destinations.dest1, is, os, null)
|
new Endpoint(destinations.dest1, is, os, null)
|
||||||
}
|
}
|
||||||
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
i2pAcceptorMock.demand.accept { Thread.sleep(Integer.MAX_VALUE) }
|
||||||
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
connectionEstablisherMock.demand.isInProgress(destinations.dest1) { false }
|
||||||
connectionManagerMock.demand.isConnected { dest ->
|
connectionManagerMock.demand.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
connectionManagerMock.demand.hasPeerSlots() { false }
|
connectionManagerMock.demand.hasPeerSlots() { false }
|
||||||
trustServiceMock.demand.getLevel { dest ->
|
trustServiceMock.demand.getLevel { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
hostCacheMock.ignore.getGoodHosts { n -> [destinations.dest2] }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
outputStream.write("MuWire peer".bytes)
|
outputStream.write("MuWire peer".bytes)
|
||||||
byte [] OK = new byte[6]
|
byte [] OK = new byte[6]
|
||||||
def dis = new DataInputStream(inputStream)
|
def dis = new DataInputStream(inputStream)
|
||||||
dis.readFully(OK)
|
dis.readFully(OK)
|
||||||
assert OK == "REJECT".bytes
|
assert OK == "REJECT".bytes
|
||||||
|
|
||||||
short payloadSize = dis.readUnsignedShort()
|
short payloadSize = dis.readUnsignedShort()
|
||||||
byte[] payload = new byte[payloadSize]
|
byte[] payload = new byte[payloadSize]
|
||||||
dis.readFully(payload)
|
dis.readFully(payload)
|
||||||
assert dis.read() == -1
|
assert dis.read() == -1
|
||||||
|
|
||||||
def json = new JsonSlurper()
|
def json = new JsonSlurper()
|
||||||
json = json.parse(payload)
|
json = json.parse(payload)
|
||||||
assert json.tryHosts != null
|
assert json.tryHosts != null
|
||||||
assert json.tryHosts.size() == 1
|
assert json.tryHosts.size() == 1
|
||||||
assert json.tryHosts.contains(destinations.dest2.toBase64())
|
assert json.tryHosts.contains(destinations.dest2.toBase64())
|
||||||
|
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,271 +17,271 @@ import groovy.mock.interceptor.MockFor
|
|||||||
|
|
||||||
class ConnectionEstablisherTest {
|
class ConnectionEstablisherTest {
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
final Destinations destinations = new Destinations()
|
final Destinations destinations = new Destinations()
|
||||||
List<ConnectionEvent> connectionEvents
|
List<ConnectionEvent> connectionEvents
|
||||||
List<HostDiscoveredEvent> discoveredEvents
|
List<HostDiscoveredEvent> discoveredEvents
|
||||||
DataInputStream inputStream
|
DataInputStream inputStream
|
||||||
DataOutputStream outputStream
|
DataOutputStream outputStream
|
||||||
|
|
||||||
def i2pConnectorMock
|
def i2pConnectorMock
|
||||||
I2PConnector i2pConnector
|
I2PConnector i2pConnector
|
||||||
|
|
||||||
MuWireSettings settings
|
MuWireSettings settings
|
||||||
|
|
||||||
def connectionManagerMock
|
def connectionManagerMock
|
||||||
ConnectionManager connectionManager
|
ConnectionManager connectionManager
|
||||||
|
|
||||||
def hostCacheMock
|
def hostCacheMock
|
||||||
HostCache hostCache
|
HostCache hostCache
|
||||||
|
|
||||||
ConnectionEstablisher establisher
|
ConnectionEstablisher establisher
|
||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
connectionEvents = new CopyOnWriteArrayList()
|
connectionEvents = new CopyOnWriteArrayList()
|
||||||
discoveredEvents = new CopyOnWriteArrayList()
|
discoveredEvents = new CopyOnWriteArrayList()
|
||||||
def listener = new Object() {
|
def listener = new Object() {
|
||||||
void onConnectionEvent(ConnectionEvent e) {
|
void onConnectionEvent(ConnectionEvent e) {
|
||||||
connectionEvents.add(e)
|
connectionEvents.add(e)
|
||||||
}
|
}
|
||||||
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
void onHostDiscoveredEvent(HostDiscoveredEvent e) {
|
||||||
discoveredEvents.add e
|
discoveredEvents.add e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
eventBus.register(ConnectionEvent.class, listener)
|
eventBus.register(ConnectionEvent.class, listener)
|
||||||
eventBus.register(HostDiscoveredEvent.class, listener)
|
eventBus.register(HostDiscoveredEvent.class, listener)
|
||||||
i2pConnectorMock = new MockFor(I2PConnector.class)
|
i2pConnectorMock = new MockFor(I2PConnector.class)
|
||||||
connectionManagerMock = new MockFor(ConnectionManager.class)
|
connectionManagerMock = new MockFor(ConnectionManager.class)
|
||||||
hostCacheMock = new MockFor(HostCache.class)
|
hostCacheMock = new MockFor(HostCache.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
establisher?.stop()
|
establisher?.stop()
|
||||||
i2pConnectorMock.verify i2pConnector
|
i2pConnectorMock.verify i2pConnector
|
||||||
connectionManagerMock.verify connectionManager
|
connectionManagerMock.verify connectionManager
|
||||||
hostCacheMock.verify hostCache
|
hostCacheMock.verify hostCache
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMocks() {
|
private void initMocks() {
|
||||||
i2pConnector = i2pConnectorMock.proxyInstance()
|
i2pConnector = i2pConnectorMock.proxyInstance()
|
||||||
connectionManager = connectionManagerMock.proxyInstance()
|
connectionManager = connectionManagerMock.proxyInstance()
|
||||||
hostCache = hostCacheMock.proxyInstance()
|
hostCache = hostCacheMock.proxyInstance()
|
||||||
establisher = new ConnectionEstablisher(eventBus, i2pConnector, settings, connectionManager, hostCache)
|
establisher = new ConnectionEstablisher(eventBus, i2pConnector, settings, connectionManager, hostCache)
|
||||||
establisher.start()
|
establisher.start()
|
||||||
Thread.sleep(250)
|
Thread.sleep(250)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectFails() {
|
void testConnectFails() {
|
||||||
settings = new MuWireSettings()
|
settings = new MuWireSettings()
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
throw new IOException()
|
throw new IOException()
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.FAILED
|
assert event.status == ConnectionAttemptStatus.FAILED
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionSucceedsPeer() {
|
void testConnectionSucceedsPeer() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {false}
|
boolean isLeaf() {false}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire peer".bytes
|
assert header == "MuWire peer".bytes
|
||||||
|
|
||||||
outputStream.write("OK".bytes)
|
outputStream.write("OK".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionSucceedsLeaf() {
|
void testConnectionSucceedsLeaf() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {true}
|
boolean isLeaf() {true}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire leaf".bytes
|
assert header == "MuWire leaf".bytes
|
||||||
|
|
||||||
outputStream.write("OK".bytes)
|
outputStream.write("OK".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
assert event.status == ConnectionAttemptStatus.SUCCESSFUL
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionRejected() {
|
void testConnectionRejected() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {false}
|
boolean isLeaf() {false}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire peer".bytes
|
assert header == "MuWire peer".bytes
|
||||||
|
|
||||||
outputStream.write("REJECT".bytes)
|
outputStream.write("REJECT".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
assert discoveredEvents.isEmpty()
|
assert discoveredEvents.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testConnectionRejectedSuggestions() {
|
void testConnectionRejectedSuggestions() {
|
||||||
settings = new MuWireSettings() {
|
settings = new MuWireSettings() {
|
||||||
boolean isLeaf() {false}
|
boolean isLeaf() {false}
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.needsConnections {
|
connectionManagerMock.ignore.needsConnections {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
hostCacheMock.ignore.getHosts { num ->
|
hostCacheMock.ignore.getHosts { num ->
|
||||||
assert num == 1
|
assert num == 1
|
||||||
[destinations.dest1]
|
[destinations.dest1]
|
||||||
}
|
}
|
||||||
connectionManagerMock.ignore.isConnected { dest ->
|
connectionManagerMock.ignore.isConnected { dest ->
|
||||||
assert dest == destinations.dest1
|
assert dest == destinations.dest1
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
i2pConnectorMock.demand.connect { dest ->
|
i2pConnectorMock.demand.connect { dest ->
|
||||||
PipedOutputStream os = new PipedOutputStream()
|
PipedOutputStream os = new PipedOutputStream()
|
||||||
inputStream = new DataInputStream(new PipedInputStream(os))
|
inputStream = new DataInputStream(new PipedInputStream(os))
|
||||||
PipedInputStream is = new PipedInputStream()
|
PipedInputStream is = new PipedInputStream()
|
||||||
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
outputStream = new DataOutputStream(new PipedOutputStream(is))
|
||||||
new Endpoint(dest, is, os, null)
|
new Endpoint(dest, is, os, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
byte [] header = new byte[11]
|
byte [] header = new byte[11]
|
||||||
inputStream.readFully(header)
|
inputStream.readFully(header)
|
||||||
assert header == "MuWire peer".bytes
|
assert header == "MuWire peer".bytes
|
||||||
|
|
||||||
outputStream.write("REJECT".bytes)
|
outputStream.write("REJECT".bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.tryHosts = [destinations.dest2.toBase64()]
|
json.tryHosts = [destinations.dest2.toBase64()]
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
outputStream.writeShort(json.bytes.length)
|
outputStream.writeShort(json.bytes.length)
|
||||||
outputStream.write(json.bytes)
|
outputStream.write(json.bytes)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
|
|
||||||
assert connectionEvents.size() == 1
|
assert connectionEvents.size() == 1
|
||||||
def event = connectionEvents[0]
|
def event = connectionEvents[0]
|
||||||
assert event.endpoint.destination == destinations.dest1
|
assert event.endpoint.destination == destinations.dest1
|
||||||
assert event.incoming == false
|
assert event.incoming == false
|
||||||
assert event.status == ConnectionAttemptStatus.REJECTED
|
assert event.status == ConnectionAttemptStatus.REJECTED
|
||||||
|
|
||||||
assert discoveredEvents.size() == 1
|
assert discoveredEvents.size() == 1
|
||||||
event = discoveredEvents[0]
|
event = discoveredEvents[0]
|
||||||
assert event.destination == destinations.dest2
|
assert event.destination == destinations.dest2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,76 +8,76 @@ import org.junit.Test
|
|||||||
|
|
||||||
class FileHasherTest extends GroovyTestCase {
|
class FileHasherTest extends GroovyTestCase {
|
||||||
|
|
||||||
def hasher = new FileHasher()
|
def hasher = new FileHasher()
|
||||||
File tmp
|
File tmp
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void setUp() {
|
void setUp() {
|
||||||
tmp = File.createTempFile("testFile", "test")
|
tmp = File.createTempFile("testFile", "test")
|
||||||
tmp.deleteOnExit()
|
tmp.deleteOnExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void tearDown() {
|
void tearDown() {
|
||||||
tmp?.delete()
|
tmp?.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPieceSize() {
|
void testPieceSize() {
|
||||||
assert 17 == FileHasher.getPieceSize(1000000)
|
assert 17 == FileHasher.getPieceSize(1000000)
|
||||||
assert 17 == FileHasher.getPieceSize(100000000)
|
assert 17 == FileHasher.getPieceSize(100000000)
|
||||||
assert 24 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
assert 24 == FileHasher.getPieceSize(FileHasher.MAX_SIZE)
|
||||||
shouldFail IllegalArgumentException, {
|
shouldFail IllegalArgumentException, {
|
||||||
FileHasher.getPieceSize(Long.MAX_VALUE)
|
FileHasher.getPieceSize(Long.MAX_VALUE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1Byte() {
|
void testHash1Byte() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
fos.write(0)
|
fos.write(0)
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile(tmp)
|
def ih = hasher.hashFile(tmp)
|
||||||
assert ih.getHashList().length == 32
|
assert ih.getHashList().length == 32
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1PieceExact() {
|
void testHash1PieceExact() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ 0x1 << 18]
|
byte [] b = new byte[ 0x1 << 18]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 64
|
assert ih.getHashList().length == 64
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1Piece1Byte() {
|
void testHash1Piece1Byte() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ (0x1 << 18) + 1]
|
byte [] b = new byte[ (0x1 << 18) + 1]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 96
|
assert ih.getHashList().length == 96
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash2Pieces() {
|
void testHash2Pieces() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ (0x1 << 19)]
|
byte [] b = new byte[ (0x1 << 19)]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 128
|
assert ih.getHashList().length == 128
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash2Pieces2Bytes() {
|
void testHash2Pieces2Bytes() {
|
||||||
def fos = new FileOutputStream(tmp)
|
def fos = new FileOutputStream(tmp)
|
||||||
byte [] b = new byte[ (0x1 << 19) + 2]
|
byte [] b = new byte[ (0x1 << 19) + 2]
|
||||||
fos.write b
|
fos.write b
|
||||||
fos.close()
|
fos.close()
|
||||||
def ih = hasher.hashFile tmp
|
def ih = hasher.hashFile tmp
|
||||||
assert ih.getHashList().length == 160
|
assert ih.getHashList().length == 160
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,177 +12,177 @@ import com.muwire.core.search.SearchEvent
|
|||||||
|
|
||||||
class FileManagerTest {
|
class FileManagerTest {
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
|
|
||||||
FileManager manager
|
FileManager manager
|
||||||
volatile ResultsEvent results
|
volatile ResultsEvent results
|
||||||
|
|
||||||
def listener = new Object() {
|
def listener = new Object() {
|
||||||
void onResultsEvent(ResultsEvent e) {
|
void onResultsEvent(ResultsEvent e) {
|
||||||
results = e
|
results = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
eventBus.register(ResultsEvent.class, listener)
|
eventBus.register(ResultsEvent.class, listener)
|
||||||
manager = new FileManager(eventBus, new MuWireSettings())
|
manager = new FileManager(eventBus, new MuWireSettings())
|
||||||
results = null
|
results = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash1Result() {
|
void testHash1Result() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih, 0)
|
SharedFile sf = new SharedFile(f,ih, 0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||||
|
|
||||||
manager.onSearchEvent(se)
|
manager.onSearchEvent(se)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf)
|
assert results.results.contains(sf)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash2Results() {
|
void testHash2Results() {
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
SearchEvent se = new SearchEvent(searchHash: ih.getRoot(), uuid: uuid)
|
||||||
|
|
||||||
manager.onSearchEvent(se)
|
manager.onSearchEvent(se)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 2
|
assert results.results.size() == 2
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
assert results.results.contains(sf2)
|
assert results.results.contains(sf2)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHash0Results() {
|
void testHash0Results() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih, 0)
|
SharedFile sf = new SharedFile(f,ih, 0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
manager.onSearchEvent new SearchEvent(searchHash: new byte[32], uuid: UUID.randomUUID())
|
manager.onSearchEvent new SearchEvent(searchHash: new byte[32], uuid: UUID.randomUUID())
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results == null
|
assert results == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testKeyword1Result() {
|
void testKeyword1Result() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih,0)
|
SharedFile sf = new SharedFile(f,ih,0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["a"], uuid:uuid)
|
manager.onSearchEvent new SearchEvent(searchTerms: ["a"], uuid:uuid)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf)
|
assert results.results.contains(sf)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testKeyword2Results() {
|
void testKeyword2Results() {
|
||||||
File f1 = new File("a b.c")
|
File f1 = new File("a b.c")
|
||||||
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
||||||
|
|
||||||
File f2 = new File("c d.e")
|
File f2 = new File("c d.e")
|
||||||
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
||||||
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
||||||
|
|
||||||
UUID uuid = UUID.randomUUID()
|
UUID uuid = UUID.randomUUID()
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["c"], uuid:uuid)
|
manager.onSearchEvent new SearchEvent(searchTerms: ["c"], uuid:uuid)
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 2
|
assert results.results.size() == 2
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
assert results.results.contains(sf2)
|
assert results.results.contains(sf2)
|
||||||
assert results.uuid == uuid
|
assert results.uuid == uuid
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testKeyword0Results() {
|
void testKeyword0Results() {
|
||||||
File f = new File("a b.c")
|
File f = new File("a b.c")
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf = new SharedFile(f,ih,0)
|
SharedFile sf = new SharedFile(f,ih,0)
|
||||||
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
FileHashedEvent fhe = new FileHashedEvent(sharedFile: sf)
|
||||||
manager.onFileHashedEvent(fhe)
|
manager.onFileHashedEvent(fhe)
|
||||||
|
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"], uuid: UUID.randomUUID())
|
manager.onSearchEvent new SearchEvent(searchTerms: ["d"], uuid: UUID.randomUUID())
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
|
|
||||||
assert results == null
|
assert results == null
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemoveFileExistingHash() {
|
void testRemoveFileExistingHash() {
|
||||||
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
InfoHash ih = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
SharedFile sf1 = new SharedFile(new File("a b.c"), ih, 0)
|
||||||
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
SharedFile sf2 = new SharedFile(new File("d e.f"), ih, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf1)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile : sf2)
|
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())
|
manager.onSearchEvent new SearchEvent(searchHash : ih.getRoot())
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemoveFile() {
|
void testRemoveFile() {
|
||||||
File f1 = new File("a b.c")
|
File f1 = new File("a b.c")
|
||||||
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
InfoHash ih1 = InfoHash.fromHashList(new byte[32])
|
||||||
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
SharedFile sf1 = new SharedFile(f1, ih1, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf1)
|
||||||
|
|
||||||
File f2 = new File("c d.e")
|
File f2 = new File("c d.e")
|
||||||
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
InfoHash ih2 = InfoHash.fromHashList(new byte[64])
|
||||||
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
SharedFile sf2 = new SharedFile(f2, ih2, 0)
|
||||||
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
manager.onFileLoadedEvent new FileLoadedEvent(loadedFile: sf2)
|
||||||
|
|
||||||
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
manager.onFileUnsharedEvent new FileUnsharedEvent(unsharedFile: sf2)
|
||||||
|
|
||||||
// 1 match left
|
// 1 match left
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["c"])
|
manager.onSearchEvent new SearchEvent(searchTerms: ["c"])
|
||||||
Thread.sleep(20)
|
Thread.sleep(20)
|
||||||
assert results != null
|
assert results != null
|
||||||
assert results.results.size() == 1
|
assert results.results.size() == 1
|
||||||
assert results.results.contains(sf1)
|
assert results.results.contains(sf1)
|
||||||
|
|
||||||
// no match
|
// no match
|
||||||
results = null
|
results = null
|
||||||
manager.onSearchEvent new SearchEvent(searchTerms: ["d"])
|
manager.onSearchEvent new SearchEvent(searchTerms: ["d"])
|
||||||
assert results == null
|
assert results == null
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,54 +12,54 @@ import com.muwire.core.MuWireSettings
|
|||||||
|
|
||||||
class HasherServiceTest {
|
class HasherServiceTest {
|
||||||
|
|
||||||
HasherService service
|
HasherService service
|
||||||
FileHasher hasher
|
FileHasher hasher
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
def listener = new ArrayBlockingQueue(100) {
|
def listener = new ArrayBlockingQueue(100) {
|
||||||
void onFileHashedEvent(FileHashedEvent evt) {
|
void onFileHashedEvent(FileHashedEvent evt) {
|
||||||
offer evt
|
offer evt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
hasher = new FileHasher()
|
hasher = new FileHasher()
|
||||||
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
service = new HasherService(hasher, eventBus, new FileManager(eventBus, new MuWireSettings()))
|
||||||
eventBus.register(FileHashedEvent.class, listener)
|
eventBus.register(FileHashedEvent.class, listener)
|
||||||
eventBus.register(FileSharedEvent.class, service)
|
eventBus.register(FileSharedEvent.class, service)
|
||||||
service.start()
|
service.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
listener.clear()
|
listener.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSingleFile() {
|
void testSingleFile() {
|
||||||
File f = new File("build.gradle")
|
File f = new File("build.gradle")
|
||||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||||
Thread.sleep(100)
|
Thread.sleep(100)
|
||||||
def hashed = listener.poll()
|
def hashed = listener.poll()
|
||||||
assert hashed instanceof FileHashedEvent
|
assert hashed instanceof FileHashedEvent
|
||||||
assert hashed.sharedFile.file == f.getCanonicalFile()
|
assert hashed.sharedFile.file == f.getCanonicalFile()
|
||||||
assert hashed.sharedFile.infoHash != null
|
assert hashed.sharedFile.infoHash != null
|
||||||
assert listener.isEmpty()
|
assert listener.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDirectory() {
|
void testDirectory() {
|
||||||
File f = new File(".")
|
File f = new File(".")
|
||||||
service.onFileSharedEvent new FileSharedEvent(file: f)
|
service.onFileSharedEvent new FileSharedEvent(file: f)
|
||||||
Set<String> fileNames = new HashSet<>()
|
Set<String> fileNames = new HashSet<>()
|
||||||
while (true) {
|
while (true) {
|
||||||
def hashed = listener.poll(1000, TimeUnit.MILLISECONDS)
|
def hashed = listener.poll(1000, TimeUnit.MILLISECONDS)
|
||||||
if (hashed == null)
|
if (hashed == null)
|
||||||
break
|
break
|
||||||
fileNames.add(hashed.sharedFile?.file?.getName())
|
fileNames.add(hashed.sharedFile?.file?.getName())
|
||||||
}
|
}
|
||||||
assert fileNames.contains("build.gradle")
|
assert fileNames.contains("build.gradle")
|
||||||
assert fileNames.contains("HasherServiceTest.groovy")
|
assert fileNames.contains("HasherServiceTest.groovy")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,193 +17,193 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
class PersisterServiceLoadingTest {
|
class PersisterServiceLoadingTest {
|
||||||
|
|
||||||
class Listener {
|
class Listener {
|
||||||
def publishedFiles = []
|
def publishedFiles = []
|
||||||
def onFileLoadedEvent(FileLoadedEvent e) {
|
def onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
publishedFiles.add(e.loadedFile)
|
publishedFiles.add(e.loadedFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventBus eventBus
|
EventBus eventBus
|
||||||
Listener listener
|
Listener listener
|
||||||
File sharedDir
|
File sharedDir
|
||||||
File sharedFile1, sharedFile2
|
File sharedFile1, sharedFile2
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void setup() {
|
void setup() {
|
||||||
eventBus = new EventBus()
|
eventBus = new EventBus()
|
||||||
listener = new Listener()
|
listener = new Listener()
|
||||||
eventBus.register(FileLoadedEvent.class, listener)
|
eventBus.register(FileLoadedEvent.class, listener)
|
||||||
|
|
||||||
sharedDir = new File("sharedDir")
|
sharedDir = new File("sharedDir")
|
||||||
sharedDir.mkdir()
|
sharedDir.mkdir()
|
||||||
sharedDir.deleteOnExit()
|
sharedDir.deleteOnExit()
|
||||||
|
|
||||||
sharedFile1 = new File(sharedDir,"file1")
|
sharedFile1 = new File(sharedDir,"file1")
|
||||||
sharedFile1.deleteOnExit()
|
sharedFile1.deleteOnExit()
|
||||||
|
|
||||||
sharedFile2 = new File(sharedDir,"file2")
|
sharedFile2 = new File(sharedDir,"file2")
|
||||||
sharedFile2.deleteOnExit()
|
sharedFile2.deleteOnExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeToSharedFile(File file, int size) {
|
private void writeToSharedFile(File file, int size) {
|
||||||
FileOutputStream fos = new FileOutputStream(file);
|
FileOutputStream fos = new FileOutputStream(file);
|
||||||
fos.write new byte[size]
|
fos.write new byte[size]
|
||||||
fos.close()
|
fos.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
private File initPersisted() {
|
private File initPersisted() {
|
||||||
File persisted = new File("persisted")
|
File persisted = new File("persisted")
|
||||||
if (persisted.exists())
|
if (persisted.exists())
|
||||||
persisted.delete()
|
persisted.delete()
|
||||||
persisted.deleteOnExit()
|
persisted.deleteOnExit()
|
||||||
persisted
|
persisted
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test1SharedFile1Piece() {
|
void test1SharedFile1Piece() {
|
||||||
writeToSharedFile(sharedFile1, 1)
|
writeToSharedFile(sharedFile1, 1)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
|
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = getSharedFileJsonName(sharedFile1)
|
json.file = getSharedFileJsonName(sharedFile1)
|
||||||
json.length = 1
|
json.length = 1
|
||||||
json.infoHash = Base64.encode(ih1.getRoot())
|
json.infoHash = Base64.encode(ih1.getRoot())
|
||||||
json.hashList = [Base64.encode(ih1.getHashList())]
|
json.hashList = [Base64.encode(ih1.getHashList())]
|
||||||
|
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
|
|
||||||
File persisted = initPersisted()
|
File persisted = initPersisted()
|
||||||
persisted.write json
|
persisted.write json
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
def loadedFile = listener.publishedFiles[0]
|
def loadedFile = listener.publishedFiles[0]
|
||||||
assert loadedFile != null
|
assert loadedFile != null
|
||||||
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
||||||
assert loadedFile.infoHash == ih1
|
assert loadedFile.infoHash == ih1
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getSharedFileJsonName(File sharedFile) {
|
private static String getSharedFileJsonName(File sharedFile) {
|
||||||
def encoded = DataUtil.encodei18nString(sharedFile.getCanonicalFile().toString())
|
def encoded = DataUtil.encodei18nString(sharedFile.getCanonicalFile().toString())
|
||||||
Base64.encode(encoded)
|
Base64.encode(encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test1SharedFile2Pieces() {
|
public void test1SharedFile2Pieces() {
|
||||||
writeToSharedFile(sharedFile1, (0x1 << 18) + 1)
|
writeToSharedFile(sharedFile1, (0x1 << 18) + 1)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
|
|
||||||
assert ih1.getHashList().length == 96
|
assert ih1.getHashList().length == 96
|
||||||
|
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.file = getSharedFileJsonName(sharedFile1)
|
json.file = getSharedFileJsonName(sharedFile1)
|
||||||
json.length = sharedFile1.length()
|
json.length = sharedFile1.length()
|
||||||
json.infoHash = Base64.encode ih1.getRoot()
|
json.infoHash = Base64.encode ih1.getRoot()
|
||||||
|
|
||||||
byte [] tmp = new byte[32]
|
byte [] tmp = new byte[32]
|
||||||
System.arraycopy(ih1.getHashList(), 0, tmp, 0, 32)
|
System.arraycopy(ih1.getHashList(), 0, tmp, 0, 32)
|
||||||
String hash1 = Base64.encode(tmp)
|
String hash1 = Base64.encode(tmp)
|
||||||
System.arraycopy(ih1.getHashList(), 32, tmp, 0, 32)
|
System.arraycopy(ih1.getHashList(), 32, tmp, 0, 32)
|
||||||
String hash2 = Base64.encode(tmp)
|
String hash2 = Base64.encode(tmp)
|
||||||
System.arraycopy(ih1.getHashList(), 64, tmp, 0, 32)
|
System.arraycopy(ih1.getHashList(), 64, tmp, 0, 32)
|
||||||
String hash3 = Base64.encode(tmp)
|
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()
|
File persisted = initPersisted()
|
||||||
persisted.write json
|
persisted.write json
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
def loadedFile = listener.publishedFiles[0]
|
def loadedFile = listener.publishedFiles[0]
|
||||||
assert loadedFile != null
|
assert loadedFile != null
|
||||||
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
assert loadedFile.file == sharedFile1.getCanonicalFile()
|
||||||
assert loadedFile.infoHash == ih1
|
assert loadedFile.infoHash == ih1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test2SharedFiles() {
|
void test2SharedFiles() {
|
||||||
writeToSharedFile(sharedFile1, 1)
|
writeToSharedFile(sharedFile1, 1)
|
||||||
writeToSharedFile(sharedFile2, 2)
|
writeToSharedFile(sharedFile2, 2)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
InfoHash ih2 = fh.hashFile(sharedFile2)
|
InfoHash ih2 = fh.hashFile(sharedFile2)
|
||||||
|
|
||||||
assert ih1 != ih2
|
assert ih1 != ih2
|
||||||
|
|
||||||
File persisted = initPersisted()
|
File persisted = initPersisted()
|
||||||
|
|
||||||
def json1 = [:]
|
def json1 = [:]
|
||||||
json1.file = getSharedFileJsonName(sharedFile1)
|
json1.file = getSharedFileJsonName(sharedFile1)
|
||||||
json1.length = 1
|
json1.length = 1
|
||||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||||
|
|
||||||
json1 = JsonOutput.toJson(json1)
|
json1 = JsonOutput.toJson(json1)
|
||||||
|
|
||||||
def json2 = [:]
|
def json2 = [:]
|
||||||
json2.file = getSharedFileJsonName(sharedFile2)
|
json2.file = getSharedFileJsonName(sharedFile2)
|
||||||
json2.length = 2
|
json2.length = 2
|
||||||
json2.infoHash = Base64.encode(ih2.getRoot())
|
json2.infoHash = Base64.encode(ih2.getRoot())
|
||||||
json2.hashList = [Base64.encode(ih2.getHashList())]
|
json2.hashList = [Base64.encode(ih2.getHashList())]
|
||||||
|
|
||||||
json2 = JsonOutput.toJson(json2)
|
json2 = JsonOutput.toJson(json2)
|
||||||
|
|
||||||
persisted.append "$json1\n"
|
persisted.append "$json1\n"
|
||||||
persisted.append "$json2\n"
|
persisted.append "$json2\n"
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 2
|
assert listener.publishedFiles.size() == 2
|
||||||
def loadedFile1 = listener.publishedFiles[0]
|
def loadedFile1 = listener.publishedFiles[0]
|
||||||
assert loadedFile1.file == sharedFile1.getCanonicalFile()
|
assert loadedFile1.file == sharedFile1.getCanonicalFile()
|
||||||
assert loadedFile1.infoHash == ih1
|
assert loadedFile1.infoHash == ih1
|
||||||
def loadedFile2 = listener.publishedFiles[1]
|
def loadedFile2 = listener.publishedFiles[1]
|
||||||
assert loadedFile2.file == sharedFile2.getCanonicalFile()
|
assert loadedFile2.file == sharedFile2.getCanonicalFile()
|
||||||
assert loadedFile2.infoHash == ih2
|
assert loadedFile2.infoHash == ih2
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDownloadedFile() {
|
void testDownloadedFile() {
|
||||||
writeToSharedFile(sharedFile1, 1)
|
writeToSharedFile(sharedFile1, 1)
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih1 = fh.hashFile(sharedFile1)
|
InfoHash ih1 = fh.hashFile(sharedFile1)
|
||||||
|
|
||||||
File persisted = initPersisted()
|
File persisted = initPersisted()
|
||||||
|
|
||||||
Destinations dests = new Destinations()
|
Destinations dests = new Destinations()
|
||||||
def json1 = [:]
|
def json1 = [:]
|
||||||
json1.file = getSharedFileJsonName(sharedFile1)
|
json1.file = getSharedFileJsonName(sharedFile1)
|
||||||
json1.length = 1
|
json1.length = 1
|
||||||
json1.infoHash = Base64.encode(ih1.getRoot())
|
json1.infoHash = Base64.encode(ih1.getRoot())
|
||||||
json1.hashList = [Base64.encode(ih1.getHashList())]
|
json1.hashList = [Base64.encode(ih1.getHashList())]
|
||||||
json1.sources = [ dests.dest1.toBase64(), dests.dest2.toBase64()]
|
json1.sources = [ dests.dest1.toBase64(), dests.dest2.toBase64()]
|
||||||
|
|
||||||
json1 = JsonOutput.toJson(json1)
|
json1 = JsonOutput.toJson(json1)
|
||||||
persisted.write json1
|
persisted.write json1
|
||||||
|
|
||||||
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
PersisterService ps = new PersisterService(persisted, eventBus, 100, null)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(2000)
|
Thread.sleep(2000)
|
||||||
|
|
||||||
assert listener.publishedFiles.size() == 1
|
assert listener.publishedFiles.size() == 1
|
||||||
def loadedFile1 = listener.publishedFiles[0]
|
def loadedFile1 = listener.publishedFiles[0]
|
||||||
assert loadedFile1 instanceof DownloadedFile
|
assert loadedFile1 instanceof DownloadedFile
|
||||||
assert loadedFile1.sources.size() == 2
|
assert loadedFile1.sources.size() == 2
|
||||||
assert loadedFile1.sources.contains(dests.dest1)
|
assert loadedFile1.sources.contains(dests.dest1)
|
||||||
assert loadedFile1.sources.contains(dests.dest2)
|
assert loadedFile1.sources.contains(dests.dest2)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,79 +18,79 @@ import net.i2p.data.Base64
|
|||||||
|
|
||||||
class PersisterServiceSavingTest {
|
class PersisterServiceSavingTest {
|
||||||
|
|
||||||
File f
|
File f
|
||||||
FileHasher fh = new FileHasher()
|
FileHasher fh = new FileHasher()
|
||||||
InfoHash ih
|
InfoHash ih
|
||||||
SharedFile sf
|
SharedFile sf
|
||||||
def fileSource
|
def fileSource
|
||||||
EventBus eventBus = new EventBus()
|
EventBus eventBus = new EventBus()
|
||||||
File persisted
|
File persisted
|
||||||
PersisterService ps
|
PersisterService ps
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
f = new File("build.gradle")
|
f = new File("build.gradle")
|
||||||
f = f.getCanonicalFile()
|
f = f.getCanonicalFile()
|
||||||
ih = fh.hashFile(f)
|
ih = fh.hashFile(f)
|
||||||
fileSource = new FileManager(eventBus, new MuWireSettings()) {
|
fileSource = new FileManager(eventBus, new MuWireSettings()) {
|
||||||
Map<File, SharedFile> getSharedFiles() {
|
Map<File, SharedFile> getSharedFiles() {
|
||||||
Map<File, SharedFile> rv = new HashMap<>()
|
Map<File, SharedFile> rv = new HashMap<>()
|
||||||
rv.put(f, sf)
|
rv.put(f, sf)
|
||||||
rv
|
rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
persisted = new File("persisted")
|
persisted = new File("persisted")
|
||||||
persisted.delete()
|
persisted.delete()
|
||||||
persisted.deleteOnExit()
|
persisted.deleteOnExit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
ps?.stop()
|
ps?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String fromB64(String text) {
|
private static String fromB64(String text) {
|
||||||
DataUtil.readi18nString(Base64.decode(text))
|
DataUtil.readi18nString(Base64.decode(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSavingSharedFile() {
|
void testSavingSharedFile() {
|
||||||
sf = new SharedFile(f, ih, 0)
|
sf = new SharedFile(f, ih, 0)
|
||||||
|
|
||||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(1500)
|
Thread.sleep(1500)
|
||||||
|
|
||||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||||
persisted.eachLine {
|
persisted.eachLine {
|
||||||
def json = jsonSlurper.parseText(it)
|
def json = jsonSlurper.parseText(it)
|
||||||
assert fromB64(json.file) == f.toString()
|
assert fromB64(json.file) == f.toString()
|
||||||
assert json.length == f.length()
|
assert json.length == f.length()
|
||||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSavingDownloadedFile() {
|
void testSavingDownloadedFile() {
|
||||||
Destinations dests = new Destinations()
|
Destinations dests = new Destinations()
|
||||||
sf = new DownloadedFile(f, ih, 0, new HashSet([dests.dest1, dests.dest2]))
|
sf = new DownloadedFile(f, ih, 0, new HashSet([dests.dest1, dests.dest2]))
|
||||||
|
|
||||||
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
ps = new PersisterService(persisted, eventBus, 100, fileSource)
|
||||||
ps.onUILoadedEvent(null)
|
ps.onUILoadedEvent(null)
|
||||||
Thread.sleep(1500)
|
Thread.sleep(1500)
|
||||||
|
|
||||||
JsonSlurper jsonSlurper = new JsonSlurper()
|
JsonSlurper jsonSlurper = new JsonSlurper()
|
||||||
persisted.eachLine {
|
persisted.eachLine {
|
||||||
def json = jsonSlurper.parseText(it)
|
def json = jsonSlurper.parseText(it)
|
||||||
assert fromB64(json.file) == f.toString()
|
assert fromB64(json.file) == f.toString()
|
||||||
assert json.length == f.length()
|
assert json.length == f.length()
|
||||||
assert json.infoHash == Base64.encode(ih.getRoot())
|
assert json.infoHash == Base64.encode(ih.getRoot())
|
||||||
assert json.hashList == [Base64.encode(ih.getHashList())]
|
assert json.hashList == [Base64.encode(ih.getHashList())]
|
||||||
assert json.sources.size() == 2
|
assert json.sources.size() == 2
|
||||||
assert json.sources.contains(dests.dest1.toBase64())
|
assert json.sources.contains(dests.dest1.toBase64())
|
||||||
assert json.sources.contains(dests.dest2.toBase64())
|
assert json.sources.contains(dests.dest2.toBase64())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -21,250 +21,250 @@ import net.i2p.data.Destination
|
|||||||
class HostCacheTest {
|
class HostCacheTest {
|
||||||
|
|
||||||
|
|
||||||
File persist
|
File persist
|
||||||
HostCache cache
|
HostCache cache
|
||||||
|
|
||||||
def trustMock
|
def trustMock
|
||||||
TrustService trust
|
TrustService trust
|
||||||
|
|
||||||
def settingsMock
|
def settingsMock
|
||||||
MuWireSettings settings
|
MuWireSettings settings
|
||||||
|
|
||||||
Destinations destinations = new Destinations()
|
Destinations destinations = new Destinations()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
persist = new File("hostPersist")
|
persist = new File("hostPersist")
|
||||||
persist.delete()
|
persist.delete()
|
||||||
persist.deleteOnExit()
|
persist.deleteOnExit()
|
||||||
|
|
||||||
trustMock = new MockFor(TrustService.class)
|
trustMock = new MockFor(TrustService.class)
|
||||||
settingsMock = new MockFor(MuWireSettings.class)
|
settingsMock = new MockFor(MuWireSettings.class)
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
cache?.stop()
|
cache?.stop()
|
||||||
trustMock.verify trust
|
trustMock.verify trust
|
||||||
settingsMock.verify settings
|
settingsMock.verify settings
|
||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initMocks() {
|
private void initMocks() {
|
||||||
trust = trustMock.proxyInstance()
|
trust = trustMock.proxyInstance()
|
||||||
settings = settingsMock.proxyInstance()
|
settings = settingsMock.proxyInstance()
|
||||||
cache = new HostCache(trust, persist, 100, settings, new Destination())
|
cache = new HostCache(trust, persist, 100, settings, new Destination())
|
||||||
cache.start()
|
cache.start()
|
||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmpty() {
|
void testEmpty() {
|
||||||
initMocks()
|
initMocks()
|
||||||
assert cache.getHosts(5).size() == 0
|
assert cache.getHosts(5).size() == 0
|
||||||
assert cache.getGoodHosts(5).size() == 0
|
assert cache.getGoodHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnDiscoveredEvent() {
|
void testOnDiscoveredEvent() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
settingsMock.ignore.allowUntrusted { true }
|
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)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
|
|
||||||
assert cache.getGoodHosts(5).size() == 0
|
assert cache.getGoodHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnDiscoveredUntrustedHost() {
|
void testOnDiscoveredUntrustedHost() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.DISTRUSTED
|
TrustLevel.DISTRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
assert cache.getHosts(5).size() == 0
|
assert cache.getHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnDiscoverNeutralHostsProhibited() {
|
void testOnDiscoverNeutralHostsProhibited() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.NEUTRAL
|
TrustLevel.NEUTRAL
|
||||||
}
|
}
|
||||||
settingsMock.ignore.allowUntrusted { false }
|
settingsMock.ignore.allowUntrusted { false }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
|
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
assert cache.getHosts(5).size() == 0
|
assert cache.getHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void test2DiscoveredGoodHosts() {
|
void test2DiscoveredGoodHosts() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest2
|
assert d == destinations.dest2
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||||
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
trustMock.demand.getLevel{ d -> TrustLevel.TRUSTED }
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest2))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest2))
|
||||||
|
|
||||||
def rv = cache.getHosts(1)
|
def rv = cache.getHosts(1)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1) || rv.contains(destinations.dest2)
|
assert rv.contains(destinations.dest1) || rv.contains(destinations.dest2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHostFailed() {
|
void testHostFailed() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
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.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
|
@Test
|
||||||
void testFailedHostSuceeds() {
|
void testFailedHostSuceeds() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
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.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))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
|
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
|
|
||||||
rv = cache.getGoodHosts(5)
|
rv = cache.getGoodHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFailedOnceNoLongerGood() {
|
void testFailedOnceNoLongerGood() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
def endpoint = new Endpoint(destinations.dest1, null, null, null)
|
||||||
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
cache.onConnectionEvent( new ConnectionEvent(endpoint: endpoint, status: ConnectionAttemptStatus.SUCCESSFUL))
|
||||||
|
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
def rv2 = cache.getGoodHosts(5)
|
def rv2 = cache.getGoodHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
assert rv == rv2
|
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)
|
rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
assert cache.getGoodHosts(5).size() == 0
|
assert cache.getGoodHosts(5).size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDuplicateHostDiscovered() {
|
void testDuplicateHostDiscovered() {
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
trustMock.demand.getLevel { d ->
|
trustMock.demand.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
|
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
assert rv.contains(destinations.dest1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSaving() {
|
void testSaving() {
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
initMocks()
|
initMocks()
|
||||||
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
cache.onHostDiscoveredEvent(new HostDiscoveredEvent(destination: destinations.dest1))
|
||||||
Thread.sleep(150)
|
Thread.sleep(150)
|
||||||
|
|
||||||
assert persist.exists()
|
assert persist.exists()
|
||||||
int lines = 0
|
int lines = 0
|
||||||
persist.eachLine {
|
persist.eachLine {
|
||||||
lines++
|
lines++
|
||||||
JsonSlurper slurper = new JsonSlurper()
|
JsonSlurper slurper = new JsonSlurper()
|
||||||
def json = slurper.parseText(it)
|
def json = slurper.parseText(it)
|
||||||
assert json.destination == destinations.dest1.toBase64()
|
assert json.destination == destinations.dest1.toBase64()
|
||||||
assert json.failures == 0
|
assert json.failures == 0
|
||||||
assert json.successes == 0
|
assert json.successes == 0
|
||||||
}
|
}
|
||||||
assert lines == 1
|
assert lines == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoading() {
|
void testLoading() {
|
||||||
def json = [:]
|
def json = [:]
|
||||||
json.destination = destinations.dest1.toBase64()
|
json.destination = destinations.dest1.toBase64()
|
||||||
json.failures = 0
|
json.failures = 0
|
||||||
json.successes = 1
|
json.successes = 1
|
||||||
json = JsonOutput.toJson(json)
|
json = JsonOutput.toJson(json)
|
||||||
persist.append("${json}\n")
|
persist.append("${json}\n")
|
||||||
|
|
||||||
trustMock.ignore.getLevel { d ->
|
trustMock.ignore.getLevel { d ->
|
||||||
assert d == destinations.dest1
|
assert d == destinations.dest1
|
||||||
TrustLevel.TRUSTED
|
TrustLevel.TRUSTED
|
||||||
}
|
}
|
||||||
|
|
||||||
initMocks()
|
initMocks()
|
||||||
def rv = cache.getHosts(5)
|
def rv = cache.getHosts(5)
|
||||||
assert rv.size() == 1
|
assert rv.size() == 1
|
||||||
assert rv.contains(destinations.dest1)
|
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 {
|
class SearchIndexTest {
|
||||||
|
|
||||||
SearchIndex index
|
SearchIndex index
|
||||||
|
|
||||||
private void initIndex(List<String> entries) {
|
private void initIndex(List<String> entries) {
|
||||||
index = new SearchIndex()
|
index = new SearchIndex()
|
||||||
entries.each {
|
entries.each {
|
||||||
index.add(it)
|
index.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSingleTerm() {
|
void testSingleTerm() {
|
||||||
initIndex(["a b.c", "d e.f"])
|
initIndex(["a b.c", "d e.f"])
|
||||||
|
|
||||||
def found = index.search(["a"])
|
def found = index.search(["a"])
|
||||||
assert found.size() == 1
|
assert found.size() == 1
|
||||||
assert found.contains("a b.c")
|
assert found.contains("a b.c")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSingleTermOverlap() {
|
void testSingleTermOverlap() {
|
||||||
initIndex(["a b.c", "c d.e"])
|
initIndex(["a b.c", "c d.e"])
|
||||||
|
|
||||||
def found = index.search(["c"])
|
def found = index.search(["c"])
|
||||||
assert found.size() == 2
|
assert found.size() == 2
|
||||||
assert found.contains("a b.c")
|
assert found.contains("a b.c")
|
||||||
assert found.contains("c d.e")
|
assert found.contains("c d.e")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDrillDownDoesNotModifyIndex() {
|
public void testDrillDownDoesNotModifyIndex() {
|
||||||
@@ -43,46 +43,46 @@ class SearchIndexTest {
|
|||||||
assert found.contains("c d.e")
|
assert found.contains("c d.e")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDrillDown() {
|
void testDrillDown() {
|
||||||
initIndex(["a b.c", "c d.e"])
|
initIndex(["a b.c", "c d.e"])
|
||||||
|
|
||||||
def found = index.search(["c", "e"])
|
def found = index.search(["c", "e"])
|
||||||
assert found.size() == 1
|
assert found.size() == 1
|
||||||
assert found.contains("c d.e")
|
assert found.contains("c d.e")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNotFound() {
|
void testNotFound() {
|
||||||
initIndex(["a b.c"])
|
initIndex(["a b.c"])
|
||||||
def found = index.search(["d"])
|
def found = index.search(["d"])
|
||||||
assert found.size() == 0
|
assert found.size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSomeNotFound() {
|
void testSomeNotFound() {
|
||||||
initIndex(["a b.c"])
|
initIndex(["a b.c"])
|
||||||
def found = index.search(["a","d"])
|
def found = index.search(["a","d"])
|
||||||
assert found.size() == 0
|
assert found.size() == 0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemove() {
|
void testRemove() {
|
||||||
initIndex(["a b.c"])
|
initIndex(["a b.c"])
|
||||||
index.remove("a b.c")
|
index.remove("a b.c")
|
||||||
def found = index.search(["a"])
|
def found = index.search(["a"])
|
||||||
assert found.size() == 0
|
assert found.size() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testRemoveOverlap() {
|
void testRemoveOverlap() {
|
||||||
initIndex(["a b.c", "b c.d"])
|
initIndex(["a b.c", "b c.d"])
|
||||||
index.remove("a b.c")
|
index.remove("a b.c")
|
||||||
def found = index.search(["b"])
|
def found = index.search(["b"])
|
||||||
assert found.size() == 1
|
assert found.size() == 1
|
||||||
assert found.contains("b c.d")
|
assert found.contains("b c.d")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDuplicateTerm() {
|
void testDuplicateTerm() {
|
||||||
|
@@ -13,73 +13,73 @@ import net.i2p.data.Destination
|
|||||||
|
|
||||||
class TrustServiceTest {
|
class TrustServiceTest {
|
||||||
|
|
||||||
TrustService service
|
TrustService service
|
||||||
File persistGood, persistBad
|
File persistGood, persistBad
|
||||||
Personas personas = new Personas()
|
Personas personas = new Personas()
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
void before() {
|
void before() {
|
||||||
persistGood = new File("good.trust")
|
persistGood = new File("good.trust")
|
||||||
persistBad = new File("bad.trust")
|
persistBad = new File("bad.trust")
|
||||||
persistGood.delete()
|
persistGood.delete()
|
||||||
persistBad.delete()
|
persistBad.delete()
|
||||||
persistGood.deleteOnExit()
|
persistGood.deleteOnExit()
|
||||||
persistBad.deleteOnExit()
|
persistBad.deleteOnExit()
|
||||||
service = new TrustService(persistGood, persistBad, 100)
|
service = new TrustService(persistGood, persistBad, 100)
|
||||||
service.start()
|
service.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
void after() {
|
void after() {
|
||||||
service.stop()
|
service.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEmpty() {
|
void testEmpty() {
|
||||||
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona1.destination)
|
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona1.destination)
|
||||||
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona2.destination)
|
assert TrustLevel.NEUTRAL == service.getLevel(personas.persona2.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testOnEvent() {
|
void testOnEvent() {
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||||
|
|
||||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testPersist() {
|
void testPersist() {
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.TRUSTED, persona: personas.persona1)
|
||||||
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
service.onTrustEvent new TrustEvent(level: TrustLevel.DISTRUSTED, persona: personas.persona2)
|
||||||
|
|
||||||
Thread.sleep(250)
|
Thread.sleep(250)
|
||||||
def trusted = new HashSet<>()
|
def trusted = new HashSet<>()
|
||||||
persistGood.eachLine {
|
persistGood.eachLine {
|
||||||
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
trusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||||
}
|
}
|
||||||
def distrusted = new HashSet<>()
|
def distrusted = new HashSet<>()
|
||||||
persistBad.eachLine {
|
persistBad.eachLine {
|
||||||
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
distrusted.add(new Persona(new ByteArrayInputStream(Base64.decode(it))))
|
||||||
}
|
}
|
||||||
|
|
||||||
assert trusted.size() == 1
|
assert trusted.size() == 1
|
||||||
assert trusted.contains(personas.persona1)
|
assert trusted.contains(personas.persona1)
|
||||||
assert distrusted.size() == 1
|
assert distrusted.size() == 1
|
||||||
assert distrusted.contains(personas.persona2)
|
assert distrusted.contains(personas.persona2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testLoad() {
|
void testLoad() {
|
||||||
service.stop()
|
service.stop()
|
||||||
persistGood.append("${personas.persona1.toBase64()}\n")
|
persistGood.append("${personas.persona1.toBase64()}\n")
|
||||||
persistBad.append("${personas.persona2.toBase64()}\n")
|
persistBad.append("${personas.persona2.toBase64()}\n")
|
||||||
service = new TrustService(persistGood, persistBad, 100)
|
service = new TrustService(persistGood, persistBad, 100)
|
||||||
service.start()
|
service.start()
|
||||||
Thread.sleep(50)
|
Thread.sleep(50)
|
||||||
|
|
||||||
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
assert TrustLevel.TRUSTED == service.getLevel(personas.persona1.destination)
|
||||||
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
assert TrustLevel.DISTRUSTED == service.getLevel(personas.persona2.destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,41 +7,41 @@ import org.junit.Test
|
|||||||
class DataUtilTest {
|
class DataUtilTest {
|
||||||
|
|
||||||
|
|
||||||
private static void usVal(int value) {
|
private static void usVal(int value) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()
|
||||||
DataUtil.writeUnsignedShort(value, baos)
|
DataUtil.writeUnsignedShort(value, baos)
|
||||||
def is = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))
|
def is = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()))
|
||||||
assert is.readUnsignedShort() == value
|
assert is.readUnsignedShort() == value
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
void testUnsignedShort() {
|
void testUnsignedShort() {
|
||||||
usVal(0)
|
usVal(0)
|
||||||
usVal(20)
|
usVal(20)
|
||||||
usVal(Short.MAX_VALUE)
|
usVal(Short.MAX_VALUE)
|
||||||
usVal(Short.MAX_VALUE + 1)
|
usVal(Short.MAX_VALUE + 1)
|
||||||
usVal(0xFFFF)
|
usVal(0xFFFF)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
usVal(0xFFFF + 1)
|
usVal(0xFFFF + 1)
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException expected) {}
|
} catch (IllegalArgumentException expected) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static header(int value) {
|
private static header(int value) {
|
||||||
byte [] header = new byte[3]
|
byte [] header = new byte[3]
|
||||||
DataUtil.packHeader(value, header)
|
DataUtil.packHeader(value, header)
|
||||||
assert value == DataUtil.readLength(header)
|
assert value == DataUtil.readLength(header)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testHeader() {
|
void testHeader() {
|
||||||
header(0)
|
header(0)
|
||||||
header(1)
|
header(1)
|
||||||
header(556)
|
header(556)
|
||||||
header(8 * 1024 * 1024 - 1)
|
header(8 * 1024 * 1024 - 1)
|
||||||
try {
|
try {
|
||||||
header(8 * 1024 * 1024)
|
header(8 * 1024 * 1024)
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException expected) {}
|
} catch (IllegalArgumentException expected) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,20 @@
|
|||||||
group = com.muwire
|
group = com.muwire
|
||||||
version = 0.4.7
|
version = 0.4.12
|
||||||
groovyVersion = 2.4.15
|
groovyVersion = 2.4.15
|
||||||
slf4jVersion = 1.7.25
|
slf4jVersion = 1.7.25
|
||||||
spockVersion = 1.1-groovy-2.4
|
spockVersion = 1.1-groovy-2.4
|
||||||
|
grailsVersion=4.0.0
|
||||||
|
gorm.version=7.0.2.RELEASE
|
||||||
|
|
||||||
sourceCompatibility=1.8
|
sourceCompatibility=1.8
|
||||||
targetCompatibility=1.8
|
targetCompatibility=1.8
|
||||||
|
|
||||||
|
# plugin properties
|
||||||
|
author = zab@mail.i2p
|
||||||
|
signer = zab@mail.i2p
|
||||||
|
i2pVersion=0.9.41
|
||||||
|
keystorePassword=changeit
|
||||||
|
websiteURL=http://muwire.i2p
|
||||||
|
updateURLsu3=http://muwire.i2p/MuWire.su3
|
||||||
|
|
||||||
|
pack200=true
|
||||||
|
34
gradlew
vendored
34
gradlew
vendored
@@ -11,21 +11,21 @@
|
|||||||
PRG="$0"
|
PRG="$0"
|
||||||
# Need this for relative symlinks.
|
# Need this for relative symlinks.
|
||||||
while [ -h "$PRG" ] ; do
|
while [ -h "$PRG" ] ; do
|
||||||
ls=`ls -ld "$PRG"`
|
ls=$(ls -ld "$PRG")
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
link=$(expr "$ls" : '.*-> \(.*\)$')
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
PRG="$link"
|
PRG="$link"
|
||||||
else
|
else
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
PRG=$(dirname "$PRG")"/$link"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
SAVED="$(pwd)"
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
cd "$(dirname "$PRG")/" >/dev/null
|
||||||
APP_HOME="`pwd -P`"
|
APP_HOME="$(pwd -P)"
|
||||||
cd "$SAVED" >/dev/null
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
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.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS=""
|
DEFAULT_JVM_OPTS=""
|
||||||
@@ -49,7 +49,7 @@ cygwin=false
|
|||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$(uname)" in
|
||||||
CYGWIN* )
|
CYGWIN* )
|
||||||
cygwin=true
|
cygwin=true
|
||||||
;;
|
;;
|
||||||
@@ -90,7 +90,7 @@ fi
|
|||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
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 [ $? -eq 0 ] ; then
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
@@ -111,12 +111,12 @@ fi
|
|||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
if $cygwin ; then
|
if $cygwin ; then
|
||||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
APP_HOME=$(cygpath --path --mixed "$APP_HOME")
|
||||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
CLASSPATH=$(cygpath --path --mixed "$CLASSPATH")
|
||||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
JAVACMD=$(cygpath --unix "$JAVACMD")
|
||||||
|
|
||||||
# We build the pattern for arguments to be converted via cygpath
|
# 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=""
|
SEP=""
|
||||||
for dir in $ROOTDIRSRAW ; do
|
for dir in $ROOTDIRSRAW ; do
|
||||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
@@ -130,13 +130,13 @@ if $cygwin ; then
|
|||||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
i=0
|
i=0
|
||||||
for arg in "$@" ; do
|
for arg in "$@" ; do
|
||||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
CHECK=$(echo "$arg"|egrep -c "$OURCYGPATTERN" -)
|
||||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
CHECK2=$(echo "$arg"|egrep -c "^-") ### Determine if an option
|
||||||
|
|
||||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
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
|
else
|
||||||
eval `echo args$i`="\"$arg\""
|
eval $(echo args$i)="\"$arg\""
|
||||||
fi
|
fi
|
||||||
i=$((i+1))
|
i=$((i+1))
|
||||||
done
|
done
|
||||||
|
@@ -41,4 +41,9 @@ mvcGroups {
|
|||||||
view = 'com.muwire.gui.TrustListView'
|
view = 'com.muwire.gui.TrustListView'
|
||||||
controller = 'com.muwire.gui.TrustListController'
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -17,6 +17,7 @@ import com.muwire.core.Constants
|
|||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
import com.muwire.core.Persona
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.SharedFile
|
import com.muwire.core.SharedFile
|
||||||
|
import com.muwire.core.download.Downloader
|
||||||
import com.muwire.core.download.DownloadStartedEvent
|
import com.muwire.core.download.DownloadStartedEvent
|
||||||
import com.muwire.core.download.UIDownloadCancelledEvent
|
import com.muwire.core.download.UIDownloadCancelledEvent
|
||||||
import com.muwire.core.download.UIDownloadEvent
|
import com.muwire.core.download.UIDownloadEvent
|
||||||
@@ -59,6 +60,7 @@ class MainFrameController {
|
|||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params["search-terms"] = search
|
params["search-terms"] = search
|
||||||
params["uuid"] = uuid.toString()
|
params["uuid"] = uuid.toString()
|
||||||
|
params["core"] = core
|
||||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||||
model.results[uuid.toString()] = group
|
model.results[uuid.toString()] = group
|
||||||
|
|
||||||
@@ -96,6 +98,7 @@ class MainFrameController {
|
|||||||
Map<String, Object> params = new HashMap<>()
|
Map<String, Object> params = new HashMap<>()
|
||||||
params["search-terms"] = tabTitle
|
params["search-terms"] = tabTitle
|
||||||
params["uuid"] = uuid.toString()
|
params["uuid"] = uuid.toString()
|
||||||
|
params["core"] = core
|
||||||
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
def group = mvcGroup.createMVCGroup("SearchTab", uuid.toString(), params)
|
||||||
model.results[uuid.toString()] = group
|
model.results[uuid.toString()] = group
|
||||||
|
|
||||||
@@ -106,20 +109,6 @@ class MainFrameController {
|
|||||||
originator : core.me))
|
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() {
|
private int selectedDownload() {
|
||||||
def downloadsTable = builder.getVariable("downloads-table")
|
def downloadsTable = builder.getVariable("downloads-table")
|
||||||
def selected = downloadsTable.getSelectedRow()
|
def selected = downloadsTable.getSelectedRow()
|
||||||
@@ -130,39 +119,21 @@ class MainFrameController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void download() {
|
void trustPersonaFromSearch() {
|
||||||
def result = selectedResult()
|
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||||
if (result == null)
|
if (selected < 0)
|
||||||
return
|
return
|
||||||
|
Persona p = model.searches[selected].originator
|
||||||
if (!model.canDownload(result.infohash))
|
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.TRUSTED) )
|
||||||
return
|
|
||||||
|
|
||||||
def file = new File(application.context.get("muwire-settings").downloadLocation, result.name)
|
|
||||||
|
|
||||||
def selected = builder.getVariable("result-tabs").getSelectedComponent()
|
|
||||||
def group = selected.getClientProperty("mvc-group")
|
|
||||||
|
|
||||||
def resultsBucket = group.model.hashBucket[result.infohash]
|
|
||||||
def sources = group.model.sourcesBucket[result.infohash]
|
|
||||||
|
|
||||||
core.eventBus.publish(new UIDownloadEvent(result : resultsBucket, sources: sources, target : file))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
void trust() {
|
void distrustPersonaFromSearch() {
|
||||||
def result = selectedResult()
|
int selected = builder.getVariable("searches-table").getSelectedRow()
|
||||||
if (result == null)
|
if (selected < 0)
|
||||||
return // TODO disable button
|
return
|
||||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.TRUSTED))
|
Persona p = model.searches[selected].originator
|
||||||
}
|
core.eventBus.publish( new TrustEvent(persona : p, level : TrustLevel.DISTRUSTED) )
|
||||||
|
|
||||||
@ControllerAction
|
|
||||||
void distrust() {
|
|
||||||
def result = selectedResult()
|
|
||||||
if (result == null)
|
|
||||||
return // TODO disable button
|
|
||||||
core.eventBus.publish( new TrustEvent(persona : result.sender, level : TrustLevel.DISTRUSTED))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ControllerAction
|
@ControllerAction
|
||||||
@@ -187,6 +158,23 @@ class MainFrameController {
|
|||||||
core.eventBus.publish(new UIDownloadPausedEvent())
|
core.eventBus.publish(new UIDownloadPausedEvent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ControllerAction
|
||||||
|
void clear() {
|
||||||
|
def toRemove = []
|
||||||
|
model.downloads.each {
|
||||||
|
if (it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED) {
|
||||||
|
toRemove << it
|
||||||
|
} else if (it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED) {
|
||||||
|
toRemove << it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toRemove.each {
|
||||||
|
model.downloads.remove(it)
|
||||||
|
}
|
||||||
|
model.clearButtonEnabled = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private void markTrust(String tableName, TrustLevel level, def list) {
|
private void markTrust(String tableName, TrustLevel level, def list) {
|
||||||
int row = view.getSelectedTrustTablesRow(tableName)
|
int row = view.getSelectedTrustTablesRow(tableName)
|
||||||
if (row < 0)
|
if (row < 0)
|
||||||
|
@@ -6,6 +6,65 @@ import griffon.inject.MVCMember
|
|||||||
import griffon.metadata.ArtifactProviderFor
|
import griffon.metadata.ArtifactProviderFor
|
||||||
import javax.annotation.Nonnull
|
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)
|
@ArtifactProviderFor(GriffonController)
|
||||||
class SearchTabController {
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
@@ -43,7 +43,7 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
|
|
||||||
application.context.put("muwire-home", home.getAbsolutePath())
|
application.context.put("muwire-home", home.getAbsolutePath())
|
||||||
|
|
||||||
System.getProperties().setProperty("awt.useSystemAAFontSettings", "true")
|
System.getProperties().setProperty("awt.useSystemAAFontSettings", "gasp")
|
||||||
|
|
||||||
def guiPropsFile = new File(home, "gui.properties")
|
def guiPropsFile = new File(home, "gui.properties")
|
||||||
UISettings uiSettings
|
UISettings uiSettings
|
||||||
@@ -86,6 +86,8 @@ class Initialize extends AbstractLifecycleHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LookAndFeel chosen = lookAndFeel('system', 'gtk')
|
LookAndFeel chosen = lookAndFeel('system', 'gtk')
|
||||||
|
if (chosen == null)
|
||||||
|
chosen = lookAndFeel('metal')
|
||||||
uiSettings.lnf = chosen.getID()
|
uiSettings.lnf = chosen.getID()
|
||||||
log.info("ended up applying $chosen.name")
|
log.info("ended up applying $chosen.name")
|
||||||
}
|
}
|
||||||
|
@@ -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
|
package com.muwire.gui
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
import javax.annotation.Nonnull
|
import javax.annotation.Nonnull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -15,11 +17,13 @@ import com.muwire.core.RouterDisconnectedEvent
|
|||||||
import com.muwire.core.connection.ConnectionAttemptStatus
|
import com.muwire.core.connection.ConnectionAttemptStatus
|
||||||
import com.muwire.core.connection.ConnectionEvent
|
import com.muwire.core.connection.ConnectionEvent
|
||||||
import com.muwire.core.connection.DisconnectionEvent
|
import com.muwire.core.connection.DisconnectionEvent
|
||||||
|
import com.muwire.core.content.ContentControlEvent
|
||||||
import com.muwire.core.download.DownloadStartedEvent
|
import com.muwire.core.download.DownloadStartedEvent
|
||||||
import com.muwire.core.download.Downloader
|
import com.muwire.core.download.Downloader
|
||||||
import com.muwire.core.files.AllFilesLoadedEvent
|
import com.muwire.core.files.AllFilesLoadedEvent
|
||||||
import com.muwire.core.files.FileDownloadedEvent
|
import com.muwire.core.files.FileDownloadedEvent
|
||||||
import com.muwire.core.files.FileHashedEvent
|
import com.muwire.core.files.FileHashedEvent
|
||||||
|
import com.muwire.core.files.FileHashingEvent
|
||||||
import com.muwire.core.files.FileLoadedEvent
|
import com.muwire.core.files.FileLoadedEvent
|
||||||
import com.muwire.core.files.FileSharedEvent
|
import com.muwire.core.files.FileSharedEvent
|
||||||
import com.muwire.core.files.FileUnsharedEvent
|
import com.muwire.core.files.FileUnsharedEvent
|
||||||
@@ -70,11 +74,12 @@ class MainFrameModel {
|
|||||||
|
|
||||||
@Observable int connections
|
@Observable int connections
|
||||||
@Observable String me
|
@Observable String me
|
||||||
@Observable boolean downloadActionEnabled
|
@Observable int loadedFiles
|
||||||
@Observable boolean trustButtonsEnabled
|
@Observable File hashingFile
|
||||||
@Observable boolean cancelButtonEnabled
|
@Observable boolean cancelButtonEnabled
|
||||||
@Observable boolean retryButtonEnabled
|
@Observable boolean retryButtonEnabled
|
||||||
@Observable boolean pauseButtonEnabled
|
@Observable boolean pauseButtonEnabled
|
||||||
|
@Observable boolean clearButtonEnabled
|
||||||
@Observable String resumeButtonText
|
@Observable String resumeButtonText
|
||||||
@Observable boolean subscribeButtonEnabled
|
@Observable boolean subscribeButtonEnabled
|
||||||
@Observable boolean markNeutralFromTrustedButtonEnabled
|
@Observable boolean markNeutralFromTrustedButtonEnabled
|
||||||
@@ -85,7 +90,13 @@ class MainFrameModel {
|
|||||||
@Observable boolean updateButtonEnabled
|
@Observable boolean updateButtonEnabled
|
||||||
@Observable boolean unsubscribeButtonEnabled
|
@Observable boolean unsubscribeButtonEnabled
|
||||||
|
|
||||||
private final Set<InfoHash> infoHashes = new HashSet<>()
|
@Observable boolean searchesPaneButtonEnabled
|
||||||
|
@Observable boolean downloadsPaneButtonEnabled
|
||||||
|
@Observable boolean uploadsPaneButtonEnabled
|
||||||
|
@Observable boolean monitorPaneButtonEnabled
|
||||||
|
@Observable boolean trustPaneButtonEnabled
|
||||||
|
|
||||||
|
@Observable Downloader downloader
|
||||||
|
|
||||||
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
|
private final Set<InfoHash> downloadInfoHashes = new HashSet<>()
|
||||||
|
|
||||||
@@ -113,17 +124,26 @@ class MainFrameModel {
|
|||||||
return
|
return
|
||||||
|
|
||||||
// remove cancelled or finished downloads
|
// remove cancelled or finished downloads
|
||||||
def toRemove = []
|
if (!clearButtonEnabled || uiSettings.clearCancelledDownloads || uiSettings.clearFinishedDownloads) {
|
||||||
downloads.each {
|
def toRemove = []
|
||||||
if (uiSettings.clearCancelledDownloads &&
|
downloads.each {
|
||||||
it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED)
|
if (it.downloader.getCurrentState() == Downloader.DownloadState.CANCELLED) {
|
||||||
toRemove << it
|
if (uiSettings.clearCancelledDownloads) {
|
||||||
if (uiSettings.clearFinishedDownloads &&
|
toRemove << it
|
||||||
it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED)
|
} else {
|
||||||
toRemove << it
|
clearButtonEnabled = true
|
||||||
}
|
}
|
||||||
toRemove.each {
|
} else if (it.downloader.getCurrentState() == Downloader.DownloadState.FINISHED) {
|
||||||
downloads.remove(it)
|
if (uiSettings.clearFinishedDownloads) {
|
||||||
|
toRemove << it
|
||||||
|
} else {
|
||||||
|
clearButtonEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toRemove.each {
|
||||||
|
downloads.remove(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.getVariable("uploads-table")?.model.fireTableDataChanged()
|
builder.getVariable("uploads-table")?.model.fireTableDataChanged()
|
||||||
@@ -145,6 +165,7 @@ class MainFrameModel {
|
|||||||
core.eventBus.register(ConnectionEvent.class, this)
|
core.eventBus.register(ConnectionEvent.class, this)
|
||||||
core.eventBus.register(DisconnectionEvent.class, this)
|
core.eventBus.register(DisconnectionEvent.class, this)
|
||||||
core.eventBus.register(FileHashedEvent.class, this)
|
core.eventBus.register(FileHashedEvent.class, this)
|
||||||
|
core.eventBus.register(FileHashingEvent.class, this)
|
||||||
core.eventBus.register(FileLoadedEvent.class, this)
|
core.eventBus.register(FileLoadedEvent.class, this)
|
||||||
core.eventBus.register(UploadEvent.class, this)
|
core.eventBus.register(UploadEvent.class, this)
|
||||||
core.eventBus.register(UploadFinishedEvent.class, this)
|
core.eventBus.register(UploadFinishedEvent.class, this)
|
||||||
@@ -158,6 +179,13 @@ class MainFrameModel {
|
|||||||
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
core.eventBus.register(UpdateDownloadedEvent.class, this)
|
||||||
core.eventBus.register(TrustSubscriptionUpdatedEvent.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({
|
timer.schedule({
|
||||||
if (core.shutdown.get())
|
if (core.shutdown.get())
|
||||||
return
|
return
|
||||||
@@ -186,6 +214,12 @@ class MainFrameModel {
|
|||||||
distrusted.addAll(core.trustService.bad.values())
|
distrusted.addAll(core.trustService.bad.values())
|
||||||
|
|
||||||
resumeButtonText = "Retry"
|
resumeButtonText = "Retry"
|
||||||
|
|
||||||
|
searchesPaneButtonEnabled = false
|
||||||
|
downloadsPaneButtonEnabled = true
|
||||||
|
uploadsPaneButtonEnabled = true
|
||||||
|
monitorPaneButtonEnabled = true
|
||||||
|
trustPaneButtonEnabled = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -261,36 +295,40 @@ class MainFrameModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onFileHashingEvent(FileHashingEvent e) {
|
||||||
|
runInsideUIAsync {
|
||||||
|
loadedFiles = shared.size()
|
||||||
|
hashingFile = e.hashingFile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void onFileHashedEvent(FileHashedEvent e) {
|
void onFileHashedEvent(FileHashedEvent e) {
|
||||||
|
runInsideUIAsync {
|
||||||
|
hashingFile = null
|
||||||
|
}
|
||||||
if (e.error != null)
|
if (e.error != null)
|
||||||
return // TODO do something
|
return // TODO do something
|
||||||
if (infoHashes.contains(e.sharedFile.infoHash))
|
|
||||||
return
|
|
||||||
infoHashes.add(e.sharedFile.infoHash)
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared << e.sharedFile
|
shared << e.sharedFile
|
||||||
|
loadedFiles = shared.size()
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
JTable table = builder.getVariable("shared-files-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileLoadedEvent(FileLoadedEvent e) {
|
void onFileLoadedEvent(FileLoadedEvent e) {
|
||||||
if (infoHashes.contains(e.loadedFile.infoHash))
|
|
||||||
return
|
|
||||||
infoHashes.add(e.loadedFile.infoHash)
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared << e.loadedFile
|
shared << e.loadedFile
|
||||||
|
loadedFiles = shared.size()
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
JTable table = builder.getVariable("shared-files-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
void onFileUnsharedEvent(FileUnsharedEvent e) {
|
||||||
InfoHash infohash = e.unsharedFile.infoHash
|
|
||||||
if (!infoHashes.remove(infohash))
|
|
||||||
return
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared.remove(e.unsharedFile)
|
shared.remove(e.unsharedFile)
|
||||||
|
loadedFiles = shared.size()
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
JTable table = builder.getVariable("shared-files-table")
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
@@ -360,10 +398,32 @@ class MainFrameModel {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
runInsideUIAsync {
|
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)
|
while(searches.size() > 200)
|
||||||
searches.removeLast()
|
searches.removeLast()
|
||||||
JTable table = builder.getVariable("searches-table")
|
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,6 +432,18 @@ class MainFrameModel {
|
|||||||
String search
|
String search
|
||||||
Destination replyTo
|
Destination replyTo
|
||||||
Persona originator
|
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) {
|
void onUpdateAvailableEvent(UpdateAvailableEvent e) {
|
||||||
@@ -397,7 +469,6 @@ class MainFrameModel {
|
|||||||
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
void onFileDownloadedEvent(FileDownloadedEvent e) {
|
||||||
if (!core.muOptions.shareDownloadedFiles)
|
if (!core.muOptions.shareDownloadedFiles)
|
||||||
return
|
return
|
||||||
infoHashes.add(e.downloadedFile.infoHash)
|
|
||||||
runInsideUIAsync {
|
runInsideUIAsync {
|
||||||
shared << e.downloadedFile
|
shared << e.downloadedFile
|
||||||
JTable table = builder.getVariable("shared-files-table")
|
JTable table = builder.getVariable("shared-files-table")
|
||||||
|
@@ -5,6 +5,7 @@ import javax.inject.Inject
|
|||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
|
|
||||||
import com.muwire.core.Core
|
import com.muwire.core.Core
|
||||||
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.search.UIResultEvent
|
import com.muwire.core.search.UIResultEvent
|
||||||
|
|
||||||
import griffon.core.artifact.GriffonModel
|
import griffon.core.artifact.GriffonModel
|
||||||
@@ -18,12 +19,17 @@ class SearchTabModel {
|
|||||||
@MVCMember @Nonnull
|
@MVCMember @Nonnull
|
||||||
FactoryBuilderSupport builder
|
FactoryBuilderSupport builder
|
||||||
|
|
||||||
|
@Observable boolean downloadActionEnabled
|
||||||
|
@Observable boolean trustButtonsEnabled
|
||||||
|
|
||||||
Core core
|
Core core
|
||||||
UISettings uiSettings
|
UISettings uiSettings
|
||||||
String uuid
|
String uuid
|
||||||
|
def senders = []
|
||||||
def results = []
|
def results = []
|
||||||
def hashBucket = [:]
|
def hashBucket = [:]
|
||||||
def sourcesBucket = [:]
|
def sourcesBucket = [:]
|
||||||
|
def sendersBucket = new LinkedHashMap<>()
|
||||||
|
|
||||||
|
|
||||||
void mvcGroupInit(Map<String, String> args) {
|
void mvcGroupInit(Map<String, String> args) {
|
||||||
@@ -48,6 +54,15 @@ class SearchTabModel {
|
|||||||
}
|
}
|
||||||
bucket << e
|
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)
|
Set sourceBucket = sourcesBucket.get(e.infohash)
|
||||||
if (sourceBucket == null) {
|
if (sourceBucket == null) {
|
||||||
sourceBucket = new HashSet()
|
sourceBucket = new HashSet()
|
||||||
@@ -55,8 +70,7 @@ class SearchTabModel {
|
|||||||
}
|
}
|
||||||
sourceBucket.addAll(e.sources)
|
sourceBucket.addAll(e.sources)
|
||||||
|
|
||||||
results << e
|
JTable table = builder.getVariable("senders-table")
|
||||||
JTable table = builder.getVariable("results-table")
|
|
||||||
table.model.fireTableDataChanged()
|
table.model.fireTableDataChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,6 +87,14 @@ class SearchTabModel {
|
|||||||
hashBucket[it.infohash] = bucket
|
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)
|
Set sourceBucket = sourcesBucket.get(it.infohash)
|
||||||
if (sourceBucket == null) {
|
if (sourceBucket == null) {
|
||||||
sourceBucket = new HashSet()
|
sourceBucket = new HashSet()
|
||||||
@@ -81,9 +103,9 @@ class SearchTabModel {
|
|||||||
sourceBucket.addAll(it.sources)
|
sourceBucket.addAll(it.sources)
|
||||||
|
|
||||||
bucket << it
|
bucket << it
|
||||||
results << it
|
senderBucket << it
|
||||||
}
|
}
|
||||||
JTable table = builder.getVariable("results-table")
|
JTable table = builder.getVariable("senders-table")
|
||||||
table.model.fireTableDataChanged()
|
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.download.Downloader
|
||||||
import com.muwire.core.files.FileSharedEvent
|
import com.muwire.core.files.FileSharedEvent
|
||||||
import com.muwire.core.trust.RemoteTrustList
|
import com.muwire.core.trust.RemoteTrustList
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.CardLayout
|
import java.awt.CardLayout
|
||||||
import java.awt.FlowLayout
|
import java.awt.FlowLayout
|
||||||
@@ -73,6 +72,11 @@ class MainFrameView {
|
|||||||
menuBar {
|
menuBar {
|
||||||
menu (text : "Options") {
|
menu (text : "Options") {
|
||||||
menuItem("Configuration", actionPerformed : {mvcGroup.createMVCGroup("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") {
|
menu (text : "Status") {
|
||||||
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
menuItem("MuWire", actionPerformed : {mvcGroup.createMVCGroup("mu-wire-status")})
|
||||||
@@ -85,11 +89,12 @@ class MainFrameView {
|
|||||||
borderLayout()
|
borderLayout()
|
||||||
panel (constraints: BorderLayout.WEST) {
|
panel (constraints: BorderLayout.WEST) {
|
||||||
gridLayout(rows:1, cols: 2)
|
gridLayout(rows:1, cols: 2)
|
||||||
button(text: "Searches", actionPerformed : showSearchWindow)
|
button(text: "Searches", enabled : bind{model.searchesPaneButtonEnabled},actionPerformed : showSearchWindow)
|
||||||
button(text: "Uploads", actionPerformed : showUploadsWindow)
|
button(text: "Downloads", enabled : bind{model.downloadsPaneButtonEnabled}, actionPerformed : showDownloadsWindow)
|
||||||
|
button(text: "Uploads", enabled : bind{model.uploadsPaneButtonEnabled}, actionPerformed : showUploadsWindow)
|
||||||
if (settings.showMonitor)
|
if (settings.showMonitor)
|
||||||
button(text: "Monitor", actionPerformed : showMonitorWindow)
|
button(text: "Monitor", enabled: bind{model.monitorPaneButtonEnabled},actionPerformed : showMonitorWindow)
|
||||||
button(text: "Trust", actionPerformed : showTrustWindow)
|
button(text: "Trust", enabled:bind{model.trustPaneButtonEnabled},actionPerformed : showTrustWindow)
|
||||||
}
|
}
|
||||||
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
|
panel(id: "top-panel", constraints: BorderLayout.CENTER) {
|
||||||
cardLayout()
|
cardLayout()
|
||||||
@@ -113,37 +118,19 @@ class MainFrameView {
|
|||||||
cardLayout()
|
cardLayout()
|
||||||
panel (constraints : "search window") {
|
panel (constraints : "search window") {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
splitPane( orientation : JSplitPane.VERTICAL_SPLIT, dividerLocation : 500,
|
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
||||||
continuousLayout : true, constraints : BorderLayout.CENTER) {
|
}
|
||||||
panel (constraints : JSplitPane.TOP) {
|
panel (constraints: "downloads window") {
|
||||||
borderLayout()
|
gridLayout(rows : 1, cols : 1)
|
||||||
tabbedPane(id : "result-tabs", constraints: BorderLayout.CENTER)
|
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 500 ) {
|
||||||
panel(constraints : BorderLayout.SOUTH) {
|
panel {
|
||||||
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()
|
borderLayout()
|
||||||
scrollPane (constraints : BorderLayout.CENTER) {
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
downloadsTable = table(id : "downloads-table", autoCreateRowSorter : true) {
|
||||||
tableModel(list: model.downloads) {
|
tableModel(list: model.downloads) {
|
||||||
closureColumn(header: "Name", preferredWidth: 300, type: String, read : {row -> row.downloader.file.getName()})
|
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: "Status", preferredWidth: 50, type: String, read : {row -> row.downloader.getCurrentState().toString()})
|
||||||
closureColumn(header: "Progress", preferredWidth: 70, type: String, read: { row ->
|
closureColumn(header: "Progress", preferredWidth: 70, type: Downloader, read: { row -> row.downloader })
|
||||||
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 ->
|
closureColumn(header: "Speed", preferredWidth: 50, type:String, read :{row ->
|
||||||
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
DataHelper.formatSize2Decimal(row.downloader.speed(), false) + "B/sec"
|
||||||
})
|
})
|
||||||
@@ -152,8 +139,39 @@ class MainFrameView {
|
|||||||
}
|
}
|
||||||
panel (constraints : BorderLayout.SOUTH) {
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
button(text: "Pause", enabled : bind {model.pauseButtonEnabled}, pauseAction)
|
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)
|
button(text: bind { model.resumeButtonText }, enabled : bind {model.retryButtonEnabled}, resumeAction)
|
||||||
|
button(text: "Cancel", enabled : bind {model.cancelButtonEnabled }, cancelAction)
|
||||||
|
button(text: "Clear Done", enabled : bind {model.clearButtonEnabled}, clearAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel {
|
||||||
|
borderLayout()
|
||||||
|
panel(constraints : BorderLayout.NORTH) {
|
||||||
|
label(text : "Download Details")
|
||||||
|
}
|
||||||
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
|
panel (id : "download-details-panel") {
|
||||||
|
cardLayout()
|
||||||
|
panel (constraints : "select-download") {
|
||||||
|
label(text : "Select a download to view details")
|
||||||
|
}
|
||||||
|
panel(constraints : "download-selected") {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Download Location:", constraints : gbc(gridx:0, gridy:0))
|
||||||
|
label(text : bind {model.downloader?.file?.getAbsolutePath()},
|
||||||
|
constraints: gbc(gridx:1, gridy:0, gridwidth: 2, insets : [0,0,0,20]))
|
||||||
|
label(text : "Piece Size", constraints : gbc(gridx: 0, gridy:1))
|
||||||
|
label(text : bind {model.downloader?.pieceSize}, constraints : gbc(gridx:1, gridy:1))
|
||||||
|
label(text : "Known Sources:", constraints : gbc(gridx:3, gridy: 0))
|
||||||
|
label(text : bind {model.downloader?.activeWorkers?.size()}, constraints : gbc(gridx:4, gridy:0, insets : [0,0,0,20]))
|
||||||
|
label(text : "Active Sources:", constraints : gbc(gridx:3, gridy:1))
|
||||||
|
label(text : bind {model.downloader?.activeWorkers()}, constraints : gbc(gridx:4, gridy:1, insets : [0,0,0,20]))
|
||||||
|
label(text : "Total Pieces:", constraints : gbc(gridx:5, gridy: 0))
|
||||||
|
label(text : bind {model.downloader?.nPieces}, constraints : gbc(gridx:6, gridy:0, insets : [0,0,0,20]))
|
||||||
|
label(text : "Done Pieces:", constraints: gbc(gridx:5, gridy: 1))
|
||||||
|
label(text : bind {model.downloader?.donePieces()}, constraints : gbc(gridx:6, gridy:1, insets : [0,0,0,20]))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,36 +179,53 @@ class MainFrameView {
|
|||||||
panel (constraints: "uploads window"){
|
panel (constraints: "uploads window"){
|
||||||
gridLayout(cols : 1, rows : 2)
|
gridLayout(cols : 1, rows : 2)
|
||||||
panel {
|
panel {
|
||||||
gridLayout(cols : 2, rows : 1)
|
borderLayout()
|
||||||
panel {
|
panel (constraints : BorderLayout.NORTH) {
|
||||||
borderLayout()
|
label(text : bind {
|
||||||
panel (constraints : BorderLayout.NORTH) {
|
if (model.hashingFile == null) {
|
||||||
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
""
|
||||||
|
} 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) {
|
panel {
|
||||||
table(id : "watched-directories-table", autoCreateRowSorter: true) {
|
borderLayout()
|
||||||
tableModel(list : model.watched) {
|
scrollPane(constraints : BorderLayout.CENTER) {
|
||||||
closureColumn(header: "Watched Directories", type : String, read : { it })
|
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 {
|
panel (constraints : BorderLayout.SOUTH) {
|
||||||
borderLayout()
|
gridLayout(rows:1, cols:2)
|
||||||
panel (constraints : BorderLayout.NORTH) {
|
panel {
|
||||||
|
button(text : "Add directories to watch", actionPerformed : watchDirectories)
|
||||||
button(text : "Share files", actionPerformed : shareFiles)
|
button(text : "Share files", actionPerformed : shareFiles)
|
||||||
}
|
}
|
||||||
scrollPane(constraints : BorderLayout.CENTER) {
|
panel {
|
||||||
table(id : "shared-files-table", autoCreateRowSorter: true) {
|
label("Shared:")
|
||||||
tableModel(list : model.shared) {
|
label(text : bind {model.loadedFiles.toString()})
|
||||||
closureColumn(header : "Name", preferredWidth : 500, type : String, read : {row -> row.getCachedPath()})
|
|
||||||
closureColumn(header : "Size", preferredWidth : 100, type : Long, read : {row -> row.getCachedLength() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel {
|
panel (border : etchedBorder()) {
|
||||||
borderLayout()
|
borderLayout()
|
||||||
panel (constraints : BorderLayout.NORTH){
|
panel (constraints : BorderLayout.NORTH){
|
||||||
label("Uploads")
|
label("Uploads")
|
||||||
@@ -218,7 +253,7 @@ class MainFrameView {
|
|||||||
if (size >= 0 ) {
|
if (size >= 0 ) {
|
||||||
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
|
totalSize = " of " + DataHelper.formatSize2Decimal(size, false) + "B"
|
||||||
}
|
}
|
||||||
"${percent}%" + totalSize + " ($done/$pieces pcs)"
|
String.format("%02d", percent) + "% ${totalSize} ($done/$pieces pcs)".toString()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -265,6 +300,14 @@ class MainFrameView {
|
|||||||
return it.replyTo.toBase32()
|
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))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,16 +396,21 @@ class MainFrameView {
|
|||||||
def selectionModel = downloadsTable.getSelectionModel()
|
def selectionModel = downloadsTable.getSelectionModel()
|
||||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
selectionModel.addListSelectionListener({
|
selectionModel.addListSelectionListener({
|
||||||
|
def downloadDetailsPanel = builder.getVariable("download-details-panel")
|
||||||
int selectedRow = selectedDownloaderRow()
|
int selectedRow = selectedDownloaderRow()
|
||||||
if (selectedRow < 0) {
|
if (selectedRow < 0) {
|
||||||
model.cancelButtonEnabled = false
|
model.cancelButtonEnabled = false
|
||||||
model.retryButtonEnabled = false
|
model.retryButtonEnabled = false
|
||||||
model.pauseButtonEnabled = false
|
model.pauseButtonEnabled = false
|
||||||
|
model.downloader = null
|
||||||
|
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"select-download")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
def downloader = model.downloads[selectedRow]?.downloader
|
def downloader = model.downloads[selectedRow]?.downloader
|
||||||
if (downloader == null)
|
if (downloader == null)
|
||||||
return
|
return
|
||||||
|
model.downloader = downloader
|
||||||
|
downloadDetailsPanel.getLayout().show(downloadDetailsPanel,"download-selected")
|
||||||
switch(downloader.getCurrentState()) {
|
switch(downloader.getCurrentState()) {
|
||||||
case Downloader.DownloadState.CONNECTING :
|
case Downloader.DownloadState.CONNECTING :
|
||||||
case Downloader.DownloadState.DOWNLOADING :
|
case Downloader.DownloadState.DOWNLOADING :
|
||||||
@@ -393,9 +441,11 @@ class MainFrameView {
|
|||||||
def centerRenderer = new DefaultTableCellRenderer()
|
def centerRenderer = new DefaultTableCellRenderer()
|
||||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||||
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
|
downloadsTable.setDefaultRenderer(Integer.class, centerRenderer)
|
||||||
|
downloadsTable.setDefaultRenderer(Downloader.class, new DownloadProgressRenderer())
|
||||||
|
|
||||||
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
|
downloadsTable.rowSorter.addRowSorterListener({evt -> lastDownloadSortEvent = evt})
|
||||||
downloadsTable.rowSorter.setSortsOnUpdates(true)
|
downloadsTable.rowSorter.setSortsOnUpdates(true)
|
||||||
|
downloadsTable.rowSorter.setComparator(2, new DownloaderComparator())
|
||||||
|
|
||||||
downloadsTable.addMouseListener(new MouseAdapter() {
|
downloadsTable.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
@@ -440,9 +490,18 @@ class MainFrameView {
|
|||||||
// searches table
|
// searches table
|
||||||
def searchesTable = builder.getVariable("searches-table")
|
def searchesTable = builder.getVariable("searches-table")
|
||||||
JPopupMenu searchTableMenu = new JPopupMenu()
|
JPopupMenu searchTableMenu = new JPopupMenu()
|
||||||
|
|
||||||
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
|
JMenuItem copySearchToClipboard = new JMenuItem("Copy search to clipboard")
|
||||||
copySearchToClipboard.addActionListener({mvcGroup.view.copySearchToClipboard(searchesTable)})
|
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(copySearchToClipboard)
|
||||||
|
searchTableMenu.add(trustSearcher)
|
||||||
|
searchTableMenu.add(distrustSearcher)
|
||||||
|
|
||||||
searchesTable.addMouseListener(new MouseAdapter() {
|
searchesTable.addMouseListener(new MouseAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
@@ -661,21 +720,51 @@ class MainFrameView {
|
|||||||
def showSearchWindow = {
|
def showSearchWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel, "search window")
|
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 showUploadsWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel, "uploads window")
|
cardsPanel.getLayout().show(cardsPanel, "uploads window")
|
||||||
|
model.searchesPaneButtonEnabled = true
|
||||||
|
model.downloadsPaneButtonEnabled = true
|
||||||
|
model.uploadsPaneButtonEnabled = false
|
||||||
|
model.monitorPaneButtonEnabled = true
|
||||||
|
model.trustPaneButtonEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
def showMonitorWindow = {
|
def showMonitorWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel,"monitor window")
|
cardsPanel.getLayout().show(cardsPanel,"monitor window")
|
||||||
|
model.searchesPaneButtonEnabled = true
|
||||||
|
model.downloadsPaneButtonEnabled = true
|
||||||
|
model.uploadsPaneButtonEnabled = true
|
||||||
|
model.monitorPaneButtonEnabled = false
|
||||||
|
model.trustPaneButtonEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
def showTrustWindow = {
|
def showTrustWindow = {
|
||||||
def cardsPanel = builder.getVariable("cards-panel")
|
def cardsPanel = builder.getVariable("cards-panel")
|
||||||
cardsPanel.getLayout().show(cardsPanel,"trust window")
|
cardsPanel.getLayout().show(cardsPanel,"trust window")
|
||||||
|
model.searchesPaneButtonEnabled = true
|
||||||
|
model.downloadsPaneButtonEnabled = true
|
||||||
|
model.uploadsPaneButtonEnabled = true
|
||||||
|
model.monitorPaneButtonEnabled = true
|
||||||
|
model.trustPaneButtonEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
def shareFiles = {
|
def shareFiles = {
|
||||||
|
@@ -117,9 +117,9 @@ class OptionsView {
|
|||||||
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
|
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
|
||||||
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
|
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
|
||||||
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
|
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
|
||||||
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
|
label(text : "Automatically Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
|
||||||
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
|
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
|
||||||
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
|
label(text : "Automatically Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
|
||||||
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
|
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
|
||||||
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
|
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
|
||||||
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
|
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
|
||||||
|
183
gui/griffon-app/views/com/muwire/gui/OptionsView.groovy.orig
Normal file
183
gui/griffon-app/views/com/muwire/gui/OptionsView.groovy.orig
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
package com.muwire.gui
|
||||||
|
|
||||||
|
import griffon.core.artifact.GriffonView
|
||||||
|
import griffon.inject.MVCMember
|
||||||
|
import griffon.metadata.ArtifactProviderFor
|
||||||
|
|
||||||
|
import javax.swing.JDialog
|
||||||
|
import javax.swing.JPanel
|
||||||
|
import javax.swing.JTabbedPane
|
||||||
|
import javax.swing.SwingConstants
|
||||||
|
|
||||||
|
import com.muwire.core.Core
|
||||||
|
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.event.WindowAdapter
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull
|
||||||
|
|
||||||
|
@ArtifactProviderFor(GriffonView)
|
||||||
|
class OptionsView {
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
FactoryBuilderSupport builder
|
||||||
|
@MVCMember @Nonnull
|
||||||
|
OptionsModel model
|
||||||
|
|
||||||
|
def d
|
||||||
|
def p
|
||||||
|
def i
|
||||||
|
def u
|
||||||
|
def bandwidth
|
||||||
|
def trust
|
||||||
|
|
||||||
|
def retryField
|
||||||
|
def updateField
|
||||||
|
def autoDownloadUpdateCheckbox
|
||||||
|
def shareDownloadedCheckbox
|
||||||
|
|
||||||
|
def inboundLengthField
|
||||||
|
def inboundQuantityField
|
||||||
|
def outboundLengthField
|
||||||
|
def outboundQuantityField
|
||||||
|
def i2pUDPPortField
|
||||||
|
def i2pNTCPPortField
|
||||||
|
|
||||||
|
def lnfField
|
||||||
|
def monitorCheckbox
|
||||||
|
def fontField
|
||||||
|
def clearCancelledDownloadsCheckbox
|
||||||
|
def clearFinishedDownloadsCheckbox
|
||||||
|
def excludeLocalResultCheckbox
|
||||||
|
def showSearchHashesCheckbox
|
||||||
|
|
||||||
|
def inBwField
|
||||||
|
def outBwField
|
||||||
|
|
||||||
|
def allowUntrustedCheckbox
|
||||||
|
def allowTrustListsCheckbox
|
||||||
|
def trustListIntervalField
|
||||||
|
|
||||||
|
def buttonsPanel
|
||||||
|
|
||||||
|
def mainFrame
|
||||||
|
|
||||||
|
void initUI() {
|
||||||
|
mainFrame = application.windowManager.findWindow("main-frame")
|
||||||
|
d = new JDialog(mainFrame, "Options", true)
|
||||||
|
d.setResizable(false)
|
||||||
|
p = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Retry failed downloads every", constraints : gbc(gridx: 0, gridy: 0))
|
||||||
|
retryField = textField(text : bind { model.downloadRetryInterval }, columns : 2, constraints : gbc(gridx: 1, gridy: 0))
|
||||||
|
label(text : "minutes", constraints : gbc(gridx : 2, gridy: 0))
|
||||||
|
|
||||||
|
label(text : "Check for updates every", constraints : gbc(gridx : 0, gridy: 1))
|
||||||
|
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 1))
|
||||||
|
label(text : "hours", constraints : gbc(gridx: 2, gridy : 1))
|
||||||
|
|
||||||
|
label(text : "Download updates automatically", constraints: gbc(gridx :0, gridy : 2))
|
||||||
|
autoDownloadUpdateCheckbox = checkBox(selected : bind {model.autoDownloadUpdate}, constraints : gbc(gridx:1, gridy : 2))
|
||||||
|
|
||||||
|
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:3))
|
||||||
|
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:3))
|
||||||
|
|
||||||
|
label(text : "Save downloaded files to:", constraints: gbc(gridx:0, gridy:4))
|
||||||
|
button(text : "Choose", constraints : gbc(gridx : 1, gridy:4), downloadLocationAction)
|
||||||
|
label(text : bind {model.downloadLocation}, constraints: gbc(gridx:0, gridy:5, gridwidth:2))
|
||||||
|
|
||||||
|
}
|
||||||
|
i = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||||
|
label(text : "Inbound Length", constraints : gbc(gridx:0, gridy:1))
|
||||||
|
inboundLengthField = textField(text : bind {model.inboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:1))
|
||||||
|
label(text : "Inbound Quantity", constraints : gbc(gridx:0, gridy:2))
|
||||||
|
inboundQuantityField = textField(text : bind {model.inboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:2))
|
||||||
|
label(text : "Outbound Length", constraints : gbc(gridx:0, gridy:3))
|
||||||
|
outboundLengthField = textField(text : bind {model.outboundLength}, columns : 2, constraints : gbc(gridx:1, gridy:3))
|
||||||
|
label(text : "Outbound Quantity", constraints : gbc(gridx:0, gridy:4))
|
||||||
|
outboundQuantityField = textField(text : bind {model.outboundQuantity}, columns : 2, constraints : gbc(gridx:1, gridy:4))
|
||||||
|
|
||||||
|
Core core = application.context.get("core")
|
||||||
|
if (core.router != null) {
|
||||||
|
label(text : "TCP Port", constraints : gbc(gridx :0, gridy: 5))
|
||||||
|
i2pNTCPPortField = textField(text : bind {model.i2pNTCPPort}, columns : 4, constraints : gbc(gridx:1, gridy:5))
|
||||||
|
label(text : "UDP Port", constraints : gbc(gridx :0, gridy: 6))
|
||||||
|
i2pUDPPortField = textField(text : bind {model.i2pUDPPort}, columns : 4, constraints : gbc(gridx:1, gridy:6))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
u = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||||
|
label(text : "Look And Feel", constraints : gbc(gridx: 0, gridy:1))
|
||||||
|
lnfField = textField(text : bind {model.lnf}, columns : 4, constraints : gbc(gridx : 1, gridy : 1))
|
||||||
|
label(text : "Font", constraints : gbc(gridx: 0, gridy : 2))
|
||||||
|
fontField = textField(text : bind {model.font}, columns : 4, constraints : gbc(gridx : 1, gridy:2))
|
||||||
|
// label(text : "Show Monitor", constraints : gbc(gridx :0, gridy: 3))
|
||||||
|
// monitorCheckbox = checkBox(selected : bind {model.showMonitor}, constraints : gbc(gridx : 1, gridy: 3))
|
||||||
|
label(text : "Clear Cancelled Downloads", constraints: gbc(gridx: 0, gridy:4))
|
||||||
|
clearCancelledDownloadsCheckbox = checkBox(selected : bind {model.clearCancelledDownloads}, constraints : gbc(gridx : 1, gridy:4))
|
||||||
|
label(text : "Clear Finished Downloads", constraints: gbc(gridx: 0, gridy:5))
|
||||||
|
clearFinishedDownloadsCheckbox = checkBox(selected : bind {model.clearFinishedDownloads}, constraints : gbc(gridx : 1, gridy:5))
|
||||||
|
label(text : "Exclude Local Files From Results", constraints: gbc(gridx:0, gridy:6))
|
||||||
|
excludeLocalResultCheckbox = checkBox(selected : bind {model.excludeLocalResult}, constraints : gbc(gridx: 1, gridy : 6))
|
||||||
|
// label(text : "Show Hash Searches In Monitor", constraints: gbc(gridx:0, gridy:7))
|
||||||
|
// showSearchHashesCheckbox = checkBox(selected : bind {model.showSearchHashes}, constraints : gbc(gridx: 1, gridy: 7))
|
||||||
|
}
|
||||||
|
bandwidth = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Changing these settings requires a restart", constraints : gbc(gridx : 0, gridy : 0, gridwidth: 2))
|
||||||
|
label(text : "Inbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 1))
|
||||||
|
inBwField = textField(text : bind {model.inBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 1))
|
||||||
|
label(text : "Outbound bandwidth (KB)", constraints : gbc(gridx: 0, gridy : 2))
|
||||||
|
outBwField = textField(text : bind {model.outBw}, columns : 3, constraints : gbc(gridx : 1, gridy : 2))
|
||||||
|
}
|
||||||
|
trust = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
label(text : "Allow only trusted connections", constraints : gbc(gridx: 0, gridy : 0))
|
||||||
|
allowUntrustedCheckbox = checkBox(selected : bind {model.onlyTrusted}, constraints : gbc(gridx: 1, gridy : 0))
|
||||||
|
label(text : "Allow others to view my trust list", constraints : gbc(gridx: 0, gridy : 1))
|
||||||
|
allowTrustListsCheckbox = checkBox(selected : bind {model.trustLists}, constraints : gbc(gridx: 1, gridy : 1))
|
||||||
|
label(text : "Update trust lists every ", constraints : gbc(gridx:0, gridy:2))
|
||||||
|
trustListIntervalField = textField(text : bind {model.trustListInterval}, constraints:gbc(gridx:1, gridy:2))
|
||||||
|
label(text : "hours", constraints : gbc(gridx: 2, gridy:2))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
buttonsPanel = builder.panel {
|
||||||
|
gridBagLayout()
|
||||||
|
button(text : "Save", constraints : gbc(gridx : 1, gridy: 2), saveAction)
|
||||||
|
button(text : "Cancel", constraints : gbc(gridx : 2, gridy: 2), cancelAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mvcGroupInit(Map<String,String> args) {
|
||||||
|
def tabbedPane = new JTabbedPane()
|
||||||
|
tabbedPane.addTab("MuWire", p)
|
||||||
|
tabbedPane.addTab("I2P", i)
|
||||||
|
tabbedPane.addTab("GUI", u)
|
||||||
|
Core core = application.context.get("core")
|
||||||
|
if (core.router != null) {
|
||||||
|
tabbedPane.addTab("Bandwidth", bandwidth)
|
||||||
|
}
|
||||||
|
tabbedPane.addTab("Trust", trust)
|
||||||
|
|
||||||
|
JPanel panel = new JPanel()
|
||||||
|
panel.setLayout(new BorderLayout())
|
||||||
|
panel.add(tabbedPane, BorderLayout.CENTER)
|
||||||
|
panel.add(buttonsPanel, BorderLayout.SOUTH)
|
||||||
|
|
||||||
|
d.getContentPane().add(panel)
|
||||||
|
d.pack()
|
||||||
|
d.setLocationRelativeTo(mainFrame)
|
||||||
|
d.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE)
|
||||||
|
d.addWindowListener(new WindowAdapter() {
|
||||||
|
public void windowClosed(WindowEvent e) {
|
||||||
|
mvcGroup.destroy()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
d.show()
|
||||||
|
}
|
||||||
|
}
|
@@ -11,11 +11,13 @@ import javax.swing.JComponent
|
|||||||
import javax.swing.JLabel
|
import javax.swing.JLabel
|
||||||
import javax.swing.JMenuItem
|
import javax.swing.JMenuItem
|
||||||
import javax.swing.JPopupMenu
|
import javax.swing.JPopupMenu
|
||||||
|
import javax.swing.JSplitPane
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
import javax.swing.ListSelectionModel
|
import javax.swing.ListSelectionModel
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
import javax.swing.table.DefaultTableCellRenderer
|
import javax.swing.table.DefaultTableCellRenderer
|
||||||
|
|
||||||
|
import com.muwire.core.Persona
|
||||||
import com.muwire.core.util.DataUtil
|
import com.muwire.core.util.DataUtil
|
||||||
|
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
@@ -37,23 +39,51 @@ class SearchTabView {
|
|||||||
def pane
|
def pane
|
||||||
def parent
|
def parent
|
||||||
def searchTerms
|
def searchTerms
|
||||||
|
def sendersTable
|
||||||
|
def lastSendersSortEvent
|
||||||
def resultsTable
|
def resultsTable
|
||||||
def lastSortEvent
|
def lastSortEvent
|
||||||
|
|
||||||
void initUI() {
|
void initUI() {
|
||||||
builder.with {
|
builder.with {
|
||||||
def resultsTable
|
def resultsTable
|
||||||
def pane = scrollPane {
|
def sendersTable
|
||||||
resultsTable = table(id : "results-table", autoCreateRowSorter : true) {
|
def pane = panel {
|
||||||
tableModel(list: model.results) {
|
gridLayout(rows :1, cols : 1)
|
||||||
closureColumn(header: "Name", preferredWidth: 350, type: String, read : {row -> row.name.replace('<','_')})
|
splitPane(orientation: JSplitPane.VERTICAL_SPLIT, continuousLayout : true, dividerLocation: 300 ) {
|
||||||
closureColumn(header: "Size", preferredWidth: 20, type: Long, read : {row -> row.size})
|
panel {
|
||||||
closureColumn(header: "Direct Sources", preferredWidth: 50, type : Integer, read : { row -> model.hashBucket[row.infohash].size()})
|
borderLayout()
|
||||||
closureColumn(header: "Possible Sources", preferredWidth : 50, type : Integer, read : {row -> model.sourcesBucket[row.infohash].size()})
|
scrollPane (constraints : BorderLayout.CENTER) {
|
||||||
closureColumn(header: "Sender", preferredWidth: 170, type: String, read : {row -> row.sender.getHumanReadableName()})
|
sendersTable = table(id : "senders-table", autoCreateRowSorter : true) {
|
||||||
closureColumn(header: "Trust", preferredWidth: 50, type: String, read : {row ->
|
tableModel(list : model.senders) {
|
||||||
model.core.trustService.getLevel(row.sender.destination).toString()
|
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 +93,19 @@ class SearchTabView {
|
|||||||
this.pane.putClientProperty("results-table",resultsTable)
|
this.pane.putClientProperty("results-table",resultsTable)
|
||||||
|
|
||||||
this.resultsTable = resultsTable
|
this.resultsTable = resultsTable
|
||||||
|
this.sendersTable = sendersTable
|
||||||
|
|
||||||
def selectionModel = resultsTable.getSelectionModel()
|
def selectionModel = resultsTable.getSelectionModel()
|
||||||
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
|
||||||
selectionModel.addListSelectionListener( {
|
selectionModel.addListSelectionListener( {
|
||||||
int row = resultsTable.getSelectedRow()
|
int row = resultsTable.getSelectedRow()
|
||||||
if (row < 0)
|
if (row < 0) {
|
||||||
|
model.downloadActionEnabled = false
|
||||||
return
|
return
|
||||||
|
}
|
||||||
if (lastSortEvent != null)
|
if (lastSortEvent != null)
|
||||||
row = resultsTable.rowSorter.convertRowIndexToModel(row)
|
row = resultsTable.rowSorter.convertRowIndexToModel(row)
|
||||||
mvcGroup.parentGroup.model.trustButtonsEnabled = true
|
model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
|
||||||
mvcGroup.parentGroup.model.downloadActionEnabled = mvcGroup.parentGroup.model.canDownload(model.results[row].infohash)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,12 +130,11 @@ class SearchTabView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent.setTabComponentAt(index, tabPanel)
|
parent.setTabComponentAt(index, tabPanel)
|
||||||
|
mvcGroup.parentGroup.view.showSearchWindow.call()
|
||||||
|
|
||||||
def centerRenderer = new DefaultTableCellRenderer()
|
def centerRenderer = new DefaultTableCellRenderer()
|
||||||
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
centerRenderer.setHorizontalAlignment(JLabel.CENTER)
|
||||||
resultsTable.columnModel.getColumn(1).setCellRenderer(centerRenderer)
|
|
||||||
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
resultsTable.setDefaultRenderer(Integer.class,centerRenderer)
|
||||||
resultsTable.columnModel.getColumn(4).setCellRenderer(centerRenderer)
|
|
||||||
|
|
||||||
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
resultsTable.columnModel.getColumn(1).setCellRenderer(new SizeRenderer())
|
||||||
|
|
||||||
@@ -118,7 +149,7 @@ class SearchTabView {
|
|||||||
if (e.button == MouseEvent.BUTTON3)
|
if (e.button == MouseEvent.BUTTON3)
|
||||||
showPopupMenu(e)
|
showPopupMenu(e)
|
||||||
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
|
else if (e.button == MouseEvent.BUTTON1 && e.clickCount == 2)
|
||||||
mvcGroup.parentGroup.controller.download()
|
mvcGroup.controller.download()
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void mouseReleased(MouseEvent e) {
|
public void mouseReleased(MouseEvent e) {
|
||||||
@@ -126,21 +157,42 @@ class SearchTabView {
|
|||||||
showPopupMenu(e)
|
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 = {
|
def closeTab = {
|
||||||
int index = parent.indexOfTab(searchTerms)
|
int index = parent.indexOfTab(searchTerms)
|
||||||
parent.removeTabAt(index)
|
parent.removeTabAt(index)
|
||||||
mvcGroup.parentGroup.model.trustButtonsEnabled = false
|
model.trustButtonsEnabled = false
|
||||||
mvcGroup.parentGroup.model.downloadActionEnabled = false
|
model.downloadActionEnabled = false
|
||||||
mvcGroup.destroy()
|
mvcGroup.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
def showPopupMenu(MouseEvent e) {
|
def showPopupMenu(MouseEvent e) {
|
||||||
JPopupMenu menu = new JPopupMenu()
|
JPopupMenu menu = new JPopupMenu()
|
||||||
if (mvcGroup.parentGroup.model.downloadActionEnabled) {
|
if (model.downloadActionEnabled) {
|
||||||
JMenuItem download = new JMenuItem("Download")
|
JMenuItem download = new JMenuItem("Download")
|
||||||
download.addActionListener({mvcGroup.parentGroup.controller.download()})
|
download.addActionListener({mvcGroup.controller.download()})
|
||||||
menu.add(download)
|
menu.add(download)
|
||||||
}
|
}
|
||||||
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
JMenuItem copyHashToClipboard = new JMenuItem("Copy hash to clipboard")
|
||||||
@@ -160,4 +212,13 @@ class SearchTabView {
|
|||||||
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
def clipboard = Toolkit.getDefaultToolkit().getSystemClipboard()
|
||||||
clipboard.setContents(selection, null)
|
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}".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!')
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user