Compare commits

..

85 Commits

Author SHA1 Message Date
Zlatin Balevsky
549e8c2d98 Release 0.4.12 2019-09-22 16:55:04 +01:00
Zlatin Balevsky
b54d24db0d new update server destination 2019-09-22 16:47:35 +01:00
Zlatin Balevsky
fa12e84345 stronger sig type 2019-09-22 16:23:01 +01:00
Zlatin Balevsky
6430ff2691 bump i2p libs version 2019-09-22 16:13:12 +01:00
Zlatin Balevsky
591313c81c point to the pkg project 2019-09-20 21:09:53 +01:00
Zlatin Balevsky
ce7b6a0c65 change to gasp AA font table, try metal lnf if the others fail 2019-09-16 15:06:45 +01:00
Zlatin Balevsky
5c4d4c4580 embedded router will not work without reseed certificates, so remove it 2019-09-16 15:04:34 +01:00
Zlatin Balevsky
4cb864ff9f update version 2019-09-16 15:03:20 +01:00
Zlatin Balevsky
417675ad07 update dark_trion's hostcache address 2019-07-22 21:48:29 +01:00
Zlatin Balevsky
9513e5ba3c update todo 2019-07-20 13:15:44 +01:00
Zlatin Balevsky
85610cf169 add new host-cache 2019-07-15 22:05:09 +01:00
Zlatin Balevsky
e8322384b8 Release 0.4.11 2019-07-15 14:28:21 +01:00
Zlatin Balevsky
179279ed30 Merge branch 'master' of https://github.com/zlatinb/muwire 2019-07-14 06:19:18 +01:00
Zlatin Balevsky
ae79f0fded Clear Done button, thanks to Aegon 2019-07-14 06:19:05 +01:00
Zlatin Balevsky
ed878b3762 Merge pull request #11 from zetok/readme
Add info about the default I2CP port to README.md
2019-07-12 09:17:24 +01:00
Zetok Zalbavar
623cca0ef2 Add info about the default I2CP port to README.md
Also:
 - improved formatting a bit
 - removed trailing whitespaces
2019-07-12 07:28:12 +01:00
Zlatin Balevsky
eaa883c3ba count duplicate files towards total in Uploads panel 2019-07-11 23:28:12 +01:00
Zlatin Balevsky
7ae8076865 disable webui for now 2019-07-11 22:29:47 +01:00
Zlatin Balevsky
b1aa92661c do not pack200 some jars because of duplicate entries 2019-07-11 20:42:24 +01:00
Zlatin Balevsky
9ed94c8376 do not include tomcat runtime 2019-07-11 20:41:57 +01:00
Zlatin Balevsky
fa6aea1abe attempt to produce an I2P plugin 2019-07-11 19:49:04 +01:00
Zlatin Balevsky
0de84e704b hello webui 2019-07-11 18:34:27 +01:00
Zlatin Balevsky
a767dda044 add empty grails project for a web ui 2019-07-11 17:56:42 +01:00
Zlatin Balevsky
56e9235d7b avoid FS call to get file length 2019-07-11 15:28:25 +01:00
Zlatin Balevsky
2fba9a74ce persist files.json every minute 2019-07-11 14:32:57 +01:00
Zlatin Balevsky
2bb6826906 canonicalize all files before they enter FileManager and do not look for absolute path on persistence 2019-07-11 14:32:12 +01:00
Zlatin Balevsky
9f339629a9 remove unnecessary canonicalization 2019-07-11 11:58:20 +01:00
Zlatin Balevsky
58d4207f94 Release 0.4.10 2019-07-11 05:09:05 +01:00
Zlatin Balevsky
32577a28dc some download stats 2019-07-11 05:00:25 +01:00
Zlatin Balevsky
f7b43304d4 use split pane in downloads tab as well 2019-07-11 03:57:49 +01:00
Zlatin Balevsky
dcbe09886d split pane instead of gridlayout 2019-07-11 03:48:05 +01:00
Zlatin Balevsky
5a54b2dcda shift focus to search pane on search 2019-07-10 22:33:21 +01:00
Zlatin Balevsky
581293b24f column sizes 2019-07-10 22:27:07 +01:00
Zlatin Balevsky
cd072b9f76 enable/disable download button correctly 2019-07-10 22:23:20 +01:00
Zlatin Balevsky
6b74fc5956 fix trust/distrust buttons 2019-07-10 22:17:32 +01:00
Zlatin Balevsky
3de2f872bb show results per sender 2019-07-10 22:08:18 +01:00
Zlatin Balevsky
fcde917d08 fix context menu and double-click 2019-07-10 21:26:13 +01:00
Zlatin Balevsky
4ded065010 move buttons onto search result tab 2019-07-10 21:23:00 +01:00
Zlatin Balevsky
18a1c7091a move downloads to their own pane 2019-07-10 20:54:45 +01:00
Zlatin Balevsky
46aee19f80 disable the button of the currently open pane 2019-07-10 20:37:09 +01:00
Zlatin Balevsky
92dd7064c6 Release 0.4.9 2019-07-10 12:02:36 +01:00
Zlatin Balevsky
b2e4dda677 rearrange tables 2019-07-10 11:55:06 +01:00
Zlatin Balevsky
e77a2c8961 clear hits table on refresh 2019-07-09 21:42:52 +01:00
Zlatin Balevsky
ee2fd2ef68 single hit per search uuid 2019-07-09 21:22:31 +01:00
Zlatin Balevsky
3f95d2bf1d trust and distrust buttons 2019-07-09 21:15:08 +01:00
Zlatin Balevsky
1390983732 populate hits table 2019-07-09 21:05:49 +01:00
Zlatin Balevsky
ce660cefe9 deleting of rules 2019-07-09 20:50:07 +01:00
Zlatin Balevsky
72b81eb886 fix matching 2019-07-09 20:27:28 +01:00
Zlatin Balevsky
57d593a68a persist watched keywords and regexes 2019-07-09 20:11:29 +01:00
Zlatin Balevsky
39a81a3376 hook up rule creation 2019-07-09 19:53:40 +01:00
Zlatin Balevsky
fd0bf17c24 add ability to unregister event listeners 2019-07-09 19:53:08 +01:00
Zlatin Balevsky
ac12bff69b wip on content control panel ui 2019-07-09 19:20:06 +01:00
Zlatin Balevsky
feef773bac hook up content control panel to rest of UI 2019-07-09 17:55:36 +01:00
Zlatin Balevsky
239d8f12a7 wip on core side of content management 2019-07-09 17:13:09 +01:00
Zlatin Balevsky
8bbc61a7cb add settings for watched keywords and regexes 2019-07-09 16:50:51 +01:00
Zlatin Balevsky
7f31c4477f matchers for keywords 2019-07-09 11:47:55 +01:00
Zlatin Balevsky
6bad67c1bf Release 0.4.8 2019-07-08 18:30:19 +01:00
Zlatin Balevsky
c76e6dc99f Merge pull request #9 from zetok/backticks
Replace deprecated backticks with $() for command substitution
2019-07-08 08:24:37 +01:00
Zetok Zalbavar
acf9db0db3 Replace deprecated backticks with $() for command substitution
Although it's a Bash FAQ, the point also applies to POSIX-compatible
shells: https://mywiki.wooledge.org/BashFAQ/082
2019-07-08 06:29:33 +01:00
Zlatin Balevsky
69b4f0b547 Add trust/distrust action from monitor window. Thanks Aegon 2019-07-07 15:31:21 +01:00
Zlatin Balevsky
80e165b505 fix download size in renderer, thanks Aegon 2019-07-07 11:17:56 +01:00
Zlatin Balevsky
bcce55b873 fix integer overflow 2019-07-07 10:58:39 +01:00
Zlatin Balevsky
d5c92560db fix integer overflow 2019-07-07 10:56:14 +01:00
Zlatin Balevsky
f827c1c9bf Home directories for different OSes 2019-07-07 09:14:13 +01:00
Zlatin Balevsky
88c5f1a02d Add GPG key link 2019-07-07 09:04:52 +01:00
Zlatin Balevsky
d8e44f5f39 kill other workers if download is finished 2019-07-06 22:21:13 +01:00
Zlatin Balevsky
72ff47ffe5 use custom renderer and comparator for download progress 2019-07-06 12:53:49 +01:00
Zlatin Balevsky
066ee2c96d wrong list 2019-07-06 11:28:04 +01:00
Zlatin Balevsky
0a8016dea7 enable stealing of pieces from other download workers 2019-07-06 11:26:18 +01:00
Zlatin Balevsky
db36367b11 avoid AIOOBE 2019-07-06 11:00:31 +01:00
Zlatin Balevsky
b6c9ccb7f6 return up to 9 X-Alts 2019-07-06 09:03:27 +01:00
Zlatin Balevsky
a9dc636bce write pieces every time a downloader finishes 2019-07-06 00:52:49 +01:00
Zlatin Balevsky
3cc0574d11 working partial pieces 2019-07-06 00:47:45 +01:00
Zlatin Balevsky
20fab9b16d work on partial piece persistence 2019-07-06 00:17:46 +01:00
Zlatin Balevsky
4015818323 center buttons 2019-07-05 17:15:50 +01:00
Zlatin Balevsky
f569d45c8c reallign tables 2019-07-05 17:07:14 +01:00
Zlatin Balevsky
3773647869 remove diff rejects 2019-07-05 16:24:57 +01:00
Zlatin Balevsky
29cdbf018c remove trailing spaces 2019-07-05 16:24:19 +01:00
Zlatin Balevsky
94bb7022eb tabs -> spaces 2019-07-05 16:22:34 +01:00
Zlatin Balevsky
39808302df Show which file is hashing, thanks to Aegon 2019-07-05 16:20:03 +01:00
Zlatin Balevsky
2d22f9c39e override router log manager 2019-07-05 12:32:23 +01:00
Zlatin Balevsky
ee8f80bab6 up i2p to 0.9.41 2019-07-05 12:26:48 +01:00
Zlatin Balevsky
3e6242e583 break when matching search is found 2019-07-04 18:12:22 +01:00
Zlatin Balevsky
41181616ee compact display of incoming searches, thanks Aegon 2019-07-04 17:59:53 +01:00
Zlatin Balevsky
eb2530ca32 fix sorting of download/upload tables thanks Aegon 2019-07-04 17:58:06 +01:00
241 changed files with 31506 additions and 4831 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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'
} }

View File

@@ -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"

View File

@@ -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"

View File

@@ -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'

View File

@@ -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

View File

@@ -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"
} }
} }

View File

@@ -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)
}
} }

View File

@@ -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

View File

@@ -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)
} }
} }

View File

@@ -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()

View File

@@ -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]

View File

@@ -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)

View File

@@ -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"
} }
} }

View File

@@ -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()
} }
} }
} }

View File

@@ -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()}"
} }
} }

View File

@@ -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()}"
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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() {

View File

@@ -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()
} }
} }

View File

@@ -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) {
} }
} }

View File

@@ -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) {
} }
} }

View File

@@ -0,0 +1,9 @@
package com.muwire.core.content
import com.muwire.core.Event
class ContentControlEvent extends Event {
String term
boolean regex
boolean add
}

View File

@@ -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) }
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,9 @@
package com.muwire.core.content
import com.muwire.core.Persona
class Match {
Persona persona
String [] keywords
long timestamp
}

View 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)
}
}
}

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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))
} }

View File

@@ -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")
}
} }
} }

View File

@@ -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
} }

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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()
}
}

View File

@@ -5,5 +5,5 @@ import com.muwire.core.SharedFile
class FileLoadedEvent extends Event { class FileLoadedEvent extends Event {
SharedFile loadedFile SharedFile loadedFile
} }

View File

@@ -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)

View File

@@ -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() {

View File

@@ -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
} }

View File

@@ -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())))
} }
} }
}
} }

View File

@@ -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
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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

View File

@@ -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
} }
} }
} }
} }
} }

View File

@@ -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"
} }
} }

View File

@@ -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()}"
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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()}" +

View File

@@ -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
} }

View File

@@ -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() {

View File

@@ -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()
[] []
} }
} }

View File

@@ -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()}"
} }
} }

View File

@@ -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
} }

View File

@@ -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
} }
} }
} }

View File

@@ -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==")
} }

View File

@@ -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))

View File

@@ -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)

View File

@@ -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;
} }
} }

View File

@@ -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;
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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
} }

View File

@@ -6,5 +6,5 @@ package com.muwire.core.hostcache;
* *
*/ */
public enum CrawlerResponse { public enum CrawlerResponse {
ALL, REGISTERED, NONE ALL, REGISTERED, NONE
} }

View File

@@ -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
} }

View File

@@ -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")
} }

View File

@@ -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)
} }
} }

View File

@@ -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);
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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")
} }
} }

View File

@@ -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)
} }
} }

View File

@@ -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())
} }
} }
} }

View File

@@ -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
} }
} }

View File

@@ -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() {

View File

@@ -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)
} }
} }

View File

@@ -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) {}
} }
} }

View File

@@ -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
View File

@@ -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

View File

@@ -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'
}
} }

View File

@@ -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)
}
}
}

View File

@@ -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)

View File

@@ -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))
}
} }

View File

@@ -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")
} }

View File

@@ -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()
}
}
}

View File

@@ -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")

View File

@@ -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()
} }
} }

View 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()
}
}

View File

@@ -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 = {

View File

@@ -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))

View 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()
}
}

View File

@@ -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
}
} }

View File

@@ -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!')
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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